Project

General

Profile

Patch #2525 » 0001-Redmine-management-of-Git-repositories.patch.test

Tomek Piotrowski, 2009-03-18 21:55

 
1
diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb
2
index d41977d..14f1dc6 100644
3
--- a/app/helpers/repositories_helper.rb
4
+++ b/app/helpers/repositories_helper.rb
5
@@ -164,7 +164,11 @@ module RepositoriesHelper
6
   end
7
 
8
   def git_field_tags(form, repository)
9
-      content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
10
+      if Setting.serve_git_repositories? and (repository == nil or repository.url.blank?)
11
+          content_tag('p', form.text_field(:url, :value => GitManager.repositories_root + '/' + repository.project.identifier + '.git'), :label => 'Path to .git directory', :size => 60, :required => true)
12
+      else
13
+          content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
14
+      end
15
   end
16
 
17
   def cvs_field_tags(form, repository)
18
diff --git a/app/models/authorized_keys_entry.rb b/app/models/authorized_keys_entry.rb
19
new file mode 100644
20
index 0000000..3f9f4b8
21
--- /dev/null
22
+++ b/app/models/authorized_keys_entry.rb
23
@@ -0,0 +1,190 @@
24
+require "strscan"
25
+require 'thread'
26
+
27
+class AuthorizedKeysEntry
28
+  COMMENT=/^#/
29
+  BLANK=/^\s+$/
30
+  AUTHORIZED_KEYS_PARSER=/^(?:(.+) )?(ssh-dss|ssh-rsa) ([^ ]+)(?: (.+))?$/
31
+  KEY_TYPES=['ssh-dss','ssh-rsa']
32
+  KEY_FORMAT=/\A[\w\/\+\=]+\z/
33
+  IDENTIFIER_FORMAT=/\A[\w@\.\-]+\z/
34
+  @@authorized_keys_filename="~/.ssh/authorized_keys"
35
+
36
+  attr_reader :identifier, :key, :type
37
+  attr_accessor :options
38
+
39
+  @authorized_keys_lock = Mutex.new
40
+
41
+  # Create object from string from authorized_keys
42
+  def initialize(authorized_keys_string = nil)
43
+    @key = ''
44
+    @options = []
45
+    create_from_authorized_keys(authorized_keys_string) if authorized_keys_string != nil
46
+  end
47
+
48
+  def identifier=(identifier)
49
+    @identifier = identifier
50
+    @validated = false
51
+  end
52
+
53
+  def key=(key)
54
+    @key = key
55
+    @validated = false
56
+  end
57
+
58
+  def type=(type)
59
+    @type = type
60
+    @validated = false
61
+  end
62
+
63
+  def save
64
+    raise "Invalid key format" if !validate
65
+    AuthorizedKeysEntry.save_entry(self)
66
+  end
67
+
68
+  def self.find_by_identifier(identifier)
69
+    read_entries do |entry|
70
+      return entry if entry.identifier == identifier
71
+    end
72
+    return nil
73
+  end
74
+
75
+  def validate
76
+    return true if @key == ""
77
+    return false if !@options.kind_of?(Array)
78
+    return true if @validated
79
+    # Clean key from any garbage user might have entered while copying the key
80
+    @key = @key.gsub(/\A(ssh-dss|ssh-rsa)\s/,"").gsub(/\s\w+@[\.\w\-\n]+\s*\Z/,"").gsub(/\s/,'')
81
+    return false if (@key=~KEY_FORMAT) == nil or !KEY_TYPES.include?(@type)
82
+    return false if (@identifier=~IDENTIFIER_FORMAT) == nil
83
+      
84
+    Tempfile.open("redmine") do |file|
85
+      file << get_id_dsa_pub_format
86
+      file.close
87
+      @validated = system "ssh-keygen -B -f #{file.path()} &> /dev/null"
88
+    end
89
+    return @validated
90
+  end
91
+
92
+  def to_s
93
+    get_authorized_keys_format
94
+  end
95
+
96
+  def self.authorized_keys_filename=(filename)
97
+    @@authorized_keys_filename = filename
98
+  end
99
+
100
+  private
101
+
102
+  def create_from_authorized_keys(line)
103
+    m = AUTHORIZED_KEYS_PARSER.match(line)
104
+    raise ArgumentError, "Invalid format of authorized_keys entry" if m == nil
105
+
106
+    @options, @type, @key, @identifier = m[1], m[2], m[3], m[4]
107
+    @options = AuthorizedKeysEntry.parse_options(@options)
108
+  end
109
+
110
+  def get_authorized_keys_format
111
+    raise ArgumentError, 'Entry must have the type set' if @type == nil
112
+    s = ""
113
+    s += @options.join(",") + " " if !@options.empty?
114
+    s += @type + " " + @key
115
+    s += " " + @identifier if @identifier
116
+    return s
117
+  end
118
+
119
+  def get_id_dsa_pub_format
120
+    raise ArgumentError, 'Entry must have the type set' if @type == nil
121
+    s = ""
122
+    s += @type + " " + @key
123
+    s += " " + @identifier if @identifier
124
+    return s
125
+  end
126
+
127
+  def self.parse_options(options)
128
+    result = []
129
+    return result if options == nil
130
+    scanner = StringScanner.new(options)
131
+    while !scanner.eos?
132
+      scanner.skip(/[ \t]*/)
133
+      # scan a long option
134
+      if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/)
135
+        result << out
136
+      else
137
+        # found an unscannable token, let's abort
138
+        break
139
+      end
140
+      # eat a comma
141
+      scanner.skip(/[\t]*,[\t]*/)
142
+    end
143
+    return result
144
+  end
145
+
146
+  def self.read_entries
147
+    filename = File.expand_path(@@authorized_keys_filename)
148
+    return if !File.exists?(filename)
149
+
150
+    File.open(filename) do |file|
151
+      file.flock(File::LOCK_SH)
152
+      begin
153
+        file.each_line do |line|
154
+          next if COMMENT.match(line) or BLANK.match(line)
155
+          yield AuthorizedKeysEntry.new(line)
156
+        end
157
+      ensure
158
+        file.flock(File::LOCK_UN)
159
+      end
160
+    end 
161
+  end
162
+
163
+  def self.save_entry(entry)
164
+    filename = File.expand_path(@@authorized_keys_filename)
165
+
166
+    FileUtils.mkdir_p(File.dirname(filename))
167
+
168
+    found = false
169
+    entries = []
170
+    file = nil
171
+    begin
172
+      if File.exist?(filename)
173
+        file = File.open(filename, "r")
174
+        # TODO: somehow handle flock blocking
175
+        file.flock(File::LOCK_EX)
176
+        file.each_line do |line|
177
+          next if COMMENT.match(line) or BLANK.match(line)
178
+          entries << AuthorizedKeysEntry.new(line)
179
+        end
180
+
181
+        entries.each do |e|
182
+          if entry.identifier == e.identifier
183
+            return if entry.key == e.key and entry.type == e.type
184
+
185
+            found = true
186
+	    if entry.key != ""
187
+              e.key = entry.key
188
+              e.type = entry.type
189
+              e.options = entry.options
190
+            else
191
+              entries.delete(e)
192
+            end
193
+	    break
194
+          end
195
+        end
196
+      end
197
+
198
+      if !found and entry.key != ""
199
+        entries << entry
200
+      end
201
+ 
202
+      File.open(filename, "w") do |write_file|
203
+        write_file.flock(File::LOCK_EX) if file == nil
204
+        entries.each do |entry|
205
+          write_file << entry.to_s + "\n"
206
+        end
207
+      end
208
+    ensure
209
+      file.close if file != nil
210
+    end
211
+  end
212
+end
213
+
214
diff --git a/app/models/changeset.rb b/app/models/changeset.rb
215
index 759d240..744fc2c 100644
216
--- a/app/models/changeset.rb
217
+++ b/app/models/changeset.rb
218
@@ -70,56 +70,74 @@ class Changeset < ActiveRecord::Base
219
     scan_comment_for_issue_ids
220
   end
221
   require 'pp'
222
-  
223
-  def scan_comment_for_issue_ids
224
-    return if comments.blank?
225
+
226
+  # returns issue ids found in message
227
+  # three arrays are returned, references issues, fixed ones and wrong numbers (not Issues)
228
+  def self.find_issue_ids(message, project)
229
+    return [[],[]] if message.blank?
230
     # keywords used to reference issues
231
     ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
232
     # keywords used to fix issues
233
     fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
234
-    # status and optional done ratio applied
235
-    fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
236
-    done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
237
     
238
     kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
239
-    return if kw_regexp.blank?
240
+    return [[],[]] if kw_regexp.blank?
241
     
242
     referenced_issues = []
243
+    fixed_issues = []
244
+    wrong_issue_ids = []
245
     
246
     if ref_keywords.delete('*')
247
       # find any issue ID in the comments
248
       target_issue_ids = []
249
-      comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
250
-      referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids)
251
+      message.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
252
+      found_issues = project.issues.find_all_by_id(target_issue_ids)
253
+      referenced_issues += found_issues
254
+      found_issues.each { |issue| target_issue_ids.delete(issue.id.to_s) }
255
+      wrong_issue_ids += target_issue_ids
256
     end
257
     
258
-    comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
259
+    message.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
260
       action = match[0]
261
       target_issue_ids = match[1].scan(/\d+/)
262
-      target_issues = repository.project.issues.find_all_by_id(target_issue_ids)
263
-      if fix_status && fix_keywords.include?(action.downcase)
264
-        # update status of issues
265
-        logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
266
-        target_issues.each do |issue|
267
-          # the issue may have been updated by the closure of another one (eg. duplicate)
268
-          issue.reload
269
-          # don't change the status is the issue is closed
270
-          next if issue.status.is_closed?
271
-          csettext = "r#{self.revision}"
272
-          if self.scmid && (! (csettext =~ /^r[0-9]+$/))
273
-            csettext = "commit:\"#{self.scmid}\""
274
-          end
275
-          journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext))
276
-          issue.status = fix_status
277
-          issue.done_ratio = done_ratio if done_ratio
278
-          issue.save
279
-          Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
280
-        end
281
+      target_issues = project.issues.find_all_by_id(target_issue_ids)
282
+      target_issues.each { |issue| target_issue_ids.delete(issue.id.to_s) }
283
+      wrong_issue_ids += target_issue_ids
284
+      if fix_keywords.include?(action.downcase)
285
+        fixed_issues += target_issues
286
+      else
287
+        referenced_issues += target_issues
288
       end
289
-      referenced_issues += target_issues
290
     end
291
-    
292
-    self.issues = referenced_issues.uniq
293
+    return [referenced_issues.uniq, fixed_issues.uniq, wrong_issue_ids.uniq]
294
+  end
295
+  
296
+  def scan_comment_for_issue_ids
297
+    return if comments.blank?
298
+    # status and optional done ratio applied
299
+    fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
300
+    done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
301
+
302
+    referenced_issues, fixed_issues, wrong_issues = Changeset.find_issue_ids(comments, repository.project)
303
+
304
+    # update status of issues
305
+    logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
306
+    fixed_issues.each do |issue|
307
+      # the issue may have been updated by the closure of another one (eg. duplicate)
308
+      issue.reload
309
+      # don't change the status is the issue is closed
310
+      next if issue.status.is_closed?
311
+      csettext = "r#{self.revision}"
312
+      if self.scmid && (! (csettext =~ /^r[0-9]+$/))
313
+        csettext = "commit:\"#{self.scmid}\""
314
+      end
315
+      journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext))
316
+      issue.status = fix_status
317
+      issue.done_ratio = done_ratio if done_ratio
318
+      issue.save
319
+      Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
320
+    end
321
+    self.issues = (referenced_issues + fixed_issues).uniq
322
   end
323
   
324
   # Returns the previous changeset
325
diff --git a/app/models/git_checks/committer.rb b/app/models/git_checks/committer.rb
326
new file mode 100644
327
index 0000000..40cea0b
328
--- /dev/null
329
+++ b/app/models/git_checks/committer.rb
330
@@ -0,0 +1,14 @@
331
+class GitChecks::Committer < GitManager::CommitCheck
332
+  def self.check(revision, branch, user, role, project, git)
333
+    # Check: committer name and email
334
+    if revision.author != "#{user.name} <#{user.mail}>"
335
+      return [ "Commit author name or email is wrong",
336
+	       "    Execute following commands and _recreate_ commit:",
337
+               "    git config --global user.name \"#{user.name}\"",
338
+	       "    git config --global user.email #{user.mail}",
339
+      ]
340
+    end
341
+    return []
342
+  end
343
+end
344
+
345
diff --git a/app/models/git_checks/delete_branch.rb b/app/models/git_checks/delete_branch.rb
346
new file mode 100644
347
index 0000000..94a96eb
348
--- /dev/null
349
+++ b/app/models/git_checks/delete_branch.rb
350
@@ -0,0 +1,9 @@
351
+class GitChecks::DeleteBranch < GitManager::RefCheck
352
+  def self.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git)
353
+    if new_rev_type == "delete"
354
+      return "Deleting branch can be done only by repository manager"
355
+    end
356
+    return []
357
+  end
358
+end
359
+
360
diff --git a/app/models/git_checks/fast_forward.rb b/app/models/git_checks/fast_forward.rb
361
new file mode 100644
362
index 0000000..03b74c5
363
--- /dev/null
364
+++ b/app/models/git_checks/fast_forward.rb
365
@@ -0,0 +1,10 @@
366
+class GitChecks::FastForward < GitManager::RefCheck
367
+  def self.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git)
368
+    # Check: fast forward
369
+    if old_rev != git.class::EMPTY_COMMIT and git.merge_base(old_rev, new_rev) != old_rev
370
+      return "Only fast-forward commits are allowed"
371
+    end
372
+    return []
373
+  end
374
+end
375
+
376
diff --git a/app/models/git_checks/initial_commit.rb b/app/models/git_checks/initial_commit.rb
377
new file mode 100644
378
index 0000000..c08344e
379
--- /dev/null
380
+++ b/app/models/git_checks/initial_commit.rb
381
@@ -0,0 +1,9 @@
382
+class GitChecks::InitialCommit < GitManager::RefCheck
383
+  def self.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git)
384
+    if old_rev == git.class::EMPTY_COMMIT and !role.allowed_to?(:manage_repository)
385
+      return "Initial commit can be done only by repository manager"
386
+    end
387
+    return []
388
+  end
389
+end
390
+
391
diff --git a/app/models/git_checks/issue.rb b/app/models/git_checks/issue.rb
392
new file mode 100644
393
index 0000000..99b11a8
394
--- /dev/null
395
+++ b/app/models/git_checks/issue.rb
396
@@ -0,0 +1,29 @@
397
+class GitChecks::Issue < GitManager::CommitCheck
398
+  def self.check(revision, branch, user, role, project, git)
399
+    referenced_issues, fixes_issues, wrong_issues = Changeset.find_issue_ids(revision.message, project)
400
+    issues = referenced_issues + fixes_issues
401
+
402
+    errors = []
403
+
404
+    if !wrong_issues.empty?
405
+      errors << "Some issue numbers are wrong: #{wrong_issues.join(',')}"
406
+    end
407
+
408
+    issues.each do |issue_number|
409
+      issue = Issue.find(issue_number)
410
+      if issue.closed?
411
+        errors << "Issue \##{issue.id} is closed. Reopen it and commit again"
412
+      end
413
+      if issue.assigned_to != user
414
+        if issue.assigned_to != nil
415
+          owner_info = "It belongs to #{issue.assigned_to.firstname} #{issue.assigned_to.lastname}"
416
+        else
417
+          owner_info = "It is unassigned"
418
+        end
419
+        errors << "Issue \##{issue.id} is not assigned to You. #{owner_info}"
420
+      end
421
+    end
422
+    return errors
423
+  end
424
+end
425
+
426
diff --git a/app/models/git_manager.rb b/app/models/git_manager.rb
427
new file mode 100644
428
index 0000000..2136144
429
--- /dev/null
430
+++ b/app/models/git_manager.rb
431
@@ -0,0 +1,256 @@
432
+require 'net/http'
433
+require 'uri'
434
+require 'redmine/scm/adapters/git_adapter'
435
+
436
+class GitManager
437
+  COMMANDS_READONLY = [
438
+    'git-upload-pack',
439
+  ]
440
+  COMMANDS_WRITE = [
441
+    'git-receive-pack',
442
+  ]
443
+  # TODO: make configurable
444
+  REPOSITORIES_ROOT='~/repositories/'
445
+  REDMINE_LOGIN_ENV_NAME='REDMINE_LOGIN'
446
+
447
+  def self.repositories_root
448
+    return File.expand_path(REPOSITORIES_ROOT)
449
+  end
450
+
451
+  
452
+  # Executed by SSH when somebody logs into Redmine's SSH account
453
+  # using public key.
454
+  # Restricts the user to access only Git repositories he is allowed to.
455
+  def self.serve
456
+    begin
457
+      command = serve_get_original_command
458
+      user = serve_get_user
459
+      git_command, project_identifier = parse_git_command(command)
460
+      project, repository = find_project_and_repo(project_identifier)
461
+      role = user.role_for_project(project)
462
+
463
+      if COMMANDS_READONLY.include?(git_command)
464
+        if !role.allowed_to?(:view_changesets)
465
+          raise "User #{user} (#{role.name}) is not allowed to read project #{project}\n"
466
+        end
467
+        if !repository.scm.info
468
+          raise "Empty repository. Push some commit first"
469
+        end
470
+      elsif COMMANDS_WRITE.include?(git_command)
471
+        if !role.allowed_to?(:commit_access)
472
+          raise "User #{user} (#{role.name}) is not allowed to write to project #{project}\n"
473
+        end
474
+        if !repository.scm.info
475
+          warn "Creating new repository.\n"
476
+          repository.scm.init(project.description)
477
+        end
478
+      else
479
+        raise "Unknown command '#{verb}'"
480
+      end
481
+    
482
+    rescue
483
+      warn "Error: #{$!}.\n"
484
+      exit 1
485
+    end
486
+
487
+    ENV[REDMINE_LOGIN_ENV_NAME]=user.login
488
+    exec 'git', 'shell', '-c', "#{git_command} '#{repository.url}'"
489
+  end
490
+
491
+  def self.get_authorized_keys_options_for_login(login)
492
+    return [ 'command="ruby ' + RAILS_ROOT + '/script/runner GitManager.serve \'' + login + '\' -e ' + ENV["RAILS_ENV"] +
493
+	  '"', 'no-port-forwarding','no-X11-forwarding','no-agent-forwarding','no-pty' ]
494
+  end
495
+
496
+
497
+  private
498
+  def self.serve_get_original_command
499
+    command = ENV["SSH_ORIGINAL_COMMAND"]
500
+    if command == nil or command==""
501
+      raise "SSH_ORIGINAL_COMMAND not set. Use Git to access this account"
502
+    end
503
+    if command=~/\n/
504
+      raise "Command contains new line character"
505
+    end
506
+    return command
507
+  end
508
+
509
+  def self.serve_get_user
510
+    login = ARGV[0]
511
+    if login == nil or login==""
512
+      raise "Needs login as parameter"
513
+    end
514
+    user = User.find_by_login(login)
515
+    if user == nil
516
+      raise "User not found #{login}"
517
+    end
518
+    return user
519
+  end
520
+
521
+  def self.parse_git_command(command)
522
+    verb, args = command.split(" ",2)
523
+    if verb == 'git'
524
+       subverb, args = args.split(" ",2)
525
+       verb = "%s-%s" % [ verb, subverb ]
526
+    end
527
+    project_identifier = args.gsub("'","")
528
+    return verb, project_identifier
529
+  end
530
+
531
+  def self.find_project_and_repo(project_identifier)
532
+    project = Project.find_by_identifier(project_identifier)
533
+    if project == nil
534
+      raise "Project not found \"#{project_identifier}\""
535
+    end
536
+
537
+    repository = project.repository
538
+    if repository == nil
539
+      raise "Project #{project} does not have repository\n"
540
+    end
541
+
542
+    if repository.class != Repository::Git
543
+      raise "Project #{project} has non git repository #{repository.type}"
544
+    end
545
+    return project, repository
546
+  end
547
+
548
+
549
+  public
550
+
551
+  class RefCheck
552
+    @@checks = []
553
+
554
+    # Check Git ref, subclassess should override
555
+    def check(old_rev, new_rev, new_rev_type, branch, user, role, project, git)
556
+      return true
557
+    end
558
+
559
+    def self.inherited(subclass)
560
+      @@checks << subclass
561
+    end
562
+
563
+    def self.get_checks
564
+      return @@checks
565
+    end
566
+  end
567
+  class CommitCheck
568
+    @@checks = []
569
+
570
+    # Check Git ref, subclassess should override
571
+    def check(revision, branch, user, role, project, git)
572
+      return true
573
+    end
574
+
575
+    def self.inherited(subclass)
576
+      @@checks << subclass
577
+    end
578
+
579
+    def self.get_checks
580
+      return @@checks
581
+    end
582
+  end
583
+
584
+  def self.load_checks
585
+    files = Dir.glob(RAILS_ROOT + "/app/models/git_checks/*.rb")
586
+    files.each do |f|
587
+      f.sub!(/\A#{RAILS_ROOT}/,'')
588
+      f.split('/')[3..-1].join('/').split('.').first.camelize.constantize
589
+    end
590
+  end
591
+  load_checks
592
+
593
+  def self.check_commits
594
+    login = ENV[REDMINE_LOGIN_ENV_NAME]
595
+    if login == nil
596
+      warn "Redmine: Local user, not running checks"
597
+      exit 0
598
+    end
599
+
600
+    begin
601
+      user = User.find_by_login(login)
602
+      raise "User not found" if user == nil
603
+      repository = Repository.find_by_url(Dir.getwd)
604
+      raise "Repository not managed by Redmine" if repository == nil
605
+      raise "Non git repository" if repository.class != Repository::Git
606
+      project = repository.project
607
+      role = user.role_for_project(project)
608
+      git = repository.scm
609
+      
610
+      warn ""
611
+      warn "-------------------------------------------------------------"
612
+      warn "Redmine is checking your changes for correctness..."
613
+      warn "Authenticated as #{user.name} (#{role.name} in #{project.name})"
614
+
615
+      error_found = false
616
+
617
+      warn "Changes:"
618
+      STDIN.each_line do |line|
619
+        old_rev, new_rev, branch = line.split(" ")
620
+      
621
+        if new_rev == git.class::EMPTY_COMMIT
622
+          new_rev_type = "delete"
623
+        else
624
+          new_rev_type = git.get_object_type(new_rev)
625
+        end
626
+        revisions = nil
627
+        if new_rev_type == "commit"
628
+          revisions = git.revisions("", old_rev, new_rev)
629
+        end
630
+        warn "    Ref: #{branch} type: #{new_rev_type}"
631
+
632
+	errors = []
633
+	# TODO: make checks configurable
634
+        RefCheck.get_checks.each do |check|
635
+          result = check.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git)
636
+          if result.kind_of?(Array)
637
+            errors += result
638
+	  else
639
+            errors << result
640
+          end
641
+        end
642
+        errors.each do |error|
643
+          warn "            Error: #{error}"
644
+        end
645
+	error_found = true if !errors.empty?
646
+
647
+        if revisions != nil
648
+	  revisions.each do |revision|
649
+	    warn "        Commit: #{revision.identifier}"
650
+
651
+	    errors = []
652
+            CommitCheck.get_checks.each do |check|
653
+              result = check.check(revision, branch, user, role, project, git)
654
+              if result.kind_of?(Array)
655
+                errors += result
656
+	      else
657
+                errors << result
658
+              end
659
+            end
660
+            errors.each do |error|
661
+              warn "            Error: #{error}"
662
+            end
663
+	    error_found = true if !errors.empty?
664
+          end
665
+        end
666
+      end
667
+    end
668
+    
669
+    if error_found
670
+      if !role.allowed_to?(:manage_repository)
671
+        warn "Some commits were rejected. Correct them and try the push again."
672
+        warn "-------------------------------------------------------------"
673
+	warn ""
674
+        exit 1
675
+      else
676
+        warn "You are repository manager. Checks results are ignored. "
677
+        warn "-------------------------------------------------------------"
678
+	exit 0
679
+      end
680
+    end
681
+    warn "Changes look OK"
682
+    warn "-------------------------------------------------------------"
683
+    exit 0
684
+  end
685
+
686
+end
687
+
688
diff --git a/app/models/user.rb b/app/models/user.rb
689
index 9692390..c1606b6 100644
690
--- a/app/models/user.rb
691
+++ b/app/models/user.rb
692
@@ -65,6 +65,38 @@ class User < ActiveRecord::Base
693
   validates_length_of :password, :minimum => 4, :allow_nil => true
694
   validates_confirmation_of :password, :allow_nil => true
695
 
696
+  def ssh_key
697
+    return nil if ! Setting.serve_git_repositories?
698
+    load_ssh_key if @ssh_key_entry == nil
699
+    return @ssh_key_entry.key
700
+  end
701
+
702
+  def ssh_key_type
703
+    return nil if ! Setting.serve_git_repositories?
704
+    load_ssh_key if @ssh_key_entry == nil
705
+    return @ssh_key_entry.type
706
+  end
707
+
708
+  def ssh_key=(key)
709
+    return if ! Setting.serve_git_repositories?
710
+    new_ssh_key if @ssh_key_entry == nil
711
+    key = key.strip if key != nil
712
+    @ssh_key_entry.key = key
713
+  end
714
+
715
+  def ssh_key_type=(key_type)
716
+    return if ! Setting.serve_git_repositories?
717
+    new_ssh_key if @ssh_key_entry == nil
718
+    @ssh_key_entry.type = key_type
719
+  end
720
+
721
+  def validate
722
+    return if !Setting.serve_git_repositories?
723
+    return if @ssh_key_entry == nil
724
+
725
+    errors.add(:ssh_key, "is not valid") if !@ssh_key_entry.validate
726
+  end
727
+
728
   def before_create
729
     self.mail_notification = false
730
     true
731
@@ -74,6 +106,15 @@ class User < ActiveRecord::Base
732
     # update hashed_password if password was set
733
     self.hashed_password = User.hash_password(self.password) if self.password
734
   end
735
+
736
+  def after_save
737
+    return if ! Setting.serve_git_repositories?
738
+    return if @ssh_key_entry == nil
739
+    
740
+    @ssh_key_entry.options = GitManager.get_authorized_keys_options_for_login(login)
741
+
742
+    @ssh_key_entry.save 
743
+  end
744
   
745
   def reload(*args)
746
     @name = nil
747
@@ -271,6 +312,16 @@ private
748
   def self.hash_password(clear_password)
749
     Digest::SHA1.hexdigest(clear_password || "")
750
   end
751
+
752
+  def new_ssh_key
753
+    @ssh_key_entry = AuthorizedKeysEntry.new
754
+    @ssh_key_entry.identifier = login
755
+  end
756
+
757
+  def load_ssh_key
758
+    @ssh_key_entry = AuthorizedKeysEntry.find_by_identifier(login)
759
+    new_ssh_key if @ssh_key_entry == nil
760
+  end
761
 end
762
 
763
 class AnonymousUser < User
764
diff --git a/app/views/my/account.rhtml b/app/views/my/account.rhtml
765
index f4b726f..448cc33 100644
766
--- a/app/views/my/account.rhtml
767
+++ b/app/views/my/account.rhtml
768
@@ -15,6 +15,11 @@
769
 <p><%= f.text_field :lastname, :required => true %></p>
770
 <p><%= f.text_field :mail, :required => true %></p>
771
 <p><%= f.select :language, lang_options_for_select %></p>
772
+<% if Setting.serve_git_repositories? %>
773
+  <p><%= f.select :ssh_key_type, AuthorizedKeysEntry::KEY_TYPES %></p>
774
+  <p><%= f.text_area :ssh_key, :rows => 10 %></p>
775
+<% end %>
776
+
777
 </div>
778
 
779
 <%= submit_tag l(:button_save) %>
780
diff --git a/app/views/settings/_repositories.rhtml b/app/views/settings/_repositories.rhtml
781
index a8c9244..189648f 100644
782
--- a/app/views/settings/_repositories.rhtml
783
+++ b/app/views/settings/_repositories.rhtml
784
@@ -14,6 +14,14 @@
785
 <%= hidden_field_tag 'settings[enabled_scm][]', '' %>
786
 </p>
787
 
788
+<% # TODO: Should be disabled when Git SCM is not enabled 
789
+%>
790
+<p><label><%= l(:setting_serve_git_repositories) %></label>
791
+<%= check_box_tag 'settings[serve_git_repositories]', 1, Setting.serve_git_repositories? %>
792
+<%= hidden_field_tag 'settings[serve_git_repositories]', 0 %>
793
+</p>
794
+
795
+
796
 <p><label><%= l(:setting_repositories_encodings) %></label>
797
 <%= text_field_tag 'settings[repositories_encodings]', Setting.repositories_encodings, :size => 60 %><br /><em><%= l(:text_comma_separated) %></em></p>
798
 
799
diff --git a/config/settings.yml b/config/settings.yml
800
index 5006445..a3af36c 100644
801
--- a/config/settings.yml
802
+++ b/config/settings.yml
803
@@ -77,6 +77,8 @@ autofetch_changesets:
804
   default: 1
805
 sys_api_enabled:
806
   default: 0
807
+serve_git_repositories:
808
+  default: 0
809
 commit_ref_keywords:
810
   default: 'refs,references,IssueID'
811
 commit_fix_keywords:
812
diff --git a/lang/bg.yml b/lang/bg.yml
813
index 61f7520..b9acd01 100644
814
--- a/lang/bg.yml
815
+++ b/lang/bg.yml
816
@@ -92,6 +92,8 @@ field_is_required: Задължително
817
 field_firstname: Име
818
 field_lastname: Фамилия
819
 field_mail: Email
820
+field_ssh_key: SSH Public Key
821
+field_ssh_key_type: SSH Public Key Type
822
 field_filename: Файл
823
 field_filesize: Големина
824
 field_downloads: Downloads
825
@@ -181,6 +183,7 @@ setting_wiki_compression: Wiki компресиране на историята
826
 setting_feeds_limit: Лимит на Feeds
827
 setting_autofetch_changesets: Автоматично обработване на ревизиите
828
 setting_sys_api_enabled: Разрешаване на WS за управление
829
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
830
 setting_commit_ref_keywords: Отбелязващи ключови думи
831
 setting_commit_fix_keywords: Приключващи ключови думи
832
 setting_autologin: Автоматичен вход
833
diff --git a/lang/ca.yml b/lang/ca.yml
834
index 0ce265f..b1bdcc7 100644
835
--- a/lang/ca.yml
836
+++ b/lang/ca.yml
837
@@ -106,6 +106,8 @@ field_is_required: Necessari
838
 field_firstname: Nom
839
 field_lastname: Cognom
840
 field_mail: Correu electrònic 
841
+field_ssh_key: SSH Public Key
842
+field_ssh_key_type: SSH Public Key Type
843
 field_filename: Fitxer
844
 field_filesize: Mida
845
 field_downloads: Baixades
846
@@ -202,6 +204,7 @@ setting_feeds_limit: Límit de contingut del canal
847
 setting_default_projects_public: Els projectes nous són públics per defecte
848
 setting_autofetch_changesets: Omple automàticament les publicacions
849
 setting_sys_api_enabled: Habilita el WS per a la gestió del dipòsit
850
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
851
 setting_commit_ref_keywords: Paraules claus per a la referència
852
 setting_commit_fix_keywords: Paraules claus per a la correcció
853
 setting_autologin: Entrada automàtica
854
diff --git a/lang/cs.yml b/lang/cs.yml
855
index 534da67..a50733b 100644
856
--- a/lang/cs.yml
857
+++ b/lang/cs.yml
858
@@ -106,6 +106,8 @@ field_is_required: Povinné pole
859
 field_firstname: Jméno
860
 field_lastname: Příjmení
861
 field_mail: Email
862
+field_ssh_key: SSH Public Key
863
+field_ssh_key_type: SSH Public Key Type
864
 field_filename: Soubor
865
 field_filesize: Velikost
866
 field_downloads: Staženo
867
@@ -201,6 +203,7 @@ setting_feeds_limit: Feed content limit
868
 setting_default_projects_public: Nové projekty nastavovat jako veřejné
869
 setting_autofetch_changesets: Autofetch commits
870
 setting_sys_api_enabled: Povolit WS pro správu repozitory
871
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
872
 setting_commit_ref_keywords: Klíčová slova pro odkazy
873
 setting_commit_fix_keywords: Klíčová slova pro uzavření
874
 setting_autologin: Automatické přihlašování
875
diff --git a/lang/da.yml b/lang/da.yml
876
index 2edbc52..a1bf133 100644
877
--- a/lang/da.yml
878
+++ b/lang/da.yml
879
@@ -106,6 +106,8 @@ field_is_required: Skal udfyldes
880
 field_firstname: Fornavn
881
 field_lastname: Efternavn
882
 field_mail: E-mail
883
+field_ssh_key: SSH Public Key
884
+field_ssh_key_type: SSH Public Key Type
885
 field_filename: Fil
886
 field_filesize: Størrelse
887
 field_downloads: Downloads
888
@@ -204,6 +206,7 @@ setting_autofetch_changesets: Hent automatisk commits
889
 setting_sys_api_enabled: Aktiver webservice til versionsstyring
890
 setting_commit_ref_keywords: Nøgleord for sagsreferencer
891
 setting_commit_fix_keywords: Nøgleord for lukning af sager
892
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
893
 setting_autologin: Autologin
894
 setting_date_format: Datoformat
895
 setting_time_format: Tidsformat
896
diff --git a/lang/de.yml b/lang/de.yml
897
index c403b48..2362c41 100644
898
--- a/lang/de.yml
899
+++ b/lang/de.yml
900
@@ -106,6 +106,8 @@ field_is_required: Erforderlich
901
 field_firstname: Vorname
902
 field_lastname: Nachname
903
 field_mail: E-Mail
904
+field_ssh_key: SSH Public Key
905
+field_ssh_key_type: SSH Public Key Type
906
 field_filename: Datei
907
 field_filesize: Größe
908
 field_downloads: Downloads
909
@@ -203,6 +205,7 @@ setting_feeds_limit: Max. Anzahl Einträge pro Atom-Feed
910
 setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich
911
 setting_autofetch_changesets: Changesets automatisch abrufen
912
 setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen
913
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
914
 setting_commit_ref_keywords: Schlüsselwörter (Beziehungen)
915
 setting_commit_fix_keywords: Schlüsselwörter (Status)
916
 setting_autologin: Automatische Anmeldung
917
diff --git a/lang/en.yml b/lang/en.yml
918
index dad948e..f2a4ca3 100644
919
--- a/lang/en.yml
920
+++ b/lang/en.yml
921
@@ -108,6 +108,8 @@ field_is_required: Required
922
 field_firstname: Firstname
923
 field_lastname: Lastname
924
 field_mail: Email
925
+field_ssh_key: SSH Public Key
926
+field_ssh_key_type: SSH Public Key Type
927
 field_filename: File
928
 field_filesize: Size
929
 field_downloads: Downloads
930
@@ -205,6 +207,7 @@ setting_feeds_limit: Feed content limit
931
 setting_default_projects_public: New projects are public by default
932
 setting_autofetch_changesets: Autofetch commits
933
 setting_sys_api_enabled: Enable WS for repository management
934
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
935
 setting_commit_ref_keywords: Referencing keywords
936
 setting_commit_fix_keywords: Fixing keywords
937
 setting_autologin: Autologin
938
diff --git a/lang/es.yml b/lang/es.yml
939
index 1729292..b979921 100644
940
--- a/lang/es.yml
941
+++ b/lang/es.yml
942
@@ -150,6 +150,8 @@ field_last_login_on: Última conexión
943
 field_lastname: Apellido
944
 field_login: Identificador
945
 field_mail: Correo electrónico
946
+field_ssh_key: SSH Public Key
947
+field_ssh_key_type: SSH Public Key Type
948
 field_mail_notification: Notificaciones por correo
949
 field_max_length: Longitud máxima
950
 field_min_length: Longitud mínima
951
@@ -627,6 +629,7 @@ setting_repositories_encodings: Codificaciones del repositorio
952
 setting_self_registration: Registro permitido
953
 setting_sequential_project_identifiers: Generar identificadores de proyecto
954
 setting_sys_api_enabled: Habilitar SW para la gestión del repositorio
955
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
956
 setting_text_formatting: Formato de texto
957
 setting_time_format: Formato de hora
958
 setting_user_format: Formato de nombre de usuario
959
diff --git a/lang/fi.yml b/lang/fi.yml
960
index 244f858..12b7db7 100644
961
--- a/lang/fi.yml
962
+++ b/lang/fi.yml
963
@@ -101,6 +101,8 @@ field_is_required: Vaaditaan
964
 field_firstname: Etunimi
965
 field_lastname: Sukunimi
966
 field_mail: Sähköposti
967
+field_ssh_key: SSH Public Key
968
+field_ssh_key_type: SSH Public Key Type
969
 field_filename: Tiedosto
970
 field_filesize: Koko
971
 field_downloads: Latausta
972
@@ -194,6 +196,7 @@ setting_wiki_compression: Wiki historian pakkaus
973
 setting_feeds_limit: Syötteen sisällön raja
974
 setting_autofetch_changesets: Automaattisten muutosjoukkojen haku
975
 setting_sys_api_enabled: Salli WS tietovaraston hallintaan
976
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
977
 setting_commit_ref_keywords: Viittaavat hakusanat
978
 setting_commit_fix_keywords: Korjaavat hakusanat
979
 setting_autologin: Automaatinen kirjautuminen
980
diff --git a/lang/fr.yml b/lang/fr.yml
981
index 374a0d8..dc6b772 100644
982
--- a/lang/fr.yml
983
+++ b/lang/fr.yml
984
@@ -108,6 +108,8 @@ field_is_required: Obligatoire
985
 field_firstname: Prénom
986
 field_lastname: Nom
987
 field_mail: Email
988
+field_ssh_key: SSH Public Key
989
+field_ssh_key_type: SSH Public Key Type
990
 field_filename: Fichier
991
 field_filesize: Taille
992
 field_downloads: Téléchargements
993
@@ -205,6 +207,7 @@ setting_feeds_limit: Limite du contenu des flux RSS
994
 setting_default_projects_public: Définir les nouveaux projects comme publics par défaut
995
 setting_autofetch_changesets: Récupération auto. des commits
996
 setting_sys_api_enabled: Activer les WS pour la gestion des dépôts
997
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
998
 setting_commit_ref_keywords: Mot-clés de référencement
999
 setting_commit_fix_keywords: Mot-clés de résolution
1000
 setting_autologin: Autologin
1001
diff --git a/lang/he.yml b/lang/he.yml
1002
index b10b732..8abfb0e 100644
1003
--- a/lang/he.yml
1004
+++ b/lang/he.yml
1005
@@ -94,6 +94,8 @@ field_is_required: נדרש
1006
 field_firstname: שם פרטי
1007
 field_lastname: שם משפחה
1008
 field_mail: דוא"ל
1009
+field_ssh_key: SSH Public Key
1010
+field_ssh_key_type: SSH Public Key Type
1011
 field_filename: קובץ
1012
 field_filesize: גודל
1013
 field_downloads: הורדות
1014
@@ -184,6 +186,7 @@ setting_wiki_compression: כיווץ היסטורית WIKI
1015
 setting_feeds_limit: גבול תוכן הזנות
1016
 setting_autofetch_changesets: משיכה אוטומתי של עידכונים
1017
 setting_sys_api_enabled: אפשר WS לניהול המאגר
1018
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1019
 setting_commit_ref_keywords: מילות מפתח מקשרות
1020
 setting_commit_fix_keywords: מילות מפתח מתקנות
1021
 setting_autologin: חיבור אוטומטי
1022
diff --git a/lang/hu.yml b/lang/hu.yml
1023
index 059b3ee..1c95590 100644
1024
--- a/lang/hu.yml
1025
+++ b/lang/hu.yml
1026
@@ -103,6 +103,8 @@ field_is_required: Kötelező
1027
 field_firstname: Keresztnév
1028
 field_lastname: Vezetéknév
1029
 field_mail: E-mail
1030
+field_ssh_key: SSH Public Key
1031
+field_ssh_key_type: SSH Public Key Type
1032
 field_filename: Fájl
1033
 field_filesize: Méret
1034
 field_downloads: Letöltések
1035
@@ -198,6 +200,7 @@ setting_feeds_limit: RSS tartalom korlát
1036
 setting_default_projects_public: Az új projektek alapértelmezés szerint nyilvánosak
1037
 setting_autofetch_changesets: Commitok automatikus lehúzása
1038
 setting_sys_api_enabled: WS engedélyezése a tárolók kezeléséhez
1039
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1040
 setting_commit_ref_keywords: Hivatkozó kulcsszavak
1041
 setting_commit_fix_keywords: Javítások kulcsszavai
1042
 setting_autologin: Automatikus bejelentkezés
1043
diff --git a/lang/it.yml b/lang/it.yml
1044
index f3798b4..db35a4a 100644
1045
--- a/lang/it.yml
1046
+++ b/lang/it.yml
1047
@@ -92,6 +92,8 @@ field_is_required: Richiesto
1048
 field_firstname: Nome
1049
 field_lastname: Cognome
1050
 field_mail: Email
1051
+field_ssh_key: SSH Public Key
1052
+field_ssh_key_type: SSH Public Key Type
1053
 field_filename: File
1054
 field_filesize: Dimensione
1055
 field_downloads: Download
1056
@@ -181,6 +183,7 @@ setting_wiki_compression: Comprimi cronologia wiki
1057
 setting_feeds_limit: Limite contenuti del feed
1058
 setting_autofetch_changesets: Acquisisci automaticamente le commit
1059
 setting_sys_api_enabled: Abilita WS per la gestione del repository
1060
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1061
 setting_commit_ref_keywords: Referencing keywords
1062
 setting_commit_fix_keywords: Fixing keywords
1063
 setting_autologin: Login automatico
1064
diff --git a/lang/ja.yml b/lang/ja.yml
1065
index c1279b3..0875e66 100644
1066
--- a/lang/ja.yml
1067
+++ b/lang/ja.yml
1068
@@ -93,6 +93,8 @@ field_is_required: 必須
1069
 field_firstname: 名前
1070
 field_lastname: 苗字
1071
 field_mail: メールアドレス
1072
+field_ssh_key: SSH Public Key
1073
+field_ssh_key_type: SSH Public Key Type
1074
 field_filename: ファイル
1075
 field_filesize: サイズ
1076
 field_downloads: ダウンロード
1077
@@ -182,6 +184,7 @@ setting_wiki_compression: Wiki履歴を圧縮する
1078
 setting_feeds_limit: フィード内容の上限
1079
 setting_autofetch_changesets: コミットを自動取得する
1080
 setting_sys_api_enabled: リポジトリ管理用のWeb Serviceを有効にする
1081
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1082
 setting_commit_ref_keywords: 参照用キーワード
1083
 setting_commit_fix_keywords: 修正用キーワード
1084
 setting_autologin: 自動ログイン
1085
diff --git a/lang/ko.yml b/lang/ko.yml
1086
index f7958f9..3818080 100644
1087
--- a/lang/ko.yml
1088
+++ b/lang/ko.yml
1089
@@ -94,6 +94,8 @@ field_is_required: 필수
1090
 field_firstname: 이름
1091
 field_lastname: 성
1092
 field_mail: 메일
1093
+field_ssh_key: SSH Public Key
1094
+field_ssh_key_type: SSH Public Key Type
1095
 field_filename: 파일
1096
 field_filesize: 크기
1097
 field_downloads: 다운로드
1098
@@ -184,6 +186,7 @@ setting_wiki_compression: 위키 이력 압축
1099
 setting_feeds_limit: 내용 피드(RSS Feed) 제한 개수
1100
 setting_autofetch_changesets: 커밋된 변경묶음을 자동으로 가져오기
1101
 setting_sys_api_enabled: 저장소 관리자에 WS 를 허용
1102
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1103
 setting_commit_ref_keywords: 일감 참조에 사용할 키워드들
1104
 setting_commit_fix_keywords: 일감 해결에 사용할 키워드들 
1105
 setting_autologin: 자동 로그인
1106
diff --git a/lang/lt.yml b/lang/lt.yml
1107
index 3cdbcf0..ca56fb8 100644
1108
--- a/lang/lt.yml
1109
+++ b/lang/lt.yml
1110
@@ -106,6 +106,8 @@ field_is_required: Reikalaujama
1111
 field_firstname: Vardas
1112
 field_lastname: Pavardė
1113
 field_mail: Email
1114
+field_ssh_key: SSH Public Key
1115
+field_ssh_key_type: SSH Public Key Type
1116
 field_filename: Byla
1117
 field_filesize: Dydis
1118
 field_downloads: Atsiuntimai
1119
@@ -203,6 +205,7 @@ setting_feeds_limit: Perdavimo turinio riba
1120
 setting_default_projects_public: Naujas projektas viešas pagal nutylėjimą
1121
 setting_autofetch_changesets: Automatinis pakeitimų siuntimas
1122
 setting_sys_api_enabled: Įgalinkite WS sandėlio vadybai
1123
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1124
 setting_commit_ref_keywords: Nurodymo reikšminiai žodžiai
1125
 setting_commit_fix_keywords: Fiksavimo reikšminiai žodžiai
1126
 setting_autologin: Autoregistracija
1127
diff --git a/lang/nl.yml b/lang/nl.yml
1128
index ed10b4d..1d2bcbc 100644
1129
--- a/lang/nl.yml
1130
+++ b/lang/nl.yml
1131
@@ -148,6 +148,8 @@ field_attr_mail: E-mail attribuut
1132
 field_onthefly: On-the-fly aanmaken van een gebruiker
1133
 field_start_date: Start
1134
 field_done_ratio: %% Gereed
1135
+field_ssh_key: SSH Public Key
1136
+field_ssh_key_type: SSH Public Key Type
1137
 field_auth_source: Authenticatiemethode
1138
 field_hide_mail: Verberg mijn e-mailadres
1139
 field_comments: Commentaar
1140
@@ -638,6 +640,7 @@ field_parent_title: Bovenliggende pagina
1141
 label_issue_watchers: Monitoren
1142
 setting_commit_logs_encoding: Encodering van commit berichten
1143
 button_quote: Citaat
1144
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1145
 setting_sequential_project_identifiers: Genereer sequentiële projectidentiteiten
1146
 notice_unable_delete_version: Niet mogelijk om deze versie te verwijderen.
1147
 label_renamed: hernoemd
1148
diff --git a/lang/no.yml b/lang/no.yml
1149
index c8f131f..aa9c843 100644
1150
--- a/lang/no.yml
1151
+++ b/lang/no.yml
1152
@@ -105,6 +105,8 @@ field_is_required: Kreves
1153
 field_firstname: Fornavn
1154
 field_lastname: Etternavn
1155
 field_mail: E-post
1156
+field_ssh_key: SSH Public Key
1157
+field_ssh_key_type: SSH Public Key Type
1158
 field_filename: Fil
1159
 field_filesize: Størrelse
1160
 field_downloads: Nedlastinger
1161
@@ -200,6 +202,7 @@ setting_feeds_limit: Innholdsgrense for Feed
1162
 setting_default_projects_public: Nye prosjekter er offentlige som standard
1163
 setting_autofetch_changesets: Autohenting av innsendinger
1164
 setting_sys_api_enabled: Aktiver webservice for depot-administrasjon
1165
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1166
 setting_commit_ref_keywords: Nøkkelord for referanse
1167
 setting_commit_fix_keywords: Nøkkelord for retting
1168
 setting_autologin: Autoinnlogging
1169
diff --git a/lang/pl.yml b/lang/pl.yml
1170
index 6b28f0e..31c2dae 100644
1171
--- a/lang/pl.yml
1172
+++ b/lang/pl.yml
1173
@@ -161,6 +161,8 @@ field_last_login_on: Ostatnie połączenie
1174
 field_lastname: Nazwisko
1175
 field_login: Login
1176
 field_mail: Email
1177
+field_ssh_key: Klucz publiczny SSH
1178
+field_ssh_key_type: Typ klucza SSH
1179
 field_mail_notification: Powiadomienia Email
1180
 field_max_length: Maksymalna długość
1181
 field_min_length: Minimalna długość
1182
@@ -657,6 +659,7 @@ setting_repositories_encodings: Kodowanie repozytoriów
1183
 setting_self_registration: Własna rejestracja umożliwiona
1184
 setting_sequential_project_identifiers: Generuj sekwencyjne identyfikatory projektów
1185
 setting_sys_api_enabled: Włączenie WS do zarządzania repozytorium
1186
+setting_serve_git_repositories: Udostępnij repozytoria GIT poprzez konto SSH Redmine
1187
 setting_text_formatting: Formatowanie tekstu
1188
 setting_time_format: Format czasu
1189
 setting_user_format: Personalny format wyświetlania
1190
diff --git a/lang/pt-br.yml b/lang/pt-br.yml
1191
index ecbbc0c..716a1d7 100644
1192
--- a/lang/pt-br.yml
1193
+++ b/lang/pt-br.yml
1194
@@ -105,6 +105,8 @@ field_is_required: Obrigatório
1195
 field_firstname: Nome
1196
 field_lastname: Sobrenome
1197
 field_mail: Email
1198
+field_ssh_key: SSH Public Key
1199
+field_ssh_key_type: SSH Public Key Type
1200
 field_filename: Arquivo
1201
 field_filesize: Tamanho
1202
 field_downloads: Downloads
1203
@@ -201,6 +203,7 @@ setting_feeds_limit: Limite do Feed
1204
 setting_default_projects_public: Novos projetos são públicos por padrão
1205
 setting_autofetch_changesets: Auto-obter commits
1206
 setting_sys_api_enabled: Ativa WS para gerenciamento do repositório
1207
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1208
 setting_commit_ref_keywords: Palavras de referência
1209
 setting_commit_fix_keywords: Palavras de fechamento
1210
 setting_autologin: Auto-login
1211
diff --git a/lang/pt.yml b/lang/pt.yml
1212
index ec42386..6149d34 100644
1213
--- a/lang/pt.yml
1214
+++ b/lang/pt.yml
1215
@@ -107,6 +107,8 @@ field_is_required: Obrigatório
1216
 field_firstname: Nome
1217
 field_lastname: Apelido
1218
 field_mail: E-mail
1219
+field_ssh_key: SSH Public Key
1220
+field_ssh_key_type: SSH Public Key Type
1221
 field_filename: Ficheiro
1222
 field_filesize: Tamanho
1223
 field_downloads: Downloads
1224
@@ -203,6 +205,7 @@ setting_feeds_limit: Limite de conteúdo do feed
1225
 setting_default_projects_public: Projectos novos são públicos por omissão
1226
 setting_autofetch_changesets: Buscar automaticamente commits
1227
 setting_sys_api_enabled: Activar Web Service para gestão do repositório
1228
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1229
 setting_commit_ref_keywords: Palavras-chave de referência
1230
 setting_commit_fix_keywords: Palavras-chave de fecho
1231
 setting_autologin: Login automático
1232
diff --git a/lang/ro.yml b/lang/ro.yml
1233
index b0df48d..25be71e 100644
1234
--- a/lang/ro.yml
1235
+++ b/lang/ro.yml
1236
@@ -92,6 +92,8 @@ field_is_required: Obligatoriu
1237
 field_firstname: Nume
1238
 field_lastname: Prenume
1239
 field_mail: Email
1240
+field_ssh_key: SSH Public Key
1241
+field_ssh_key_type: SSH Public Key Type
1242
 field_filename: Fisier
1243
 field_filesize: Marimea fisierului
1244
 field_downloads: Download
1245
@@ -181,6 +183,7 @@ setting_wiki_compression: Compresie istoric wiki
1246
 setting_feeds_limit: Limita continut feed
1247
 setting_autofetch_changesets: Autofetch commits
1248
 setting_sys_api_enabled: Setare WS pentru managementul stocului (repository)
1249
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1250
 setting_commit_ref_keywords: Cuvinte cheie de referinta
1251
 setting_commit_fix_keywords: Cuvinte cheie de rezolvare
1252
 setting_autologin: Autentificare automata
1253
diff --git a/lang/ru.yml b/lang/ru.yml
1254
index 4943eea..91db5b1 100644
1255
--- a/lang/ru.yml
1256
+++ b/lang/ru.yml
1257
@@ -165,6 +165,8 @@ field_last_login_on: Последнее подключение
1258
 field_lastname: Фамилия
1259
 field_login: Пользователь
1260
 field_mail: Email
1261
+field_ssh_key: SSH Public Key
1262
+field_ssh_key_type: SSH Public Key Type
1263
 field_mail_notification: Уведомления по email
1264
 field_max_length: Максимальная длина
1265
 field_min_length: Минимальная длина
1266
@@ -673,6 +675,7 @@ setting_repositories_encodings: Кодировки хранилища
1267
 setting_self_registration: Возможна саморегистрация
1268
 setting_sequential_project_identifiers: Генерировать последовательные идентификаторы проектов
1269
 setting_sys_api_enabled: Разрешить WS для управления хранилищем
1270
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1271
 setting_text_formatting: Форматирование текста
1272
 setting_time_format: Формат времени
1273
 setting_user_format: Формат отображения имени
1274
diff --git a/lang/sk.yml b/lang/sk.yml
1275
index ffae2d6..5297432 100644
1276
--- a/lang/sk.yml
1277
+++ b/lang/sk.yml
1278
@@ -106,6 +106,8 @@ field_is_required: Povinné pole
1279
 field_firstname: Meno
1280
 field_lastname: Priezvisko
1281
 field_mail: Email
1282
+field_ssh_key: SSH Public Key
1283
+field_ssh_key_type: SSH Public Key Type
1284
 field_filename: Súbor
1285
 field_filesize: Veľkosť
1286
 field_downloads: Stiahnuté
1287
@@ -201,6 +203,7 @@ setting_feeds_limit: Limit zobrazených položiek (Atom feed)
1288
 setting_default_projects_public: Nové projekty nastavovať ako verejné
1289
 setting_autofetch_changesets: Automatický prenos zmien
1290
 setting_sys_api_enabled: Povolit WS pre správu repozitory
1291
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1292
 setting_commit_ref_keywords: Klúčové slová pre odkazy
1293
 setting_commit_fix_keywords: Klúčové slová pre uzavretie
1294
 setting_autologin: Automatické prihlasovanie
1295
@@ -698,10 +701,10 @@ label_example: Príklad
1296
 permission_edit_own_messages: Edit own messages
1297
 permission_delete_own_messages: Delete own messages
1298
 text_repository_usernames_mapping: "Select or 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."
1299
-label_user_activity: "%s's activity"
1300
-label_updated_time_by: Updated by %s %s ago
1301
-text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
1302
-setting_diff_max_lines_displayed: Max number of diff lines displayed
1303
-text_plugin_assets_writable: Plugin assets directory writable
1304
-warning_attachments_not_saved: "%d file(s) could not be saved."
1305
-button_create_and_continue: Create and continue
1306
+label_user_activity: "%s's activity"
1307
+label_updated_time_by: Updated by %s %s ago
1308
+text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
1309
+setting_diff_max_lines_displayed: Max number of diff lines displayed
1310
+text_plugin_assets_writable: Plugin assets directory writable
1311
+warning_attachments_not_saved: "%d file(s) could not be saved."
1312
+button_create_and_continue: Create and continue
1313
diff --git a/lang/sr.yml b/lang/sr.yml
1314
index 7070c4a..39031e6 100644
1315
--- a/lang/sr.yml
1316
+++ b/lang/sr.yml
1317
@@ -96,6 +96,8 @@ field_is_required: Zahtevano
1318
 field_firstname: Ime
1319
 field_lastname: Prezime
1320
 field_mail: Email
1321
+field_ssh_key: SSH Public Key
1322
+field_ssh_key_type: SSH Public Key Type
1323
 field_filename: Fajl
1324
 field_filesize: Veličina
1325
 field_downloads: Preuzimanja
1326
@@ -186,6 +188,7 @@ setting_wiki_compression: Kompresija wiki history-a
1327
 setting_feeds_limit: Feed content limit
1328
 setting_autofetch_changesets: Autofetch commits
1329
 setting_sys_api_enabled: Ukljuci WS za menadžment spremišta
1330
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1331
 setting_commit_ref_keywords: Referentne ključne reči
1332
 setting_commit_fix_keywords: Fiksne ključne reči
1333
 setting_autologin: Automatsko prijavljivanje
1334
diff --git a/lang/sv.yml b/lang/sv.yml
1335
index 852c7fe..29a80e4 100644
1336
--- a/lang/sv.yml
1337
+++ b/lang/sv.yml
1338
@@ -106,6 +106,8 @@ field_is_required: Obligatorisk
1339
 field_firstname: Förnamn
1340
 field_lastname: Efternamn
1341
 field_mail: Mail
1342
+field_ssh_key: SSH Public Key
1343
+field_ssh_key_type: SSH Public Key Type
1344
 field_filename: Fil
1345
 field_filesize: Storlek
1346
 field_downloads: Nerladdningar
1347
@@ -203,6 +205,7 @@ setting_feeds_limit: Innehållsgräns för Feed
1348
 setting_default_projects_public: Nya projekt är publika som standard
1349
 setting_autofetch_changesets: Automatisk hämtning av commits
1350
 setting_sys_api_enabled: Aktivera WS för repository-hantering
1351
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1352
 setting_commit_ref_keywords: Referens-nyckelord
1353
 setting_commit_fix_keywords: Fix-nyckelord
1354
 setting_autologin: Automatisk inloggning
1355
diff --git a/lang/th.yml b/lang/th.yml
1356
index c0a3859..e8d93f1 100644
1357
--- a/lang/th.yml
1358
+++ b/lang/th.yml
1359
@@ -103,6 +103,8 @@ field_is_required: องใส
1360
 field_firstname: ื่
1361
 field_lastname: นามสก
1362
 field_mail: เมล
1363
+field_ssh_key: SSH Public Key
1364
+field_ssh_key_type: SSH Public Key Type
1365
 field_filename: แฟ
1366
 field_filesize: ขนาด
1367
 field_downloads: ดาวนโหลด
1368
@@ -198,6 +200,7 @@ setting_feeds_limit: จำนวน Feed
1369
 setting_default_projects_public: โครงการใหมาเริ่มตนเป สาธารณะ
1370
 setting_autofetch_changesets:  commits ตโนม
1371
 setting_sys_api_enabled: เปดใช WS สำหรบการจดการที่เกบตนฉบ
1372
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1373
 setting_commit_ref_keywords: คำสำคัญ Referencing
1374
 setting_commit_fix_keywords: คำสำคัญ Fixing
1375
 setting_autologin: เข้าระบบอัตโนมัติ
1376
diff --git a/lang/tr.yml b/lang/tr.yml
1377
index 8136367..1bb9eb3 100644
1378
--- a/lang/tr.yml
1379
+++ b/lang/tr.yml
1380
@@ -102,6 +102,8 @@ field_is_required: Gerekli
1381
 field_firstname: Ad
1382
 field_lastname: Soyad
1383
 field_mail: E-Posta
1384
+field_ssh_key: SSH Public Key
1385
+field_ssh_key_type: SSH Public Key Type
1386
 field_filename: Dosya
1387
 field_filesize: Boyut
1388
 field_downloads: İndirilenler
1389
@@ -197,6 +199,7 @@ setting_feeds_limit: Haber yayını içerik limiti
1390
 setting_default_projects_public: Yeni projeler varsayılan olarak herkese açık
1391
 setting_autofetch_changesets: Otomatik gönderi al
1392
 setting_sys_api_enabled: Depo yönetimi için WS'yi etkinleştir
1393
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1394
 setting_commit_ref_keywords: Başvuru Kelimeleri
1395
 setting_commit_fix_keywords: Sabitleme kelimeleri
1396
 setting_autologin: Otomatik Giriş
1397
diff --git a/lang/uk.yml b/lang/uk.yml
1398
index be121cb..3a5840e 100644
1399
--- a/lang/uk.yml
1400
+++ b/lang/uk.yml
1401
@@ -97,6 +97,8 @@ field_is_required: Необхідно
1402
 field_firstname: Ім'я
1403
 field_lastname: Прізвище
1404
 field_mail: Ел. пошта
1405
+field_ssh_key: SSH Public Key
1406
+field_ssh_key_type: SSH Public Key Type
1407
 field_filename: Файл
1408
 field_filesize: Розмір
1409
 field_downloads: Завантаження
1410
@@ -189,6 +191,7 @@ setting_wiki_compression: Стиснення історії Wiki
1411
 setting_feeds_limit: Обмеження змісту подачі
1412
 setting_autofetch_changesets: Автоматично доставати доповнення
1413
 setting_sys_api_enabled: Дозволити WS для управління репозиторієм
1414
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1415
 setting_commit_ref_keywords: Ключові слова для посилання
1416
 setting_commit_fix_keywords: Призначення ключових слів
1417
 setting_autologin: Автоматичний вхід
1418
diff --git a/lang/vn.yml b/lang/vn.yml
1419
index 908e232..24b800f 100644
1420
--- a/lang/vn.yml
1421
+++ b/lang/vn.yml
1422
@@ -106,6 +106,8 @@ field_is_required: Bắt buộc
1423
 field_firstname: Tên lót + Tên
1424
 field_lastname: Họ
1425
 field_mail: Email
1426
+field_ssh_key: SSH Public Key
1427
+field_ssh_key_type: SSH Public Key Type
1428
 field_filename: Tập tin
1429
 field_filesize: Cỡ
1430
 field_downloads: Tải về
1431
@@ -202,6 +204,7 @@ setting_feeds_limit: Giới hạn nội dung của feed
1432
 setting_default_projects_public: Dự án mặc định là công cộng
1433
 setting_autofetch_changesets: Autofetch commits
1434
 setting_sys_api_enabled: Enable WS for repository management
1435
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1436
 setting_commit_ref_keywords: Từ khóa tham khảo
1437
 setting_commit_fix_keywords: Từ khóa chỉ vấn đề đã giải quyết
1438
 setting_autologin: Tự động đăng nhập
1439
diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml
1440
index 50eccf4..5ccd4a0 100644
1441
--- a/lang/zh-tw.yml
1442
+++ b/lang/zh-tw.yml
1443
@@ -106,6 +106,8 @@ field_is_required: 必填
1444
 field_firstname: 名字
1445
 field_lastname: 姓氏
1446
 field_mail: 電子郵件
1447
+field_ssh_key: SSH Public Key
1448
+field_ssh_key_type: SSH Public Key Type
1449
 field_filename: 檔案名稱
1450
 field_filesize: 大小
1451
 field_downloads: 下載次數
1452
@@ -203,6 +205,7 @@ setting_feeds_limit: RSS 新聞限制
1453
 setting_autofetch_changesets: 自動取得送交版次
1454
 setting_default_projects_public: 新建立之專案預設為公開
1455
 setting_sys_api_enabled: 啟用管理版本庫之網頁服務 (Web Service)
1456
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1457
 setting_commit_ref_keywords: 送交用於參照項目之關鍵字
1458
 setting_commit_fix_keywords: 送交用於修正項目之關鍵字
1459
 setting_autologin: 自動登入
1460
diff --git a/lang/zh.yml b/lang/zh.yml
1461
index 989f329..32f889b 100644
1462
--- a/lang/zh.yml
1463
+++ b/lang/zh.yml
1464
@@ -106,6 +106,8 @@ field_is_required: 必填
1465
 field_firstname: 名字
1466
 field_lastname: 姓氏
1467
 field_mail: 邮件地址
1468
+field_ssh_key: SSH Public Key
1469
+field_ssh_key_type: SSH Public Key Type
1470
 field_filename: 文件
1471
 field_filesize: 大小
1472
 field_downloads: 下载次数
1473
@@ -203,6 +205,7 @@ setting_feeds_limit: RSS Feed内容条数限制
1474
 setting_default_projects_public: 新建项目默认为公开项目
1475
 setting_autofetch_changesets: 自动获取程序变更
1476
 setting_sys_api_enabled: 启用用于版本库管理的Web Service
1477
+setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account
1478
 setting_commit_ref_keywords: 用于引用问题的关键字
1479
 setting_commit_fix_keywords: 用于解决问题的关键字
1480
 setting_autologin: 自动登录
1481
diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb
1482
index a9e1dda..77dd6ca 100644
1483
--- a/lib/redmine/scm/adapters/git_adapter.rb
1484
+++ b/lib/redmine/scm/adapters/git_adapter.rb
1485
@@ -24,6 +24,25 @@ module Redmine
1486
         
1487
         # Git executable name
1488
         GIT_BIN = "git"
1489
+	EMPTY_COMMIT = "0000000000000000000000000000000000000000"
1490
+
1491
+        def init(description)
1492
+          FileUtils.mkdir_p "#{url}"
1493
+          cmd = "#{GIT_BIN} --git-dir #{target('')} init --template=#{RAILS_ROOT}/lib/redmine/scm/adapters/git_templates --bare"
1494
+          shellout(cmd)
1495
+          Dir["#{url}/**/*"].each do |file|
1496
+            if File.file?(file)
1497
+              File.open(file, "r") do |source|
1498
+                File.open(file + ".new", "w", File.stat(file).mode) do |dest|
1499
+                  source.each_line do |line|
1500
+                    dest << line.gsub('#{description}', description).gsub('#{RAILS_ROOT}', RAILS_ROOT).gsub('#{RAILS_ENV}', ENV["RAILS_ENV"])
1501
+                  end
1502
+                end
1503
+              end
1504
+              FileUtils.mv file + ".new", file
1505
+            end
1506
+          end
1507
+        end
1508
 
1509
         # Get the revision of a particuliar file
1510
         def get_rev (rev,path)
1511
@@ -61,7 +80,7 @@ module Redmine
1512
               elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
1513
                 key = $1
1514
                 value = $2
1515
-                if key == "Author"
1516
+                if key == "Commit"
1517
                   changeset[:author] = value
1518
                 elsif key == "CommitDate"
1519
                   changeset[:date] = value
1520
@@ -140,7 +159,7 @@ module Redmine
1521
           cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
1522
           cmd << " --reverse" if options[:reverse]
1523
           cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
1524
-          cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
1525
+          cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from and identifier_from != EMPTY_COMMIT
1526
           cmd << " #{shell_quote identifier_to} " if identifier_to
1527
           shellout(cmd) do |io|
1528
             files=[]
1529
@@ -262,6 +281,21 @@ module Redmine
1530
           return nil if $? && $?.exitstatus != 0
1531
           cat
1532
         end
1533
+
1534
+        def get_object_type(object)
1535
+          cmd = "#{GIT_BIN} --git-dir #{target('')} cat-file -t #{object}"
1536
+          type = ""
1537
+          GitAdapter.shellout(cmd) do |io|
1538
+            return io.read.strip
1539
+          end
1540
+        end
1541
+
1542
+        def merge_base(old_rev, new_rev)
1543
+          cmd = "#{GIT_BIN} --git-dir #{target('')} merge-base #{old_rev} #{new_rev}"
1544
+          GitAdapter.shellout(cmd) do |io|
1545
+            return io.read.strip
1546
+          end
1547
+        end
1548
       end
1549
     end
1550
   end
1551
diff --git a/lib/redmine/scm/adapters/git_templates/description b/lib/redmine/scm/adapters/git_templates/description
1552
new file mode 100644
1553
index 0000000..b0f81f9
1554
--- /dev/null
1555
+++ b/lib/redmine/scm/adapters/git_templates/description
1556
@@ -0,0 +1 @@
1557
+#{description}
1558
\ No newline at end of file
1559
diff --git a/lib/redmine/scm/adapters/git_templates/hooks/post-receive b/lib/redmine/scm/adapters/git_templates/hooks/post-receive
1560
new file mode 100755
1561
index 0000000..d9a4b23
1562
--- /dev/null
1563
+++ b/lib/redmine/scm/adapters/git_templates/hooks/post-receive
1564
@@ -0,0 +1 @@
1565
+ruby #{RAILS_ROOT}/script/runner Repository.fetch_changesets -e #{RAILS_ENV}
1566
\ No newline at end of file
1567
diff --git a/lib/redmine/scm/adapters/git_templates/hooks/pre-receive b/lib/redmine/scm/adapters/git_templates/hooks/pre-receive
1568
new file mode 100755
1569
index 0000000..6956009
1570
--- /dev/null
1571
+++ b/lib/redmine/scm/adapters/git_templates/hooks/pre-receive
1572
@@ -0,0 +1 @@
1573
+ruby #{RAILS_ROOT}/script/runner GitManager.check_commits -e #{RAILS_ENV}
1574
\ No newline at end of file
1575
diff --git a/lib/redmine/scm/adapters/git_templates/info/exclude b/lib/redmine/scm/adapters/git_templates/info/exclude
1576
new file mode 100644
1577
index 0000000..2c87b72
1578
--- /dev/null
1579
+++ b/lib/redmine/scm/adapters/git_templates/info/exclude
1580
@@ -0,0 +1,6 @@
1581
+# git-ls-files --others --exclude-from=.git/info/exclude
1582
+# Lines that start with '#' are comments.
1583
+# For a project mostly in C, the following would be a good set of
1584
+# exclude patterns (uncomment them if you want to use them):
1585
+# *.[oa]
1586
+# *~
1587
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
1588
index 2e60ee4..c3b66f5 100644
1589
--- a/public/stylesheets/application.css
1590
+++ b/public/stylesheets/application.css
1591
@@ -240,7 +240,7 @@ ul.properties li span {font-style:italic;}
1592
 .total-hours span.hours-int { font-size: 120%; }
1593
 
1594
 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
1595
-#user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
1596
+#user_firstname, #user_lastname, #user_mail, #user_ssh_key, #my_account_form select { width: 90%; }
1597
 
1598
 .pagination {font-size: 90%}
1599
 p.pagination {margin-top:8px;}
1600
diff --git a/test/unit/authorized_keys_entry_test.rb b/test/unit/authorized_keys_entry_test.rb
1601
new file mode 100644
1602
index 0000000..8297091
1603
--- /dev/null
1604
+++ b/test/unit/authorized_keys_entry_test.rb
1605
@@ -0,0 +1,52 @@
1606
+require File.dirname(__FILE__) + '/../test_helper'
1607
+
1608
+class AuthorizedKeysEntryTest < Test::Unit::TestCase
1609
+  def setup
1610
+    AuthorizedKeysEntry.authorized_keys_filename = File.dirname(__FILE__) + '/../../tmp/test/authorized_keys'
1611
+    e = AuthorizedKeysEntry.new
1612
+    e.identifier = 'admin'
1613
+    e.key = 'ssh-dss AAAAB3NzaC1kc3MAAACBALDC8OcQ6OfKUMUEy4xeARLmIGMB6IH4GpSQPTAIpMkRvmyIc99NiRVl1TSlASQQephL0XSH6R2vP+XEzY/tSGIzGugyIkErV2AZ8JL/BwZI1euVE3tjBDoL+6xpdqMCgUwWh2vrL/Qe8DOoil2nTtnHJBTz0wpkYHe+DDKda7ttAAAAFQDZ13q/5MPUvT9AjyeUvWZe2lFC/QAAAIAikSLreTOTeDjKkSrRrhV2RByfT5wyat/0rulzU9wp6KOre9XtH5VNhlGAz0Sj5MgoxUJuHrlIjSNoxnx9S3h1TKHaZ2Ov5xKc4AtfnmH5LwyXdT9QzbsB1NL4xDAjBPHGQCEypxRVz3zLp9gt6gCzBszbS563pIfUmqikaI9iwgAAAIA8/GFBKSn0E4kFsWRSiUeRyEeXBKPbghMcFePOp/v23xZrNGTso6AKCXPMvLcVZbpiZY4wIL0nftYdQNcu3ubDIKWVKIfdisWpKxgejbKQZMQWQRPZZVxT/UtkPuF4mjJyynbdc0Fn78gxfySnmpvfoGhVMJYd+Xjjagr4sU8Hpw== sadf@sdfsd.kdl.sdfoij'
1614
+    e.type = 'ssh-dss'
1615
+    e.options = [ 'command="test"' ]
1616
+
1617
+    e.save
1618
+  end
1619
+
1620
+  def test_read
1621
+    e = AuthorizedKeysEntry.find_by_identifier('admin')
1622
+    assert e.key =~ /\AAAAAB3NzaC1kc3/
1623
+    assert e.type == 'ssh-dss'
1624
+    assert e.options.kind_of?(Array)
1625
+  end
1626
+
1627
+  def test_verify_wrong
1628
+    e = AuthorizedKeysEntry.new
1629
+    e.identifier = 'a'
1630
+    e.key = 'b'
1631
+    e.type = 'ssh-dss'
1632
+    e.options = []
1633
+    assert !e.validate 
1634
+  end
1635
+
1636
+  def test_replace
1637
+    e = AuthorizedKeysEntry.new
1638
+    e.identifier = 'admin'
1639
+    e.key = 'AAAAB3NzaC1yc2EAAAABIwAAAEEAsUX/IBulIRL9y49x5AEuXad+nDmGTY1Ad+c2YFJPMQgJWtY+cDUrUNDBxyvYADDzFldeNMDAwAeBFSfQL8h3ew=='
1640
+    e.type = 'ssh-rsa'
1641
+    e.save
1642
+
1643
+    assert AuthorizedKeysEntry.find_by_identifier('admin').type = 'ssh-rsa'
1644
+  end
1645
+
1646
+  def test_delete
1647
+    e = AuthorizedKeysEntry.new
1648
+    e.identifier = 'admin'
1649
+    e.key = ''
1650
+    e.type = ''
1651
+    e.options = []
1652
+    e.save
1653
+
1654
+    assert AuthorizedKeysEntry.find_by_identifier('admin') == nil
1655
+  end
1656
+end
1657
+
1658
-- 
1659
1.6.1.3
1660

    
(4-4/5)