Feature #34981 » identifiers.diff
| app/controllers/application_controller.rb | ||
|---|---|---|
| 325 | 325 |
def find_project(project_id=params[:id]) |
| 326 | 326 |
@project = Project.find(project_id) |
| 327 | 327 |
rescue ActiveRecord::RecordNotFound |
| 328 |
render_404 |
|
| 328 |
project_alias = Project.find_alias(project_id) |
|
| 329 |
if @project = project_alias&.project |
|
| 330 |
flash[:warning] = l(:warning_identifier_renamed, alias: project_alias.identifier, identifier: @project.identifier) |
|
| 331 |
else |
|
| 332 |
render_404 |
|
| 333 |
end |
|
| 329 | 334 |
end |
| 330 | 335 | |
| 331 | 336 |
# Find project of id params[:project_id] |
| app/controllers/repositories_controller.rb | ||
|---|---|---|
| 333 | 333 |
REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
|
| 334 | 334 | |
| 335 | 335 |
def find_project_repository |
| 336 |
@project = Project.find(params[:id])
|
|
| 336 |
return unless find_project
|
|
| 337 | 337 |
if params[:repository_id].present? |
| 338 | 338 |
@repository = @project.repositories.find_by_identifier_param(params[:repository_id]) |
| 339 |
unless @repository |
|
| 340 |
repository_alias = @project.repositories.find_alias(params[:repository_id]) |
|
| 341 |
@repository = repository_alias&.repository |
|
| 342 |
if @repository |
|
| 343 |
flash[:warning] = l(:warning_identifier_renamed, alias: repository_alias.identifier, identifier: @repository.identifier) |
|
| 344 |
end |
|
| 345 |
end |
|
| 339 | 346 |
else |
| 340 | 347 |
@repository = @project.repository |
| 341 | 348 |
end |
| app/models/project.rb | ||
|---|---|---|
| 52 | 52 |
has_many :repositories, :dependent => :destroy |
| 53 | 53 |
has_many :changesets, :through => :repository |
| 54 | 54 |
has_one :wiki, :dependent => :destroy |
| 55 |
has_one :project_identifier, :primary_key => 'identifier', :foreign_key => 'identifier' |
|
| 56 |
has_many :project_identifiers, :dependent => :destroy |
|
| 55 | 57 |
# Custom field for the project issues |
| 56 | 58 |
has_and_belongs_to_many :issue_custom_fields, |
| 57 | 59 |
lambda {order(:position)},
|
| ... | ... | |
| 72 | 74 |
:author => nil |
| 73 | 75 | |
| 74 | 76 |
validates_presence_of :name, :identifier |
| 75 |
validates_uniqueness_of :identifier, :if => proc {|p| p.identifier_changed?}, :case_sensitive => true
|
|
| 77 |
validates :identifier, :if => proc {|p| p.identifier_changed? && p.identifier.present?}, :project_identifier_uniqueness => true
|
|
| 76 | 78 |
validates_length_of :name, :maximum => 255 |
| 77 | 79 |
validates_length_of :homepage, :maximum => 255 |
| 78 | 80 |
validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH |
| 79 | 81 |
# downcase letters, digits, dashes but not digits only |
| 80 | 82 |
validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, |
| 81 | 83 |
:if => proc {|p| p.identifier_changed?}
|
| 84 |
after_validation :reset_invalid_identifier |
|
| 85 | ||
| 82 | 86 |
# reserved words |
| 83 | 87 |
validates_exclusion_of :identifier, :in => %w(new) |
| 84 | 88 |
validate :validate_parent |
| 85 | 89 | |
| 90 |
after_save :update_identifier, |
|
| 91 |
:if => proc {|project| project.saved_change_to_identifier? && project.identifier.present? && !project.new_record?}
|
|
| 86 | 92 |
after_save :update_inherited_members, |
| 87 | 93 |
:if => proc {|project| project.saved_change_to_inherit_members?}
|
| 88 | 94 |
after_save :remove_inherited_member_roles, :add_inherited_member_roles, |
| ... | ... | |
| 137 | 143 |
end |
| 138 | 144 |
end |
| 139 | 145 | |
| 140 |
def identifier=(identifier) |
|
| 141 |
super unless identifier_frozen? |
|
| 142 |
end |
|
| 143 | ||
| 144 |
def identifier_frozen? |
|
| 145 |
errors[:identifier].blank? && !(new_record? || identifier.blank?) |
|
| 146 |
end |
|
| 147 | 146 | |
| 148 | 147 |
# returns latest created projects |
| 149 | 148 |
# non public projects will be returned only if user is a member of those |
| ... | ... | |
| 350 | 349 |
end |
| 351 | 350 |
end |
| 352 | 351 | |
| 352 |
def self.find_alias(*args) |
|
| 353 |
if args.first && args.first.is_a?(String) && !/^\d*$/.match?(args.first) |
|
| 354 |
scope = ProjectIdentifier |
|
| 355 |
scope = scope.where(project_id: current_scope) if scope_attributes? |
|
| 356 |
scope.find_by_identifier(*args) |
|
| 357 |
end |
|
| 358 |
end |
|
| 359 | ||
| 353 | 360 |
def self.find_by_param(*args) |
| 354 | 361 |
self.find(*args) |
| 355 | 362 |
end |
| ... | ... | |
| 993 | 1000 |
end |
| 994 | 1001 |
end |
| 995 | 1002 | |
| 1003 |
def update_identifier |
|
| 1004 |
self.project_identifiers << project_identifiers.build(:identifier => identifier, :project_id => self.id) |
|
| 1005 |
end |
|
| 1006 | ||
| 996 | 1007 |
def remove_inherited_member_roles |
| 997 | 1008 |
member_roles = MemberRole.where(:member_id => membership_ids).to_a |
| 998 | 1009 |
member_role_ids = member_roles.map(&:id) |
| ... | ... | |
| 1032 | 1043 |
end |
| 1033 | 1044 |
end |
| 1034 | 1045 | |
| 1046 |
def reset_invalid_identifier |
|
| 1047 |
if errors[:identifier].present? |
|
| 1048 |
self.identifier = identifier_was |
|
| 1049 |
end |
|
| 1050 |
end |
|
| 1051 | ||
| 1035 | 1052 |
# Copies wiki from +project+ |
| 1036 | 1053 |
def copy_wiki(project) |
| 1037 | 1054 |
# Check that the source project has a wiki first |
| app/models/project_identifier.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006-2021 Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
class ProjectIdentifier < ActiveRecord::Base |
|
| 21 |
belongs_to :project |
|
| 22 | ||
| 23 |
validates :identifier, :uniqueness => { :case_sensitive => true }, :presence => true
|
|
| 24 |
validates :project_id, :presence => true |
|
| 25 |
end |
|
| app/models/repository.rb | ||
|---|---|---|
| 29 | 29 |
belongs_to :project |
| 30 | 30 |
has_many :changesets, lambda{order("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC")}
|
| 31 | 31 |
has_many :filechanges, :class_name => 'Change', :through => :changesets |
| 32 |
has_one :repository_identifier, :primary_key => 'identifier', :foreign_key => 'identifier' |
|
| 33 |
has_many :repository_identifiers, :dependent => :destroy |
|
| 32 | 34 | |
| 33 | 35 |
serialize :extra_info |
| 34 | 36 | |
| 35 | 37 |
before_validation :normalize_identifier |
| 36 | 38 |
before_save :check_default |
| 39 |
after_save :update_identifier, |
|
| 40 |
:if => proc {|repository| repository.saved_change_to_identifier? && !repository.new_record? && repository.project}
|
|
| 37 | 41 | |
| 38 | 42 |
# Raw SQL to delete changesets and changes in the database |
| 39 | 43 |
# has_many :changesets, :dependent => :destroy is too slow for big repositories |
| ... | ... | |
| 43 | 47 |
validates_length_of :password, :maximum => 255, :allow_nil => true |
| 44 | 48 |
validates_length_of :root_url, :url, maximum: 255 |
| 45 | 49 |
validates_length_of :identifier, :maximum => IDENTIFIER_MAX_LENGTH, :allow_blank => true |
| 46 |
validates_uniqueness_of :identifier, :scope => :project_id, :case_sensitive => true
|
|
| 50 |
validates :identifier, :if => proc { |r| r.identifier_changed? }, :repository_identifier_uniqueness => true
|
|
| 47 | 51 |
validates_exclusion_of :identifier, :in => %w(browse show entry raw changes annotate diff statistics graph revisions revision) |
| 48 | 52 |
# donwcase letters, digits, dashes, underscores but not digits only |
| 49 | 53 |
validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true |
| 50 | 54 |
# Checks if the SCM is enabled when creating a repository |
| 51 | 55 |
validate :repo_create_validation, :on => :create |
| 52 | 56 |
validate :validate_repository_path |
| 57 |
after_validation :reset_invalid_identifier |
|
| 53 | 58 | |
| 54 | 59 |
safe_attributes( |
| 55 | 60 |
'identifier', |
| ... | ... | |
| 124 | 129 |
end |
| 125 | 130 |
end |
| 126 | 131 | |
| 127 |
def identifier=(identifier) |
|
| 128 |
super unless identifier_frozen? |
|
| 129 |
end |
|
| 130 | ||
| 131 |
def identifier_frozen? |
|
| 132 |
errors[:identifier].blank? && !(new_record? || identifier.blank?) |
|
| 133 |
end |
|
| 134 | ||
| 135 | 132 |
def identifier_param |
| 136 | 133 |
if identifier.present? |
| 137 | 134 |
identifier |
| ... | ... | |
| 150 | 147 |
end |
| 151 | 148 |
end |
| 152 | 149 | |
| 150 |
def reset_invalid_identifier |
|
| 151 |
if errors[:identifier].present? |
|
| 152 |
self.identifier = identifier_was |
|
| 153 |
end |
|
| 154 |
end |
|
| 155 | ||
| 153 | 156 |
def self.find_by_identifier_param(param) |
| 154 | 157 |
if /^\d+$/.match?(param.to_s) |
| 155 | 158 |
find_by_id(param) |
| ... | ... | |
| 158 | 161 |
end |
| 159 | 162 |
end |
| 160 | 163 | |
| 164 |
def self.find_alias(param) |
|
| 165 |
if !/^\d+$/.match?(param.to_s) |
|
| 166 |
scope = RepositoryIdentifier |
|
| 167 |
scope = scope.where(repository_id: current_scope) if scope_attributes? |
|
| 168 |
scope.find_by_identifier(param) |
|
| 169 |
end |
|
| 170 |
end |
|
| 171 | ||
| 161 | 172 |
# TODO: should return an empty hash instead of nil to avoid many ||{}
|
| 162 | 173 |
def extra_info |
| 163 | 174 |
h = read_attribute(:extra_info) |
| ... | ... | |
| 481 | 492 |
self.identifier = identifier.to_s.strip |
| 482 | 493 |
end |
| 483 | 494 | |
| 495 |
def update_identifier |
|
| 496 |
self.repository_identifiers << repository_identifiers.build(:project_id => project.id, :identifier => identifier, :repository_id => self.id) |
|
| 497 |
end |
|
| 498 | ||
| 484 | 499 |
def check_default |
| 485 | 500 |
if !is_default? && set_as_default? |
| 486 | 501 |
self.is_default = true |
| app/models/repository_identifier.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006-2021 Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 | ||
| 20 |
class RepositoryIdentifier < ActiveRecord::Base |
|
| 21 |
belongs_to :repository |
|
| 22 |
belongs_to :project |
|
| 23 | ||
| 24 |
validates :identifier, :uniqueness => { :scope => :project_id, :case_sensitive => true }
|
|
| 25 |
validates :repository_id, :presence => true |
|
| 26 |
end |
|
| app/views/projects/_form.html.erb | ||
|---|---|---|
| 5 | 5 |
<p><%= f.text_field :name, :required => true, :size => 60 %></p> |
| 6 | 6 | |
| 7 | 7 |
<p><%= f.text_area :description, :rows => 8, :class => 'wiki-edit' %></p> |
| 8 |
<p><%= f.text_field :identifier, :required => true, :size => 60, :disabled => @project.identifier_frozen?, :maxlength => Project::IDENTIFIER_MAX_LENGTH %>
|
|
| 9 |
<% unless @project.identifier_frozen? %>
|
|
| 8 |
<p> |
|
| 9 |
<%= f.text_field :identifier, :required => true, :size => 60, :maxlength => Project::IDENTIFIER_MAX_LENGTH %>
|
|
| 10 | 10 |
<em class="info"><%= l(:text_length_between, :min => 1, :max => Project::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_project_identifier_info).html_safe %></em> |
| 11 |
<% end %></p>
|
|
| 11 |
</p> |
|
| 12 | 12 |
<p><%= f.text_field :homepage, :size => 60 %></p> |
| 13 | 13 |
<p> |
| 14 | 14 |
<%= f.check_box :is_public %> |
| ... | ... | |
| 44 | 44 |
<% end %> |
| 45 | 45 |
<!--[eoform:project]--> |
| 46 | 46 | |
| 47 |
<% unless @project.identifier_frozen? %> |
|
| 48 |
<% content_for :header_tags do %> |
|
| 49 |
<%= javascript_include_tag 'project_identifier' %> |
|
| 50 |
<% end %> |
|
| 47 |
<% content_for :header_tags do %> |
|
| 48 |
<%= javascript_include_tag 'project_identifier' %> |
|
| 51 | 49 |
<% end %> |
| 52 | 50 | |
| 53 | 51 |
<% if !User.current.admin? && @project.inherit_members? && @project.parent && User.current.member_of?(@project.parent) %> |
| app/views/repositories/_form.html.erb | ||
|---|---|---|
| 10 | 10 | |
| 11 | 11 |
<p><%= f.check_box :is_default, :label => :field_repository_is_default %></p> |
| 12 | 12 |
<p> |
| 13 |
<%= f.text_field :identifier, :disabled => @repository.identifier_frozen? %> |
|
| 14 |
<% unless @repository.identifier_frozen? %> |
|
| 13 |
<%= f.text_field :identifier %> |
|
| 15 | 14 |
<em class="info"> |
| 16 | 15 |
<%= l(:text_length_between, :min => 1, :max => Repository::IDENTIFIER_MAX_LENGTH) %> <%= l(:text_repository_identifier_info).html_safe %> |
| 17 | 16 |
</em> |
| 18 |
<% end %> |
|
| 19 | 17 |
</p> |
| 20 | 18 | |
| 21 | 19 |
<% button_disabled = true %> |
| config/locales/en.yml | ||
|---|---|---|
| 116 | 116 |
too_short: "is too short (minimum is %{count} characters)"
|
| 117 | 117 |
wrong_length: "is the wrong length (should be %{count} characters)"
|
| 118 | 118 |
taken: "has already been taken" |
| 119 |
used: "has already been used once" |
|
| 119 | 120 |
not_a_number: "is not a number" |
| 120 | 121 |
not_a_date: "is not a valid date" |
| 121 | 122 |
greater_than: "must be greater than %{count}"
|
| ... | ... | |
| 1199 | 1200 |
text_tip_issue_begin_day: issue beginning this day |
| 1200 | 1201 |
text_tip_issue_end_day: issue ending this day |
| 1201 | 1202 |
text_tip_issue_begin_end_day: issue beginning and ending this day |
| 1202 |
text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
|
|
| 1203 |
text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.' |
|
| 1203 | 1204 |
text_caracters_maximum: "%{count} characters maximum."
|
| 1204 | 1205 |
text_caracters_minimum: "Must be at least %{count} characters long."
|
| 1205 | 1206 |
text_characters_must_contain: "Must contain %{character_classes}."
|
| ... | ... | |
| 1312 | 1313 |
description_all_columns: All Columns |
| 1313 | 1314 |
description_issue_category_reassign: Choose issue category |
| 1314 | 1315 |
description_wiki_subpages_reassign: Choose new parent page |
| 1315 |
text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
|
|
| 1316 |
text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.' |
|
| 1316 | 1317 |
text_login_required_html: When not requiring authentication, public projects and their contents are openly available on the network. You can <a href="%{anonymous_role_path}">edit the applicable permissions</a>.
|
| 1317 | 1318 |
label_login_required_yes: "Yes" |
| 1318 | 1319 |
label_login_required_no: "No, allow anonymous access to public projects" |
| ... | ... | |
| 1356 | 1357 | |
| 1357 | 1358 |
text_user_destroy_confirmation: "Are you sure you want to delete this user and remove all references to them? This cannot be undone. Often, locking a user instead of deleting them is the better solution. To confirm, please enter their login (%{login}) below."
|
| 1358 | 1359 |
text_project_destroy_enter_identifier: "To confirm, please enter the project's identifier (%{identifier}) below."
|
| 1360 | ||
| 1361 |
warning_identifier_renamed: 'The identifier has been renamed from "%{alias}" to "%{identifier}", please change your url'
|
|
| db/migrate/20210402143502_create_identifiers.rb | ||
|---|---|---|
| 1 |
class CreateIdentifiers < ActiveRecord::Migration[5.2] |
|
| 2 |
def change |
|
| 3 |
create_table :project_identifiers do |t| |
|
| 4 |
t.references :project, :null => false, :index => true |
|
| 5 |
t.string :identifier, :null => false, :index => { :unique => true }
|
|
| 6 |
t.timestamps |
|
| 7 |
end |
|
| 8 | ||
| 9 |
create_table :repository_identifiers do |t| |
|
| 10 |
t.references :project, :null => false |
|
| 11 |
t.references :repository, :null => false, :index => true |
|
| 12 |
t.string :identifier, :null => false |
|
| 13 |
t.timestamps |
|
| 14 |
end |
|
| 15 |
add_index :repository_identifiers, [:project_id, :identifier], :unique => true |
|
| 16 |
end |
|
| 17 |
end |
|
| db/migrate/20210402153405_migrate_identifiers.rb | ||
|---|---|---|
| 1 |
class MigrateIdentifiers < ActiveRecord::Migration[5.2] |
|
| 2 |
def up |
|
| 3 |
Project.where.not(identifier: nil).find_each do |project| |
|
| 4 |
ProjectIdentifier.create!(project: project, identifier: project.identifier) |
|
| 5 |
end |
|
| 6 | ||
| 7 |
Repository.where.not(identifier: nil).find_each do |repository| |
|
| 8 |
RepositoryIdentifier.create!(project: repository.project, respository: repository, identifier: repository.identifier) |
|
| 9 |
end |
|
| 10 |
end |
|
| 11 |
end |
|
| lib/redmine/core_ext/active_record.rb | ||
|---|---|---|
| 27 | 27 |
end |
| 28 | 28 |
end |
| 29 | 29 |
end |
| 30 | ||
| 31 |
class IdentifierUniquenessValidator < ActiveRecord::Validations::UniquenessValidator |
|
| 32 |
include Redmine::I18n |
|
| 33 | ||
| 34 |
def initialize(options) |
|
| 35 |
options[:case_sensitive] = true |
|
| 36 |
super |
|
| 37 |
end |
|
| 38 |
end |
|
| 39 | ||
| 40 |
class ProjectIdentifierUniquenessValidator < IdentifierUniquenessValidator |
|
| 41 |
def validate_each(record, attribute, value) |
|
| 42 |
super |
|
| 43 |
if record.errors[attribute].empty? |
|
| 44 |
identifier = ProjectIdentifier.new |
|
| 45 |
ProjectIdentifier.validators_on(:identifier).each do |validator| |
|
| 46 |
validator.validate_each(identifier, :identifier, value) |
|
| 47 |
end |
|
| 48 |
if identifier.errors.any? |
|
| 49 |
record.errors.add attribute, l(:used, :scope => [:activerecord, :errors, :messages]) |
|
| 50 |
end |
|
| 51 |
end |
|
| 52 |
end |
|
| 53 |
end |
|
| 54 | ||
| 55 |
class RepositoryIdentifierUniquenessValidator < IdentifierUniquenessValidator |
|
| 56 |
def initialize(options) |
|
| 57 |
options[:scope] = :project_id |
|
| 58 |
super |
|
| 59 |
end |
|
| 60 | ||
| 61 |
def validate_each(record, attribute, value) |
|
| 62 |
super |
|
| 63 |
if record.errors[attribute].empty? |
|
| 64 |
identifier = RepositoryIdentifier.new(project: record.project) |
|
| 65 |
RepositoryIdentifier.validators_on(:identifier).each do |validator| |
|
| 66 |
validator.validate_each(identifier, :identifier, value) |
|
| 67 |
end |
|
| 68 |
if identifier.errors.any? |
|
| 69 |
record.errors.add attribute, l(:used, :scope => [:activerecord, :errors, :messages]) |
|
| 70 |
end |
|
| 71 |
end |
|
| 72 |
end |
|
| 73 |
end |
|
| test/functional/issues_controller_test.rb | ||
|---|---|---|
| 119 | 119 |
end |
| 120 | 120 |
end |
| 121 | 121 | |
| 122 |
def test_index_find_previous_project_identifier |
|
| 123 |
Project.where(:id => 1).update_all(:identifier => 'new_identifier') |
|
| 124 |
ProjectIdentifier.create(project_id: 1, identifier: 'previous_identifier') |
|
| 125 | ||
| 126 |
get(:index, :params => {:project_id => 'new_identifier'})
|
|
| 127 |
assert_response :success |
|
| 128 |
assert_select '.flash.warning', :count => 0 |
|
| 129 | ||
| 130 |
get(:index, :params => {:project_id => 'previous_identifier'})
|
|
| 131 |
assert_response :success |
|
| 132 |
assert_select '.flash.warning', :text => I18n.t(:warning_identifier_renamed, alias: 'previous_identifier', identifier: 'new_identifier') |
|
| 133 | ||
| 134 |
get(:index, :params => {:project_id => 'unknown_identifier'})
|
|
| 135 |
assert_response 404 |
|
| 136 |
end |
|
| 137 | ||
| 122 | 138 |
def test_index_should_list_issues_of_closed_subprojects |
| 123 | 139 |
@request.session[:user_id] = 1 |
| 124 | 140 |
project = Project.find(1) |
| test/functional/repositories_controller_test.rb | ||
|---|---|---|
| 236 | 236 |
assert_select 'table.changesets' |
| 237 | 237 |
end |
| 238 | 238 | |
| 239 |
def test_revisions_with_previous_repository_identifier |
|
| 240 |
repository = Repository::Subversion.create!(:project_id => 1, :identifier => 'new_identifier', :url => 'file:///foo') |
|
| 241 |
RepositoryIdentifier.create(project_id: 1, identifier: 'previous_identifier', repository_id: repository.id) |
|
| 242 |
get( |
|
| 243 |
:revisions, |
|
| 244 |
:params => {
|
|
| 245 |
:id => 1, |
|
| 246 |
:repository_id => 'new_identifier' |
|
| 247 |
} |
|
| 248 |
) |
|
| 249 |
assert_response :success |
|
| 250 |
assert_select '.flash.warning', :count => 0 |
|
| 251 | ||
| 252 |
get( |
|
| 253 |
:revisions, |
|
| 254 |
:params => {
|
|
| 255 |
:id => 1, |
|
| 256 |
:repository_id => 'previous_identifier' |
|
| 257 |
} |
|
| 258 |
) |
|
| 259 |
assert_response :success |
|
| 260 |
assert_select '.flash.warning', :text => I18n.t(:warning_identifier_renamed, alias: 'previous_identifier', identifier: 'new_identifier') |
|
| 261 | ||
| 262 |
get( |
|
| 263 |
:revisions, |
|
| 264 |
:params => {
|
|
| 265 |
:id => 1, |
|
| 266 |
:repository_id => 'unknown_identifier' |
|
| 267 |
} |
|
| 268 |
) |
|
| 269 |
assert_response 404 |
|
| 270 |
end |
|
| 271 | ||
| 239 | 272 |
def test_revisions_for_other_repository |
| 240 | 273 |
repository = Repository::Subversion.create!(:project_id => 1, :identifier => 'foo', :url => 'file:///foo') |
| 241 | 274 |
get( |
| test/unit/project_test.rb | ||
|---|---|---|
| 132 | 132 |
end |
| 133 | 133 |
end |
| 134 | 134 | |
| 135 |
def test_identifier_should_not_be_frozen_for_a_new_project |
|
| 136 |
assert_equal false, Project.new.identifier_frozen? |
|
| 137 |
end |
|
| 138 | ||
| 139 |
def test_identifier_should_not_be_frozen_for_a_saved_project_with_blank_identifier |
|
| 140 |
Project.where(:id => 1).update_all(["identifier = ''"]) |
|
| 141 |
assert_equal false, Project.find(1).identifier_frozen? |
|
| 135 |
def test_identifier_should_validate_used_identifiers |
|
| 136 |
p = Project.find(1) |
|
| 137 |
p.identifier = 'test' |
|
| 138 |
assert p.save |
|
| 139 |
p.identifier = 'test2' |
|
| 140 |
assert p.save |
|
| 141 |
p.identifier = 'test' |
|
| 142 |
assert !p.save |
|
| 143 |
assert p.errors['identifier'].present? |
|
| 142 | 144 |
end |
| 143 | 145 | |
| 144 |
def test_identifier_should_be_frozen_for_a_saved_project_with_valid_identifier |
|
| 145 |
assert_equal true, Project.find(1).identifier_frozen? |
|
| 146 |
end |
|
| 147 | 146 | |
| 148 | 147 |
def test_to_param_should_be_nil_for_new_records |
| 149 | 148 |
project = Project.new |
| test/unit/repository_git_test.rb | ||
|---|---|---|
| 46 | 46 |
end |
| 47 | 47 | |
| 48 | 48 |
def test_nondefault_repo_with_blank_identifier_destruction |
| 49 |
Repository.delete_all
|
|
| 49 |
Repository.destroy_all
|
|
| 50 | 50 | |
| 51 | 51 |
repo1 = |
| 52 | 52 |
Repository::Git.new( |
| test/unit/repository_test.rb | ||
|---|---|---|
| 114 | 114 |
assert !r.save |
| 115 | 115 |
end |
| 116 | 116 | |
| 117 |
def test_identifier_should_validate_used_identifiers |
|
| 118 |
r = Repository::Subversion.new(:project_id => 3, :identifier => 'test', :url => 'file:///bar') |
|
| 119 |
assert r.save |
|
| 120 |
r.identifier = 'test2' |
|
| 121 |
assert r.save |
|
| 122 |
r.identifier = 'test' |
|
| 123 |
assert !r.save |
|
| 124 |
assert r.errors['identifier'].present? |
|
| 125 |
end |
|
| 126 | ||
| 127 |
def test_identifier_should_allow_same_identifier_on_multiple_projects |
|
| 128 |
r = Repository::Subversion.new(:project_id => 3, :identifier => 'test', :url => 'file:///bar') |
|
| 129 |
assert r.save |
|
| 130 |
r = Repository::Subversion.new(:project_id => 4, :identifier => 'test', :url => 'file:///bar') |
|
| 131 |
assert r.save |
|
| 132 |
end |
|
| 133 | ||
| 117 | 134 |
def test_first_repository_should_be_set_as_default |
| 118 | 135 |
repository1 = |
| 119 | 136 |
Repository::Subversion. |
| ... | ... | |
| 179 | 196 |
assert r.save |
| 180 | 197 |
end |
| 181 | 198 | |
| 182 |
def test_identifier_should_not_be_frozen_for_a_new_repository |
|
| 183 |
assert_equal false, Repository.new.identifier_frozen? |
|
| 184 |
end |
|
| 185 | ||
| 186 |
def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier |
|
| 187 |
Repository.where(:id => 10).update_all(["identifier = ''"]) |
|
| 188 |
assert_equal false, Repository.find(10).identifier_frozen? |
|
| 189 |
end |
|
| 190 | ||
| 191 |
def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier |
|
| 192 |
Repository.where(:id => 10).update_all(["identifier = 'abc123'"]) |
|
| 193 |
assert_equal true, Repository.find(10).identifier_frozen? |
|
| 194 |
end |
|
| 195 | ||
| 196 |
def test_identifier_should_not_accept_change_if_frozen |
|
| 197 |
r = Repository.new(:identifier => 'foo') |
|
| 198 |
r.stubs(:identifier_frozen?).returns(true) |
|
| 199 | ||
| 200 |
r.identifier = 'bar' |
|
| 201 |
assert_equal 'foo', r.identifier |
|
| 202 |
end |
|
| 203 | ||
| 204 |
def test_identifier_should_accept_change_if_not_frozen |
|
| 205 |
r = Repository.new(:identifier => 'foo') |
|
| 206 |
r.stubs(:identifier_frozen?).returns(false) |
|
| 207 | ||
| 208 |
r.identifier = 'bar' |
|
| 209 |
assert_equal 'bar', r.identifier |
|
| 210 |
end |
|
| 211 | 199 | |
| 212 | 200 |
def test_destroy |
| 213 | 201 |
repository = Repository.find(10) |