<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>app/views/repositories/committers.rhtml</filename>
    </added>
    <added>
      <filename>db/migrate/100_add_changesets_user_id.rb</filename>
    </added>
    <added>
      <filename>db/migrate/101_populate_changesets_user_id.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -44,6 +44,20 @@ class RepositoriesController &lt; ApplicationController
     render(:update) {|page| page.replace_html &quot;tab-content-repository&quot;, :partial =&gt; 'projects/settings/repository'}
   end
   
+  def committers
+    @committers = @repository.committers
+    @users = @project.users
+    additional_user_ids = @committers.collect(&amp;:last).collect(&amp;:to_i) - @users.collect(&amp;: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 =&gt; 'committers', :id =&gt; @project
+    end
+  end
+  
   def destroy
     @repository.destroy
     redirect_to :controller =&gt; 'projects', :action =&gt; 'settings', :id =&gt; @project, :tab =&gt; 'repository'</diff>
      <filename>app/controllers/repositories_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -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,13 +19,13 @@ require 'iconv'
 
 class Changeset &lt; ActiveRecord::Base
   belongs_to :repository
+  belongs_to :user
   has_many :changes, :dependent =&gt; :delete_all
   has_and_belongs_to_many :issues
 
   acts_as_event :title =&gt; Proc.new {|o| &quot;#{l(:label_revision)} #{o.revision}&quot; + (o.comments.blank? ? '' : (': ' + o.comments))},
                 :description =&gt; :comments,
                 :datetime =&gt; :committed_on,
-                :author =&gt; :committer,
                 :url =&gt; Proc.new {|o| {:controller =&gt; 'repositories', :action =&gt; 'revision', :id =&gt; o.repository.project_id, :rev =&gt; o.revision}}
                 
   acts_as_searchable :columns =&gt; 'comments',
@@ -57,6 +57,14 @@ class Changeset &lt; ActiveRecord::Base
     repository.project
   end
   
+  def author
+    user || committer.to_s.split('&lt;').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 @@ class Changeset &lt; ActiveRecord::Base
           issue.reload
           # don't change the status is the issue is closed
           next if issue.status.is_closed?
-          user = committer_user || User.anonymous
           csettext = &quot;r#{self.revision}&quot;
           if self.scmid &amp;&amp; (! (csettext =~ /^r[0-9]+$/))
             csettext = &quot;commit:\&quot;#{self.scmid}\&quot;&quot;
           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 @@ class Changeset &lt; ActiveRecord::Base
     
     self.issues = referenced_issues.uniq
   end
-
-  # Returns the Redmine User corresponding to the committer
-  def committer_user
-    if committer &amp;&amp; committer.strip =~ /^([^&lt;]+)(&lt;(.*)&gt;)?$/
-      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 @@ class Changeset &lt; ActiveRecord::Base
   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</diff>
      <filename>app/models/changeset.rb</filename>
    </modified>
    <modified>
      <diff>@@ -96,6 +96,45 @@ class Repository &lt; ActiveRecord::Base
     self.changesets.each(&amp;:scan_comment_for_issue_ids)
   end
   
+  # Returns an array of committers usernames and associated user_id
+  def committers
+    @committers ||= Changeset.connection.select_rows(&quot;SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}&quot;)
+  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 &amp;&amp; (new_user_id.to_i != user_id.to_i)
+          new_user_id = (new_user_id.to_i &gt; 0 ? new_user_id.to_i : nil)
+          Changeset.update_all(&quot;user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }&quot;, [&quot;repository_id = ? AND committer = ?&quot;, 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 =&gt; {:committer =&gt; committer}, :include =&gt; :user)
+      if c &amp;&amp; c.user
+        c.user
+      elsif committer.strip =~ /^([^&lt;]+)(&lt;(.*)&gt;)?$/
+        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 &quot;Repository.fetch_changesets&quot;</diff>
      <filename>app/models/repository.rb</filename>
    </modified>
    <modified>
      <diff>@@ -37,6 +37,7 @@ class User &lt; ActiveRecord::Base
   has_many :members, :dependent =&gt; :delete_all
   has_many :projects, :through =&gt; :memberships
   has_many :issue_categories, :foreign_key =&gt; 'assigned_to_id', :dependent =&gt; :nullify
+  has_many :changesets, :dependent =&gt; :nullify
   has_one :preference, :dependent =&gt; :destroy, :class_name =&gt; 'UserPreference'
   has_one :rss_token, :dependent =&gt; :destroy, :class_name =&gt; 'Token', :conditions =&gt; &quot;action='feeds'&quot;
   belongs_to :auth_source</diff>
      <filename>app/models/user.rb</filename>
    </modified>
    <modified>
      <diff>@@ -2,7 +2,7 @@
     &lt;div class=&quot;changeset &lt;%= cycle('odd', 'even') %&gt;&quot;&gt;
     &lt;p&gt;&lt;%= link_to(&quot;#{l(:label_revision)} #{changeset.revision}&quot;,
                 :controller =&gt; 'repositories', :action =&gt; 'revision', :id =&gt; @project, :rev =&gt; changeset.revision) %&gt;&lt;br /&gt;
-        &lt;span class=&quot;author&quot;&gt;&lt;%= authoring(changeset.committed_on, changeset.committer) %&gt;&lt;/span&gt;&lt;/p&gt;
+        &lt;span class=&quot;author&quot;&gt;&lt;%= authoring(changeset.committed_on, changeset.author) %&gt;&lt;/span&gt;&lt;/p&gt;
     &lt;%= textilizable(changeset, :comments) %&gt;
     &lt;/div&gt;
 &lt;% end %&gt;</diff>
      <filename>app/views/issues/_changesets.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -79,7 +79,7 @@
 	    pdf.Ln	
 		for changeset in @issue.changesets
 			pdf.SetFontStyle('B',8)
-		    pdf.Cell(190,5, format_time(changeset.committed_on) + &quot; - &quot; + changeset.committer)
+		    pdf.Cell(190,5, format_time(changeset.committed_on) + &quot; - &quot; + changeset.author)
 		    pdf.Ln
 			unless changeset.comments.blank?
 				pdf.SetFontStyle('',8)</diff>
      <filename>app/views/issues/show.rfpdf</filename>
    </modified>
    <modified>
      <diff>@@ -11,10 +11,13 @@
 &lt;/div&gt;
 
 &lt;div class=&quot;contextual&quot;&gt;
+&lt;% if @repository &amp;&amp; !@repository.new_record? %&gt;
+&lt;%= link_to(l(:label_user_plural), {:controller =&gt; 'repositories', :action =&gt; 'committers', :id =&gt; @project}, :class =&gt; 'icon icon-user') %&gt;
 &lt;%= link_to(l(:button_delete), {:controller =&gt; 'repositories', :action =&gt; 'destroy', :id =&gt; @project},
             :confirm =&gt; l(:text_are_you_sure),
             :method =&gt; :post,
-            :class =&gt; 'icon icon-del') if @repository &amp;&amp; !@repository.new_record? %&gt;
+            :class =&gt; 'icon icon-del') %&gt;
+&lt;% end %&gt;
 &lt;/div&gt;
 
 &lt;%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled =&gt; @repository.nil?) %&gt;</diff>
      <filename>app/views/projects/settings/_repository.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -15,10 +15,10 @@
           :class =&gt; (entry.is_dir? ? 'icon icon-folder' : 'icon icon-file')%&gt;
 &lt;/td&gt;
 &lt;td class=&quot;size&quot;&gt;&lt;%= (entry.size ? number_to_human_size(entry.size) : &quot;?&quot;) unless entry.is_dir? %&gt;&lt;/td&gt;
+&lt;% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev &amp;&amp; entry.lastrev.identifier %&gt;
 &lt;td class=&quot;revision&quot;&gt;&lt;%= link_to(format_revision(entry.lastrev.name), :action =&gt; 'revision', :id =&gt; @project, :rev =&gt; entry.lastrev.identifier) if entry.lastrev &amp;&amp; entry.lastrev.identifier %&gt;&lt;/td&gt;
 &lt;td class=&quot;age&quot;&gt;&lt;%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev &amp;&amp; entry.lastrev.time %&gt;&lt;/td&gt;
-&lt;td class=&quot;author&quot;&gt;&lt;%=h(entry.lastrev.author.to_s.split('&lt;').first) if entry.lastrev %&gt;&lt;/td&gt;
-&lt;% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev &amp;&amp; entry.lastrev.identifier %&gt;
+&lt;td class=&quot;author&quot;&gt;&lt;%= changeset.nil? ? h(entry.lastrev.author.to_s.split('&lt;').first) : changeset.author if entry.lastrev %&gt;&lt;/td&gt;
 &lt;td class=&quot;comments&quot;&gt;&lt;%=h truncate(changeset.comments, 50) unless changeset.nil? %&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;% end %&gt;</diff>
      <filename>app/views/repositories/_dir_list_content.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -17,7 +17,7 @@
 &lt;td class=&quot;checkbox&quot;&gt;&lt;%= radio_button_tag('rev', changeset.revision, (line_num==1), :id =&gt; &quot;cb-#{line_num}&quot;, :onclick =&gt; &quot;$('cbto-#{line_num+1}').checked=true;&quot;) if show_diff &amp;&amp; (line_num &lt; revisions.size) %&gt;&lt;/td&gt;
 &lt;td class=&quot;checkbox&quot;&gt;&lt;%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id =&gt; &quot;cbto-#{line_num}&quot;, :onclick =&gt; &quot;if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}&quot;) if show_diff &amp;&amp; (line_num &gt; 1) %&gt;&lt;/td&gt;
 &lt;td class=&quot;committed_on&quot;&gt;&lt;%= format_time(changeset.committed_on) %&gt;&lt;/td&gt;
-&lt;td class=&quot;author&quot;&gt;&lt;%=h changeset.committer.to_s.split('&lt;').first %&gt;&lt;/td&gt;
+&lt;td class=&quot;author&quot;&gt;&lt;%=h changeset.author %&gt;&lt;/td&gt;
 &lt;td class=&quot;comments&quot;&gt;&lt;%= textilizable(truncate_at_line_break(changeset.comments)) %&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;% line_num += 1 %&gt;</diff>
      <filename>app/views/repositories/_revisions.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -22,7 +22,7 @@
 &lt;h2&gt;&lt;%= l(:label_revision) %&gt; &lt;%= format_revision(@changeset.revision) %&gt;&lt;/h2&gt;
 
 &lt;p&gt;&lt;% if @changeset.scmid %&gt;ID: &lt;%= @changeset.scmid %&gt;&lt;br /&gt;&lt;% end %&gt;
-&lt;em&gt;&lt;%= @changeset.committer.to_s.split('&lt;').first %&gt;, &lt;%= format_time(@changeset.committed_on) %&gt;&lt;/em&gt;&lt;/p&gt;
+&lt;span class=&quot;author&quot;&gt;&lt;%= authoring(@changeset.committed_on, @changeset.author) %&gt;&lt;/span&gt;&lt;/p&gt;
 
 &lt;%= textilizable @changeset.comments %&gt;
 </diff>
      <filename>app/views/repositories/revision.rhtml</filename>
    </modified>
    <modified>
      <diff>@@ -689,3 +689,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/bg.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/ca.yml</filename>
    </modified>
    <modified>
      <diff>@@ -694,3 +694,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/cs.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/da.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/de.yml</filename>
    </modified>
    <modified>
      <diff>@@ -665,6 +665,7 @@ text_user_wrote: '%s wrote:'
 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: &quot;Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them.&quot;
+text_repository_usernames_mapping: &quot;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.&quot;
 
 default_role_manager: Manager
 default_role_developper: Developer</diff>
      <filename>lang/en.yml</filename>
    </modified>
    <modified>
      <diff>@@ -692,3 +692,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/es.yml</filename>
    </modified>
    <modified>
      <diff>@@ -689,3 +689,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/fi.yml</filename>
    </modified>
    <modified>
      <diff>@@ -665,6 +665,7 @@ text_user_wrote: '%s a &#233;crit:'
 text_enumeration_destroy_question: 'Cette valeur est affect&#233;e &#224; %d objets.'
 text_enumeration_category_reassign_to: 'R&#233;affecter les objets &#224; cette valeur:'
 text_email_delivery_not_configured: &quot;L'envoi de mail n'est pas configur&#233;, les notifications sont d&#233;sactiv&#233;es.\nConfigurez votre serveur SMTP dans config/email.yml et red&#233;marrez l'application pour les activer.&quot;
+text_repository_usernames_mapping: &quot;Vous pouvez s&#233;lectionner ou modifier l'utilisateur Redmine associ&#233; &#224; chaque nom d'utilisateur figurant dans l'historique du d&#233;p&#244;t.\nLes utilisateurs avec le m&#234;me identifiant ou la m&#234;me adresse mail seront automatiquement associ&#233;s.&quot;
 
 default_role_manager: Manager
 default_role_developper: D&#233;veloppeur</diff>
      <filename>lang/fr.yml</filename>
    </modified>
    <modified>
      <diff>@@ -689,3 +689,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/he.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Id&#337;napl&#243;k szerkeszt&#233;se
 permission_edit_own_issue_notes: Saj&#225;t jegyzetek szerkeszt&#233;se
 setting_gravatar_enabled: Felhaszn&#225;l&#243;i f&#233;nyk&#233;pek enged&#233;lyez&#233;se
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/hu.yml</filename>
    </modified>
    <modified>
      <diff>@@ -689,3 +689,4 @@ permission_edit_time_entries: Modifica time logs
 permission_edit_own_issue_notes: Modifica proprie note
 setting_gravatar_enabled: Usa icone utente Gravatar
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/it.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/ja.yml</filename>
    </modified>
    <modified>
      <diff>@@ -689,3 +689,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/ko.yml</filename>
    </modified>
    <modified>
      <diff>@@ -691,3 +691,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/lt.yml</filename>
    </modified>
    <modified>
      <diff>@@ -691,3 +691,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/nl.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/no.yml</filename>
    </modified>
    <modified>
      <diff>@@ -724,3 +724,4 @@ permission_edit_own_issue_notes: Edycja w&#322;asnych notatek
 setting_gravatar_enabled: U&#380;ywaj ikon u&#380;ytkownik&#243;w Gravatar
 
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/pl.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Editar tempo gasto
 permission_edit_own_issue_notes: Editar pr&#243;prias notas
 setting_gravatar_enabled: Usar &#237;cones do Gravatar
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/pt-br.yml</filename>
    </modified>
    <modified>
      <diff>@@ -691,3 +691,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/pt.yml</filename>
    </modified>
    <modified>
      <diff>@@ -689,3 +689,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/ro.yml</filename>
    </modified>
    <modified>
      <diff>@@ -722,3 +722,4 @@ text_user_mail_option: &quot;&#1044;&#1083;&#1103; &#1085;&#1077;&#1074;&#1099;&#1073;&#1088;&#1072;&#1085;&#1085;&#1099;&#1093; &#1087;&#1088;&#1086;&#1077;&#1082;&#1090;&#1086;&#1074;, &#1042;&#1099; &#1073;
 text_user_wrote: '%s &#1085;&#1072;&#1087;&#1080;&#1089;&#1072;&#1083;(&#1072;):'
 text_wiki_destroy_confirmation: &#1042;&#1099; &#1091;&#1074;&#1077;&#1088;&#1077;&#1085;&#1099;, &#1095;&#1090;&#1086; &#1093;&#1086;&#1090;&#1080;&#1090;&#1077; &#1091;&#1076;&#1072;&#1083;&#1080;&#1090;&#1100; &#1076;&#1072;&#1085;&#1085;&#1091;&#1102; Wiki &#1080; &#1074;&#1089;&#1077; &#1077;&#1077; &#1089;&#1086;&#1076;&#1077;&#1088;&#1078;&#1080;&#1084;&#1086;&#1077;?
 text_workflow_edit: &#1042;&#1099;&#1073;&#1077;&#1088;&#1080;&#1090;&#1077; &#1088;&#1086;&#1083;&#1100; &#1080; &#1090;&#1088;&#1077;&#1082;&#1077;&#1088; &#1076;&#1083;&#1103; &#1088;&#1077;&#1076;&#1072;&#1082;&#1090;&#1080;&#1088;&#1086;&#1074;&#1072;&#1085;&#1080;&#1103; &#1087;&#1086;&#1089;&#1083;&#1077;&#1076;&#1086;&#1074;&#1072;&#1090;&#1077;&#1083;&#1100;&#1085;&#1086;&#1089;&#1090;&#1080; &#1089;&#1086;&#1089;&#1090;&#1086;&#1103;&#1085;&#1080;&#1081;
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/ru.yml</filename>
    </modified>
    <modified>
      <diff>@@ -694,3 +694,4 @@ permission_edit_time_entries: Edit&#225;cia &#269;asov&#253;ch z&#225;znamov
 permission_edit_own_issue_notes: Edit&#225;cia vlastn&#253;ch pozn&#225;mok &#250;lohy
 setting_gravatar_enabled: Pou&#382;itie u&#382;ivate&#318;sk&#253;ch Gravatar ikon
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/sk.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/sr.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/sv.yml</filename>
    </modified>
    <modified>
      <diff>@@ -692,3 +692,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/th.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/tr.yml</filename>
    </modified>
    <modified>
      <diff>@@ -691,3 +691,4 @@ permission_edit_time_entries: Edit time logs
 permission_edit_own_issue_notes: Edit own notes
 setting_gravatar_enabled: Use Gravatar user icons
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/uk.yml</filename>
    </modified>
    <modified>
      <diff>@@ -690,3 +690,4 @@ permission_save_queries: Save queries
 permission_edit_time_entries: Edit time logs
 permission_edit_own_time_entries: Edit own time logs
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/vn.yml</filename>
    </modified>
    <modified>
      <diff>@@ -691,3 +691,4 @@ enumeration_issue_priorities: &#38917;&#30446;&#20778;&#20808;&#27402;
 enumeration_doc_categories: &#25991;&#20214;&#20998;&#39006;
 enumeration_activities: &#27963;&#21205; (&#26178;&#38291;&#36861;&#36452;)
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/zh-tw.yml</filename>
    </modified>
    <modified>
      <diff>@@ -691,3 +691,4 @@ enumeration_issue_priorities: &#38382;&#39064;&#20248;&#20808;&#32423;
 enumeration_doc_categories: &#25991;&#26723;&#31867;&#21035;
 enumeration_activities: &#27963;&#21160;&#65288;&#26102;&#38388;&#36319;&#36394;&#65289;
 label_example: Example
+text_repository_usernames_mapping: &quot;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.&quot;</diff>
      <filename>lang/zh.yml</filename>
    </modified>
    <modified>
      <diff>@@ -88,7 +88,7 @@ Redmine::AccessControl.map do |map|
   end
     
   map.project_module :repository do |map|
-    map.permission :manage_repository, {:repositories =&gt; [:edit, :destroy]}, :require =&gt; :member
+    map.permission :manage_repository, {:repositories =&gt; [:edit, :committers, :destroy]}, :require =&gt; :member
     map.permission :browse_repository, :repositories =&gt; [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
     map.permission :view_changesets, :repositories =&gt; [:show, :revisions, :revision]
     map.permission :commit_access, {}</diff>
      <filename>lib/redmine.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,6 +7,7 @@ changesets_001:
   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 @@ changesets_002:
   comments: 'This commit fixes #1, #2 and references #1 &amp; #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 @@ changesets_003:
     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 @@ changesets_004:
     IssueID 4 2
   repository_id: 10
   committer: dlopper
+  user_id: 3
   
\ No newline at end of file</diff>
      <filename>test/fixtures/changesets.yml</filename>
    </modified>
    <modified>
      <diff>@@ -43,6 +43,7 @@ roles_001:
     - :view_files
     - :manage_files
     - :browse_repository
+    - :manage_repository
     - :view_changesets
 
   position: 1</diff>
      <filename>test/fixtures/roles.yml</filename>
    </modified>
    <modified>
      <diff>@@ -61,4 +61,38 @@ class RepositoriesControllerTest &lt; Test::Unit::TestCase
     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 =&gt; Project.find(1).repository, :committer =&gt; 'foo', :committed_on =&gt; Time.now, :revision =&gt; 100, :comments =&gt; 'Committed by foo.')
+    
+    get :committers, :id =&gt; 1
+    assert_response :success
+    assert_template 'committers'
+    
+    assert_tag :td, :content =&gt; 'dlopper',
+                    :sibling =&gt; { :tag =&gt; 'td',
+                                  :child =&gt; { :tag =&gt; 'select', :attributes =&gt; { :name =&gt; 'committers[dlopper]' },
+                                                                :child =&gt; { :tag =&gt; 'option', :content =&gt; 'Dave Lopper',
+                                                                                              :attributes =&gt; { :value =&gt; '3', :selected =&gt; 'selected' }}}}
+    assert_tag :td, :content =&gt; 'foo',
+                    :sibling =&gt; { :tag =&gt; 'td',
+                                  :child =&gt; { :tag =&gt; 'select', :attributes =&gt; { :name =&gt; 'committers[foo]' }}}
+    assert_no_tag :td, :content =&gt; 'foo',
+                       :sibling =&gt; { :tag =&gt; 'td',
+                                     :descendant =&gt; { :tag =&gt; 'option', :attributes =&gt; { :selected =&gt; 'selected' }}}
+  end
+
+  def test_map_committers
+    @request.session[:user_id] = 2
+    # add a commit with an unknown user
+    c = Changeset.create!(:repository =&gt; Project.find(1).repository, :committer =&gt; 'foo', :committed_on =&gt; Time.now, :revision =&gt; 100, :comments =&gt; 'Committed by foo.')
+    
+    assert_no_difference &quot;Changeset.count(:conditions =&gt; 'user_id = 3')&quot; do
+      post :committers, :id =&gt; 1, :committers =&gt; { 'foo' =&gt; '2', 'dlopper' =&gt; '3'}
+      assert_redirected_to '/repositories/committers/ecookbook'
+      assert_equal User.find(2), c.reload.user
+    end
+  end
 end</diff>
      <filename>test/functional/repositories_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -40,6 +40,7 @@ class RepositoryGitTest &lt; Test::Unit::TestCase
       commit = @repository.changesets.find(:first, :order =&gt; 'committed_on ASC')
       assert_equal &quot;Initial import.\nThe repository contains 3 files.&quot;, commit.comments
       assert_equal &quot;jsmith &lt;jsmith@foo.bar&gt;&quot;, commit.committer
+      assert_equal User.find_by_login('jsmith'), commit.user
       # TODO: add a commit with commit time &lt;&gt; author time to the test repository
       assert_equal &quot;2007-12-14 09:22:52&quot;.to_time, commit.committed_on
       assert_equal &quot;2007-12-14&quot;.to_date, commit.commit_date</diff>
      <filename>test/unit/repository_git_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -127,4 +127,26 @@ class RepositoryTest &lt; Test::Unit::TestCase
     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 &quot;Changeset.count(:conditions =&gt; 'user_id &lt;&gt; 2')&quot; do
+      c = Changeset.create!(:repository =&gt; @repository, :committer =&gt; 'foo', :committed_on =&gt; Time.now, :revision =&gt; 100, :comments =&gt; 'Committed by foo.')
+      assert_nil c.user
+      @repository.committer_ids = {'foo' =&gt; '2'}
+      assert_equal User.find(2), c.reload.user
+      # committer is now mapped
+      c = Changeset.create!(:repository =&gt; @repository, :committer =&gt; 'foo', :committed_on =&gt; Time.now, :revision =&gt; 101, :comments =&gt; 'Another commit by foo.')
+      assert_equal User.find(2), c.user
+    end
+  end
+  
+  def test_auto_user_mapping_by_username
+    c = Changeset.create!(:repository =&gt; @repository, :committer =&gt; 'jsmith', :committed_on =&gt; Time.now, :revision =&gt; 100, :comments =&gt; 'Committed by john.')
+    assert_equal User.find(2), c.user
+  end
+  
+  def test_auto_user_mapping_by_email
+    c = Changeset.create!(:repository =&gt; @repository, :committer =&gt; 'john &lt;jsmith@somenet.foo&gt;', :committed_on =&gt; Time.now, :revision =&gt; 100, :comments =&gt; 'Committed by john.')
+    assert_equal User.find(2), c.user
+  end
 end</diff>
      <filename>test/unit/repository_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>f6b2be81b9cb09485a08e58fc73d9290fd544148</id>
    </parent>
  </parents>
  <author>
    <name>Jean-Philippe Lang</name>
    <email>jp_lang@yahoo.fr</email>
  </author>
  <url>http://github.com/edavis10/redmine/commit/79c33bbc838fd837e516fd60569dbcf7917bd534</url>
  <id>79c33bbc838fd837e516fd60569dbcf7917bd534</id>
  <committed-date>2008-11-10T10:59:06-08:00</committed-date>
  <authored-date>2008-11-10T10:59:06-08:00</authored-date>
  <message>Maps repository users to Redmine users (#1383).
Users with same username or email are automatically mapped. Mapping can be manually adjusted in repository settings. Multiple usernames can be mapped to the same Redmine user.

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2006 e93f8b46-1217-0410-a6f0-8f06a7374b81</message>
  <tree>20695e6f03f08925d6be3c96846668d979b4b3b3</tree>
  <committer>
    <name>Jean-Philippe Lang</name>
    <email>jp_lang@yahoo.fr</email>
  </committer>
</commit>
