Project

General

Profile

Feature #1518 » patch_commit_messages4.diff

Jonas von Andrian, 2008-07-06 18:38

View differences:

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

  
143
the comment
144
EOL
145
    
146
    c = Changeset.new(:repository => Project.find(1).repository,
147
                      :committed_on => Time.now,
148
                      :comments => comment)
149

  
150
    c.parse_comment
151
    
152

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

  
157
  end
158

  
159
  def test_extract_time
160
    c = Changeset.new
161
    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"]
162
    
163
    time_formats.each do |format|
164
      assert_equal format, c.send(:extract_time!, "time #{format}")
165
    end
166
  end
167

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

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

  
183
    c.parse_comment
184

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

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

  
199
    c.parse_comment
200

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

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

  
215
    c.parse_comment
216

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

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

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

  
230
    c.parse_comment
231

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

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

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

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

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

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

  
258
    c.parse_comment
259

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

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

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

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

  
107 98
  def self.scm_name
108 99
    'Abstract'
109 100
  end
app/models/changeset.rb (working copy)
34 34
  validates_presence_of :repository_id, :revision, :committed_on, :commit_date
35 35
  validates_uniqueness_of :revision, :scope => :repository_id
36 36
  validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
37

  
38
  after_create :parse_comment
37 39
  
38 40
  def revision=(r)
39 41
    write_attribute :revision, (r.nil? ? nil : r.to_s)
......
52 54
    repository.project
53 55
  end
54 56
  
55
  def after_create
56
    scan_comment_for_issue_ids
57
  # This starts the comment parsing. Executed by an after_save filter
58
  def parse_comment
59
    return if comments.blank?
60

  
61
    keywords = (ref_keywords + fix_keywords)
62
    return if keywords.blank?
63

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

  
67
  # Returns the previous changeset
68
  def previous
69
    @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
70
  end
71

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

  
77
 protected
78

  
79
  # This parses the whole comment. Therefore the comment gets split into parts.
80
  def process_issues_marked_by(ticket_keywords)
73 81
    referenced_issues = []
74
    
75
    if ref_keywords.delete('*')
76
      # find any issue ID in the comments
77
      target_issue_ids = []
78
      comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
79
      referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
80
    end
81
    
82
    comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
82
    comments.scan( splitting_regexp(ticket_keywords) ).each do |match|
83 83
      action = match[0]
84 84
      target_issue_ids = match[1].scan(/\d+/)
85
      rest = match.last
86

  
85 87
      target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
86
      if fix_status && fix_keywords.include?(action.downcase)
88
      process_part(action, target_issues, rest)
89

  
90
      referenced_issues += target_issues
91
    end
92

  
93
    self.issues = referenced_issues.uniq
94
  end
95

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

  
108
  # Process_part analyses the part and executes ticket changes, time logs etc.
109
  def process_part(action,target_issues,rest)
110
    if Setting.advanced_commit_parsing?
111
      time = extract_time!(rest)
112
      ratio = extract_ratio!(rest)
113
    end
114

  
115
    target_issues.each do |issue|
116
      journal = init_journal(issue)
117
      if fix_status && action && fix_keywords.include?(action.downcase)
87 118
        # update status of issues
88 119
        logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
89
        target_issues.each do |issue|
90
          # the issue may have been updated by the closure of another one (eg. duplicate)
91
          issue.reload
92
          # don't change the status is the issue is closed
93
          next if issue.status.is_closed?
94
          user = committer_user || User.anonymous
95
          csettext = "r#{self.revision}"
96
          if self.scmid && (! (csettext =~ /^r[0-9]+$/))
97
            csettext = "commit:\"#{self.scmid}\""
98
          end
99
          journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
100
          issue.status = fix_status
101
          issue.done_ratio = done_ratio if done_ratio
102
          issue.save
103
          Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
120
        # the issue may have been updated by the closure of another one (eg. duplicate)
121
        issue.reload
122
        # don't change the status is the issue is closed
123
        break if issue.status.is_closed?
124
        issue.status = fix_status
125
        issue.done_ratio = done_ratio if done_ratio
126
      else
127
        if ratio and !issue.status.is_closed?
128
          issue.done_ratio = ratio
104 129
        end
105 130
      end
106
      referenced_issues += target_issues
131
      if time && issue.time_entries.find(:first, :conditions => ['spent_on = ? AND comments = ? AND user_id = ?',committed_on.to_date,rest[0..254],committer_user.id]).nil?
132
        time_entry = TimeEntry.new( :hours => time,
133
                                    :spent_on => committed_on.to_date,
134
                                    :activity_id => activity_id,
135
                                    :comments => rest[0..254],
136
                                    :user => committer_user)
137
        time_entry.hours /= target_issues.length
138
        issue.time_entries << time_entry
139
      end
140
      if issue.changed?
141
        issue.save
142
        Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
143
      end
107 144
    end
108
    
109
    self.issues = referenced_issues.uniq
110 145
  end
111 146

  
147
  # init the journal for our issue
148
  def init_journal(issue)
149
    csettext = "r#{self.revision}"
150
    if self.scmid && (! (csettext =~ /^r[0-9]+$/))
151
      csettext = "commit:\"#{self.scmid}\""
152
    end
153
    issue.init_journal(committer_user, l(:text_status_changed_by_changeset, csettext))
154
  end
155

  
156
  # extracts the time
157
  def extract_time!(string)
158
    extract!(/(?:#{time_keywords.join("|")})[\s:]+(\d+[.,:hm ]*\d*[m ]*)/,string)
159
  end
160

  
161
  # extracts the ratio
162
  def extract_ratio!(string)
163
    extract!(/(?:#{ratio_keywords.join("|")})[\s:]+(\d+)%?/,string)
164
  end
165

  
166
  # generic extract function. Notice the !. The original string is silently manipulated
167
  def extract!(regexp,string)
168
    if match = string.match(/(.*?)#{regexp}(.*)/mi)
169
      replacement = if match[1] && !match[1].strip.empty?
170
                      match[1].strip + ' ' + match[3].strip
171
                    else
172
                      match[3].strip
173
                    end
174
      string.replace(replacement)
175
      match[2]
176
    end
177
  end
178

  
179
  # keywords used to reference issues
180
  def ref_keywords
181
    @ref_keywords ||= Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
182
  end
183

  
184
  # keywords used to fix issues
185
  def fix_keywords
186
    @fix_keywords ||= Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
187
  end
188

  
189
  # keywords used to set the ratio of the issues
190
  def ratio_keywords
191
    @ratio_keywords ||= Setting.commit_ratio_keywords.downcase.split(',').collect(&:strip)
192
  end
193

  
194
  # keywords used to log time of an issue
195
  def time_keywords
196
    @time_keywords ||= Setting.commit_time_keywords.downcase.split(',').collect(&:strip)
197
  end
198

  
199
  # status if an issue is fixed
200
  def fix_status
201
    @fix_status ||= IssueStatus.find_by_id(Setting.commit_fix_status_id)
202
  end
203

  
204
  # the ratio if an issue is fixed
205
  def done_ratio
206
    @done_ratio ||= Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
207
  end
208

  
209
  # gets the default activity id or the id of the first
210
  def activity_id
211
    @activity_id ||= (Enumeration.default('ACTI') || Enumeration::get_values('ACTI').first).id
212
  end
213

  
112 214
  # Returns the Redmine User corresponding to the committer
215
  # or the anonymous user
113 216
  def committer_user
114
    if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
217
    @user ||= if committer && committer.strip =~ /^([^<]+)(<(.*)>)?$/
115 218
      username, email = $1.strip, $3
116 219
      u = User.find_by_login(username)
117 220
      u ||= User.find_by_mail(email) unless email.blank?
118 221
      u
119
    end
222
    end || User.anonymous
120 223
  end
121 224
  
122
  # Returns the previous changeset
123
  def previous
124
    @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
125
  end
126

  
127
  # Returns the next changeset
128
  def next
129
    @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
130
  end
131 225
end
app/views/settings/_repositories.rhtml (working copy)
29 29
<br /><em><%= l(:text_comma_separated) %></em></p>
30 30
</fieldset>
31 31

  
32
<fieldset class="box tabular settings"><legend><%= l(:text_issues_advanced_commit_message_keywords) %></legend>
33
<p><label><%= l(:setting_advanced_commit_keywords) %></label>
34
<%= 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>
35

  
36
<div id="advanced_keywords" <%= Setting.advanced_commit_parsing? ? '' : 'style="display:none"' %>>
37
<p><label><%= l(:setting_commit_time_keywords) %></label>
38
<%= text_field_tag 'settings[commit_time_keywords]', Setting.commit_time_keywords, :size => 30 %>
39
<br /><em><%= l(:text_comma_separated) %></em></p>
40
<p><label><%= l(:setting_commit_ratio_keywords) %></label>
41
<%= text_field_tag 'settings[commit_ratio_keywords]', Setting.commit_ratio_keywords, :size => 30 %>
42
<br /><em><%= l(:text_comma_separated) %></em></p>
43
</div>
44
</fieldset>
45

  
32 46
<%= submit_tag l(:button_save) %>
33 47
<% end %>
lang/en.yml (working copy)
202 202
setting_sys_api_enabled: Enable WS for repository management
203 203
setting_commit_ref_keywords: Referencing keywords
204 204
setting_commit_fix_keywords: Fixing keywords
205
setting_advanced_commit_keywords: Enable advanced keywords
206
setting_commit_time_keywords: Time logging keywords
207
setting_commit_ratio_keywords: Done ratio keywords
205 208
setting_autologin: Autologin
206 209
setting_date_format: Date format
207 210
setting_time_format: Time format
......
584 587
text_unallowed_characters: Unallowed characters
585 588
text_comma_separated: Multiple values allowed (comma separated).
586 589
text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
590
text_issues_advanced_commit_message_keywords: Logging time and setting issue ratios via commit messages
587 591
text_issue_added: Issue %s has been reported by %s.
588 592
text_issue_updated: Issue %s has been updated by %s.
589 593
text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
lang/de.yml (working copy)
200 200
setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen
201 201
setting_commit_ref_keywords: Schlüsselwörter (Beziehungen)
202 202
setting_commit_fix_keywords: Schlüsselwörter (Status)
203
setting_advanced_commit_keywords: Erweiterte Schlüsselwörter aktivieren
204
setting_commit_time_keywords: Schlüsselwörter (Zeiterfassung)
205
setting_commit_ratio_keywords: Schlüsselwörter (Erledigt)
203 206
setting_autologin: Automatische Anmeldung
204 207
setting_date_format: Datumsformat
205 208
setting_time_format: Zeitformat
......
574 577
text_unallowed_characters: Nicht erlaubte Zeichen
575 578
text_comma_separated: Mehrere Werte erlaubt (durch Komma getrennt).
576 579
text_issues_ref_in_commit_messages: Ticket-Beziehungen und -Status in Commit-Log-Meldungen
580
text_issues_advanced_commit_message_keywords: Ticket-Zeiterfassung und -Erledigt in Commit-Log-Meldungen
577 581
text_issue_added: Ticket %s wurde erstellt by %s.
578 582
text_issue_updated: Ticket %s wurde aktualisiert by %s.
579 583
text_wiki_destroy_confirmation: Sind Sie sicher, dass Sie dieses Wiki mit sämtlichem Inhalt löschen möchten?
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'
84 90
# autologin duration in days
85 91
# 0 means autologin is disabled 
86 92
autologin:
(4-4/7)