Index: trunk/app/controllers/repositories_controller.rb
===================================================================
--- trunk/app/controllers/repositories_controller.rb	(revision 2005)
+++ trunk/app/controllers/repositories_controller.rb	(revision 2006)
@@ -44,6 +44,20 @@
     render(:update) {|page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository'}
   end
   
+  def committers
+    @committers = @repository.committers
+    @users = @project.users
+    additional_user_ids = @committers.collect(&:last).collect(&:to_i) - @users.collect(&:id)
+    @users += User.find_all_by_id(additional_user_ids) unless additional_user_ids.empty?
+    @users.compact!
+    @users.sort!
+    if request.post?
+      @repository.committer_ids = params[:committers]
+      flash[:notice] = l(:notice_successful_update)
+      redirect_to :action => 'committers', :id => @project
+    end
+  end
+  
   def destroy
     @repository.destroy
     redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository'
Index: trunk/app/models/changeset.rb
===================================================================
--- trunk/app/models/changeset.rb	(revision 2005)
+++ trunk/app/models/changeset.rb	(revision 2006)
@@ -1,5 +1,5 @@
-# redMine - project management software
-# Copyright (C) 2006-2007  Jean-Philippe Lang
+# Redmine - project management software
+# Copyright (C) 2006-2008  Jean-Philippe Lang
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -19,6 +19,7 @@
 
 class Changeset < ActiveRecord::Base
   belongs_to :repository
+  belongs_to :user
   has_many :changes, :dependent => :delete_all
   has_and_belongs_to_many :issues
 
@@ -25,7 +26,6 @@
   acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.comments.blank? ? '' : (': ' + o.comments))},
                 :description => :comments,
                 :datetime => :committed_on,
-                :author => :committer,
                 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project_id, :rev => o.revision}}
                 
   acts_as_searchable :columns => 'comments',
@@ -57,6 +57,14 @@
     repository.project
   end
   
+  def author
+    user || committer.to_s.split('<').first
+  end
+  
+  def before_create
+    self.user = repository.find_committer_user(committer)
+  end
+  
   def after_create
     scan_comment_for_issue_ids
   end
@@ -96,12 +104,11 @@
           issue.reload
           # don't change the status is the issue is closed
           next if issue.status.is_closed?
-          user = committer_user || User.anonymous
           csettext = "r#{self.revision}"
           if self.scmid && (! (csettext =~ /^r[0-9]+$/))
             csettext = "commit:\"#{self.scmid}\""
           end
-          journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
+          journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext))
           issue.status = fix_status
           issue.done_ratio = done_ratio if done_ratio
           issue.save
@@ -113,16 +120,6 @@
     
     self.issues = referenced_issues.uniq
   end
-
-  # Returns the Redmine User corresponding to the committer
-  def committer_user
-    if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
-      username, email = $1.strip, $3
-      u = User.find_by_login(username)
-      u ||= User.find_by_mail(email) unless email.blank?
-      u
-    end
-  end
   
   # Returns the previous changeset
   def previous
@@ -140,7 +137,8 @@
   end
   
   private
-  
+
+
   def self.to_utf8(str)
     return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
     encoding = Setting.commit_logs_encoding.to_s.strip
Index: trunk/app/models/repository.rb
===================================================================
--- trunk/app/models/repository.rb	(revision 2005)
+++ trunk/app/models/repository.rb	(revision 2006)
@@ -96,6 +96,45 @@
     self.changesets.each(&:scan_comment_for_issue_ids)
   end
   
+  # Returns an array of committers usernames and associated user_id
+  def committers
+    @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
+  end
+  
+  # Maps committers username to a user ids
+  def committer_ids=(h)
+    if h.is_a?(Hash)
+      committers.each do |committer, user_id|
+        new_user_id = h[committer]
+        if new_user_id && (new_user_id.to_i != user_id.to_i)
+          new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
+          Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
+        end
+      end
+      @committers = nil
+      true
+    else
+      false
+    end
+  end
+  
+  # Returns the Redmine User corresponding to the given +committer+
+  # It will return nil if the committer is not yet mapped and if no User
+  # with the same username or email was found
+  def find_committer_user(committer)
+    if committer
+      c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
+      if c && c.user
+        c.user
+      elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
+        username, email = $1.strip, $3
+        u = User.find_by_login(username)
+        u ||= User.find_by_mail(email) unless email.blank?
+        u
+      end
+    end
+  end
+  
   # fetch new changesets for all repositories
   # can be called periodically by an external script
   # eg. ruby script/runner "Repository.fetch_changesets"
Index: trunk/app/models/user.rb
===================================================================
--- trunk/app/models/user.rb	(revision 2005)
+++ trunk/app/models/user.rb	(revision 2006)
@@ -37,6 +37,7 @@
   has_many :members, :dependent => :delete_all
   has_many :projects, :through => :memberships
   has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
+  has_many :changesets, :dependent => :nullify
   has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
   has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
   belongs_to :auth_source
Index: trunk/app/views/issues/_changesets.rhtml
===================================================================
--- trunk/app/views/issues/_changesets.rhtml	(revision 2005)
+++ trunk/app/views/issues/_changesets.rhtml	(revision 2006)
@@ -2,7 +2,7 @@
     <div class="changeset <%= cycle('odd', 'even') %>">
     <p><%= link_to("#{l(:label_revision)} #{changeset.revision}",
                 :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision) %><br />
-        <span class="author"><%= authoring(changeset.committed_on, changeset.committer) %></span></p>
+        <span class="author"><%= authoring(changeset.committed_on, changeset.author) %></span></p>
     <%= textilizable(changeset, :comments) %>
     </div>
 <% end %>
Index: trunk/app/views/issues/show.rfpdf
===================================================================
--- trunk/app/views/issues/show.rfpdf	(revision 2005)
+++ trunk/app/views/issues/show.rfpdf	(revision 2006)
@@ -79,7 +79,7 @@
 	    pdf.Ln	
 		for changeset in @issue.changesets
 			pdf.SetFontStyle('B',8)
-		    pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.committer)
+		    pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author)
 		    pdf.Ln
 			unless changeset.comments.blank?
 				pdf.SetFontStyle('',8)
Index: trunk/app/views/projects/settings/_repository.rhtml
===================================================================
--- trunk/app/views/projects/settings/_repository.rhtml	(revision 2005)
+++ trunk/app/views/projects/settings/_repository.rhtml	(revision 2006)
@@ -11,10 +11,13 @@
 </div>
 
 <div class="contextual">
+<% if @repository && !@repository.new_record? %>
+<%= link_to(l(:label_user_plural), {:controller => 'repositories', :action => 'committers', :id => @project}, :class => 'icon icon-user') %>
 <%= link_to(l(:button_delete), {:controller => 'repositories', :action => 'destroy', :id => @project},
             :confirm => l(:text_are_you_sure),
             :method => :post,
-            :class => 'icon icon-del') if @repository && !@repository.new_record? %>
+            :class => 'icon icon-del') %>
+<% end %>
 </div>
 
 <%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled => @repository.nil?) %>
Index: trunk/app/views/repositories/_dir_list_content.rhtml
===================================================================
--- trunk/app/views/repositories/_dir_list_content.rhtml	(revision 2005)
+++ trunk/app/views/repositories/_dir_list_content.rhtml	(revision 2006)
@@ -15,10 +15,10 @@
           :class => (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%>
 </td>
 <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
+<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
 <td class="revision"><%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
 <td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
-<td class="author"><%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %></td>
-<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
+<td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td>
 <td class="comments"><%=h truncate(changeset.comments, 50) unless changeset.nil? %></td>
 </tr>
 <% end %>
Index: trunk/app/views/repositories/_revisions.rhtml
===================================================================
--- trunk/app/views/repositories/_revisions.rhtml	(revision 2005)
+++ trunk/app/views/repositories/_revisions.rhtml	(revision 2006)
@@ -17,7 +17,7 @@
 <td class="checkbox"><%= radio_button_tag('rev', changeset.revision, (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.revision, (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>
-<td class="author"><%=h changeset.committer.to_s.split('<').first %></td>
+<td class="author"><%=h changeset.author %></td>
 <td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td>
 </tr>
 <% line_num += 1 %>
Index: trunk/app/views/repositories/committers.rhtml
===================================================================
--- trunk/app/views/repositories/committers.rhtml	(nonexistent)
+++ trunk/app/views/repositories/committers.rhtml	(revision 2006)
@@ -0,0 +1,29 @@
+<h2><%= l(:label_repository) %></h2>
+
+<%= simple_format(l(:text_repository_usernames_mapping)) %>
+
+<% if @committers.empty? %>
+<p class="nodata"><%= l(:label_no_data) %></p>
+<% else %>
+
+<% form_tag({}) do %>
+<table class="list">
+<thead>
+	<tr>
+		<th><%= l(:field_login) %></th>
+		<th><%= l(:label_user) %></th>
+	</tr>
+</thead>
+<tbody>
+<% @committers.each do |committer, user_id| -%>
+	<tr class="<%= cycle 'odd', 'even' %>">
+		<td><%=h committer %></td>
+		<td><%= select_tag "committers[#{committer}]", content_tag('option', "-- #{l :actionview_instancetag_blank_option} --", :value => '') + options_from_collection_for_select(@users, 'id', 'name', user_id.to_i) %></td>
+	</tr>
+<% end -%>
+</tbody>
+</table>
+<p><%= submit_tag(l(:button_update)) %></p>
+<% end %>
+
+<% end %>
\ No newline at end of file

Property changes on: trunk/app/views/repositories/committers.rhtml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: trunk/app/views/repositories/revision.rhtml
===================================================================
--- trunk/app/views/repositories/revision.rhtml	(revision 2005)
+++ trunk/app/views/repositories/revision.rhtml	(revision 2006)
@@ -22,7 +22,7 @@
 <h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
 
 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
-<em><%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %></em></p>
+<span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p>
 
 <%= textilizable @changeset.comments %>
 
Index: trunk/db/migrate/100_add_changesets_user_id.rb
===================================================================
--- trunk/db/migrate/100_add_changesets_user_id.rb	(nonexistent)
+++ trunk/db/migrate/100_add_changesets_user_id.rb	(revision 2006)
@@ -0,0 +1,9 @@
+class AddChangesetsUserId < ActiveRecord::Migration
+  def self.up
+    add_column :changesets, :user_id, :integer, :default => nil
+  end
+
+  def self.down
+    remove_column :changesets, :user_id
+  end
+end

Property changes on: trunk/db/migrate/100_add_changesets_user_id.rb
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: trunk/db/migrate/101_populate_changesets_user_id.rb
===================================================================
--- trunk/db/migrate/101_populate_changesets_user_id.rb	(nonexistent)
+++ trunk/db/migrate/101_populate_changesets_user_id.rb	(revision 2006)
@@ -0,0 +1,18 @@
+class PopulateChangesetsUserId < ActiveRecord::Migration
+  def self.up
+    committers = Changeset.connection.select_values("SELECT DISTINCT committer FROM #{Changeset.table_name}")
+    committers.each do |committer|
+      next if committer.blank?
+      if committer.strip =~ /^([^<]+)(<(.*)>)?$/
+        username, email = $1.strip, $3
+        u = User.find_by_login(username)
+        u ||= User.find_by_mail(email) unless email.blank?
+        Changeset.update_all("user_id = #{u.id}", ["committer = ?", committer]) unless u.nil?
+      end
+    end
+  end
+
+  def self.down
+    Changeset.update_all('user_id = NULL')
+  end
+end

Property changes on: trunk/db/migrate/101_populate_changesets_user_id.rb
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: trunk/lang/bg.yml
===================================================================
--- trunk/lang/bg.yml	(revision 2005)
+++ trunk/lang/bg.yml	(revision 2006)
@@ -689,3 +689,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/ca.yml
===================================================================
--- trunk/lang/ca.yml	(revision 2005)
+++ trunk/lang/ca.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/cs.yml
===================================================================
--- trunk/lang/cs.yml	(revision 2005)
+++ trunk/lang/cs.yml	(revision 2006)
@@ -694,3 +694,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/da.yml
===================================================================
--- trunk/lang/da.yml	(revision 2005)
+++ trunk/lang/da.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/de.yml
===================================================================
--- trunk/lang/de.yml	(revision 2005)
+++ trunk/lang/de.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/en.yml
===================================================================
--- trunk/lang/en.yml	(revision 2005)
+++ trunk/lang/en.yml	(revision 2006)
@@ -665,6 +665,7 @@
 text_enumeration_destroy_question: '%d objects are assigned to this value.'
 text_enumeration_category_reassign_to: 'Reassign them to this value:'
 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
 
 default_role_manager: Manager
 default_role_developper: Developer
Index: trunk/lang/es.yml
===================================================================
--- trunk/lang/es.yml	(revision 2005)
+++ trunk/lang/es.yml	(revision 2006)
@@ -692,3 +692,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/fi.yml
===================================================================
--- trunk/lang/fi.yml	(revision 2005)
+++ trunk/lang/fi.yml	(revision 2006)
@@ -689,3 +689,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/fr.yml
===================================================================
--- trunk/lang/fr.yml	(revision 2005)
+++ trunk/lang/fr.yml	(revision 2006)
@@ -665,6 +665,7 @@
 text_enumeration_destroy_question: 'Cette valeur est affectée à %d objets.'
 text_enumeration_category_reassign_to: 'Réaffecter les objets à cette valeur:'
 text_email_delivery_not_configured: "L'envoi de mail n'est pas configuré, les notifications sont désactivées.\nConfigurez votre serveur SMTP dans config/email.yml et redémarrez l'application pour les activer."
+text_repository_usernames_mapping: "Vous pouvez sélectionner ou modifier l'utilisateur Redmine associé à chaque nom d'utilisateur figurant dans l'historique du dépôt.\nLes utilisateurs avec le même identifiant ou la même adresse mail seront automatiquement associés."
 
 default_role_manager: Manager
 default_role_developper: Développeur
Index: trunk/lang/he.yml
===================================================================
--- trunk/lang/he.yml	(revision 2005)
+++ trunk/lang/he.yml	(revision 2006)
@@ -689,3 +689,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/hu.yml
===================================================================
--- trunk/lang/hu.yml	(revision 2005)
+++ trunk/lang/hu.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Saját jegyzetek szerkesztése
 setting_gravatar_enabled: Felhasználói fényképek engedélyezése
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/it.yml
===================================================================
--- trunk/lang/it.yml	(revision 2005)
+++ trunk/lang/it.yml	(revision 2006)
@@ -689,3 +689,4 @@
 permission_edit_own_issue_notes: Modifica proprie note
 setting_gravatar_enabled: Usa icone utente Gravatar
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/ja.yml
===================================================================
--- trunk/lang/ja.yml	(revision 2005)
+++ trunk/lang/ja.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/ko.yml
===================================================================
--- trunk/lang/ko.yml	(revision 2005)
+++ trunk/lang/ko.yml	(revision 2006)
@@ -689,3 +689,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/lt.yml
===================================================================
--- trunk/lang/lt.yml	(revision 2005)
+++ trunk/lang/lt.yml	(revision 2006)
@@ -691,3 +691,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/nl.yml
===================================================================
--- trunk/lang/nl.yml	(revision 2005)
+++ trunk/lang/nl.yml	(revision 2006)
@@ -691,3 +691,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/no.yml
===================================================================
--- trunk/lang/no.yml	(revision 2005)
+++ trunk/lang/no.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/pl.yml
===================================================================
--- trunk/lang/pl.yml	(revision 2005)
+++ trunk/lang/pl.yml	(revision 2006)
@@ -724,3 +724,4 @@
 setting_gravatar_enabled: Używaj ikon użytkowników Gravatar
 
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/pt-br.yml
===================================================================
--- trunk/lang/pt-br.yml	(revision 2005)
+++ trunk/lang/pt-br.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Editar próprias notas
 setting_gravatar_enabled: Usar ícones do Gravatar
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/pt.yml
===================================================================
--- trunk/lang/pt.yml	(revision 2005)
+++ trunk/lang/pt.yml	(revision 2006)
@@ -691,3 +691,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/ro.yml
===================================================================
--- trunk/lang/ro.yml	(revision 2005)
+++ trunk/lang/ro.yml	(revision 2006)
@@ -689,3 +689,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/ru.yml
===================================================================
--- trunk/lang/ru.yml	(revision 2005)
+++ trunk/lang/ru.yml	(revision 2006)
@@ -722,3 +722,4 @@
 text_user_wrote: '%s написал(а):'
 text_wiki_destroy_confirmation: Вы уверены, что хотите удалить данную Wiki и все ее содержимое?
 text_workflow_edit: Выберите роль и трекер для редактирования последовательности состояний
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/sk.yml
===================================================================
--- trunk/lang/sk.yml	(revision 2005)
+++ trunk/lang/sk.yml	(revision 2006)
@@ -694,3 +694,4 @@
 permission_edit_own_issue_notes: Editácia vlastných poznámok úlohy
 setting_gravatar_enabled: Použitie uživateľských Gravatar ikon
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/sr.yml
===================================================================
--- trunk/lang/sr.yml	(revision 2005)
+++ trunk/lang/sr.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/sv.yml
===================================================================
--- trunk/lang/sv.yml	(revision 2005)
+++ trunk/lang/sv.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/th.yml
===================================================================
--- trunk/lang/th.yml	(revision 2005)
+++ trunk/lang/th.yml	(revision 2006)
@@ -692,3 +692,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/tr.yml
===================================================================
--- trunk/lang/tr.yml	(revision 2005)
+++ trunk/lang/tr.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/uk.yml
===================================================================
--- trunk/lang/uk.yml	(revision 2005)
+++ trunk/lang/uk.yml	(revision 2006)
@@ -691,3 +691,4 @@
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/vn.yml
===================================================================
--- trunk/lang/vn.yml	(revision 2005)
+++ trunk/lang/vn.yml	(revision 2006)
@@ -690,3 +690,4 @@
 permission_edit_time_entries: Edit time logs
 permission_edit_own_time_entries: Edit own time logs
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/zh-tw.yml
===================================================================
--- trunk/lang/zh-tw.yml	(revision 2005)
+++ trunk/lang/zh-tw.yml	(revision 2006)
@@ -691,3 +691,4 @@
 enumeration_doc_categories: 文件分類
 enumeration_activities: 活動 (時間追蹤)
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lang/zh.yml
===================================================================
--- trunk/lang/zh.yml	(revision 2005)
+++ trunk/lang/zh.yml	(revision 2006)
@@ -691,3 +691,4 @@
 enumeration_doc_categories: 文档类别
 enumeration_activities: 活动（时间跟踪）
 label_example: Example
+text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
Index: trunk/lib/redmine.rb
===================================================================
--- trunk/lib/redmine.rb	(revision 2005)
+++ trunk/lib/redmine.rb	(revision 2006)
@@ -88,7 +88,7 @@
   end
     
   map.project_module :repository do |map|
-    map.permission :manage_repository, {:repositories => [:edit, :destroy]}, :require => :member
+    map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
     map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
     map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
     map.permission :commit_access, {}
Index: trunk/test/fixtures/changesets.yml
===================================================================
--- trunk/test/fixtures/changesets.yml	(revision 2005)
+++ trunk/test/fixtures/changesets.yml	(revision 2006)
@@ -7,6 +7,7 @@
   comments: My very first commit
   repository_id: 10
   committer: dlopper
+  user_id: 3
 changesets_002: 
   commit_date: 2007-04-12
   committed_on: 2007-04-12 15:14:44 +02:00
@@ -15,6 +16,7 @@
   comments: 'This commit fixes #1, #2 and references #1 & #3'
   repository_id: 10
   committer: dlopper
+  user_id: 3
 changesets_003: 
   commit_date: 2007-04-12
   committed_on: 2007-04-12 15:14:44 +02:00
@@ -25,6 +27,7 @@
     IssueID 666 3
   repository_id: 10
   committer: dlopper
+  user_id: 3
 changesets_004: 
   commit_date: 2007-04-12
   committed_on: 2007-04-12 15:14:44 +02:00
@@ -35,4 +38,5 @@
     IssueID 4 2
   repository_id: 10
   committer: dlopper
+  user_id: 3
   
\ No newline at end of file
Index: trunk/test/fixtures/roles.yml
===================================================================
--- trunk/test/fixtures/roles.yml	(revision 2005)
+++ trunk/test/fixtures/roles.yml	(revision 2006)
@@ -43,6 +43,7 @@
     - :view_files
     - :manage_files
     - :browse_repository
+    - :manage_repository
     - :view_changesets
 
   position: 1
Index: trunk/test/functional/repositories_controller_test.rb
===================================================================
--- trunk/test/functional/repositories_controller_test.rb	(revision 2005)
+++ trunk/test/functional/repositories_controller_test.rb	(revision 2006)
@@ -61,4 +61,38 @@
     assert_response :success
     assert_equal 'image/svg+xml', @response.content_type
   end
+  
+  def test_committers
+    @request.session[:user_id] = 2
+    # add a commit with an unknown user
+    Changeset.create!(:repository => Project.find(1).repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
+    
+    get :committers, :id => 1
+    assert_response :success
+    assert_template 'committers'
+    
+    assert_tag :td, :content => 'dlopper',
+                    :sibling => { :tag => 'td',
+                                  :child => { :tag => 'select', :attributes => { :name => 'committers[dlopper]' },
+                                                                :child => { :tag => 'option', :content => 'Dave Lopper',
+                                                                                              :attributes => { :value => '3', :selected => 'selected' }}}}
+    assert_tag :td, :content => 'foo',
+                    :sibling => { :tag => 'td',
+                                  :child => { :tag => 'select', :attributes => { :name => 'committers[foo]' }}}
+    assert_no_tag :td, :content => 'foo',
+                       :sibling => { :tag => 'td',
+                                     :descendant => { :tag => 'option', :attributes => { :selected => 'selected' }}}
+  end
+
+  def test_map_committers
+    @request.session[:user_id] = 2
+    # add a commit with an unknown user
+    c = Changeset.create!(:repository => Project.find(1).repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
+    
+    assert_no_difference "Changeset.count(:conditions => 'user_id = 3')" do
+      post :committers, :id => 1, :committers => { 'foo' => '2', 'dlopper' => '3'}
+      assert_redirected_to '/repositories/committers/ecookbook'
+      assert_equal User.find(2), c.reload.user
+    end
+  end
 end
Index: trunk/test/unit/repository_git_test.rb
===================================================================
--- trunk/test/unit/repository_git_test.rb	(revision 2005)
+++ trunk/test/unit/repository_git_test.rb	(revision 2006)
@@ -40,6 +40,7 @@
       commit = @repository.changesets.find(:first, :order => 'committed_on ASC')
       assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
       assert_equal "jsmith <jsmith@foo.bar>", commit.committer
+      assert_equal User.find_by_login('jsmith'), commit.user
       # TODO: add a commit with commit time <> author time to the test repository
       assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on
       assert_equal "2007-12-14".to_date, commit.commit_date
Index: trunk/test/unit/repository_test.rb
===================================================================
--- trunk/test/unit/repository_test.rb	(revision 2005)
+++ trunk/test/unit/repository_test.rb	(revision 2006)
@@ -127,4 +127,26 @@
     assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
     assert_equal 'foo', repository.root_url
   end
+  
+  def test_manual_user_mapping
+    assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
+      c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
+      assert_nil c.user
+      @repository.committer_ids = {'foo' => '2'}
+      assert_equal User.find(2), c.reload.user
+      # committer is now mapped
+      c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 101, :comments => 'Another commit by foo.')
+      assert_equal User.find(2), c.user
+    end
+  end
+  
+  def test_auto_user_mapping_by_username
+    c = Changeset.create!(:repository => @repository, :committer => 'jsmith', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
+    assert_equal User.find(2), c.user
+  end
+  
+  def test_auto_user_mapping_by_email
+    c = Changeset.create!(:repository => @repository, :committer => 'john <jsmith@somenet.foo>', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
+    assert_equal User.find(2), c.user
+  end
 end
