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 |