logtime_changeratio_via_commit-message_extended-r1926.diff

Extended patch against r1926. - Mischa The Evil, 2008-10-06 02:12

Download (27.1 KB)

View differences:

app/models/changeset.rb (working copy)
40 40
  validates_uniqueness_of :revision, :scope => :repository_id
41 41
  validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
42 42
  
43
  after_create :parse_comment
44
  
43 45
  def revision=(r)
44 46
    write_attribute :revision, (r.nil? ? nil : r.to_s)
45 47
  end
......
57 59
    repository.project
58 60
  end
59 61
  
60
  def after_create
61
    scan_comment_for_issue_ids
62
  # This starts the comment parsing. Executed by an after_create filter
63
  def parse_comment
64
    return if comments.blank?
65

  
66
    keywords = (ref_keywords + fix_keywords)
67
    return if keywords.blank?
68

  
69
    process_issues_marked_by(keywords)
62 70
  end
63
  require 'pp'
64
  
65
  def scan_comment_for_issue_ids
66
    return if comments.blank?
67
    # keywords used to reference issues
68
    ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
69
    # keywords used to fix issues
70
    fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
71
    # status and optional done ratio applied
72
    fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
73
    done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
74
    
75
    kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
76
    return if kw_regexp.blank?
77
    
71

  
72
  # Returns the previous changeset
73
  def previous
74
    @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
75
  end
76

  
77
  # Returns the next changeset
78
  def next
79
    @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
80
  end
81

  
82
 protected
83

  
84
  # This parses the whole comment. Therefore the comment gets split into parts.
85
  def process_issues_marked_by(ticket_keywords)
78 86
    referenced_issues = []
79
    
80
    if ref_keywords.delete('*')
81
      # find any issue ID in the comments
82
      target_issue_ids = []
83
      comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
84
      referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
85
    end
86
    
87
    comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
87
    comments.scan( splitting_regexp(ticket_keywords) ).each do |match|
88 88
      action = match[0]
89 89
      target_issue_ids = match[1].scan(/\d+/)
90
      rest = match.last
91
      
90 92
      target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
91
      if fix_status && fix_keywords.include?(action.downcase)
92
        # update status of issues
93
        logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
94
        target_issues.each do |issue|
95
          # the issue may have been updated by the closure of another one (eg. duplicate)
96
          issue.reload
97
          # don't change the status is the issue is closed
98
          next if issue.status.is_closed?
99
          user = committer_user || User.anonymous
100
          csettext = "r#{self.revision}"
101
          if self.scmid && (! (csettext =~ /^r[0-9]+$/))
102
            csettext = "commit:\"#{self.scmid}\""
103
          end
104
          journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
105
          issue.status = fix_status
106
          issue.done_ratio = done_ratio if done_ratio
107
          issue.save
108
          Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
109
        end
110
      end
93
      process_part(action, target_issues, rest)
94

  
111 95
      referenced_issues += target_issues
112 96
    end
113
    
97

  
114 98
    self.issues = referenced_issues.uniq
115 99
  end
116 100

  
101
  # returns a regexp that splits the long comment into parts
102
  #
103
  # Each part starts with a valid ticket reference and 
104
  # either ends with one or ends at the end of the comment
105
  def splitting_regexp(ticket_keywords)
106
    ref_any = ticket_keywords.delete('*')
107
    joined_kw = ticket_keywords.join("|")
108
    first = "(#{joined_kw})#{ref_any ? '*' : '+' }"
109
    second = joined_kw + (ref_any ? '|#' : '')
110
    /#{first}[\s:]*(([\s,;&]*#?\d+)+)(.*?)(?=#{second}|\Z)/im 
111
  end
112

  
113
  # Process_part analyses the part and executes ticket changes, time logs etc.
114
  def process_part(action,target_issues,rest)
115
    # initialize three variables (time, ratio and timelogcomment) when advanced commit parsing is active
116
    if Setting.advanced_commit_parsing?
117
      time = extract_time!(rest)
118
      ratio = extract_ratio!(rest)
119
      timelogcomment = extract_timelogcomment!(rest)
120
      
121
      # use changeset-id as timelog-comment if time is not nil (so timelog should be created) && no timelog-comment is given && Setting.commit_timelog_default_comment?
122
      if !time.nil? && timelogcomment.nil? && Setting.commit_timelog_default_comment?
123
        timelogcomment = "r#{self.revision}"
124
      # use blank timelog-comment if time is not nil (so timelog should be created) && no timelog-comment is given && Setting.commit_timelog_default_comment isnt set
125
      elsif !time.nil? && timelogcomment.nil?
126
        timelogcomment = ""
127
      end
128
    end
129

  
130
    target_issues.each do |issue|
131
      if fix_status && action && fix_keywords.include?(action.downcase)
132
        # create an issue-journal if the issue is closed due to fix_keywords
133
        journal = init_journal(issue)    
134
        # add debug messages if issue is fixed
135
        logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
136
        # the issue may have been updated by the closure of another one (eg. duplicate)
137
        issue.reload
138
        # don't change the status if the issue is closed
139
        break if issue.status.is_closed?
140
        # update the issue-status and issue-done_ratio due to fix_keywords
141
        issue.status = fix_status
142
        issue.done_ratio = done_ratio if done_ratio
143
      elsif action && !fix_keywords.include?(action.downcase) && ratio
144
        # create an issue-journal if the issue is not updated due to fix_keywords && the issue is updated due to done_ratio_keywords (which requires Setting.advanced_commit_parsing active)
145
        journal = init_journal_r(issue)
146
        # the issue may have been updated by the closure of another one (eg. duplicate)
147
        issue.reload
148
        # don't change the done-ratio if the issue is closed
149
        break if issue.status.is_closed?
150
        # update the issue-done_ratio due to ratio_keywords
151
        issue.done_ratio = ratio
152
      elsif Setting.commit_ref_keywords == '*' && ratio
153
        # create an issue-journal if ref_keywords equals '*' && the issue is updated due to done_ratio_keywords (which requires Setting.advanced_commit_parsing active)
154
        journal = init_journal_r(issue)
155
        # the issue may have been updated by the closure of another one (eg. duplicate)
156
        issue.reload
157
        # don't change the done-ratio if the issue is closed
158
        break if issue.status.is_closed?
159
        # update the issue-done_ratio due to ratio_keywords
160
        issue.done_ratio = ratio
161
      end
162
      
163
      if time && issue.time_entries.find(:first, :conditions => ['spent_on = ? AND comments = ? AND user_id = ?',committed_on.to_date,timelogcomment[0..254],committer_user.id]).nil?
164
        time_entry = TimeEntry.new( :hours => time,
165
                                    :spent_on => committed_on.to_date,
166
                                    :activity_id => activity_id,
167
                                    :comments => timelogcomment[0..254],
168
                                    :user => committer_user)
169
        time_entry.hours /= target_issues.length
170
        issue.time_entries << time_entry
171
      end
172
      
173
      if issue.changed?
174
        issue.save
175
        Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
176
      end
177
    end
178
  end
179

  
180
  # init the journal for our issue
181
  def init_journal(issue)
182
    csettext = "r#{self.revision}"
183
    if self.scmid && (! (csettext =~ /^r[0-9]+$/))
184
      csettext = "commit:\"#{self.scmid}\""
185
    end
186
    issue.init_journal(committer_user, l(:text_status_changed_by_changeset, csettext))
187
  end
188

  
189
  # init the journal for our issue when only the issue-done_ratio is changed
190
  def init_journal_r(issue)
191
    csettext = "r#{self.revision}"
192
    if self.scmid && (! (csettext =~ /^r[0-9]+$/))
193
      csettext = "commit:\"#{self.scmid}\""
194
    end
195
    issue.init_journal(committer_user, l(:text_ratio_changed_by_changeset, csettext))
196
  end
197

  
198
  # extracts the time
199
  def extract_time!(string)
200
    extract!(/(?:#{time_keywords.join("|")})[\s:]+(\d+[.,:hm ]*\d*[m ]*)/,string)
201
  end
202

  
203
  # extracts the ratio
204
  def extract_ratio!(string)
205
    extract!(/(?:#{ratio_keywords.join("|")})[\s:]+(\d+)%?/,string)
206
  end
207

  
208
  # extracts the timelogcomment
209
  def extract_timelogcomment!(string)
210
    extract!(/(?:#{timelogcomment_keywords.join("|")})[\s:]+(.*)?/,string)
211
  end
212
  
213
  # generic extract function. Notice the !. The original string is silently manipulated
214
  def extract!(regexp,string)
215
    if match = string.match(/(.*?)#{regexp}(.*)/mi)
216
      replacement = if match[1] && !match[1].strip.empty?
217
                      match[1].strip + ' ' + match[3].strip
218
                    else
219
                      match[3].strip
220
                    end
221
      string.replace(replacement)
222
      match[2]
223
    end
224
  end
225

  
226
  # keywords used to reference issues
227
  def ref_keywords
228
    @ref_keywords ||= Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
229
  end
230

  
231
  # keywords used to fix issues
232
  def fix_keywords
233
    @fix_keywords ||= Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
234
  end
235

  
236
  # keywords used to set the ratio of the issues
237
  def ratio_keywords
238
    @ratio_keywords ||= Setting.commit_ratio_keywords.downcase.split(',').collect(&:strip)
239
  end
240

  
241
  # keywords used to log time of an issue
242
  def time_keywords
243
    @time_keywords ||= Setting.commit_time_keywords.downcase.split(',').collect(&:strip)
244
  end
245

  
246
  # keywords used to set the comment of the timelog
247
  def timelogcomment_keywords
248
    @timelogcomment_keywords ||= Setting.commit_timelogcomment_keywords.downcase.split(',').collect(&:strip)
249
  end
250
  
251
  # status if an issue is fixed
252
  def fix_status
253
    @fix_status ||= IssueStatus.find_by_id(Setting.commit_fix_status_id)
254
  end
255

  
256
  # the ratio if an issue is fixed
257
  def done_ratio
258
    @done_ratio ||= Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
259
  end
260

  
261
  # gets the activity id for the timelog created via a commit-message from the global-settings
262
  def activity_id
263
    @activity_id ||= Setting.commit_timelog_activity_id
264
  end
265

  
117 266
  # Returns the Redmine User corresponding to the committer
267
  # or the anonymous user
118 268
  def committer_user
119
    if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
269
    @user ||= if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
120 270
      username, email = $1.strip, $3
121 271
      u = User.find_by_login(username)
122 272
      u ||= User.find_by_mail(email) unless email.blank?
123 273
      u
124
    end
274
    end || User.anonymous
125 275
  end
126 276
  
127
  # Returns the previous changeset
128
  def previous
129
    @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
130
  end
131

  
132
  # Returns the next changeset
133
  def next
134
    @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
135
  end
136
  
137 277
  # Strips and reencodes a commit log before insertion into the database
138 278
  def self.normalize_comments(str)
139 279
    to_utf8(str.to_s.strip)
app/models/repository.rb (working copy)
92 92
    @latest_changeset ||= changesets.find(:first)
93 93
  end
94 94
    
95
  def scan_changesets_for_issue_ids
96
    self.changesets.each(&:scan_comment_for_issue_ids)
97
  end
98
  
99 95
  # fetch new changesets for all repositories
100 96
  # can be called periodically by an external script
101 97
  # eg. ruby script/runner "Repository.fetch_changesets"
......
103 99
    find(:all).each(&:fetch_changesets)
104 100
  end
105 101
  
106
  # scan changeset comments to find related and fixed issues for all repositories
107
  def self.scan_changesets_for_issue_ids
108
    find(:all).each(&:scan_changesets_for_issue_ids)
109
  end
110

  
111 102
  def self.scm_name
112 103
    'Abstract'
113 104
  end
app/views/settings/_repositories.rhtml (working copy)
32 32
<br /><em><%= l(:text_comma_separated) %></em></p>
33 33
</fieldset>
34 34

  
35
<fieldset class="box tabular settings"><legend><%= l(:text_issues_advanced_commit_message_keywords) %></legend>
36
<p><label><%= l(:setting_advanced_commit_keywords) %></label>
37
<%= check_box_tag 'settings[advanced_commit_parsing]', 1, Setting.advanced_commit_parsing?, :onclick=>"Element.toggle('advanced_keywords'); return true;" %><%= hidden_field_tag 'settings[advanced_commit_parsing]', 0 %></p>
38

  
39
<div id="advanced_keywords" <%= Setting.advanced_commit_parsing? ? '' : 'style="display:none"' %>>
40
<p><label><%= l(:setting_commit_time_keywords) %></label>
41
<%= text_field_tag 'settings[commit_time_keywords]', Setting.commit_time_keywords, :size => 30 %>
42
<br /><em><%= l(:text_comma_separated) %></em></p>
43
<p><label><%= l(:setting_commit_timelogcomment_keywords) %></label>
44
<%= text_field_tag 'settings[commit_timelogcomment_keywords]', Setting.commit_timelogcomment_keywords, :size => 30 %>
45
<br /><em><%= l(:text_comma_separated) %></em></p>
46
<p><label><%= l(:setting_commit_timelog_activity_id) %></label>
47
<%= select_tag 'settings[commit_timelog_activity_id]', options_for_select(Enumeration::get_values('ACTI').collect{|enumeration| [enumeration.name, enumeration.id.to_s]}, Setting.commit_timelog_activity_id) %></p>
48
<p><label><%= l(:setting_commit_timelog_default_comment) %></label>
49
<%= check_box_tag 'settings[commit_timelog_default_comment]', 1, Setting.commit_timelog_default_comment? %><%= hidden_field_tag 'settings[commit_timelog_default_comment]', 0 %></p>
50
<p><label><%= l(:setting_commit_ratio_keywords) %></label>
51
<%= text_field_tag 'settings[commit_ratio_keywords]', Setting.commit_ratio_keywords, :size => 30 %>
52
<br /><em><%= l(:text_comma_separated) %></em></p>
53
</div>
54
</fieldset>
55

  
35 56
<%= submit_tag l(:button_save) %>
36 57
<% end %>
config/settings.yml (working copy)
81 81
  default: 0
82 82
commit_fix_done_ratio:
83 83
  default: 100
84
advanced_commit_parsing:
85
  default: 0
86
commit_time_keywords:
87
  default: 'time,log'
88
commit_ratio_keywords:
89
  default: 'done,ratio'
90
commit_timelogcomment_keywords:
91
  default: 'timelogcomment'
92
commit_timelog_activity_id:
93
  format: int
94
  default: 0
95
commit_timelog_default_comment:
96
  default: 0
84 97
# autologin duration in days
85 98
# 0 means autologin is disabled 
86 99
autologin:
lang/en.yml (working copy)
641 641
enumeration_issue_priorities: Issue priorities
642 642
enumeration_doc_categories: Document categories
643 643
enumeration_activities: Activities (time tracking)
644

  
645
setting_advanced_commit_keywords: Enable advanced keywords
646
setting_commit_time_keywords: Time logging keywords
647
setting_commit_ratio_keywords: Done ratio keywords
648
setting_commit_timelogcomment_keywords: Time logging comment keywords
649
text_issues_advanced_commit_message_keywords: Logging time and setting issue ratios via commit messages
650
setting_commit_timelog_activity_id: Default Activity-type for timelogs created via commit messages
651
text_ratio_changed_by_changeset: Partially applied in changeset %s.
652
setting_commit_timelog_default_comment: Use changeset-identifier if timelog comment is missing (else comment is blank)
test/unit/changeset_test.rb (working copy)
32 32
    c = Changeset.new(:repository => Project.find(1).repository,
33 33
                      :committed_on => Time.now,
34 34
                      :comments => 'New commit (#2). Fixes #1')
35
    c.scan_comment_for_issue_ids
36
    
35

  
36
    c.parse_comment
37

  
37 38
    assert_equal [1, 2], c.issue_ids.sort
38 39
    fixed = Issue.find(1)
39 40
    assert fixed.closed?
40 41
    assert_equal 90, fixed.done_ratio
41 42
  end
43

  
44
  def test_not_associate_any_mentioned_tickets
45
    Setting.commit_ref_keywords = 'key'
46
    
47
    c = Changeset.new(:repository => Project.find(1).repository,
48
                      :committed_on => Time.now,
49
                      :comments => 'New commit (#2). #1')
50

  
51
    c.parse_comment
52

  
53
    assert_equal [], c.issue_ids
54
  end
42 55
  
56
  def test_fixes_multiple_tickets
57
    Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
58
    Setting.commit_fix_keywords = 'fixes , closes'
59
    
60
    c = Changeset.new(:repository => Project.find(1).repository,
61
                      :committed_on => Time.now,
62
                      :comments => 'Fixes #1,#2')
63

  
64
    c.parse_comment
65

  
66
    assert_equal [1, 2], c.issue_ids.sort
67
  end
68
  
43 69
  def test_ref_keywords_any_line_start
44 70
    Setting.commit_ref_keywords = '*'
45 71

  
46 72
    c = Changeset.new(:repository => Project.find(1).repository,
47 73
                      :committed_on => Time.now,
48 74
                      :comments => '#1 is the reason of this commit')
49
    c.scan_comment_for_issue_ids
75
    c.parse_comment
50 76

  
51 77
    assert_equal [1], c.issue_ids.sort
52 78
  end
53 79

  
80
  def test_log_time_without_issues_should_do_nothing
81
    count = TimeEntry.count
82
    Setting.advanced_commit_parsing = 1
83
    Setting.commit_ref_keywords = '*'
84

  
85
    c = Changeset.new(:repository => Project.find(1).repository,
86
                      :committed_on => Time.now,
87
                      :comments => 'time 3,5')
88
    c.parse_comment
89

  
90
    assert_equal count, TimeEntry.count
91
  end
92

  
93
  def test_log_time_should_work
94
    Setting.advanced_commit_parsing = 1
95
    Setting.commit_ref_keywords = '*'
96
    
97
    comment = <<-EOL
98
#1
99
time 3,5
100
EOL
101
    
102
    time = Time.now
103
    c = Changeset.new(:repository => Project.find(1).repository,
104
                      :committed_on => time,
105
                      :comments => comment)
106

  
107
    c.parse_comment
108

  
109
    time_entry = TimeEntry.last
110
    assert_equal 3.5, time_entry.hours
111
  end
112

  
113
  def test_log_time_should_not_create_duplicate_logs
114
    count = TimeEntry.count
115
    Setting.advanced_commit_parsing = 1
116
    Setting.commit_ref_keywords = '*'
117

  
118
    committed_on = Time.now
119
    
120
    comment = <<-EOL
121
#1
122
time 3,5
123
EOL
124
    
125
    c = Changeset.new(:repository => Project.find(1).repository,
126
                      :committed_on => committed_on,
127
                      :comments => comment)
128

  
129
    c.parse_comment
130
    c.parse_comment
131

  
132
    assert_equal count+1, TimeEntry.count
133
  end
134

  
135
  def test_log_time_splits_the_time_equally
136
    Setting.commit_fix_keywords = 'fixes , closes'
137
    Setting.advanced_commit_parsing = 1
138

  
139
    comment = <<-EOL
140
fixes #1,#2
141
time 3
142
timelogcomment the comment
143
EOL
144
    
145
    c = Changeset.new(:repository => Project.find(1).repository,
146
                      :committed_on => Time.now,
147
                      :comments => comment)
148

  
149
    c.parse_comment
150
    
151

  
152
    time_entry = TimeEntry.find(:first, :order => 'id DESC')
153
    assert_equal 1.5, time_entry.hours
154
    assert_equal 'the comment', time_entry.comments
155

  
156
  end
157

  
158
  def test_extract_time
159
    c = Changeset.new
160
    time_formats =  [ "2", "21.1", "2,1","7:12", "10h", "10 h", "45m", "45 m", "3h15", "3h 15", "3 h 15", "3 h 15m", "3 h 15 m"]
161
    
162
    time_formats.each do |format|
163
      assert_equal format, c.send(:extract_time!, "time #{format}")
164
    end
165
  end
166

  
167
  def test_set_done_ratio
168
    Setting.commit_ref_keywords = '*'
169
    Setting.advanced_commit_parsing = 1
170

  
171
    comment = <<-EOL
172
#1
173
done 50%
174
#2
175
done 40
176
EOL
177
    
178
    c = Changeset.new(:repository => Project.find(1).repository,
179
                      :committed_on => Time.now,
180
                      :comments => comment)
181

  
182
    c.parse_comment
183

  
184
    assert_equal [1, 2], c.issue_ids.sort
185
    assert_equal 50, Issue.find(1).done_ratio
186
    assert_equal 40, Issue.find(2).done_ratio
187
  end
188

  
189
  def test_set_committer_identified_by_email
190
    Setting.commit_fix_keywords = 'fixes'
191
    user = User.find(:first)
192
    
193
    c = Changeset.new(:repository => Project.find(1).repository,
194
                      :committed_on => Time.now,
195
                      :committer => "arnie<#{user.mail}>",
196
                      :comments => 'Fixes #1')
197

  
198
    c.parse_comment
199

  
200
    fixed = Issue.find(1)
201
    assert fixed.closed?
202
    assert_equal user, fixed.journals.find(:first, :order => 'id DESC').user
203
  end
204

  
205
  def test_set_committer_identified_by_login
206
    Setting.commit_fix_keywords = 'fixes'
207
    user = User.find(:first)
208
    
209
    c = Changeset.new(:repository => Project.find(1).repository,
210
                      :committed_on => Time.now,
211
                      :committer => user.login,
212
                      :comments => 'Fixes #1')
213

  
214
    c.parse_comment
215

  
216
    fixed = Issue.find(1)
217
    assert fixed.closed?
218
    assert_equal user, fixed.journals.find(:first, :order => 'id DESC').user
219
  end
220

  
221
  def test_set_annonymous_if_committer_unknown
222
    Setting.commit_fix_keywords = 'fixes'
223

  
224
    c = Changeset.new(:repository => Project.find(1).repository,
225
                      :committed_on => Time.now,
226
                      :committer => 'arnie',
227
                      :comments => 'Fixes #1')
228

  
229
    c.parse_comment
230

  
231
    fixed = Issue.find(1)
232
    assert fixed.closed?
233
    assert_equal User.anonymous, fixed.journals.find(:first, :order => 'id DESC').user
234
  end
235

  
236
  def test_mail_deliveries
237
    ActionMailer::Base.deliveries.clear
238

  
239
    Setting.commit_fix_keywords = 'fixes'
240
    
241
    c = Changeset.new(:repository => Project.find(1).repository,
242
                      :committed_on => Time.now,
243
                      :comments => 'Fixes #1')
244

  
245
    c.parse_comment
246
    
247
    assert_equal 1, ActionMailer::Base.deliveries.size
248
  end
249

  
250
  def test_ignore_cross_refrenced_issue_ids
251
    Setting.commit_fix_keywords = 'fixes'
252

  
253
    c = Changeset.new(:repository => Project.find(1).repository,
254
                      :committed_on => Time.now,
255
                      :comments => 'Fixes #1234')
256

  
257
    c.parse_comment
258

  
259
    assert_equal [], c.issue_ids.sort
260
  end
261

  
54 262
  def test_previous
55 263
    changeset = Changeset.find_by_revision('3')
56 264
    assert_equal Changeset.find_by_revision('2'), changeset.previous
......
70 278
    changeset = Changeset.find_by_revision('4')
71 279
    assert_nil changeset.next
72 280
  end
281

  
282
  def test_for_changeset_comments_strip
283
    comment = <<-COMMENT
284
    This is a loooooooooooooooooooooooooooong comment                                                   
285
                                                                                                       
286
                                                                                            
287
    COMMENT
288
    changeset = Changeset.new :comments => comment
289
    assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
290
  end
291
  
292

  
73 293
end
test/unit/repository_test.rb (working copy)
65 65
    Setting.delete_all
66 66
  end
67 67
  
68
  def test_scan_changesets_for_issue_ids
69
    # choosing a status to apply to fix issues
70
    Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
71
    Setting.commit_fix_done_ratio = "90"
72
    Setting.commit_ref_keywords = 'refs , references, IssueID'
73
    Setting.commit_fix_keywords = 'fixes , closes'
74
    Setting.default_language = 'en'
75
    ActionMailer::Base.deliveries.clear
76
    
77
    # make sure issue 1 is not already closed
78
    fixed_issue = Issue.find(1)
79
    assert !fixed_issue.status.is_closed?
80
    old_status = fixed_issue.status
81
        
82
    Repository.scan_changesets_for_issue_ids
83
    assert_equal [101, 102], Issue.find(3).changeset_ids
84
    
85
    # fixed issues
86
    fixed_issue.reload
87
    assert fixed_issue.status.is_closed?
88
    assert_equal 90, fixed_issue.done_ratio
89
    assert_equal [101], fixed_issue.changeset_ids
90
    
91
    # issue change
92
    journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
93
    assert_equal User.find_by_login('dlopper'), journal.user
94
    assert_equal 'Applied in changeset r2.', journal.notes
95
    
96
    # 2 email notifications
97
    assert_equal 2, ActionMailer::Base.deliveries.size
98
    mail = ActionMailer::Base.deliveries.first
99
    assert_kind_of TMail::Mail, mail
100
    assert mail.subject.starts_with?("[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
101
    assert mail.body.include?("Status changed from #{old_status} to #{fixed_issue.status}")
102
    
103
    # ignoring commits referencing an issue of another project
104
    assert_equal [], Issue.find(4).changesets
105
  end
106
  
107
  def test_for_changeset_comments_strip
108
    repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
109
    comment = <<-COMMENT
110
    This is a loooooooooooooooooooooooooooong comment                                                   
111
                                                                                                       
112
                                                                                            
113
    COMMENT
114
    changeset = Changeset.new(
115
      :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
116
      :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
117
    assert( changeset.save )
118
    assert_not_equal( comment, changeset.comments )
119
    assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
120
  end
121
  
122 68
  def test_for_urls_strip
123 69
    repository = Repository::Cvs.create(:project => Project.find(4), :url => ' :pserver:login:password@host:/path/to/the/repository',
124 70
                                                                     :root_url => 'foo  ')