Patch #2525 » 0002-Redmine-management-of-Git-repositories.patch
| app/helpers/repositories_helper.rb | ||
|---|---|---|
| 164 | 164 | end | 
| 165 | 165 | |
| 166 | 166 | def git_field_tags(form, repository) | 
| 167 |       content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) | |
| 167 | if Setting.serve_git_repositories? and (repository == nil or repository.url.blank?) | |
| 168 |           content_tag('p', form.text_field(:url, :value => GitManager.repositories_root + '/' + repository.project.identifier + '.git'), :label => 'Path to .git directory', :size => 60, :required => true) | |
| 169 | else | |
| 170 |           content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) | |
| 171 | end | |
| 168 | 172 | end | 
| 169 | 173 | |
| 170 | 174 | def cvs_field_tags(form, repository) | 
| app/models/authorized_keys_entry.rb | ||
|---|---|---|
| 1 | require "strscan" | |
| 2 | require 'thread' | |
| 3 | ||
| 4 | class AuthorizedKeysEntry | |
| 5 | COMMENT=/^#/ | |
| 6 | BLANK=/^\s+$/ | |
| 7 | AUTHORIZED_KEYS_PARSER=/^(?:(.+) )?(ssh-dss|ssh-rsa) ([^ ]+)(?: (.+))?$/ | |
| 8 | KEY_TYPES=['ssh-dss','ssh-rsa'] | |
| 9 | KEY_FORMAT=/\A[\w\/\+\=]+\z/ | |
| 10 | IDENTIFIER_FORMAT=/\A[\w@\.\-]+\z/ | |
| 11 | @@authorized_keys_filename="~/.ssh/authorized_keys" | |
| 12 | ||
| 13 | attr_reader :identifier, :key, :type | |
| 14 | attr_accessor :options | |
| 15 | ||
| 16 | @authorized_keys_lock = Mutex.new | |
| 17 | ||
| 18 | # Create object from string from authorized_keys | |
| 19 | def initialize(authorized_keys_string = nil) | |
| 20 | @key = '' | |
| 21 | @options = [] | |
| 22 | create_from_authorized_keys(authorized_keys_string) if authorized_keys_string != nil | |
| 23 | end | |
| 24 | ||
| 25 | def identifier=(identifier) | |
| 26 | @identifier = identifier | |
| 27 | @validated = false | |
| 28 | end | |
| 29 | ||
| 30 | def key=(key) | |
| 31 | @key = key | |
| 32 | @validated = false | |
| 33 | end | |
| 34 | ||
| 35 | def type=(type) | |
| 36 | @type = type | |
| 37 | @validated = false | |
| 38 | end | |
| 39 | ||
| 40 | def save | |
| 41 | raise "Invalid key format" if !validate | |
| 42 | AuthorizedKeysEntry.save_entry(self) | |
| 43 | end | |
| 44 | ||
| 45 | def self.find_by_identifier(identifier) | |
| 46 | read_entries do |entry| | |
| 47 | return entry if entry.identifier == identifier | |
| 48 | end | |
| 49 | return nil | |
| 50 | end | |
| 51 | ||
| 52 | def validate | |
| 53 | return true if @key == "" | |
| 54 | return false if !@options.kind_of?(Array) | |
| 55 | return true if @validated | |
| 56 | # Clean key from any garbage user might have entered while copying the key | |
| 57 | @key = @key.gsub(/\A(ssh-dss|ssh-rsa)\s/,"").gsub(/\s\w+@[\.\w\-\n]+\s*\Z/,"").gsub(/\s/,'') | |
| 58 | return false if (@key=~KEY_FORMAT) == nil or !KEY_TYPES.include?(@type) | |
| 59 | return false if (@identifier=~IDENTIFIER_FORMAT) == nil | |
| 60 |  | |
| 61 |     Tempfile.open("redmine") do |file| | |
| 62 | file << get_id_dsa_pub_format | |
| 63 | file.close | |
| 64 |       @validated = system "ssh-keygen -B -f #{file.path()} &> /dev/null" | |
| 65 | end | |
| 66 | return @validated | |
| 67 | end | |
| 68 | ||
| 69 | def to_s | |
| 70 | get_authorized_keys_format | |
| 71 | end | |
| 72 | ||
| 73 | def self.authorized_keys_filename=(filename) | |
| 74 | @@authorized_keys_filename = filename | |
| 75 | end | |
| 76 | ||
| 77 | private | |
| 78 | ||
| 79 | def create_from_authorized_keys(line) | |
| 80 | m = AUTHORIZED_KEYS_PARSER.match(line) | |
| 81 | raise ArgumentError, "Invalid format of authorized_keys entry" if m == nil | |
| 82 | ||
| 83 | @options, @type, @key, @identifier = m[1], m[2], m[3], m[4] | |
| 84 | @options = AuthorizedKeysEntry.parse_options(@options) | |
| 85 | end | |
| 86 | ||
| 87 | def get_authorized_keys_format | |
| 88 | raise ArgumentError, 'Entry must have the type set' if @type == nil | |
| 89 | s = "" | |
| 90 |     s += @options.join(",") + " " if !@options.empty? | |
| 91 | s += @type + " " + @key | |
| 92 | s += " " + @identifier if @identifier | |
| 93 | return s | |
| 94 | end | |
| 95 | ||
| 96 | def get_id_dsa_pub_format | |
| 97 | raise ArgumentError, 'Entry must have the type set' if @type == nil | |
| 98 | s = "" | |
| 99 | s += @type + " " + @key | |
| 100 | s += " " + @identifier if @identifier | |
| 101 | return s | |
| 102 | end | |
| 103 | ||
| 104 | def self.parse_options(options) | |
| 105 | result = [] | |
| 106 | return result if options == nil | |
| 107 | scanner = StringScanner.new(options) | |
| 108 | while !scanner.eos? | |
| 109 | scanner.skip(/[ \t]*/) | |
| 110 | # scan a long option | |
| 111 | if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/) | |
| 112 | result << out | |
| 113 | else | |
| 114 | # found an unscannable token, let's abort | |
| 115 | break | |
| 116 | end | |
| 117 | # eat a comma | |
| 118 | scanner.skip(/[\t]*,[\t]*/) | |
| 119 | end | |
| 120 | return result | |
| 121 | end | |
| 122 | ||
| 123 | def self.read_entries | |
| 124 | filename = File.expand_path(@@authorized_keys_filename) | |
| 125 | return if !File.exists?(filename) | |
| 126 | ||
| 127 | File.open(filename) do |file| | |
| 128 | file.flock(File::LOCK_SH) | |
| 129 | begin | |
| 130 | file.each_line do |line| | |
| 131 | next if COMMENT.match(line) or BLANK.match(line) | |
| 132 | yield AuthorizedKeysEntry.new(line) | |
| 133 | end | |
| 134 | ensure | |
| 135 | file.flock(File::LOCK_UN) | |
| 136 | end | |
| 137 | end | |
| 138 | end | |
| 139 | ||
| 140 | def self.save_entry(entry) | |
| 141 | filename = File.expand_path(@@authorized_keys_filename) | |
| 142 | ||
| 143 | FileUtils.mkdir_p(File.dirname(filename)) | |
| 144 | ||
| 145 | found = false | |
| 146 | entries = [] | |
| 147 | file = nil | |
| 148 | begin | |
| 149 | if File.exist?(filename) | |
| 150 | file = File.open(filename, "r") | |
| 151 | # TODO: somehow handle flock blocking | |
| 152 | file.flock(File::LOCK_EX) | |
| 153 | file.each_line do |line| | |
| 154 | next if COMMENT.match(line) or BLANK.match(line) | |
| 155 | entries << AuthorizedKeysEntry.new(line) | |
| 156 | end | |
| 157 | ||
| 158 | entries.each do |e| | |
| 159 | if entry.identifier == e.identifier | |
| 160 | return if entry.key == e.key and entry.type == e.type | |
| 161 | ||
| 162 | found = true | |
| 163 | if entry.key != "" | |
| 164 | e.key = entry.key | |
| 165 | e.type = entry.type | |
| 166 | e.options = entry.options | |
| 167 | else | |
| 168 | entries.delete(e) | |
| 169 | end | |
| 170 | break | |
| 171 | end | |
| 172 | end | |
| 173 | end | |
| 174 | ||
| 175 | if !found and entry.key != "" | |
| 176 | entries << entry | |
| 177 | end | |
| 178 |  | |
| 179 | File.open(filename, "w") do |write_file| | |
| 180 | write_file.flock(File::LOCK_EX) if file == nil | |
| 181 | entries.each do |entry| | |
| 182 | write_file << entry.to_s + "\n" | |
| 183 | end | |
| 184 | end | |
| 185 | ensure | |
| 186 | file.close if file != nil | |
| 187 | end | |
| 188 | end | |
| 189 | end | |
| 190 | ||
| app/models/changeset.rb | ||
|---|---|---|
| 70 | 70 | scan_comment_for_issue_ids | 
| 71 | 71 | end | 
| 72 | 72 | require 'pp' | 
| 73 |  | |
| 74 | def scan_comment_for_issue_ids | |
| 75 | return if comments.blank? | |
| 73 | ||
| 74 | # returns issue ids found in message | |
| 75 | # three arrays are returned, references issues, fixed ones and wrong numbers (not Issues) | |
| 76 | def self.find_issue_ids(message, project) | |
| 77 | return [[],[]] if message.blank? | |
| 76 | 78 | # keywords used to reference issues | 
| 77 | 79 |     ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) | 
| 78 | 80 | # keywords used to fix issues | 
| 79 | 81 |     fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip) | 
| 80 | # status and optional done ratio applied | |
| 81 | fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id) | |
| 82 | done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i | |
| 83 | 82 |  | 
| 84 | 83 |     kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|") | 
| 85 | return if kw_regexp.blank? | |
| 84 |     return [[],[]] if kw_regexp.blank? | |
| 86 | 85 |  | 
| 87 | 86 | referenced_issues = [] | 
| 87 | fixed_issues = [] | |
| 88 | wrong_issue_ids = [] | |
| 88 | 89 |  | 
| 89 | 90 |     if ref_keywords.delete('*') | 
| 90 | 91 | # find any issue ID in the comments | 
| 91 | 92 | target_issue_ids = [] | 
| 92 |       comments.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] } | |
| 93 | referenced_issues += repository.project.issues.find_all_by_id(target_issue_ids) | |
| 93 |       message.scan(%r{([\s\(,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] } | |
| 94 | found_issues = project.issues.find_all_by_id(target_issue_ids) | |
| 95 | references_issues += found_issues | |
| 96 |       found_issues.each { |issue| target_issue_ids.delete(issue.id.to_s) } | |
| 97 | wrong_issue_ids += target_issue_ids | |
| 94 | 98 | end | 
| 95 | 99 |  | 
| 96 |     comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match| | |
| 100 |     message.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match| | |
| 97 | 101 | action = match[0] | 
| 98 | 102 | target_issue_ids = match[1].scan(/\d+/) | 
| 99 | target_issues = repository.project.issues.find_all_by_id(target_issue_ids) | |
| 100 | if fix_status && fix_keywords.include?(action.downcase) | |
| 101 | # update status of issues | |
| 102 |         logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug? | |
| 103 | target_issues.each do |issue| | |
| 104 | # the issue may have been updated by the closure of another one (eg. duplicate) | |
| 105 | issue.reload | |
| 106 | # don't change the status is the issue is closed | |
| 107 | next if issue.status.is_closed? | |
| 108 |           csettext = "r#{self.revision}" | |
| 109 | if self.scmid && (! (csettext =~ /^r[0-9]+$/)) | |
| 110 |             csettext = "commit:\"#{self.scmid}\"" | |
| 111 | end | |
| 112 | journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext)) | |
| 113 | issue.status = fix_status | |
| 114 | issue.done_ratio = done_ratio if done_ratio | |
| 115 | issue.save | |
| 116 |           Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') | |
| 117 | end | |
| 103 | target_issues = project.issues.find_all_by_id(target_issue_ids) | |
| 104 |       target_issues.each { |issue| target_issue_ids.delete(issue.id.to_s) } | |
| 105 | wrong_issue_ids += target_issue_ids | |
| 106 | if fix_keywords.include?(action.downcase) | |
| 107 | fixed_issues += target_issues | |
| 108 | else | |
| 109 | referenced_issues += target_issues | |
| 118 | 110 | end | 
| 119 | referenced_issues += target_issues | |
| 120 | 111 | end | 
| 121 |  | |
| 122 | self.issues = referenced_issues.uniq | |
| 112 | return [referenced_issues.uniq, fixed_issues.uniq, wrong_issue_ids.uniq] | |
| 113 | end | |
| 114 |  | |
| 115 | def scan_comment_for_issue_ids | |
| 116 | return if comments.blank? | |
| 117 | # status and optional done ratio applied | |
| 118 | fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id) | |
| 119 | done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i | |
| 120 | ||
| 121 | referenced_issues, fixed_issues, wrong_issues = Changeset.find_issue_ids(comments, repository.project) | |
| 122 | ||
| 123 | # update status of issues | |
| 124 |     logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug? | |
| 125 | fixed_issues.each do |issue| | |
| 126 | # the issue may have been updated by the closure of another one (eg. duplicate) | |
| 127 | issue.reload | |
| 128 | # don't change the status is the issue is closed | |
| 129 | next if issue.status.is_closed? | |
| 130 |       csettext = "r#{self.revision}" | |
| 131 | if self.scmid && (! (csettext =~ /^r[0-9]+$/)) | |
| 132 |         csettext = "commit:\"#{self.scmid}\"" | |
| 133 | end | |
| 134 | journal = issue.init_journal(user || User.anonymous, l(:text_status_changed_by_changeset, csettext)) | |
| 135 | issue.status = fix_status | |
| 136 | issue.done_ratio = done_ratio if done_ratio | |
| 137 | issue.save | |
| 138 |       Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated') | |
| 139 | end | |
| 140 | self.issues = (referenced_issues + fixed_issues).uniq | |
| 123 | 141 | end | 
| 124 | 142 |  | 
| 125 | 143 | # Returns the previous changeset | 
| app/models/git_checks/committer.rb | ||
|---|---|---|
| 1 | class GitChecks::Committer < GitManager::CommitCheck | |
| 2 | def self.check(revision, branch, user, role, project, git) | |
| 3 | # Check: committer name and email | |
| 4 |     if revision.author != "#{user.name} <#{user.mail}>" | |
| 5 | return [ "Commit author name or email is wrong", | |
| 6 | " Execute following commands and _recreate_ commit:", | |
| 7 |                "    git config --global user.name \"#{user.name}\"", | |
| 8 | 	       "                git config --global user.email #{user.mail}", | |
| 9 | ] | |
| 10 | end | |
| 11 | return [] | |
| 12 | end | |
| 13 | end | |
| 14 | ||
| app/models/git_checks/delete_branch.rb | ||
|---|---|---|
| 1 | class GitChecks::DeleteBranch < GitManager::RefCheck | |
| 2 | def self.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git) | |
| 3 | if new_rev_type == "delete" | |
| 4 | return "Deleting branch can be done only by repository manager" | |
| 5 | end | |
| 6 | return [] | |
| 7 | end | |
| 8 | end | |
| 9 | ||
| app/models/git_checks/fast_forward.rb | ||
|---|---|---|
| 1 | class GitChecks::FastForward < GitManager::RefCheck | |
| 2 | def self.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git) | |
| 3 | # Check: fast forward | |
| 4 | if old_rev != git.class::EMPTY_COMMIT and git.merge_base(old_rev, new_rev) != old_rev | |
| 5 | return "Only fast-forward commits are allowed" | |
| 6 | end | |
| 7 | return [] | |
| 8 | end | |
| 9 | end | |
| 10 | ||
| app/models/git_checks/initial_commit.rb | ||
|---|---|---|
| 1 | class GitChecks::InitialCommit < GitManager::RefCheck | |
| 2 | def self.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git) | |
| 3 | if old_rev == git.class::EMPTY_COMMIT and !role.allowed_to?(:manage_repository) | |
| 4 | return "Initial commit can be done only by repository manager" | |
| 5 | end | |
| 6 | return [] | |
| 7 | end | |
| 8 | end | |
| 9 | ||
| app/models/git_checks/issue.rb | ||
|---|---|---|
| 1 | class GitChecks::Issue < GitManager::CommitCheck | |
| 2 | def self.check(revision, branch, user, role, project, git) | |
| 3 | references_issues, fixes_issues, wrong_issues = Changeset.find_issue_ids(revision.message, project) | |
| 4 | issues = references_issues + fixes_issues | |
| 5 | ||
| 6 | errors = [] | |
| 7 | ||
| 8 | if !wrong_issues.empty? | |
| 9 |       errors << "Some issue numbers are wrong: #{wrong_issues.join(',')}" | |
| 10 | end | |
| 11 | ||
| 12 | issues.each do |issue_number| | |
| 13 | issue = Issue.find(issue_number) | |
| 14 | if issue.closed? | |
| 15 |         errors << "Issue \##{issue.id} is closed. Reopen it and commit again" | |
| 16 | end | |
| 17 | if issue.assigned_to != user | |
| 18 | if issue.assigned_to != nil | |
| 19 |           owner_info = "It belongs to #{issue.assigned_to.firstname} #{issue.assigned_to.lastname}" | |
| 20 | else | |
| 21 | owner_info = "It is unassigned" | |
| 22 | end | |
| 23 |         errors << "Issue \##{issue.id} is not assigned to You. #{owner_info}" | |
| 24 | end | |
| 25 | end | |
| 26 | return errors | |
| 27 | end | |
| 28 | end | |
| 29 | ||
| app/models/git_manager.rb | ||
|---|---|---|
| 1 | require 'net/http' | |
| 2 | require 'uri' | |
| 3 | require 'redmine/scm/adapters/git_adapter' | |
| 4 | ||
| 5 | class GitManager | |
| 6 | COMMANDS_READONLY = [ | |
| 7 | 'git-upload-pack', | |
| 8 | ] | |
| 9 | COMMANDS_WRITE = [ | |
| 10 | 'git-receive-pack', | |
| 11 | ] | |
| 12 | # TODO: make configurable | |
| 13 | REPOSITORIES_ROOT='~/repositories/' | |
| 14 | REDMINE_LOGIN_ENV_NAME='REDMINE_LOGIN' | |
| 15 | ||
| 16 | def self.repositories_root | |
| 17 | return File.expand_path(REPOSITORIES_ROOT) | |
| 18 | end | |
| 19 | ||
| 20 |  | |
| 21 | # Executed by SSH when somebody logs into Redmine's SSH account | |
| 22 | # using public key. | |
| 23 | # Restricts the user to access only Git repositories he is allowed to. | |
| 24 | def self.serve | |
| 25 | begin | |
| 26 | command = serve_get_original_command | |
| 27 | user = serve_get_user | |
| 28 | git_command, project_identifier = parse_git_command(command) | |
| 29 | project, repository = find_project_and_repo(project_identifier) | |
| 30 | role = user.role_for_project(project) | |
| 31 | ||
| 32 | if COMMANDS_READONLY.include?(git_command) | |
| 33 | if !role.allowed_to?(:view_changesets) | |
| 34 |           raise "User #{user} (#{role.name}) is not allowed to read project #{project}\n" | |
| 35 | end | |
| 36 | if !repository.scm.info | |
| 37 | raise "Empty repository. Push some commit first" | |
| 38 | end | |
| 39 | elsif COMMANDS_WRITE.include?(git_command) | |
| 40 | if !role.allowed_to?(:commit_access) | |
| 41 |           raise "User #{user} (#{role.name}) is not allowed to write to project #{project}\n" | |
| 42 | end | |
| 43 | if !repository.scm.info | |
| 44 | warn "Creating new repository.\n" | |
| 45 | repository.scm.init(project.description) | |
| 46 | end | |
| 47 | else | |
| 48 |         raise "Unknown command '#{verb}'" | |
| 49 | end | |
| 50 |  | |
| 51 | rescue | |
| 52 |       warn "Error: #{$!}.\n" | |
| 53 | exit 1 | |
| 54 | end | |
| 55 | ||
| 56 | ENV[REDMINE_LOGIN_ENV_NAME]=user.login | |
| 57 |     exec 'git', 'shell', '-c', "#{git_command} '#{repository.url}'" | |
| 58 | end | |
| 59 | ||
| 60 | def self.get_authorized_keys_options_for_login(login) | |
| 61 | return [ 'command="ruby ' + RAILS_ROOT + '/script/runner GitManager.serve \'' + login + '\' -e ' + ENV["RAILS_ENV"] + | |
| 62 | '"', 'no-port-forwarding','no-X11-forwarding','no-agent-forwarding','no-pty' ] | |
| 63 | end | |
| 64 | ||
| 65 | ||
| 66 | private | |
| 67 | def self.serve_get_original_command | |
| 68 | command = ENV["SSH_ORIGINAL_COMMAND"] | |
| 69 | if command == nil or command=="" | |
| 70 | raise "SSH_ORIGINAL_COMMAND not set. Use Git to access this account" | |
| 71 | end | |
| 72 | if command=~/\n/ | |
| 73 | raise "Command contains new line character" | |
| 74 | end | |
| 75 | return command | |
| 76 | end | |
| 77 | ||
| 78 | def self.serve_get_user | |
| 79 | login = ARGV[0] | |
| 80 | if login == nil or login=="" | |
| 81 | raise "Needs login as parameter" | |
| 82 | end | |
| 83 | user = User.find_by_login(login) | |
| 84 | if user == nil | |
| 85 |       raise "User not found #{login}" | |
| 86 | end | |
| 87 | return user | |
| 88 | end | |
| 89 | ||
| 90 | def self.parse_git_command(command) | |
| 91 |     verb, args = command.split(" ",2) | |
| 92 | if verb == 'git' | |
| 93 |        subverb, args = args.split(" ",2) | |
| 94 | verb = "%s-%s" % [ verb, subverb ] | |
| 95 | end | |
| 96 |     project_identifier = args.gsub("'","") | |
| 97 | return verb, project_identifier | |
| 98 | end | |
| 99 | ||
| 100 | def self.find_project_and_repo(project_identifier) | |
| 101 | project = Project.find_by_identifier(project_identifier) | |
| 102 | if project == nil | |
| 103 |       raise "Project not found \"#{project_identifier}\"" | |
| 104 | end | |
| 105 | ||
| 106 | repository = project.repository | |
| 107 | if repository == nil | |
| 108 |       raise "Project #{project} does not have repository\n" | |
| 109 | end | |
| 110 | ||
| 111 | if repository.class != Repository::Git | |
| 112 |       raise "Project #{project} has non git repository #{repository.type}" | |
| 113 | end | |
| 114 | return project, repository | |
| 115 | end | |
| 116 | ||
| 117 | ||
| 118 | public | |
| 119 | ||
| 120 | class RefCheck | |
| 121 | @@checks = [] | |
| 122 | ||
| 123 | # Check Git ref, subclassess should override | |
| 124 | def check(old_rev, new_rev, new_rev_type, branch, user, role, project, git) | |
| 125 | return true | |
| 126 | end | |
| 127 | ||
| 128 | def self.inherited(subclass) | |
| 129 | @@checks << subclass | |
| 130 | end | |
| 131 | ||
| 132 | def self.get_checks | |
| 133 | return @@checks | |
| 134 | end | |
| 135 | end | |
| 136 | class CommitCheck | |
| 137 | @@checks = [] | |
| 138 | ||
| 139 | # Check Git ref, subclassess should override | |
| 140 | def check(revision, branch, user, role, project, git) | |
| 141 | return true | |
| 142 | end | |
| 143 | ||
| 144 | def self.inherited(subclass) | |
| 145 | @@checks << subclass | |
| 146 | end | |
| 147 | ||
| 148 | def self.get_checks | |
| 149 | return @@checks | |
| 150 | end | |
| 151 | end | |
| 152 | ||
| 153 | def self.load_checks | |
| 154 | files = Dir.glob(RAILS_ROOT + "/app/models/git_checks/*.rb") | |
| 155 | files.each do |f| | |
| 156 |       f.sub!(/\A#{RAILS_ROOT}/,'') | |
| 157 |       f.split('/')[3..-1].join('/').split('.').first.camelize.constantize | |
| 158 | end | |
| 159 | end | |
| 160 | load_checks | |
| 161 | ||
| 162 | def self.check_commits | |
| 163 | login = ENV[REDMINE_LOGIN_ENV_NAME] | |
| 164 | if login == nil | |
| 165 | warn "Redmine: Local user, not running checks" | |
| 166 | exit 0 | |
| 167 | end | |
| 168 | ||
| 169 | begin | |
| 170 | user = User.find_by_login(login) | |
| 171 | raise "User not found" if user == nil | |
| 172 | repository = Repository.find_by_url(Dir.getwd) | |
| 173 | raise "Repository not managed by Redmine" if repository == nil | |
| 174 | raise "Non git repository" if repository.class != Repository::Git | |
| 175 | project = repository.project | |
| 176 | role = user.role_for_project(project) | |
| 177 | git = repository.scm | |
| 178 |  | |
| 179 | warn "" | |
| 180 | warn "-------------------------------------------------------------" | |
| 181 | warn "Redmine is checking your changes for correctness..." | |
| 182 |       warn "Authenticated as #{user.name} (#{role.name} in #{project.name})" | |
| 183 | ||
| 184 | error_found = false | |
| 185 | ||
| 186 | warn "Changes:" | |
| 187 | STDIN.each_line do |line| | |
| 188 |         old_rev, new_rev, branch = line.split(" ") | |
| 189 |  | |
| 190 | if new_rev == git.class::EMPTY_COMMIT | |
| 191 | new_rev_type = "delete" | |
| 192 | else | |
| 193 | new_rev_type = git.get_object_type(new_rev) | |
| 194 | end | |
| 195 | revisions = nil | |
| 196 | if new_rev_type == "commit" | |
| 197 |           revisions = git.revisions("", old_rev, new_rev) | |
| 198 | end | |
| 199 |         warn "    Ref: #{branch} type: #{new_rev_type}" | |
| 200 | ||
| 201 | errors = [] | |
| 202 | # TODO: make checks configurable | |
| 203 | RefCheck.get_checks.each do |check| | |
| 204 | result = check.check(old_rev, new_rev, new_rev_type, branch, user, role, project, git) | |
| 205 | if result.kind_of?(Array) | |
| 206 | errors += result | |
| 207 | else | |
| 208 | errors << result | |
| 209 | end | |
| 210 | end | |
| 211 | errors.each do |error| | |
| 212 |           warn "            Error: #{error}" | |
| 213 | end | |
| 214 | error_found = true if !errors.empty? | |
| 215 | ||
| 216 | if revisions != nil | |
| 217 | revisions.each do |revision| | |
| 218 | 	    warn "        Commit: #{revision.identifier}" | |
| 219 | ||
| 220 | errors = [] | |
| 221 | CommitCheck.get_checks.each do |check| | |
| 222 | result = check.check(revision, branch, user, role, project, git) | |
| 223 | if result.kind_of?(Array) | |
| 224 | errors += result | |
| 225 | else | |
| 226 | errors << result | |
| 227 | end | |
| 228 | end | |
| 229 | errors.each do |error| | |
| 230 |               warn "            Error: #{error}" | |
| 231 | end | |
| 232 | error_found = true if !errors.empty? | |
| 233 | end | |
| 234 | end | |
| 235 | end | |
| 236 | end | |
| 237 |  | |
| 238 | if error_found | |
| 239 | if !role.allowed_to?(:manage_repository) | |
| 240 | warn "Some commits were rejected. Correct them and try the push again." | |
| 241 | warn "-------------------------------------------------------------" | |
| 242 | warn "" | |
| 243 | exit 1 | |
| 244 | else | |
| 245 | warn "You are repository manager. Checks results are ignored. " | |
| 246 | warn "-------------------------------------------------------------" | |
| 247 | exit 0 | |
| 248 | end | |
| 249 | end | |
| 250 | warn "Changes look OK" | |
| 251 | warn "-------------------------------------------------------------" | |
| 252 | exit 0 | |
| 253 | end | |
| 254 | ||
| 255 | end | |
| 256 | ||
| app/models/user.rb | ||
|---|---|---|
| 65 | 65 | validates_length_of :password, :minimum => 4, :allow_nil => true | 
| 66 | 66 | validates_confirmation_of :password, :allow_nil => true | 
| 67 | 67 | |
| 68 | def ssh_key | |
| 69 | return nil if ! Setting.serve_git_repositories? | |
| 70 | load_ssh_key if @ssh_key_entry == nil | |
| 71 | return @ssh_key_entry.key | |
| 72 | end | |
| 73 | ||
| 74 | def ssh_key_type | |
| 75 | return nil if ! Setting.serve_git_repositories? | |
| 76 | load_ssh_key if @ssh_key_entry == nil | |
| 77 | return @ssh_key_entry.type | |
| 78 | end | |
| 79 | ||
| 80 | def ssh_key=(key) | |
| 81 | return if ! Setting.serve_git_repositories? | |
| 82 | new_ssh_key if @ssh_key_entry == nil | |
| 83 | key = key.strip if key != nil | |
| 84 | @ssh_key_entry.key = key | |
| 85 | end | |
| 86 | ||
| 87 | def ssh_key_type=(key_type) | |
| 88 | return if ! Setting.serve_git_repositories? | |
| 89 | new_ssh_key if @ssh_key_entry == nil | |
| 90 | @ssh_key_entry.type = key_type | |
| 91 | end | |
| 92 | ||
| 93 | def validate | |
| 94 | return if !Setting.serve_git_repositories? | |
| 95 | return if @ssh_key_entry == nil | |
| 96 | ||
| 97 | errors.add(:ssh_key, "is not valid") if !@ssh_key_entry.validate | |
| 98 | end | |
| 99 | ||
| 68 | 100 | def before_create | 
| 69 | 101 | self.mail_notification = false | 
| 70 | 102 | true | 
| ... | ... | |
| 74 | 106 | # update hashed_password if password was set | 
| 75 | 107 | self.hashed_password = User.hash_password(self.password) if self.password | 
| 76 | 108 | end | 
| 109 | ||
| 110 | def after_save | |
| 111 | return if ! Setting.serve_git_repositories? | |
| 112 | return if @ssh_key_entry == nil | |
| 113 |  | |
| 114 | @ssh_key_entry.options = GitManager.get_authorized_keys_options_for_login(login) | |
| 115 | ||
| 116 | @ssh_key_entry.save | |
| 117 | end | |
| 77 | 118 |  | 
| 78 | 119 | def reload(*args) | 
| 79 | 120 | @name = nil | 
| ... | ... | |
| 271 | 312 | def self.hash_password(clear_password) | 
| 272 | 313 | Digest::SHA1.hexdigest(clear_password || "") | 
| 273 | 314 | end | 
| 315 | ||
| 316 | def new_ssh_key | |
| 317 | @ssh_key_entry = AuthorizedKeysEntry.new | |
| 318 | @ssh_key_entry.identifier = login | |
| 319 | end | |
| 320 | ||
| 321 | def load_ssh_key | |
| 322 | @ssh_key_entry = AuthorizedKeysEntry.find_by_identifier(login) | |
| 323 | new_ssh_key if @ssh_key_entry == nil | |
| 324 | end | |
| 274 | 325 | end | 
| 275 | 326 | |
| 276 | 327 | class AnonymousUser < User | 
| app/views/my/account.rhtml | ||
|---|---|---|
| 16 | 16 | <p><%= f.text_field :mail, :required => true %></p> | 
| 17 | 17 | <p><%= f.select :language, lang_options_for_select %></p> | 
| 18 | 18 | |
| 19 | <% if Setting.serve_git_repositories? %> | |
| 20 | <p><%= f.select :ssh_key_type, AuthorizedKeysEntry::KEY_TYPES %></p> | |
| 21 | <p><%= f.text_area :ssh_key, :rows => 10 %></p> | |
| 22 | <% end %> | |
| 23 | ||
| 19 | 24 | <% @user.custom_field_values.select(&:editable?).each do |value| %> | 
| 20 | 25 | <p><%= custom_field_tag_with_label :user, value %></p> | 
| 21 | 26 | <% end %> | 
| app/views/settings/_repositories.rhtml | ||
|---|---|---|
| 14 | 14 | <%= hidden_field_tag 'settings[enabled_scm][]', '' %> | 
| 15 | 15 | </p> | 
| 16 | 16 | |
| 17 | <% # TODO: Should be disabled when Git SCM is not enabled | |
| 18 | %> | |
| 19 | <p><label><%= l(:setting_serve_git_repositories) %></label> | |
| 20 | <%= check_box_tag 'settings[serve_git_repositories]', 1, Setting.serve_git_repositories? %> | |
| 21 | <%= hidden_field_tag 'settings[serve_git_repositories]', 0 %> | |
| 22 | </p> | |
| 23 | ||
| 24 | ||
| 17 | 25 | <p><label><%= l(:setting_repositories_encodings) %></label> | 
| 18 | 26 | <%= text_field_tag 'settings[repositories_encodings]', Setting.repositories_encodings, :size => 60 %><br /><em><%= l(:text_comma_separated) %></em></p> | 
| 19 | 27 | |
| config/settings.yml | ||
|---|---|---|
| 77 | 77 | default: 1 | 
| 78 | 78 | sys_api_enabled: | 
| 79 | 79 | default: 0 | 
| 80 | serve_git_repositories: | |
| 81 | default: 0 | |
| 80 | 82 | commit_ref_keywords: | 
| 81 | 83 | default: 'refs,references,IssueID' | 
| 82 | 84 | commit_fix_keywords: | 
| lang/bg.yml | ||
|---|---|---|
| 92 | 92 | field_firstname: Име | 
| 93 | 93 | field_lastname: Фамилия | 
| 94 | 94 | field_mail: Email | 
| 95 | field_ssh_key: SSH Public Key | |
| 96 | field_ssh_key_type: SSH Public Key Type | |
| 95 | 97 | field_filename: Файл | 
| 96 | 98 | field_filesize: Големина | 
| 97 | 99 | field_downloads: Downloads | 
| ... | ... | |
| 181 | 183 | setting_feeds_limit: Лимит на Feeds | 
| 182 | 184 | setting_autofetch_changesets: Автоматично обработване на ревизиите | 
| 183 | 185 | setting_sys_api_enabled: Разрешаване на WS за управление | 
| 186 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 184 | 187 | setting_commit_ref_keywords: Отбелязващи ключови думи | 
| 185 | 188 | setting_commit_fix_keywords: Приключващи ключови думи | 
| 186 | 189 | setting_autologin: Автоматичен вход | 
| lang/ca.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Nom | 
| 107 | 107 | field_lastname: Cognom | 
| 108 | 108 | field_mail: Correu electrònic | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Fitxer | 
| 110 | 112 | field_filesize: Mida | 
| 111 | 113 | field_downloads: Baixades | 
| ... | ... | |
| 202 | 204 | setting_default_projects_public: Els projectes nous són públics per defecte | 
| 203 | 205 | setting_autofetch_changesets: Omple automàticament les publicacions | 
| 204 | 206 | setting_sys_api_enabled: Habilita el WS per a la gestió del dipòsit | 
| 207 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 205 | 208 | setting_commit_ref_keywords: Paraules claus per a la referència | 
| 206 | 209 | setting_commit_fix_keywords: Paraules claus per a la correcció | 
| 207 | 210 | setting_autologin: Entrada automàtica | 
| lang/cs.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Jméno | 
| 107 | 107 | field_lastname: Příjmení | 
| 108 | 108 | field_mail: Email | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Soubor | 
| 110 | 112 | field_filesize: Velikost | 
| 111 | 113 | field_downloads: Staženo | 
| ... | ... | |
| 201 | 203 | setting_default_projects_public: Nové projekty nastavovat jako veřejné | 
| 202 | 204 | setting_autofetch_changesets: Autofetch commits | 
| 203 | 205 | setting_sys_api_enabled: Povolit WS pro správu repozitory | 
| 206 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 204 | 207 | setting_commit_ref_keywords: Klíčová slova pro odkazy | 
| 205 | 208 | setting_commit_fix_keywords: Klíčová slova pro uzavření | 
| 206 | 209 | setting_autologin: Automatické přihlašování | 
| lang/da.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Fornavn | 
| 107 | 107 | field_lastname: Efternavn | 
| 108 | 108 | field_mail: E-mail | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Fil | 
| 110 | 112 | field_filesize: Størrelse | 
| 111 | 113 | field_downloads: Downloads | 
| ... | ... | |
| 202 | 204 | setting_default_projects_public: Nye projekter er som standard offentlige | 
| 203 | 205 | setting_autofetch_changesets: Hent automatisk commits | 
| 204 | 206 | setting_sys_api_enabled: Aktiver webservice til versionsstyring | 
| 207 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 205 | 208 | setting_commit_ref_keywords: Nøgleord for sagsreferencer | 
| 206 | 209 | setting_commit_fix_keywords: Nøgleord for lukning af sager | 
| 207 | 210 | setting_autologin: Autologin | 
| lang/de.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Vorname | 
| 107 | 107 | field_lastname: Nachname | 
| 108 | 108 | field_mail: E-Mail | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Datei | 
| 110 | 112 | field_filesize: Größe | 
| 111 | 113 | field_downloads: Downloads | 
| ... | ... | |
| 203 | 205 | setting_default_projects_public: Neue Projekte sind standardmäßig öffentlich | 
| 204 | 206 | setting_autofetch_changesets: Changesets automatisch abrufen | 
| 205 | 207 | setting_sys_api_enabled: Webservice zur Verwaltung der Projektarchive benutzen | 
| 208 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 206 | 209 | setting_commit_ref_keywords: Schlüsselwörter (Beziehungen) | 
| 207 | 210 | setting_commit_fix_keywords: Schlüsselwörter (Status) | 
| 208 | 211 | setting_autologin: Automatische Anmeldung | 
| lang/en.yml | ||
|---|---|---|
| 108 | 108 | field_firstname: Firstname | 
| 109 | 109 | field_lastname: Lastname | 
| 110 | 110 | field_mail: Email | 
| 111 | field_ssh_key: SSH Public Key | |
| 112 | field_ssh_key_type: SSH Public Key Type | |
| 111 | 113 | field_filename: File | 
| 112 | 114 | field_filesize: Size | 
| 113 | 115 | field_downloads: Downloads | 
| ... | ... | |
| 206 | 208 | setting_default_projects_public: New projects are public by default | 
| 207 | 209 | setting_autofetch_changesets: Autofetch commits | 
| 208 | 210 | setting_sys_api_enabled: Enable WS for repository management | 
| 211 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 209 | 212 | setting_commit_ref_keywords: Referencing keywords | 
| 210 | 213 | setting_commit_fix_keywords: Fixing keywords | 
| 211 | 214 | setting_autologin: Autologin | 
| lang/es.yml | ||
|---|---|---|
| 150 | 150 | field_lastname: Apellido | 
| 151 | 151 | field_login: Identificador | 
| 152 | 152 | field_mail: Correo electrónico | 
| 153 | field_ssh_key: SSH Public Key | |
| 154 | field_ssh_key_type: SSH Public Key Type | |
| 153 | 155 | field_mail_notification: Notificaciones por correo | 
| 154 | 156 | field_max_length: Longitud máxima | 
| 155 | 157 | field_min_length: Longitud mínima | 
| ... | ... | |
| 627 | 629 | setting_self_registration: Registro permitido | 
| 628 | 630 | setting_sequential_project_identifiers: Generar identificadores de proyecto | 
| 629 | 631 | setting_sys_api_enabled: Habilitar SW para la gestión del repositorio | 
| 632 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 630 | 633 | setting_text_formatting: Formato de texto | 
| 631 | 634 | setting_time_format: Formato de hora | 
| 632 | 635 | setting_user_format: Formato de nombre de usuario | 
| lang/fi.yml | ||
|---|---|---|
| 101 | 101 | field_firstname: Etunimi | 
| 102 | 102 | field_lastname: Sukunimi | 
| 103 | 103 | field_mail: Sähköposti | 
| 104 | field_ssh_key: SSH Public Key | |
| 105 | field_ssh_key_type: SSH Public Key Type | |
| 104 | 106 | field_filename: Tiedosto | 
| 105 | 107 | field_filesize: Koko | 
| 106 | 108 | field_downloads: Latausta | 
| ... | ... | |
| 194 | 196 | setting_feeds_limit: Syötteen sisällön raja | 
| 195 | 197 | setting_autofetch_changesets: Automaattisten muutosjoukkojen haku | 
| 196 | 198 | setting_sys_api_enabled: Salli WS tietovaraston hallintaan | 
| 199 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 197 | 200 | setting_commit_ref_keywords: Viittaavat hakusanat | 
| 198 | 201 | setting_commit_fix_keywords: Korjaavat hakusanat | 
| 199 | 202 | setting_autologin: Automaatinen kirjautuminen | 
| lang/fr.yml | ||
|---|---|---|
| 108 | 108 | field_firstname: Prénom | 
| 109 | 109 | field_lastname: Nom | 
| 110 | 110 | field_mail: Email | 
| 111 | field_ssh_key: SSH Public Key | |
| 112 | field_ssh_key_type: SSH Public Key Type | |
| 111 | 113 | field_filename: Fichier | 
| 112 | 114 | field_filesize: Taille | 
| 113 | 115 | field_downloads: Téléchargements | 
| ... | ... | |
| 206 | 208 | setting_default_projects_public: Définir les nouveaux projects comme publics par défaut | 
| 207 | 209 | setting_autofetch_changesets: Récupération auto. des commits | 
| 208 | 210 | setting_sys_api_enabled: Activer les WS pour la gestion des dépôts | 
| 211 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 209 | 212 | setting_commit_ref_keywords: Mot-clés de référencement | 
| 210 | 213 | setting_commit_fix_keywords: Mot-clés de résolution | 
| 211 | 214 | setting_autologin: Autologin | 
| lang/he.yml | ||
|---|---|---|
| 94 | 94 | field_firstname: שם פרטי | 
| 95 | 95 | field_lastname: שם משפחה | 
| 96 | 96 | field_mail: דוא"ל | 
| 97 | field_ssh_key: SSH Public Key | |
| 98 | field_ssh_key_type: SSH Public Key Type | |
| 97 | 99 | field_filename: קובץ | 
| 98 | 100 | field_filesize: גודל | 
| 99 | 101 | field_downloads: הורדות | 
| ... | ... | |
| 184 | 186 | setting_feeds_limit: גבול תוכן הזנות | 
| 185 | 187 | setting_autofetch_changesets: משיכה אוטומתי של עידכונים | 
| 186 | 188 | setting_sys_api_enabled: אפשר WS לניהול המאגר | 
| 189 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 187 | 190 | setting_commit_ref_keywords: מילות מפתח מקשרות | 
| 188 | 191 | setting_commit_fix_keywords: מילות מפתח מתקנות | 
| 189 | 192 | setting_autologin: חיבור אוטומטי | 
| lang/hu.yml | ||
|---|---|---|
| 103 | 103 | field_firstname: Keresztnév | 
| 104 | 104 | field_lastname: Vezetéknév | 
| 105 | 105 | field_mail: E-mail | 
| 106 | field_ssh_key: SSH Public Key | |
| 107 | field_ssh_key_type: SSH Public Key Type | |
| 106 | 108 | field_filename: Fájl | 
| 107 | 109 | field_filesize: Méret | 
| 108 | 110 | field_downloads: Letöltések | 
| ... | ... | |
| 198 | 200 | setting_default_projects_public: Az új projektek alapértelmezés szerint nyilvánosak | 
| 199 | 201 | setting_autofetch_changesets: Commitok automatikus lehúzása | 
| 200 | 202 | setting_sys_api_enabled: WS engedélyezése a tárolók kezeléséhez | 
| 203 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 201 | 204 | setting_commit_ref_keywords: Hivatkozó kulcsszavak | 
| 202 | 205 | setting_commit_fix_keywords: Javítások kulcsszavai | 
| 203 | 206 | setting_autologin: Automatikus bejelentkezés | 
| lang/it.yml | ||
|---|---|---|
| 92 | 92 | field_firstname: Nome | 
| 93 | 93 | field_lastname: Cognome | 
| 94 | 94 | field_mail: Email | 
| 95 | field_ssh_key: SSH Public Key | |
| 96 | field_ssh_key_type: SSH Public Key Type | |
| 95 | 97 | field_filename: File | 
| 96 | 98 | field_filesize: Dimensione | 
| 97 | 99 | field_downloads: Download | 
| ... | ... | |
| 181 | 183 | setting_feeds_limit: Limite contenuti del feed | 
| 182 | 184 | setting_autofetch_changesets: Acquisisci automaticamente le commit | 
| 183 | 185 | setting_sys_api_enabled: Abilita WS per la gestione del repository | 
| 186 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 184 | 187 | setting_commit_ref_keywords: Referencing keywords | 
| 185 | 188 | setting_commit_fix_keywords: Fixing keywords | 
| 186 | 189 | setting_autologin: Login automatico | 
| lang/ja.yml | ||
|---|---|---|
| 93 | 93 | field_firstname: 名前 | 
| 94 | 94 | field_lastname: 苗字 | 
| 95 | 95 | field_mail: メールアドレス | 
| 96 | field_ssh_key: SSH Public Key | |
| 97 | field_ssh_key_type: SSH Public Key Type | |
| 96 | 98 | field_filename: ファイル | 
| 97 | 99 | field_filesize: サイズ | 
| 98 | 100 | field_downloads: ダウンロード | 
| ... | ... | |
| 182 | 184 | setting_feeds_limit: フィード内容の上限 | 
| 183 | 185 | setting_autofetch_changesets: コミットを自動取得する | 
| 184 | 186 | setting_sys_api_enabled: リポジトリ管理用のWeb Serviceを有効にする | 
| 187 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 185 | 188 | setting_commit_ref_keywords: 参照用キーワード | 
| 186 | 189 | setting_commit_fix_keywords: 修正用キーワード | 
| 187 | 190 | setting_autologin: 自動ログイン | 
| lang/ko.yml | ||
|---|---|---|
| 94 | 94 | field_firstname: 이름 | 
| 95 | 95 | field_lastname: 성 | 
| 96 | 96 | field_mail: 메일 | 
| 97 | field_ssh_key: SSH Public Key | |
| 98 | field_ssh_key_type: SSH Public Key Type | |
| 97 | 99 | field_filename: 파일 | 
| 98 | 100 | field_filesize: 크기 | 
| 99 | 101 | field_downloads: 다운로드 | 
| ... | ... | |
| 184 | 186 | setting_feeds_limit: 내용 피드(RSS Feed) 제한 개수 | 
| 185 | 187 | setting_autofetch_changesets: 커밋된 변경묶음을 자동으로 가져오기 | 
| 186 | 188 | setting_sys_api_enabled: 저장소 관리자에 WS 를 허용 | 
| 189 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 187 | 190 | setting_commit_ref_keywords: 일감 참조에 사용할 키워드들 | 
| 188 | 191 | setting_commit_fix_keywords: 일감 해결에 사용할 키워드들 | 
| 189 | 192 | setting_autologin: 자동 로그인 | 
| lang/lt.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Vardas | 
| 107 | 107 | field_lastname: Pavardė | 
| 108 | 108 | field_mail: Email | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Byla | 
| 110 | 112 | field_filesize: Dydis | 
| 111 | 113 | field_downloads: Atsiuntimai | 
| ... | ... | |
| 203 | 205 | setting_default_projects_public: Naujas projektas viešas pagal nutylėjimą | 
| 204 | 206 | setting_autofetch_changesets: Automatinis pakeitimų siuntimas | 
| 205 | 207 | setting_sys_api_enabled: Įgalinkite WS sandėlio vadybai | 
| 208 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 206 | 209 | setting_commit_ref_keywords: Nurodymo reikšminiai žodžiai | 
| 207 | 210 | setting_commit_fix_keywords: Fiksavimo reikšminiai žodžiai | 
| 208 | 211 | setting_autologin: Autoregistracija | 
| lang/nl.yml | ||
|---|---|---|
| 92 | 92 | field_firstname: Voornaam | 
| 93 | 93 | field_lastname: Achternaam | 
| 94 | 94 | field_mail: E-mail | 
| 95 | field_ssh_key: SSH Public Key | |
| 96 | field_ssh_key_type: SSH Public Key Type | |
| 95 | 97 | field_filename: Bestand | 
| 96 | 98 | field_filesize: Grootte | 
| 97 | 99 | field_downloads: Downloads | 
| ... | ... | |
| 181 | 183 | setting_feeds_limit: Feedinhoud limiet | 
| 182 | 184 | setting_autofetch_changesets: Haal commits automatisch op | 
| 183 | 185 | setting_sys_api_enabled: Gebruik WS voor repository beheer | 
| 186 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 184 | 187 | setting_commit_ref_keywords: Refererende trefwoorden | 
| 185 | 188 | setting_commit_fix_keywords: Gefixeerde trefwoorden | 
| 186 | 189 | setting_autologin: Automatisch inloggen | 
| lang/no.yml | ||
|---|---|---|
| 105 | 105 | field_firstname: Fornavn | 
| 106 | 106 | field_lastname: Etternavn | 
| 107 | 107 | field_mail: E-post | 
| 108 | field_ssh_key: SSH Public Key | |
| 109 | field_ssh_key_type: SSH Public Key Type | |
| 108 | 110 | field_filename: Fil | 
| 109 | 111 | field_filesize: Størrelse | 
| 110 | 112 | field_downloads: Nedlastinger | 
| ... | ... | |
| 200 | 202 | setting_default_projects_public: Nye prosjekter er offentlige som standard | 
| 201 | 203 | setting_autofetch_changesets: Autohenting av innsendinger | 
| 202 | 204 | setting_sys_api_enabled: Aktiver webservice for depot-administrasjon | 
| 205 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 203 | 206 | setting_commit_ref_keywords: Nøkkelord for referanse | 
| 204 | 207 | setting_commit_fix_keywords: Nøkkelord for retting | 
| 205 | 208 | setting_autologin: Autoinnlogging | 
| lang/pl.yml | ||
|---|---|---|
| 161 | 161 | field_lastname: Nazwisko | 
| 162 | 162 | field_login: Login | 
| 163 | 163 | field_mail: Email | 
| 164 | field_ssh_key: Klucz publiczny SSH | |
| 165 | field_ssh_key_type: Typ klucza SSH | |
| 164 | 166 | field_mail_notification: Powiadomienia Email | 
| 165 | 167 | field_max_length: Maksymalna długość | 
| 166 | 168 | field_min_length: Minimalna długość | 
| ... | ... | |
| 657 | 659 | setting_self_registration: Własna rejestracja umożliwiona | 
| 658 | 660 | setting_sequential_project_identifiers: Generuj sekwencyjne identyfikatory projektów | 
| 659 | 661 | setting_sys_api_enabled: Włączenie WS do zarządzania repozytorium | 
| 662 | setting_serve_git_repositories: Udostępnij repozytoria GIT poprzez konto SSH Redmine | |
| 660 | 663 | setting_text_formatting: Formatowanie tekstu | 
| 661 | 664 | setting_time_format: Format czasu | 
| 662 | 665 | setting_user_format: Personalny format wyświetlania | 
| lang/pt-br.yml | ||
|---|---|---|
| 105 | 105 | field_firstname: Nome | 
| 106 | 106 | field_lastname: Sobrenome | 
| 107 | 107 | field_mail: Email | 
| 108 | field_ssh_key: SSH Public Key | |
| 109 | field_ssh_key_type: SSH Public Key Type | |
| 108 | 110 | field_filename: Arquivo | 
| 109 | 111 | field_filesize: Tamanho | 
| 110 | 112 | field_downloads: Downloads | 
| ... | ... | |
| 201 | 203 | setting_default_projects_public: Novos projetos são públicos por padrão | 
| 202 | 204 | setting_autofetch_changesets: Auto-obter commits | 
| 203 | 205 | setting_sys_api_enabled: Ativa WS para gerenciamento do repositório | 
| 206 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 204 | 207 | setting_commit_ref_keywords: Palavras de referência | 
| 205 | 208 | setting_commit_fix_keywords: Palavras de fechamento | 
| 206 | 209 | setting_autologin: Auto-login | 
| lang/pt.yml | ||
|---|---|---|
| 107 | 107 | field_firstname: Nome | 
| 108 | 108 | field_lastname: Apelido | 
| 109 | 109 | field_mail: E-mail | 
| 110 | field_ssh_key: SSH Public Key | |
| 111 | field_ssh_key_type: SSH Public Key Type | |
| 110 | 112 | field_filename: Ficheiro | 
| 111 | 113 | field_filesize: Tamanho | 
| 112 | 114 | field_downloads: Downloads | 
| ... | ... | |
| 203 | 205 | setting_default_projects_public: Projectos novos são públicos por omissão | 
| 204 | 206 | setting_autofetch_changesets: Buscar automaticamente commits | 
| 205 | 207 | setting_sys_api_enabled: Activar Web Service para gestão do repositório | 
| 208 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 206 | 209 | setting_commit_ref_keywords: Palavras-chave de referência | 
| 207 | 210 | setting_commit_fix_keywords: Palavras-chave de fecho | 
| 208 | 211 | setting_autologin: Login automático | 
| lang/ro.yml | ||
|---|---|---|
| 92 | 92 | field_firstname: Nume | 
| 93 | 93 | field_lastname: Prenume | 
| 94 | 94 | field_mail: Email | 
| 95 | field_ssh_key: SSH Public Key | |
| 96 | field_ssh_key_type: SSH Public Key Type | |
| 95 | 97 | field_filename: Fisier | 
| 96 | 98 | field_filesize: Marimea fisierului | 
| 97 | 99 | field_downloads: Download | 
| ... | ... | |
| 181 | 183 | setting_feeds_limit: Limita continut feed | 
| 182 | 184 | setting_autofetch_changesets: Autofetch commits | 
| 183 | 185 | setting_sys_api_enabled: Setare WS pentru managementul stocului (repository) | 
| 186 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 184 | 187 | setting_commit_ref_keywords: Cuvinte cheie de referinta | 
| 185 | 188 | setting_commit_fix_keywords: Cuvinte cheie de rezolvare | 
| 186 | 189 | setting_autologin: Autentificare automata | 
| lang/ru.yml | ||
|---|---|---|
| 165 | 165 | field_lastname: Фамилия | 
| 166 | 166 | field_login: Пользователь | 
| 167 | 167 | field_mail: Email | 
| 168 | field_ssh_key: SSH Public Key | |
| 169 | field_ssh_key_type: SSH Public Key Type | |
| 168 | 170 | field_mail_notification: Уведомления по email | 
| 169 | 171 | field_max_length: Максимальная длина | 
| 170 | 172 | field_min_length: Минимальная длина | 
| ... | ... | |
| 673 | 675 | setting_self_registration: Возможна саморегистрация | 
| 674 | 676 | setting_sequential_project_identifiers: Генерировать последовательные идентификаторы проектов | 
| 675 | 677 | setting_sys_api_enabled: Разрешить WS для управления хранилищем | 
| 678 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 676 | 679 | setting_text_formatting: Форматирование текста | 
| 677 | 680 | setting_time_format: Формат времени | 
| 678 | 681 | setting_user_format: Формат отображения имени | 
| lang/sk.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Meno | 
| 107 | 107 | field_lastname: Priezvisko | 
| 108 | 108 | field_mail: Email | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Súbor | 
| 110 | 112 | field_filesize: Veľkosť | 
| 111 | 113 | field_downloads: Stiahnuté | 
| ... | ... | |
| 201 | 203 | setting_default_projects_public: Nové projekty nastavovať ako verejné | 
| 202 | 204 | setting_autofetch_changesets: Automatický prenos zmien | 
| 203 | 205 | setting_sys_api_enabled: Povolit WS pre správu repozitory | 
| 206 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 204 | 207 | setting_commit_ref_keywords: Klúčové slová pre odkazy | 
| 205 | 208 | setting_commit_fix_keywords: Klúčové slová pre uzavretie | 
| 206 | 209 | setting_autologin: Automatické prihlasovanie | 
| lang/sr.yml | ||
|---|---|---|
| 96 | 96 | field_firstname: Ime | 
| 97 | 97 | field_lastname: Prezime | 
| 98 | 98 | field_mail: Email | 
| 99 | field_ssh_key: SSH Public Key | |
| 100 | field_ssh_key_type: SSH Public Key Type | |
| 99 | 101 | field_filename: Fajl | 
| 100 | 102 | field_filesize: Veličina | 
| 101 | 103 | field_downloads: Preuzimanja | 
| ... | ... | |
| 186 | 188 | setting_feeds_limit: Feed content limit | 
| 187 | 189 | setting_autofetch_changesets: Autofetch commits | 
| 188 | 190 | setting_sys_api_enabled: Ukljuci WS za menadžment spremišta | 
| 191 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 189 | 192 | setting_commit_ref_keywords: Referentne ključne reči | 
| 190 | 193 | setting_commit_fix_keywords: Fiksne ključne reči | 
| 191 | 194 | setting_autologin: Automatsko prijavljivanje | 
| lang/sv.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Förnamn | 
| 107 | 107 | field_lastname: Efternamn | 
| 108 | 108 | field_mail: Mail | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Fil | 
| 110 | 112 | field_filesize: Storlek | 
| 111 | 113 | field_downloads: Nerladdningar | 
| ... | ... | |
| 203 | 205 | setting_default_projects_public: Nya projekt är publika som standard | 
| 204 | 206 | setting_autofetch_changesets: Automatisk hämtning av commits | 
| 205 | 207 | setting_sys_api_enabled: Aktivera WS för repository-hantering | 
| 208 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 206 | 209 | setting_commit_ref_keywords: Referens-nyckelord | 
| 207 | 210 | setting_commit_fix_keywords: Fix-nyckelord | 
| 208 | 211 | setting_autologin: Automatisk inloggning | 
| lang/th.yml | ||
|---|---|---|
| 103 | 103 | field_firstname: ชื่อ | 
| 104 | 104 | field_lastname: นามสกุล | 
| 105 | 105 | field_mail: อีเมล์ | 
| 106 | field_ssh_key: SSH Public Key | |
| 107 | field_ssh_key_type: SSH Public Key Type | |
| 106 | 108 | field_filename: แฟ้ม | 
| 107 | 109 | field_filesize: ขนาด | 
| 108 | 110 | field_downloads: ดาวน์โหลด | 
| ... | ... | |
| 198 | 200 | setting_default_projects_public: โครงการใหม่มีค่าเริ่มต้นเป็น สาธารณะ | 
| 199 | 201 | setting_autofetch_changesets: ดึง commits อัตโนมัติ | 
| 200 | 202 | setting_sys_api_enabled: เปิดใช้ WS สำหรับการจัดการที่เก็บต้นฉบับ | 
| 203 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 201 | 204 | setting_commit_ref_keywords: คำสำคัญ Referencing | 
| 202 | 205 | setting_commit_fix_keywords: คำสำคัญ Fixing | 
| 203 | 206 | setting_autologin: เข้าระบบอัตโนมัติ | 
| lang/tr.yml | ||
|---|---|---|
| 102 | 102 | field_firstname: Ad | 
| 103 | 103 | field_lastname: Soyad | 
| 104 | 104 | field_mail: E-Posta | 
| 105 | field_ssh_key: SSH Public Key | |
| 106 | field_ssh_key_type: SSH Public Key Type | |
| 105 | 107 | field_filename: Dosya | 
| 106 | 108 | field_filesize: Boyut | 
| 107 | 109 | field_downloads: İndirilenler | 
| ... | ... | |
| 197 | 199 | setting_default_projects_public: Yeni projeler varsayılan olarak herkese açık | 
| 198 | 200 | setting_autofetch_changesets: Otomatik gönderi al | 
| 199 | 201 | setting_sys_api_enabled: Depo yönetimi için WS'yi etkinleştir | 
| 202 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 200 | 203 | setting_commit_ref_keywords: Başvuru Kelimeleri | 
| 201 | 204 | setting_commit_fix_keywords: Sabitleme kelimeleri | 
| 202 | 205 | setting_autologin: Otomatik Giriş | 
| lang/uk.yml | ||
|---|---|---|
| 97 | 97 | field_firstname: Ім'я | 
| 98 | 98 | field_lastname: Прізвище | 
| 99 | 99 | field_mail: Ел. пошта | 
| 100 | field_ssh_key: SSH Public Key | |
| 101 | field_ssh_key_type: SSH Public Key Type | |
| 100 | 102 | field_filename: Файл | 
| 101 | 103 | field_filesize: Розмір | 
| 102 | 104 | field_downloads: Завантаження | 
| ... | ... | |
| 189 | 191 | setting_feeds_limit: Обмеження змісту подачі | 
| 190 | 192 | setting_autofetch_changesets: Автоматично доставати доповнення | 
| 191 | 193 | setting_sys_api_enabled: Дозволити WS для управління репозиторієм | 
| 194 | setting_serve_git_repositories: Serve GIT repositories using Redmine's SSH account | |
| 192 | 195 | setting_commit_ref_keywords: Ключові слова для посилання | 
| 193 | 196 | setting_commit_fix_keywords: Призначення ключових слів | 
| 194 | 197 | setting_autologin: Автоматичний вхід | 
| lang/vn.yml | ||
|---|---|---|
| 106 | 106 | field_firstname: Tên lót + Tên | 
| 107 | 107 | field_lastname: Họ | 
| 108 | 108 | field_mail: Email | 
| 109 | field_ssh_key: SSH Public Key | |
| 110 | field_ssh_key_type: SSH Public Key Type | |
| 109 | 111 | field_filename: Tập tin | 
| 110 | 112 | field_filesize: Cỡ | 
| 111 | 113 | field_downloads: Tải về | 
| ... | ... | |
| 202 | 204 | setting_default_projects_public: Dự án mặc định là công cộng |