]> source.dussan.org Git - redmine.git/commitdiff
Adds support for multiple repositories per project (#779).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 15 Jan 2012 18:19:19 +0000 (18:19 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 15 Jan 2012 18:19:19 +0000 (18:19 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8650 e93f8b46-1217-0410-a6f0-8f06a7374b81

32 files changed:
app/controllers/repositories_controller.rb
app/controllers/sys_controller.rb
app/helpers/application_helper.rb
app/helpers/repositories_helper.rb
app/models/project.rb
app/models/repository.rb
app/views/issues/_changesets.html.erb
app/views/projects/settings/_repositories.html.erb
app/views/repositories/_breadcrumbs.html.erb
app/views/repositories/_dir_list_content.html.erb
app/views/repositories/_form.html.erb
app/views/repositories/_link_to_functions.html.erb
app/views/repositories/_navigation.html.erb
app/views/repositories/_revisions.html.erb
app/views/repositories/annotate.html.erb
app/views/repositories/revision.html.erb
app/views/repositories/revisions.html.erb
app/views/repositories/show.html.erb
app/views/repositories/stats.html.erb
config/locales/en.yml
config/locales/fr.yml
config/routes.rb
db/migrate/20120115143024_add_repositories_identifier.rb [new file with mode: 0644]
db/migrate/20120115143100_add_repositories_is_default.rb [new file with mode: 0644]
db/migrate/20120115143126_set_default_repositories.rb [new file with mode: 0644]
public/stylesheets/application.css
test/exemplars/repository_exemplar.rb
test/fixtures/repositories.yml
test/functional/repositories_controller_test.rb
test/integration/routing/repositories_test.rb
test/unit/repository_test.rb
test/unit/user_test.rb

index 0a95c6adee45be0ce05486b4c8b4be2149aa4e30..539dcd9dc4017068c6706206354fccca3e5d6da5 100644 (file)
@@ -28,7 +28,6 @@ class RepositoriesController < ApplicationController
   default_search_scope :changesets
 
   before_filter :find_project_by_project_id, :only => [:new, :create]
-  before_filter :check_repository_uniqueness, :only => [:new, :create]
   before_filter :find_repository, :only => [:edit, :update, :destroy, :committers]
   before_filter :find_project_repository, :except => [:new, :create, :edit, :update, :destroy, :committers]
   before_filter :authorize
@@ -39,6 +38,7 @@ class RepositoriesController < ApplicationController
   def new
     scm = params[:repository_scm] || Redmine::Scm::Base.all.first
     @repository = Repository.factory(scm)
+    @repository.is_default = @project.repository.nil?
     @repository.project = @project
     render :layout => !request.xhr?
   end
@@ -97,6 +97,7 @@ class RepositoriesController < ApplicationController
       (show_error_not_found; return) unless @entries
       @changesets = @repository.latest_changesets(@path, @rev)
       @properties = @repository.properties(@path, @rev)
+      @repositories = @project.repositories
       render :action => 'show'
     end
   end
@@ -255,18 +256,15 @@ class RepositoriesController < ApplicationController
     render_404
   end
 
-  # TODO: remove it when multiple SCM support is added
-  def check_repository_uniqueness
-    if @project.repository
-      redirect_to settings_project_path(@project, :tab => 'repositories')
-    end
-  end
-
   REV_PARAM_RE = %r{\A[a-f0-9]*\Z}i
 
   def find_project_repository
     @project = Project.find(params[:id])
-    @repository = @project.repository
+    if params[:repository_id].present?
+      @repository = @project.repositories.find_by_identifier_param(params[:repository_id])
+    else
+      @repository = @project.repository
+    end
     (render_404; return false) unless @repository
     @path = params[:path].join('/') unless params[:path].nil?
     @path ||= ''
index 89ad19ad4909cdcad307dcebd99dfe6b7785608c..0644fed846db439fae0f01d9d148ad1ea7fd2bb5 100644 (file)
@@ -19,7 +19,7 @@ class SysController < ActionController::Base
   before_filter :check_enabled
 
   def projects
-    p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => 'identifier')
+    p = Project.active.has_module(:repository).find(:all, :include => :repository, :order => "#{Project.table_name}.identifier")
     # extra_info attribute from repository breaks activeresource client
     render :xml => p.to_xml(:only => [:id, :identifier, :name, :is_public, :status], :include => {:repository => {:only => [:id, :url]}})
   end
@@ -44,11 +44,11 @@ class SysController < ActionController::Base
     if params[:id]
       projects << Project.active.has_module(:repository).find(params[:id])
     else
-      projects = Project.active.has_module(:repository).find(:all, :include => :repository)
+      projects = Project.active.has_module(:repository).all
     end
     projects.each do |project|
-      if project.repository
-        project.repository.fetch_changesets
+      project.repositories.each do |repository|
+        repository.fetch_changesets
       end
     end
     render :nothing => true, :status => 200
index 1954351e5e7bebfef2f5f29e1d7a929a7e9cb25b..968420041c3c9f78e56d81b820e8c5061c5eaaa4 100644 (file)
@@ -106,12 +106,15 @@ module ApplicationHelper
   # Generates a link to a SCM revision
   # Options:
   # * :text - Link text (default to the formatted revision)
-  def link_to_revision(revision, project, options={})
+  def link_to_revision(revision, repository, options={})
+    if repository.is_a?(Project)
+      repository = repository.repository
+    end
     text = options.delete(:text) || format_revision(revision)
     rev = revision.respond_to?(:identifier) ? revision.identifier : revision
     link_to(
         h(text),
-        {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
+        {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
         :title => l(:label_revision_id, format_revision(revision))
       )
   end
index f3b229f2c88108eab2f2c96e00dddf3b8cab6d61..24807139eeaae450e4a74231094c5e5dcf6ffe1d 100644 (file)
@@ -92,6 +92,7 @@ module RepositoriesHelper
         text = link_to(h(text), :controller => 'repositories',
                              :action => 'show',
                              :id => @project,
+                             :repository_id => @repository.identifier_param,
                              :path => path_param,
                              :rev => @changeset.identifier)
         output << "<li class='#{style}'>#{text}"
@@ -103,12 +104,14 @@ module RepositoriesHelper
         text = link_to(h(text), :controller => 'repositories',
                              :action => 'entry',
                              :id => @project,
+                             :repository_id => @repository.identifier_param,
                              :path => path_param,
                              :rev => @changeset.identifier) unless c.action == 'D'
         text << " - #{h(c.revision)}" unless c.revision.blank?
         text << ' ('.html_safe + link_to(l(:label_diff), :controller => 'repositories',
                                        :action => 'diff',
                                        :id => @project,
+                                       :repository_id => @repository.identifier_param,
                                        :path => path_param,
                                        :rev => @changeset.identifier) + ') '.html_safe if c.action == 'M'
         text << ' '.html_safe + content_tag('span', h(c.from_path), :class => 'copied-from') unless c.from_path.blank?
index 29578df6ec1b703cf49332bbe2f6678a0ac2fb92..3760a646cdf2a91cda22512e5d71aec6b9e77c73 100644 (file)
@@ -46,7 +46,8 @@ class Project < ActiveRecord::Base
   has_many :news, :dependent => :destroy, :include => :author
   has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
   has_many :boards, :dependent => :destroy, :order => "position ASC"
-  has_one :repository, :dependent => :destroy
+  has_one :repository, :conditions => ["is_default = ?", true]
+  has_many :repositories, :dependent => :destroy
   has_many :changesets, :through => :repository
   has_one :wiki, :dependent => :destroy
   # Custom field for the project issues
index c231a8724068939d3cb14775d8bdc2d8e0bcd45a..aad078c7d2c42e53391e25af8847ab3fcf5e8c1c 100644 (file)
@@ -26,11 +26,19 @@ class Repository < ActiveRecord::Base
 
   serialize :extra_info
 
+  before_save :check_default
+
   # Raw SQL to delete changesets and changes in the database
   # has_many :changesets, :dependent => :destroy is too slow for big repositories
   before_destroy :clear_changesets
 
   validates_length_of :password, :maximum => 255, :allow_nil => true
+  validates_length_of :identifier, :maximum => 255, :allow_blank => true
+  validates_presence_of :identifier, :unless => Proc.new { |r| r.is_default? || r.set_as_default? }
+  validates_uniqueness_of :identifier, :scope => :project_id, :allow_blank => true
+  validates_exclusion_of :identifier, :in => %w(show entry raw changes annotate diff show stats graph)
+  # donwcase letters, digits, dashes but not digits only
+  validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :allow_blank => true
   # Checks if the SCM is enabled when creating a repository
   validate :repo_create_validation, :on => :create
 
@@ -65,7 +73,9 @@ class Repository < ActiveRecord::Base
     end
 
     send :attributes_without_extra_info=, p, guard_protected_attributes
-    merge_extra_info(p_extra)
+    if p_extra.keys.any?
+      merge_extra_info(p_extra)
+    end
   end
 
   # Removes leading and trailing whitespace
@@ -101,6 +111,44 @@ class Repository < ActiveRecord::Base
     self.class.scm_name
   end
 
+  def name
+    if is_default?
+      l(:field_repository_is_default)
+    elsif identifier.present?
+      identifier
+    else
+      scm_name
+    end
+  end
+
+  def identifier_param
+    if is_default?
+      nil
+    elsif identifier.present?
+      identifier
+    else
+      id.to_s
+    end
+  end
+
+  def <=>(repository)
+    if is_default?
+      -1
+    elsif repository.is_default?
+      1
+    else
+      identifier <=> repository.identifier
+    end
+  end
+
+  def self.find_by_identifier_param(param)
+    if param.to_s =~ /^\d+$/
+      find_by_id(param)
+    else
+      find_by_identifier(param)
+    end
+  end
+
   def merge_extra_info(arg)
     h = extra_info || {}
     return h if arg.nil?
@@ -269,10 +317,10 @@ class Repository < ActiveRecord::Base
   # Can be called periodically by an external script
   # eg. ruby script/runner "Repository.fetch_changesets"
   def self.fetch_changesets
-    Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
-      if project.repository
+    Project.active.has_module(:repository).all.each do |project|
+      project.repositories.each do |repository|
         begin
-          project.repository.fetch_changesets
+          repository.fetch_changesets
         rescue Redmine::Scm::Adapters::CommandFailed => e
           logger.error "scm: error during fetching changesets: #{e.message}"
         end
@@ -334,6 +382,21 @@ class Repository < ActiveRecord::Base
     ret
   end
 
+  def set_as_default?
+    new_record? && project && !Repository.first(:conditions => {:project_id => project.id})
+  end
+
+  protected
+
+  def check_default
+    if !is_default? && set_as_default?
+      self.is_default = true
+    end
+    if is_default? && is_default_changed?
+      Repository.update_all(["is_default = ?", false], ["project_id = ?", project_id])
+    end
+  end
+
   private
 
   def clear_changesets
index 76e9a2810f6332af87a2f44a6017e99a176ade0a..beab65536322750da51fd4ac83225f5983cb1dd1 100644 (file)
@@ -1,6 +1,6 @@
 <% changesets.each do |changeset| %>
     <div class="changeset <%= cycle('odd', 'even') %>">
-    <p><%= link_to_revision(changeset, changeset.project,
+    <p><%= link_to_revision(changeset, changeset.repository,
                             :text => "#{l(:label_revision)} #{changeset.format_identifier}") %><br />
         <span class="author"><%= authoring(changeset.committed_on, changeset.author) %></span></p>
     <div class="wiki">
index d74220b9ed08be1f28ff7ace247e2045ecb7eb73..fc3fd923ee0789b6922e1e51da865da8cbde88ff 100644 (file)
@@ -1,16 +1,20 @@
-<% if @project.repository %>
+<% if @project.repositories.any? %>
 <table class="list">
   <thead>
        <tr>
       <th><%= l(:label_scm) %></th>
+      <th><%= l(:field_identifier) %></th>
+      <th><%= l(:field_repository_is_default) %></th>
       <th><%= l(:label_repository) %></th>
       <th></th>
     </tr>
        </thead>
   <tbody>
-  <% repository = @project.repository %>
+  <% @project.repositories.each do |repository| %>
          <tr class="<%= cycle 'odd', 'even' %>">
                <td><%=h repository.scm_name %></td>
+      <td><%=h repository.identifier %></td>
+      <td align="center"><%= checked_image repository.is_default? %></td>
                        <td><%=h repository.url %></td>
                        <td class="buttons">
                        <% if User.current.allowed_to?(:manage_repository, @project) %>
                        <% end %>
                        </td>
          </tr>
+  <% end %>
   </tbody>
 </table>
 <% else %>
 <p class="nodata"><%= l(:label_no_data) %></p>
 <% end %>
 
-<% if @project.repository.nil? && User.current.allowed_to?(:manage_repository, @project) %>
+<% if User.current.allowed_to?(:manage_repository, @project) %>
   <p><%= link_to l(:label_repository_new), new_project_repository_path(@project), :class => 'icon icon-add' %></p>
 <% end %>
index 0d548176c0d5bb24fae0195f33a96982d0e89262..1a610ff6c9915289f6a67e94b5c8d0bcbf19374a 100644 (file)
@@ -1,4 +1,4 @@
-<%= link_to 'root', :action => 'show', :id => @project, :path => '', :rev => @rev %>
+<%= link_to 'root', :action => 'show', :id => @project, :repository_id => @repository.identifier_param, :path => '', :rev => @rev %>
 <%
 dirs = path.split('/')
 if 'file' == kind
@@ -10,12 +10,12 @@ dirs.each do |dir|
     link_path << '/' unless link_path.empty?
     link_path << "#{dir}"
     %>
-    / <%= link_to h(dir), :action => 'show', :id => @project,
+    / <%= link_to h(dir), :action => 'show', :id => @project, :repository_id => @repository.identifier_param,
                 :path => to_path_param(link_path), :rev => @rev %>
 <% end %>
 <% if filename %>
     / <%= link_to h(filename),
-                   :action => 'changes', :id => @project,
+                   :action => 'changes', :id => @project, :repository_id => @repository.identifier_param,
                    :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %>
 <% end %>
 <%
index 4a1779e28cb30e164b1e45e916a93647fbd707a8..057b7dc2efde619623f4e0beb14435641de78a18 100644 (file)
@@ -11,6 +11,7 @@
                   :url => {
                        :action => 'show',
                        :id     => @project,
+                                                                                        :repository_id => @repository.identifier_param,
                        :path   => to_path_param(ent_path),
                        :rev    => @rev,
                        :depth  => (depth + 1),
                 ) %>">&nbsp</span>
 <% end %>
 <%=  link_to h(ent_name),
-          {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(ent_path), :rev => @rev},
+          {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(ent_path), :rev => @rev},
           :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%>
 </td>
 <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
 <% changeset = @project.repository.find_changeset_by_name(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
 <% if @repository.report_last_commit %>
-<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td>
+<td class="revision"><%= link_to_revision(changeset, @repository) if changeset %></td>
 <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
 <td class="author"><%= changeset.nil? ? h(Redmine::CodesetUtil.replace_invalid_utf8(entry.lastrev.author.to_s.split('<').first)) : h(changeset.author) if entry.lastrev %></td>
 <td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td>
index bd1f61334b815300df6a3b58e2110fa164ae53c0..b233a13df377c423bcd228fad05b9d9c96db438d 100644 (file)
@@ -9,6 +9,9 @@
 <% end %>
 </p>
 
+<p><%= f.text_field :identifier %></p>
+<p><%= f.check_box :is_default, :label => :field_repository_is_default %></p>
+
 <% button_disabled = true %>
 <% if @repository %>
 <%   button_disabled = ! @repository.class.scm_available %>
index 412d5f86f05aa0c14c99936c14ab55cece4a2636..aed88ebb7ff377f0ceaee78016f0b76dbfaaae2e 100644 (file)
@@ -1,14 +1,14 @@
 <% if @entry && @entry.kind == 'file' %>
 
 <p>
-<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
+<%= link_to_if action_name != 'changes', l(:label_history), {:action => 'changes', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> |
 <% if @repository.supports_cat? %>
-    <%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
+    <%= link_to_if action_name != 'entry', l(:button_view), {:action => 'entry', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> |
 <% end %>
 <% if @repository.supports_annotate? %>
-    <%= link_to_if action_name != 'annotate', l(:button_annotate), {:action => 'annotate', :id => @project, :path => to_path_param(@path), :rev => @rev } %> |
+    <%= link_to_if action_name != 'annotate', l(:button_annotate), {:action => 'annotate', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev } %> |
 <% end %>
-<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
+<%= link_to(l(:button_download), {:action => 'entry', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(@path), :rev => @rev, :format => 'raw' }) if @repository.supports_cat? %>
 <%= "(#{number_to_human_size(@entry.size)})" if @entry.size %>
 </p>
 
index 706f06c800a2df2961eef71d993320f2f683ed57..05c19d9bebf85cb60c7ce8be197f56cacffcf532 100644 (file)
@@ -3,11 +3,12 @@
 <% end %>
 
 <%= link_to l(:label_statistics),
-            {:action => 'stats', :id => @project},
+            {:action => 'stats', :id => @project, :repository_id => @repository.identifier_param},
             :class => 'icon icon-stats' %>
 
 <% form_tag({:action => controller.action_name,
              :id => @project,
+             :repository_id => @repository.identifier_param,
              :path => to_path_param(@path),
              :rev => ''},
             {:method => :get, :id => 'revision_selector'}) do -%>
index ea55e4beda885d3a3e720993f020769b7628e535..3fd1cf690735b1805c37f48425aa04bf83c8d7e8 100644 (file)
@@ -1,5 +1,5 @@
 <% show_revision_graph = ( @repository.supports_revision_graph? && path.blank? ) %>
-<% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :path => to_path_param(path)}, :method => :get) do %>
+<% form_tag({:controller => 'repositories', :action => 'diff', :id => @project, :repository_id => @repository.identifier_param, :path => to_path_param(path)}, :method => :get) do %>
 <table class="list changesets">
 <thead><tr>
 <% if show_revision_graph %>
@@ -23,6 +23,7 @@
       <% href_base = Proc.new {|x| url_for(:controller => 'repositories',
                                            :action => 'revision',
                                            :id => project,
+                                           :repository_id => @repository.identifier_param,
                                            :rev => x) } %>
       <%= render :partial => 'revision_graph',
                  :locals => {
@@ -35,7 +36,7 @@
     </td>
   <% end %>
 <% end %>
-<td class="id"><%= link_to_revision(changeset, project) %></td>
+<td class="id"><%= link_to_revision(changeset, @repository) %></td>
 <td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
 <td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %></td>
 <td class="committed_on"><%= format_time(changeset.committed_on) %></td>
index 444ae2a3aeafc1d471bae62444759e94c3d7507d..188d9215dd2fbf0cb6e01110bd9eda50c78af203 100644 (file)
@@ -19,7 +19,7 @@
       <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
         <th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th>
         <td class="revision">
-        <%= (revision.identifier ? link_to_revision(revision, @project) : format_revision(revision)) if revision %></td>
+        <%= (revision.identifier ? link_to_revision(revision, @repository) : format_revision(revision)) if revision %></td>
         <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
         <td class="line-code"><pre><%= line %></pre></td>
       </tr>
index 6163ce17cd0deb9caff6a40628c1edf84a03a97a..7c44cd6746764ef6ad5d670e8dbfec884724db53 100644 (file)
@@ -1,13 +1,13 @@
 <div class="contextual">
   &#171;
   <% unless @changeset.previous.nil? -%>
-    <%= link_to_revision(@changeset.previous, @project, :text => l(:label_previous)) %>
+    <%= link_to_revision(@changeset.previous, @repository, :text => l(:label_previous)) %>
   <% else -%>
     <%= l(:label_previous) %>
   <% end -%>
 |
   <% unless @changeset.next.nil? -%>
-    <%= link_to_revision(@changeset.next, @project, :text => l(:label_next)) %>
+    <%= link_to_revision(@changeset.next, @repository, :text => l(:label_next)) %>
   <% else -%>
     <%= l(:label_next) %>
   <% end -%>
@@ -16,6 +16,7 @@
   <% form_tag({:controller => 'repositories',
                :action     => 'revision',
                :id         => @project,
+               :repository_id => @repository.identifier_param,
                :rev        => nil},
                :method     => :get) do %>
     <%= text_field_tag 'rev', @rev, :size => 8 %>
@@ -37,7 +38,7 @@
       <td><%= l(:label_parent_revision) %></td>
       <td>
         <%= @changeset.parents.collect{
-              |p| link_to_revision(p, @project, :text => format_revision(p))
+              |p| link_to_revision(p, @repository, :text => format_revision(p))
             }.join(", ").html_safe %>
       </td>
     </tr>
@@ -47,7 +48,7 @@
       <td><%= l(:label_child_revision) %></td>
       <td>
        <%= @changeset.children.collect{
-              |p| link_to_revision(p, @project, :text => format_revision(p))
+              |p| link_to_revision(p, @repository, :text => format_revision(p))
              }.join(", ").html_safe %>
       </td>
     </tr>
@@ -85,6 +86,7 @@
 <p><%= link_to(l(:label_view_diff),
                :action => 'diff',
                :id     => @project,
+               :repository_id => @repository.identifier_param,
                :path   => "",
                :rev    => @changeset.identifier) if @changeset.changes.any? %></p>
 
index 8974fa78834ac6dcc7388db652b7a69a56539fe1..f1d442f084ed08b851b5927454d735f9566099ef 100644 (file)
@@ -1,5 +1,5 @@
 <div class="contextual">
-<% form_tag({:action => 'revision', :id => @project}) do %>
+<% form_tag({:action => 'revision', :id => @project, :repository_id => @repository.identifier_param}) do %>
 <%=   l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %>
 <%=   submit_tag 'OK' %>
 <% end %>
index fc148e8d19239490ac7c6bb347330cf9a6ab0096..ca64c73f35a1dfd43ce85d954ff537988077af20 100644 (file)
@@ -25,7 +25,7 @@
      sep = ''
  %>
 <%   if @repository.supports_all_revisions? && @path.blank? %>
-<%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project %>
+<%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project, :repository_id => @repository.identifier_param %>
 <%   sep = '|' %>
 <%   end %>
 <%
@@ -38,6 +38,7 @@
                    :action => 'changes',
                    :path   => to_path_param(@path),
                    :id     => @project,
+                   :repository_id => @repository.identifier_param,
                    :rev    => @rev
                    %>
 <%   end %>
 <%     end %>
 
 <%     other_formats_links do |f| %>
-  <%= f.link_to 'Atom', :url => {:action => 'revisions', :id => @project, :key => User.current.rss_key} %>
+  <%= f.link_to 'Atom', :url => {:action => 'revisions', :id => @project, :repository_id => @repository.identifier_param, :key => User.current.rss_key} %>
 <%     end %>
 <%   end %>
 <% end %>
 
+<% if @repositories.size > 1 %>
+       <% content_for :sidebar do %>
+         <h3><%= l(:label_repository_plural) %></h3>
+         <%= @repositories.sort.collect {|repo|
+               link_to h(repo.name), 
+                  {:controller => 'repositories', :action => 'show', :id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil},
+                  :class => 'repository' + (repo == @repository ? ' selected' : '')
+             }.join('<br />').html_safe %></p>
+       <% end %>
+<% end %>
+
 <% content_for :header_tags do %>
 <%= stylesheet_link_tag "scm" %>
 <% end %>
index e5e7375062b5468a5b0e3113d5b56a3792ba1b77..b9a5d7d6aeeae8f1de1737f2cbe7e1a9c5b3652e 100644 (file)
@@ -1,10 +1,10 @@
 <h2><%= l(:label_statistics) %></h2>
 
 <p>
-<%= tag("embed", :width => 800, :height => 300, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :graph => "commits_per_month")) %>
+<%= tag("embed", :width => 800, :height => 300, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :repository_id => @repository.identifier_param, :graph => "commits_per_month")) %>
 </p>
 <p>
-<%= tag("embed", :width => 800, :height => 400, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :graph => "commits_per_author")) %>
+<%= tag("embed", :width => 800, :height => 400, :type => "image/svg+xml", :src => url_for(:controller => 'repositories', :action => 'graph', :id => @project, :repository_id => @repository.identifier_param, :graph => "commits_per_author")) %>
 </p>
 
 <p><%= link_to l(:button_back), :action => 'show', :id => @project %></p>
index 489011886c33dcef262bb10fca507c24409db2af..6dc4cb3d7445f2e39544237017a0338f7fbcee2b 100644 (file)
@@ -318,6 +318,7 @@ en:
   field_root_directory: Root directory
   field_cvsroot: CVSROOT
   field_cvs_module: Module
+  field_repository_is_default: Main repository
 
   setting_app_title: Application title
   setting_app_subtitle: Application subtitle
index 7f04e5cb6e348178f8c79fecc3a9dd4fa72826de..780f634d96fede60968c0fbf0ae8cca66557b522 100644 (file)
@@ -317,6 +317,7 @@ fr:
   field_issues_visibility: Visibilité des demandes
   field_is_private: Privée
   field_commit_logs_encoding: Encodage des messages de commit
+  field_repository_is_default: Dépôt principal
 
   setting_app_title: Titre de l'application
   setting_app_subtitle: Sous-titre de l'application
index 64ba34d2faf0de3ef0d42d398c7750242442c462..1a02d02c3374996a0b5a6be1d5f906587e64a9a3 100644 (file)
@@ -235,7 +235,8 @@ ActionController::Routing::Routes.draw do |map|
                                :action => 'show'
       repository_views.connect 'projects/:id/repository/statistics',
                                :action => 'stats'
-
+      repository_views.connect 'projects/:id/repository/graph',
+                               :action => 'graph'
       repository_views.connect 'projects/:id/repository/revisions',
                                :action => 'revisions'
       repository_views.connect 'projects/:id/repository/revisions.:format',
@@ -247,28 +248,39 @@ ActionController::Routing::Routes.draw do |map|
       repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format',
                                :action => 'diff'
       repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path',
-                               :action => 'entry',
-                               :format => 'raw',
-                               :requirements => { :rev => /[a-z0-9\.\-_]+/ }
+                               :action => 'entry', :format => 'raw'
       repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path',
-                               :requirements => { :rev => /[a-z0-9\.\-_]+/ }
-
+                               :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
       repository_views.connect 'projects/:id/repository/raw/*path',
                                :action => 'entry', :format => 'raw'
-      repository_views.connect 'projects/:id/repository/browse/*path',
-                               :action => 'browse'
-      repository_views.connect 'projects/:id/repository/entry/*path',
-                               :action => 'entry'
-      repository_views.connect 'projects/:id/repository/changes/*path',
-                               :action => 'changes'
-      repository_views.connect 'projects/:id/repository/annotate/*path',
-                               :action => 'annotate'
-      repository_views.connect 'projects/:id/repository/diff/*path',
+      repository_views.connect 'projects/:id/repository/:action/*path',
+                               :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
+
+      # Same routes with a repository_id
+      repository_views.connect 'projects/:id/repository/:repository_id/statistics',
+                               :action => 'stats'
+      repository_views.connect 'projects/:id/repository/:repository_id/graph',
+                               :action => 'graph'
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions',
+                               :action => 'revisions'
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions.:format',
+                               :action => 'revisions'
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev',
+                               :action => 'revision'
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff',
+                               :action => 'diff'
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff.:format',
                                :action => 'diff'
-      repository_views.connect 'projects/:id/repository/show/*path',
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/raw/*path',
+                               :action => 'entry', :format => 'raw'
+      repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/:action/*path',
+                               :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
+      repository_views.connect 'projects/:id/repository/:repository_id/raw/*path',
+                               :action => 'entry', :format => 'raw'
+      repository_views.connect 'projects/:id/repository/:repository_id/:action/*path',
+                               :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
+      repository_views.connect 'projects/:id/repository/:repository_id',
                                :action => 'show'
-      repository_views.connect 'projects/:id/repository/graph',
-                               :action => 'graph'
     end
 
     repositories.connect 'projects/:id/repository/revision',
diff --git a/db/migrate/20120115143024_add_repositories_identifier.rb b/db/migrate/20120115143024_add_repositories_identifier.rb
new file mode 100644 (file)
index 0000000..b54ebd1
--- /dev/null
@@ -0,0 +1,9 @@
+class AddRepositoriesIdentifier < ActiveRecord::Migration
+  def self.up
+    add_column :repositories, :identifier, :string
+  end
+
+  def self.down
+    remove_column :repositories, :identifier
+  end
+end
diff --git a/db/migrate/20120115143100_add_repositories_is_default.rb b/db/migrate/20120115143100_add_repositories_is_default.rb
new file mode 100644 (file)
index 0000000..87f0181
--- /dev/null
@@ -0,0 +1,9 @@
+class AddRepositoriesIsDefault < ActiveRecord::Migration
+  def self.up
+    add_column :repositories, :is_default, :boolean, :default => false
+  end
+
+  def self.down
+    remove_column :repositories, :is_default
+  end
+end
diff --git a/db/migrate/20120115143126_set_default_repositories.rb b/db/migrate/20120115143126_set_default_repositories.rb
new file mode 100644 (file)
index 0000000..cd7674e
--- /dev/null
@@ -0,0 +1,14 @@
+class SetDefaultRepositories < ActiveRecord::Migration
+  def self.up
+    Repository.update_all(["is_default = ?", false])
+    # Sets the last repository as default in case multiple repositories exist for the same project
+    Repository.connection.select_values("SELECT r.id FROM #{Repository.table_name} r" +
+      " WHERE r.id = (SELECT max(r1.id) FROM #{Repository.table_name} r1 WHERE r1.project_id = r.project_id)").each do |i|
+        Repository.update_all(["is_default = ?", true], ["id = ?", i])
+    end
+  end
+
+  def self.down
+    Repository.update_all(["is_default = ?", false])
+  end
+end
index 61c522a5aba99dd3d4cf4cacc08fbb354698fd1c..8410da60ffabbd87ce049532e286a97e1c799a01 100644 (file)
@@ -108,6 +108,7 @@ a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
 a img{ border: 0; }
 
 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
+a.repository.selected {font-weight:bold;}
 
 /***** Tables *****/
 table.list { border: 1px solid #e4e4e4;  border-collapse: collapse; width: 100%; margin-bottom: 4px; }
index 7b596ea361dd4eebb97f00e1712dcdb7db65c938..961bee7ae234fc9b3b768a100be3b5bc3ae2df0d 100644 (file)
@@ -1,5 +1,5 @@
 class Repository < ActiveRecord::Base
   generator_for :type => 'Subversion'
   generator_for :url, :start => 'file:///test/svn'
-
+  generator_for :identifier, :start => 'repo1'
 end
index 61930f395fbee59db045130fe61458da374cbf8a..ef7285f61e81a6e11405ff261a1841a3a6db859c 100644 (file)
@@ -7,6 +7,7 @@ repositories_001:
   password: ""
   login: ""
   type: Subversion
+  is_default: true
 repositories_002: 
   project_id: 2
   url: svn://localhost/test
@@ -15,3 +16,4 @@ repositories_002:
   password: ""
   login: ""
   type: Subversion
+  is_default: true
index 4310c76309e1b7db3e61d8c09296f9793c900938..817e16e2aac727b6b2c05d94b336a13524028db8 100644 (file)
@@ -43,19 +43,12 @@ class RepositoriesControllerTest < ActionController::TestCase
     assert_tag 'input', :attributes => {:name => 'repository[url]'}
   end
 
-  # TODO: remove it when multiple SCM support is added
-  def test_new_with_existing_repository
-    @request.session[:user_id] = 1
-    get :new, :project_id => 'ecookbook'
-    assert_response 302
-  end
-
   def test_create
     @request.session[:user_id] = 1
     assert_difference 'Repository.count' do
       post :create, :project_id => 'subproject1',
            :repository_scm => 'Subversion',
-           :repository => {:url => 'file:///test'}
+           :repository => {:url => 'file:///test', :is_default => '1', :identifier => ''}
     end
     assert_response 302
     repository = Repository.first(:order => 'id DESC')
@@ -113,9 +106,25 @@ class RepositoriesControllerTest < ActionController::TestCase
     get :revisions, :id => 1
     assert_response :success
     assert_template 'revisions'
+    assert_equal Repository.find(10), assigns(:repository)
+    assert_not_nil assigns(:changesets)
+  end
+
+  def test_revisions_for_other_repository
+    repository = Repository::Subversion.create!(:project_id => 1, :identifier => 'foo', :url => 'file:///foo')
+
+    get :revisions, :id => 1, :repository_id => 'foo'
+    assert_response :success
+    assert_template 'revisions'
+    assert_equal repository, assigns(:repository)
     assert_not_nil assigns(:changesets)
   end
 
+  def test_revisions_for_invalid_repository
+    get :revisions, :id => 1, :repository_id => 'foo'
+    assert_response 404
+  end
+
   def test_revision
     get :revision, :id => 1, :rev => 1
     assert_response :success
index edc49e70bafa6090f961c095c5d81a08231f4e20..d2573eb3631217752e676fff48a3f05525612a28 100644 (file)
@@ -70,6 +70,29 @@ class RoutingRepositoriesTest < ActionController::IntegrationTest
           :path => "/projects/redmine/repository/statistics" },
         { :controller => 'repositories', :action => 'stats', :id => 'redmine' }
      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/graph" },
+        { :controller => 'repositories', :action => 'graph', :id => 'redmine' }
+     )
+  end
+
+  def test_repositories_with_repository_id
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo" },
+        { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/statistics" },
+        { :controller => 'repositories', :action => 'stats', :id => 'redmine', :repository_id => 'foo' }
+     )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/graph" },
+        { :controller => 'repositories', :action => 'graph', :id => 'redmine', :repository_id => 'foo' }
+     )
   end
 
   def test_repositories_revisions
@@ -153,6 +176,87 @@ class RoutingRepositoriesTest < ActionController::IntegrationTest
       )
   end
 
+  def test_repositories_revisions_with_repository_id
+    empty_path_param = []
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions" },
+        { :controller => 'repositories', :action => 'revisions', :id => 'redmine', :repository_id => 'foo' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions.atom" },
+        { :controller => 'repositories', :action => 'revisions', :id => 'redmine', :repository_id => 'foo',
+          :format => 'atom' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457" },
+        { :controller => 'repositories', :action => 'revision', :id => 'redmine', :repository_id => 'foo',
+          :rev => '2457' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457/show" },
+        { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo',
+          :path => empty_path_param, :rev => '2457' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457/show/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'show', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] , :rev => '2457'}
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457/changes" },
+        { :controller => 'repositories', :action => 'changes', :id => 'redmine', :repository_id => 'foo',
+          :path => empty_path_param, :rev => '2457' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457/changes/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'changes', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] , :rev => '2457'}
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457/diff" },
+        { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo',
+          :rev => '2457' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2457/diff.diff" },
+        { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo',
+          :rev => '2457', :format => 'diff' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2/diff/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param], :rev => '2' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2/entry/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param], :rev => '2' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2/raw/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param], :rev => '2', :format => 'raw' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/revisions/2/annotate/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'annotate', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param], :rev => '2' }
+      )
+  end
+
   def test_repositories_non_revisions_path
     assert_routing(
         { :method => 'get',
@@ -192,7 +296,46 @@ class RoutingRepositoriesTest < ActionController::IntegrationTest
       )
   end
 
-private
+  def test_repositories_non_revisions_path_with_repository_id
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/diff/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'diff', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/browse/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'browse', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/entry/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/raw/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'entry', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param], :format => 'raw' }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/annotate/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'annotate', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] }
+      )
+    assert_routing(
+        { :method => 'get',
+          :path => "/projects/redmine/repository/foo/changes/#{@path_hash[:path]}" },
+        { :controller => 'repositories', :action => 'changes', :id => 'redmine', :repository_id => 'foo',
+          :path => @path_hash[:param] }
+      )
+  end
+
+  private
 
   def repository_path_hash(arr)
     hs = {}
index ad0fc9f5f4cfee6bd6dfcd469fa7ac36a632b6d4..275dc8bee124b0eb8c2e476fab2dbc8273bc5fc4 100644 (file)
@@ -50,6 +50,19 @@ class RepositoryTest < ActiveSupport::TestCase
     assert_equal repository, project.repository
   end
 
+  def test_first_repository_should_be_set_as_default
+    repository1 = Repository::Subversion.new(:project => Project.find(3), :identifier => 'svn1', :url => 'file:///svn1')
+    assert repository1.save
+    assert repository1.is_default?
+
+    repository2 = Repository::Subversion.new(:project => Project.find(3), :identifier => 'svn2', :url => 'file:///svn2')
+    assert repository2.save
+    assert !repository2.is_default?
+
+    assert_equal repository1, Project.find(3).repository
+    assert_equal [repository1, repository2], Project.find(3).repositories.sort
+  end
+
   def test_destroy
     changesets = Changeset.count(:all, :conditions => "repository_id = 10")
     changes = Change.count(:all, :conditions => "repository_id = 10",
index 4a1cb33377565f9c7dcef1bb09c83c036221ae85..a9484501df52e1e5fc15d0ce63f16dff83e72c30 100644 (file)
@@ -316,9 +316,8 @@ class UserTest < ActiveSupport::TestCase
 
   def test_destroy_should_nullify_changesets
     changeset = Changeset.create!(
-      :repository => Repository::Subversion.create!(
-        :project_id => 1,
-        :url => 'file:///var/svn'
+      :repository => Repository::Subversion.generate!(
+        :project_id => 1
       ),
       :revision => '12',
       :committed_on => Time.now,