From 98cbb8514b0b3394ecb9fa9894fc93c2ceeda3af Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Wed, 23 Nov 2011 16:44:25 -0800 Subject: [PATCH 001/107] Added hooks, policy and rakefile to install selinux policy for this plugin --- .gitignore | 2 + README.mkd | 38 +++ app/models/git_hosting_settings_observer.rb | 23 +- .../settings/_redmine_git_hosting.html.erb | 7 +- config/locales/bg.yml | 1 + config/locales/bs.yml | 1 + config/locales/ca.yml | 1 + config/locales/cs.yml | 1 + config/locales/da.yml | 1 + config/locales/de.yml | 1 + config/locales/el.yml | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fi.yml | 1 + config/locales/fr.yml | 1 + config/locales/gl.yml | 1 + config/locales/he.yml | 1 + config/locales/hu.yml | 1 + config/locales/id.yml | 1 + config/locales/it.yml | 1 + config/locales/ja.yml | 1 + config/locales/ko.yml | 1 + config/locales/lt.yml | 1 + config/locales/nl.yml | 1 + config/locales/no.yml | 1 + config/locales/pl.yml | 1 + config/locales/pt-BR.yml | 1 + config/locales/pt.yml | 1 + config/locales/ro.yml | 1 + config/locales/ru.yml | 1 + config/locales/sk.yml | 1 + config/locales/sl.yml | 1 + config/locales/sr.yml | 1 + config/locales/sv.yml | 1 + config/locales/th.yml | 1 + config/locales/tr.yml | 1 + config/locales/uk.yml | 1 + config/locales/vi.yml | 1 + config/locales/zh-TW.yml | 1 + config/locales/zh.yml | 1 + lib/git_hosting.rb | 62 +++- selinux/README | 67 +++++ selinux/redmine_git.fc | 9 + selinux/redmine_git.if | 131 +++++++++ selinux/redmine_git.pp | Bin 0 -> 127822 bytes selinux/redmine_git.sh | 44 +++ selinux/redmine_git.te | 69 +++++ tasks/selinux.rake | 273 ++++++++++++++++++ 48 files changed, 739 insertions(+), 22 deletions(-) create mode 100644 selinux/README create mode 100644 selinux/redmine_git.fc create mode 100644 selinux/redmine_git.if create mode 100644 selinux/redmine_git.pp create mode 100755 selinux/redmine_git.sh create mode 100644 selinux/redmine_git.te create mode 100644 tasks/selinux.rake diff --git a/.gitignore b/.gitignore index a01ee289f..5412c5a54 100755 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .*.swp +bin/ +selinux/tmp/ diff --git a/README.mkd b/README.mkd index 945b9cf82..dfae03514 100755 --- a/README.mkd +++ b/README.mkd @@ -246,6 +246,44 @@ This library allows you to quickly deploy ChiliProject, with this plugin to an u chili\_test.sh script, modifying the variables in those scripts as desired. This library is still under development, so these instructions may need to be updated in the near future. +## Selinux Configuration for redmine + +This plugin can be configured to run with selinux. We have included a rakefile in tasks/selinux.rake to assist +with installing with selinux. You should start by editing init.rb and migrating as described above. Then, you +can execute one of the selinux rake tasks (from the redmine root). For instance, the simplest option installs +a selinux configuration for both redmine and the redmine_git_hosting plugin: + + rake selinux:install RAILS_ENV=production + +This will generate the redmine_git_hosting binaries in ./bin, install a selinux policy for these binaries (called +redmine_git.pp), then install a complete context for redmine as follows: + +**(1)** Most of redmine will be marked with "public_content_rw_t". + +**(2)** The dispatch files in Rails.root/public/dispatch.* will be marked with "httpd_sys_script_exec_t" + +**(3)** The redmine_git_hosting binaries in Rails.root/vendor/plugins/redmine_git_hosting/bin will be labeled +with "httpd_redmine_git_script_exec_t", which has been crafted to allow the sudo behavior required by these +binaries. + +Note that this rake file has additional options. For instance, you can specify redmine roots with regular +expressions (not globbed expessions!) as follows (notice the use of double quotes): + + rake selinux:install RAILS_ENV=production ROOT_PATTERN="/source/.*/redmine" + +These additional options are documented in the selinux.rake file. + +Once this plugin is placed under selinux control, three of the redmine_git_hosting settings can +no longer be modified from the settings page. They are: 'gitUser', 'gitoliteIdentityFile', and +'gitoliteIdentityPublicKeyFile'. The plugin settings page will make this clear. The simplest way to +modify these options is to temporarily place your system into permissive mode, refresh the setting page, +change options, then place your system back into enforcing mode. Alternatively, you can alter the init.rb +file and reinstall the plugin. Under normal operation, you will get one selinux complaint about /bin/touch +in your log each time that you visit the plugin settings page. + +This rakefile and selinux configuration has been primarily tested on Redhat Enterprise Linux version 6.x +with apache and fcgi. Other configurations may require slight tweaking. + ## Tested Configurations This plugin has been primarily tested on Ubuntu Server 10.10 and 11.04 (32 and 64 bit) with ChiliProject v1.x, diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb index fee8d8a3f..fcd16e9ae 100644 --- a/app/models/git_hosting_settings_observer.rb +++ b/app/models/git_hosting_settings_observer.rb @@ -5,6 +5,8 @@ class GitHostingSettingsObserver < ActiveRecord::Observer @@old_hook_asynch = Setting.plugin_redmine_git_hosting['gitHooksAreAsynchronous'] @@old_http_server = Setting.plugin_redmine_git_hosting['httpServer'] @@old_git_user = Setting.plugin_redmine_git_hosting['gitUser'] + @@old_gitolite_identity = Setting.plugin_redmine_git_hosting['gitoliteIdentityFile'] + @@old_gitolite_publickey = Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile'] @@old_repo_base = Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] @@ -14,12 +16,25 @@ def reload_this_observer end end - - + def before_save(object) + if object.name == "plugin_redmine_git_hosting" && !GitHosting.bin_dir_writeable? + # If bin directory not alterable, don't alow changes to + # Git Username, or Gitolite public or private keys + valuehash = object.value + valuehash['gitUser'] = @@old_git_user + valuehash['gitoliteIdentityFile'] = @@old_gitolite_identity + valuehash['gitoliteIdentityPublicKeyFile'] = @@old_gitolite_publickey + object.value = valuehash + end + end + def after_save(object) if object.name == "plugin_redmine_git_hosting" - %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + if GitHosting.bin_dir_writeable? + %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] + end if @@old_repo_base != object.value['gitRepositoryBasePath'] GitHostingObserver.set_update_active(false) @@ -49,6 +64,8 @@ def after_save(object) @@old_hook_asynch = object.value['gitHooksAreAsynchronous'] @@old_http_server = object.value['httpServer'] @@old_git_user = object.value['gitUser'] + @@old_gitolite_identity = object.value['gitoliteIdentityFile'] + @@old_gitolite_publickey = object.value['gitoliteIdentityPublicKeyFile'] @@old_repo_base = object.value['gitRepositoryBasePath'] end diff --git a/app/views/settings/_redmine_git_hosting.html.erb b/app/views/settings/_redmine_git_hosting.html.erb index c6b1e7760..9e6527c84 100644 --- a/app/views/settings/_redmine_git_hosting.html.erb +++ b/app/views/settings/_redmine_git_hosting.html.erb @@ -26,19 +26,19 @@

- + <%= text_field_tag("settings[gitUser]", @settings['gitUser'].split(/[\r\n\t ,;]+/).join("\n"), :size => 60) %>

- + <%= text_field_tag("settings[gitoliteIdentityFile]", @settings['gitoliteIdentityFile'], :size => 60) %>

- + <%= text_field_tag("settings[gitoliteIdentityPublicKeyFile]", @settings['gitoliteIdentityPublicKeyFile'], :size => 60) %>

@@ -48,7 +48,6 @@

-

<%= l(:label_git_cache_parameters)%>

diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/da.yml b/config/locales/da.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/de.yml b/config/locales/de.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/el.yml b/config/locales/el.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/en.yml b/config/locales/en.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/es.yml b/config/locales/es.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/he.yml b/config/locales/he.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/id.yml b/config/locales/id.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/it.yml b/config/locales/it.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/no.yml b/config/locales/no.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 90c31bf3a..e9989cd0e 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -7,6 +7,7 @@ pt-BR: label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' + label_cannot_change_selinux: 'não pode ser mudada (selinux)' field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/pt.yml b/config/locales/pt.yml index dc5f5f1d6..f6db9e33d 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -7,6 +7,7 @@ pt: label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' + label_cannot_change_selinux: 'não pode ser mudada (selinux)' field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/th.yml b/config/locales/th.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 2ce8a4693..723401ce1 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -7,6 +7,7 @@ label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon field_git_http: Git Smart HTTP diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index 8bcea3432..07aa7e0e3 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -23,6 +23,10 @@ def self.web_user return @@web_user end + def self.web_user=(setuser) + @@web_user = setuser + end + def self.git_user Setting.plugin_redmine_git_hosting['gitUser'] end @@ -92,7 +96,7 @@ def self.sudo_web_to_git_user @@sudo_web_to_git_user_stamp = Time.new return @@sudo_web_to_git_user_cached end - test = %x[sudo -nu #{git_user} echo "yes"] + test = %x[#{GitHosting.git_user_runner} echo "yes"] if test.match(/yes/) @@sudo_web_to_git_user_cached = true @@sudo_web_to_git_user_stamp = Time.new @@ -146,7 +150,7 @@ def self.add_route_for_project_with_map(p,m) end end def self.get_tmp_dir - @@git_hosting_tmp_dir ||= File.join(Dir.tmpdir, "redmine_git_hosting") + @@git_hosting_tmp_dir ||= File.join(Dir.tmpdir, "redmine_git_hosting", "#{git_user}") if !File.directory?(@@git_hosting_tmp_dir) %x[mkdir -p "#{@@git_hosting_tmp_dir}"] %x[chmod 700 "#{@@git_hosting_tmp_dir}"] @@ -154,17 +158,51 @@ def self.get_tmp_dir end return @@git_hosting_tmp_dir end + def self.get_bin_dir + @@git_hosting_bin_dir ||= + Rails.root.join("vendor/plugins/redmine_git_hosting/bin") + if !File.directory?(@@git_hosting_bin_dir) + logger.error "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" + %x[mkdir -p "#{@@git_hosting_bin_dir}"] + %x[chmod 750 "#{@@git_hosting_bin_dir}"] + %x[chown #{web_user} "#{@@git_hosting_bin_dir}"] + end + if !File.directory?(@@git_hosting_bin_dir) + logger.error "Cannot create bin directory: #{@@git_hosting_bin_dir}" + end + return @@git_hosting_bin_dir + end - + @@git_bin_dir_writeable = nil + def self.bin_dir_writeable?(*option) + @@git_bin_dir_writeable = nil if option.length > 0 && option[0] == :reset + if @@git_bin_dir_writeable == nil + mybindir = get_bin_dir + mytestfile = "#{mybindir}/writecheck" + if (!File.directory?(mybindir)) + @@git_bin_dir_writeable = false + else + %x[touch "#{mytestfile}"] + if (!File.exists?("#{mytestfile}")) + @@git_bin_dir_writeable = false + else + %x[rm "#{mytestfile}"] + @@git_bin_dir_writeable = true + end + end + end + @@git_bin_dir_writeable + end def self.git_exec_path - return File.join(get_tmp_dir(), "run_git_as_git_user") + return File.join(get_bin_dir(), "run_git_as_git_user") end + def self.gitolite_ssh_path - return File.join(get_tmp_dir(), "gitolite_admin_ssh") + return File.join(get_bin_dir(), "gitolite_admin_ssh") end def self.git_user_runner_path - return File.join(get_tmp_dir(), "run_as_git_user") + return File.join(get_bin_dir(), "run_as_git_user") end @@ -189,7 +227,7 @@ def self.git_user_runner def self.update_git_exec - logger.info "Setting up #{get_tmp_dir()}" + logger.info "Setting up #{get_bin_dir()}" gitolite_key=Setting.plugin_redmine_git_hosting['gitoliteIdentityFile'] File.open(gitolite_ssh_path(), "w") do |f| @@ -211,8 +249,6 @@ def self.update_git_exec sudo_version = 100*100*(split_version[0].to_i) + 100*(split_version[1].to_i) + split_version[2].to_i sudo_version_switch = (100*100*1) + (100 * 7) + 3 - - File.open(git_exec_path(), "w") do |f| f.puts '#!/bin/sh' f.puts "if [ \"\$(whoami)\" = \"#{git_user}\" ] ; then" @@ -251,14 +287,10 @@ def self.update_git_exec f.puts '}' end if !File.exists?(git_user_runner_path()) - - File.chmod(0550, git_exec_path()) File.chmod(0550, gitolite_ssh_path()) File.chmod(0550, git_user_runner_path()) - - - + %x[chown #{web_user} -R "#{@@git_hosting_bin_dir}"] end @@ -291,7 +323,7 @@ def self.clone_or_pull_gitolite_admin # clone/pull from admin repo local_dir = get_tmp_dir() if File.exists? "#{local_dir}/gitolite-admin" - logger.info "Fethcing changes for #{local_dir}/gitolite-admin" + logger.info "Fetching changes for #{local_dir}/gitolite-admin" %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' fetch] %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' merge FETCH_HEAD] else diff --git a/selinux/README b/selinux/README new file mode 100644 index 000000000..a877fb20b --- /dev/null +++ b/selinux/README @@ -0,0 +1,67 @@ +This directory contains a selinux policy crafted to cover the sudo and +ssh scripts for the redmine_git_hosting plugin. In it, we define a new +httpd-compatible type, "httpd_redmine_git_script_exec_t" which can be +placed on the redmine_git_hosting/bin directory to allow sudo access +from redmine code. The basic assumption is that scripts placed into +this directory will be called from context "httpd_script_t" (i.e +redmine). + +Once this plugin is placed under selinux control, three of the +redmine_git_hosting settings can no longer be modified from the +settings page. They are: 'gitUser', 'gitoliteIdentityFile', and +'gitoliteIdentityPublicKeyFile'. The plugin settings page will make +this clear. The simplest way to modify these options is to +temporarily place your system into permissive mode, refresh the +setting page, change options, then place your system back into +enforcing mode. Alternatively, you can alter the init.rb file and +reinstall the plugin. Under normal operation, you will get one +selinux complaint about /bin/touch in your log each time that you +visit the plugin settings page. + +******************* INSTALLATION AND SETUP ************************* +Note that the redmine_git_hosting/bin directory must be constructed +statically so that it can be labeled. You can do this with a series +of rake tasks at the top-level of the redmine directory (after fixing +up the defaults in the redmine_git_hosting init.rb file): + + # Build bin directory with customized scripts for redmine_git_hosting, + # install new selinux policy, and install complete selinux context for + # redmine+this plugin: + + rake selinux:install RAILS_ENV=production + +Since redmine doesn't currently have a selinux install option, this +installation command is only available for this plugin. What this +will do is label the whole redmine site with "public_content_rw_t", +with the exception of the "dispatch*" files in public (set to +"httpd_script_exec_t") and the scripts in redmine_git_hosting/bin (set +to "httpd_redmine_git_script_exec_t"). + +If you happen to have multiple redmine installations, you can use a +regular expression to describe the redmine root directories (this will +translate into file context descriptions). For instance, if you have +multiple redmine installations in directories whose paths start with +"/source" and end with "redmine" you can use (notice the use of +double-quotes!): + + rake selinux:install RAILS_ENV=production ROOT_PATTERN="/source/.*/redmine" + +Somewhat less far-reaching options include: + + # Build bin directory with customized scripts for redmine_git_hosting, + # install new selinux policy, and install selinux context for + # the redmine_git_hosting plugin + + rake selinux:redmine_git_hosting:install + +Finally, for those who are hand-crafting their own file context: + + # Build bin directory with customized scripts for redmine_git_hosting + # and install new selinux policy. No file contexts will be + # installed (so that you must do customization afterwards). + + rake selinux:redmine_git_hosting:install_scripts_and_policy + + + + diff --git a/selinux/redmine_git.fc b/selinux/redmine_git.fc new file mode 100644 index 000000000..7924d2496 --- /dev/null +++ b/selinux/redmine_git.fc @@ -0,0 +1,9 @@ +# We do not install contexts as part of the policy, since it is likely +# to be overridden by the local policy for parent directories. The rakefile +# ( ../tasks/selinux.rake) will install file contexts "manually". +# +# If you really want to install a file context, uncomment the following +# and rerun the install script or rake task +# +# /.*/redmine/vendor/plugins/redmine_git_hosting/bin(/.*)? httpd_redmine_git_script_exec_t + diff --git a/selinux/redmine_git.if b/selinux/redmine_git.if new file mode 100644 index 000000000..f5e66e48a --- /dev/null +++ b/selinux/redmine_git.if @@ -0,0 +1,131 @@ + +##

policy for httpd_redmine_git_script + + +######################################## +## +## Execute a domain transition to run httpd_redmine_git_script. +## +## +## +## Domain allowed access. +## +## +# +interface(`httpd_redmine_git_script_domtrans',` + gen_require(` + type httpd_redmine_git_script_t, httpd_redmine_git_script_exec_t; + ') + + domtrans_pattern($1, httpd_redmine_git_script_exec_t, httpd_redmine_git_script_t) +') + + +######################################## +## +## Search httpd_redmine_git_script rw directories. +## +## +## +## Domain allowed access. +## +## +# +interface(`httpd_redmine_git_script_search_rw_dir',` + gen_require(` + type httpd_redmine_git_script_rw_t; + ') + + allow $1 httpd_redmine_git_script_rw_t:dir search_dir_perms; + files_search_rw($1) +') + +######################################## +## +## Read httpd_redmine_git_script rw files. +## +## +## +## Domain allowed access. +## +## +# +interface(`httpd_redmine_git_script_read_rw_files',` + gen_require(` + type httpd_redmine_git_script_rw_t; + ') + + allow $1 httpd_redmine_git_script_rw_t:file r_file_perms; + allow $1 httpd_redmine_git_script_rw_t:dir list_dir_perms; + files_search_rw($1) +') + +######################################## +## +## Create, read, write, and delete +## httpd_redmine_git_script rw files. +## +## +## +## Domain allowed access. +## +## +# +interface(`httpd_redmine_git_script_manage_rw_files',` + gen_require(` + type httpd_redmine_git_script_rw_t; + ') + + manage_files_pattern($1, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t) +') + +######################################## +## +## Create, read, write, and delete +## httpd_redmine_git_script rw dirs. +## +## +## +## Domain allowed access. +## +## +# +interface(`httpd_redmine_git_script_manage_rw_dirs',` + gen_require(` + type httpd_redmine_git_script_rw_t; + ') + + manage_dirs_pattern($1, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t) +') + + +######################################## +## +## All of the rules required to administrate +## an httpd_redmine_git_script environment +## +## +## +## Domain allowed access. +## +## +## +## +## Role allowed access. +## +## +## +# +interface(`httpd_redmine_git_script_admin',` + gen_require(` + type httpd_redmine_git_script_t; + type httpd_redmine_git_script_rw_t; + ') + + allow $1 httpd_redmine_git_script_t:process { ptrace signal_perms }; + ps_process_pattern($1, httpd_redmine_git_script_t) + + files_search_etc($1) + admin_pattern($1, httpd_redmine_git_script_rw_t) + +') diff --git a/selinux/redmine_git.pp b/selinux/redmine_git.pp new file mode 100644 index 0000000000000000000000000000000000000000..bfe21e6945a9e61a536b6baf50f119f2006732a9 GIT binary patch literal 127822 zcmeHw2b^P9b^ds8z?f!CZ??b&*05ghs6PYo1B`Xgb0oFW>&05l2x`4f|Z2y z5J(T{z4zYZB%~*h-bg1sKtehRVgKhl=bfXg(Rtb#&ztezX6|pU-urGl-*@}F(nxPR z_ofeBu(GnUO8$}L-|>eRth|-N9!B@KzM$}uPH%Fia8tiM=_W{WF?l4vh&R?=`oQ1}r|5z~d2+(rdN{ z!_Fxb^H>XIgW&QA@*|==?!yLZVVJZ|HAoonqtaMl2!Y|vWZWE&hkz(xBoMbS+-$Xy z0jq=@O4}cdp%?8$lG{jk33u8Yex}{$t3`R3ijV>lJkiH*O8mhC~T_Z8fKPP$c??)CF{aH^`-(VBBBcf=JFS z*l$adq7Jz6lG7*xybq$`&~Ld%qHCqx52P_+1P;bgo7$Ar4evnZpgx)-8jT$qM5qmn zhS`up2={QLMW34VI%jC0HhDCnE4Gu-c-TLSaxngaJ`K}ppT;d3N;85*(z|1lOcLCp za4wilp|0R+phG8BtA`P*D()OnN}bX%iZP(YuGqR{vDez`O=SNE5UZ3P{^YXN_jN&8dN9 zCL1(|R6$zpI{#qOpl~V|g~%M&9JWp%9W1%C4I(gAZS^O;alePOsIorI_lUxrcE_YY zZuWU}V$KOmEt#o*yuCx-V>^?(Vw>HG-H?xmG&!bNe`DVNqC`WY%L z9deCczcC&*dn2cNqKbB0uVqS%v8mG-OpEWytxffiAMOle0>JMJSK?n&ek2@ls9v~UZX2fK8kCSiHD zR%uNk6muFpT$eVw_uaV$HJ>?6YiB`IUY2^gtnk`B8lma2($NCcYVz96Gdv??Mc)~9 zHb=Cn1wHFIY853r$`G~Ycj-IrHdbhY>Z@Rp~Isy zQP9cc>}-K5vMjW)_0Zk8ZuuAD)C|*djrrWHd_C zV}9!On-oNJc-7?r)7d=P>2@Iu^+3XUlde5?5UpXiv(@1Z2zpYwNJA?+7%Y%j-g;r? zqV$lDXpkUi(NIds2b&#E4OtWo2)oEJAT=!>bJ!Z99NG=ZvjJ9PA(GYznh~YbTrsOO zh=715%fTiag={5Ts0_Gk$fMfe;i$Qr>z!_Ae6|RBsK2ddt3f-H;jq)T;g7H;9yv81 z*dj4J+(sLn0*EFJ$bowXc`$;98;}-xzC;MM)9L20-RMvFK?yx+quW0XtWAkWH{8Q2 zb`hvi6s}o2Jv0F<+cb%ik%f!u@NR_=_ik+Z;Nj#^7iFQ$y*$^VW>Fi#^baYh3jYBX zMi1igW>#_g_*{zmheKyZ_lJku=xp@w!;>xQ7E;f<0YO4IG<7b6bzj6QB^oZ`eHD8PGGaJb7R+;#@>AW{gQ&u+Axx12YarGiDq~Jrcz8qNsnf#R?@)pIC=n>B3_Q8U>S^XgrZYqmBMhZn09xkI>~96 z2PJ*9=EBbmnmt}RAWzl+7-0Fc(=;T7-gMmpL}|2i;G7Gz!OZ4QtdVd}CLd0idQ#Y< ziFI>-512lRrcX2s+%w6eW#HjxCxli3l!ir}ATb}tp#9M1+A|P|a|R?wwzO@v)9Ar~ zG&_z0bQLU6V(hHo;k1#d=eIk}d$gI+lle@Lk9j{D4s>ybs{fAK{ARaj%#rXgUOFt~cfJefuPhLvne(S~^K% z8b*mUavOB2*W~pen6(jyG~Ir)-Js(YdTR)kEhE~W$dMc^O7p%Hi3dC^Tj>#NT8!$b zr?E%@N4mPr-sYsaxo}7_QvwdifQO9ibKO9PLmEMyEjqy1qUXbfNPj-83UG6gv#@ZdwDuvY3B{*f`jxB4tY1|edA<^zKDIn z=7;mN1yO$dseh(WlG?6vvaR#NKsgZ~eZ@`Hwm;&gX1u6$2IMhNC zSyqqtb-UDFe2iG=j0`M8SNfM+MVC zP-~1M0~8Ln>qA=DTFucIrDFJUAidkKGdwB)QzB^fXvPP|@fPs7BX4zK$Zt^fb@)^g z<8tdHJrn9inqq>ZHbN6gh{MNjLhCmef)s3XeSruT3Hfvc??E9Bl_rCwv$Zwh_Yn|; zWd(HP5Dx`iPG0g-`_LP*U9d(8s5cB#$mau6$~H_!{RurR>eA3UK{cyO)l(^BHZukyOHRz;YN(NYVA#W_PZzh z>p@K#*f3yMp=VS3^30Bv9qtJEu&QZ+$8lNXWPkk)^j3dklANMv&%%vrWX3!t<8jPukjur^PCTO9% zArxut$Xofre8?B?(a?r&obUr3h}i=j0Fv@&qUfp7A&Y33oV`H}1S084JjwtUE`v7T zy7A+WU?HuH4};iKPkIa&Bw$w$_Z0HpQg~)#c#lpiAAQ?IG!a~hJnj|aM1wvru+_{S z22N)}5K7qgp+R(sv^QzzqURdCuY=CTJvRN&AUZsf%$~*OhJf~;r%@7brfm0yhx4O6 zDerxLM9ahBgZbJ{z`aq+-dm(oq+|=Ho7%3_%qRNtE@Im1o5KOR1h*gT@<^p?xqDr($ShcsTl}0!%nai;o_lc@ae) zJV-jH`12BkR@TUe?Kz(G4jVJ)#>6uY&MfrYf_2>wjQG_v9Xie}%M9 zi%}rI6$zv8aB`x9j)D+*)05tOqYoL#;}d!<-kTkUQZsChmUrl94#xRdv#QOjUoMOB)!$#;CJ6|p_r`1 zc#x;hkqnWe!E^4*)6_a{Vl9`h`1kbE(gn@A5 zH}b`h_Fpg8iSc+qA4{Nj?0JaM`v%PI*b+m7$vTZ4OFul_7-OUKk|kNTaU*jyS{h?K z++jq5n9jK|-K!IF!M8)uVdJnfAdVOSSQU}elck^kAyM>6k_LY=1SF9*vp;Ir!hdOV zm17V1nlzVZrJZavC*85mP7OY=!uz*bwAu+i-zVu_xjY?aXf6*&u36>Lr!JZpJgCH{ zE=h;+&j0fD%q597Xj4TWKf$Dn=jSY&8s3qAbg4_x|GHvMt}jW(Z|-xd<4aQE zzxqTX-h9oXH5_b7x43;tqI7+u|6q`Iz(9ZDlJwj9E#B{G@M?42l1!~`2k*b7XGlD~ z>N)}Pa52BiOF9B4GBH5nbI2oQ*ij`dRw|rz(rjjv73KxZJ==4{X&7B>s_pgZ90dO^ zNJ}=qd(GV(Q|LA1v#M4Yj<*JlG3Im_%t|^skXNkB#bRo1;J@n|V-$PC zlH@kA4JY&_7F7Uu3n<4sbKBKZ5W6AmeXOc5>pqWsR$?`gwxF&__-I;|V62NgRPo6w z%x75%7w58J)8^1Ia%1*0yT4_F6aA;KB>lGJ*jQr!lT|!#Gnfy32$soQy(IOvMUDqO z8Y!!ETGHt2eH!Tpx%nN`r!PrniFk^%oy;Y zivzIO12i*D=*Wz28oD?D*_qQ#!iy+zRuyiWY<25{cEx*3S=lhUacV5195ecrX=-o& z#DQ6-)x`f?r_&T|Jh9H-y2J#1FI9ik8Sy5#+o2@@YdkDvamuU*nHjMhpenPnr=5wH z|MHS_n0(ICtc>OVDNEAP7fs1x{Hi61TJ-G=va%~JT;XDVl^0RQYc^_4lOi(4OX@m3 z`T{QIb%a8)QgK~kFHu{Ph~G;R`Y&CU3{NS{>)Iu$q?4e&p0Om=WJK!`65&&~S>#dq zV|0R>mn7QOr|~atjWN~}48g3D7kiIRqx>o3qf4^N9UUVHUY({5t2$1@=)&iwxVD--+A`RI10G9| zCCiNT25KXH_z@l0E@9{(Tb4z&nHC|>PLe%++{R`<%kXH@?l+5g~M@*4kP*!EevoR!i^g3b{D0?LA}(;`lQnx(^?_bLhrJ#asn}yR+|*k+U%%e zJQC2ylIg21&JOU6hA`$5A(_kRrw-H%D@ zw^bCn4>K%uA7@zTUSU}1Vk-pK5_4B;bV=ye3=7@o8Wy@QHZ1vXGc0r)hJ|j&u+SYD z7P_BgSm=I+VWE4Kuw7|M@t?2JWuf~OhK24o85X+VV_4|^m|-dJ=M4+p-!Lq6|G==& z{Y%3__n!<4-ICIk;{HRUD?<02VWIoLbV#)-LiZ7dh3=CK3*Dz1mh#sO3*BcK7P>Dm zEOcLLSm-`S=}PujYjjoUwhRm1EyF_hv|*w9$%cjQ=NJ~cUu;?8K{*qy#`&)*E?t2Ui-M=v`bpP3~(EX5Mp?g1_ifUs2A%=zSV+;#j ztbTAUiGH$1*M;sg4GZ0hVWE4SVWE4YVWE4gVWImv!$Nn{u+Y83u<-dr!$KDi<8ZBr zC$3M^=t$_k&9KmYhhd@n4Tgp8cNrGCKVn$u{;Xl4`)h`U?(Z2Ex_@psI*x`(8)v1p zhK1xGHI^2VHLNrnkU^Kf2ZLZ;~zF$XMC68i1Du~E*@vPSg+w)Lg#k{0dn#`Gc56cZ&>L5 zt6`!0VZ%cA{yJ3^h3+MWh3?}G3*9RX3*BRiOG3Ax<%{{cMGM{M85X)PF)Zm{X;|pK z-muVpgJGdNHY{}CY*^@ireUG`1&Yf;7mu58Eiw0%7Ai4|0szUdS#S7g}F)VaH*Rat2 z62nsd*BTbO-)>mw{-9x@`_qbRLidHjmew7v&0n@?q5Ip0h3=mi7P^0HSm^$XVWIm! zhK25hIy2S8-a`!w-Nzahx=&GD7rHN3x{~~;MGM`kVWArv7P>DoEOcLCSm=I&VWE4% zu+SYE7W;29EOb9zaU^sD8xq5JEGh3@Yg z7P^07IO28UpA^%|Qp8g8AG3h9B9@v>6|g1=&dm}OIlV6BKaya@u=lKh`22#}M1 zn&A@Tw;3)oeuv=-<8LrrW&B-+Ym9%yaGmkb8jg5ir*vuQfSO;^*y3@yp-J1+c{OyK?Zqu;PJ!x3zP7Dj(I}Hon&oV4@zff^m=zf)9q5DjwE9Sq&qJ{4F z85X)fVOY}tf?=WiZo@+Nj|>al_Zk+u?>8)T|4VU2=w6^xRYmAll&+Y2kVOmKM;R8n zmm3zkR~eT4CBs7ZIfjMq#~Bv7FEcE3U#qw(bom!7h$5{sJn*hlx>9`Kk_+9lhK25@ z8Wy^5H7s<$)UcHIb%urRcNiABKV(?w{*2<9(ESy|Lia|cE5-eeMGM_OH7s=h&alw^ zpkbl=zlMeG$LKs&lky*CSm-{^u+Y6iab4(MZCL2us&plL&7y_wa}5jK7aJD3w;2|? z4Z}iLPq12bp*ym8vG+-ah3;o4j)d;#8y32+Q@WD;D=b>*ev@IL`#pw*?vEK3x<7AN z=>CRbq5B7hh3;P(j(AylpJG~PN?2$9a~8110eVVEk2J*+rs@-_VC^{Lq2V>gZ!%mg zp)ef$!?h&((**%?^0yleLg=>l0e-s4B#r?Zsq5BcTLiZw_yNW{hQo};`iH3#l(+o#E521$PT5OHaJQ)f3 zYc+LA$X{<*$UlYiA~$Eh*`fu%%CL}MH!S4!w6|3f@~13b$WILm`Oh{ib^S$(%R=|7 z4GZ0GH7s$x(2bR@ zn0uo|3*EOE7P_BjSm=J4VafmXhK25T8Wy@gY*^^trMM<^f7P(i{awRC_eDxqivKf< z7P^0LSm^$%VWInB!$SA|I+4|+yh{uV-Nzdix>qW$3*BReg>K!j(8b$Pa4jkBc@`~n zUt(D3zS6MJeZ66!`v${8mwzLHiZ1F<(GZjZd_X`XQ-LEt(bU%T~5?#su%@!?m zzt^zP{c*!W_nn4??z;>N-9I!ebpOh5#H+~r71KIX#yay~vw*cSCcTalTszKqWLV-q z$#9Y5Kf`dTjI!`n7+gz|f4(3DrEOf6>J|(92YKxZIZ5bB2TZV=1X~RPIlMM^q&oLbFEQuZh*J5j5tg&Sw|22k% z{I?kv@;_i$$X_jmQE6P-h3*}Oh3+RB z7P`+>x?=v*EL!Nk&9KmYhha(o4Tgp8cNrGCKVn$u{;cAf(ET;TLihI!3*A3AEOcM2 zbj931ShUdnH^V~r{|pP=2k5+2ll%`iEOeh>Sm-`gab4(MV_4`$hK26)4GZ1dl&%zi zlSK>N+YJldreUFb(y-8-7?$$xG%R#KOK~K0ztFJI{VKyk_gf4L-GOg@ewGvmL%8DNf2an7wc426uOTzEOak3EOd_;7P>{nC87Il!$S84!(#sy z!$S8phK25x!j_(2m|okWg>KKV&^=>V=zfY}q5HXph3=OqE(_hSH7s<$-LTO8LBqo5 zrwt3;V@kKobbr~Rh3;<~7P^08Sm^$(VWImkh9jQT@d+Qe7MK1%8e0+a7wXJZ5%Lc; zEaV?+Sja!cu#m4y;Z!;of7GG{R}BmK*szd)kzpbK3d2&@pP;xZbWa!-x}eqz0#H9 zf6}6b?k^e^y1!{y=>D-`q5Ic{BVI-RSurguRV*tXngy&?v8lXAKM8Pcbf0Tj=)Tyn(7nxY#B0z4v^XxUp|MpV-!UxYM}~#`Cm9yC*pq1#ltV(ynL zTIl|kVWImT!$S9O3`e|*{EK2*R%%#Q{%01jR>QK=RfB8C8J{vN@l(S^j{j`KCB|Q5 zxXk#g4ObX{tKn)5wl6iz(ceFVjDON_o$)Ukju`)@;-b*~W5YuCuMG>`4;U7@|7}?4 zuIkKG6uJ*KEOeh}>Dm0FExN|IU|8rr!?4gTD=rD$k2NfGUuan9zTB|T{dmJdcf+vI z9T*n6PgA;*{f!nabl+lF=zgAIq5EZu%R=|-4GZ1xG%R#~*s$2U%dpV>Rl`E}cMS{O z<4RYO|CvP#-M=?1bpO?`(EYIDiqO5k&P5fWdx>G8`*_1b_e#TJ|CnK+TQ@9puT{Dd z{XB~nx-T&-bYE#$=)PWYRp`FKu+SYF7P@aXEObB9u+aSi!@}n)4GZ1tm2Q>$>Ni`o z(EVP+LifiFN4$Vuq}e(5J2kc@oVyX%Go6S0# zzu2OM{38tu`O6GTT^~_g7rOkh2r8166)x}D7B6&fFf4R$F)VanV_4|64GY~@DP5t{ zvuL4v#<0YHieaJqxr!s9`z3~j?$;U?y5DYC=>DK#q5IQ@h3+pK7P{+7SIqymMGM_O zF)Vcd)^NnD$OjeEvQo#g^1riywK|rSEj6%qobhSH6937DiyZ$shD(gU*l?Ng*BGua z{x-u^#y?=VRtJ|y80P4onnA|DWH@5{TZ)T9_dSM%?%x;|y8mog=zhqs(7m6|Ohuvl z5W_<89&po(5)yg3Ek@q3*8$H3*B1{3*FZl7P^~;h3*}Oh3+RB z7P?PYx>DSyS+vl7n_;2*4#j1m`wfPL?spj$x<6uA%KurzLig7U3*Fx{EOh_eu+Uvo zx|01LEL!OPn_;2*e~K$Y_W?RpRfO)t4GY~T7#6xuH7xe7F)VZ=!$SA@hK264l&&Pd z$)bhs?S_SJQ*l-3o-{0UCx(UYorZ<(XBif{UuanDf0bdO`z?lr?hBNzM1P+}3*DbE zEOdWCaZTvH+py66Bf~=Xy@rME`wa`-|1vCeFVLx|CVU=bSm?e~>DG8?J<6hm?&XFf zUj84b$vO2^8e13g{Ba{nLCXrqKgZ&Q{Kpv<@-H(ix8$R~z{eBZE;f3>k;b7w7D z$bYI~sq42Yj)d-)8Wy@=XISWdhhd@nLxzR!&lncEzhYSE{*Gax+fuqh=T9wK=>DDI zh*yz+RZPoDgk|N!vw*b-%SulRTszMAjA4oY6vIW1|6Ic*#$RH%%=l{!R~Uc0;VR=F zG+bl+(}wF2xINM^NB{B+GX8DFMWOpAhK26m8Wy_$Vp!<@k71#Eq0UT2q5Dw7Lie$T zh3-=f3*F1CJf?Tlq9evt#U-H|8y31RGAwjoVOZ#Xf?=V1!m!XC8Wy^5GAwjI-LTL- zqI9MBw_CK({c^=+q5F-7h3!bSjzi3!$SAh4GZ1hH!O7j!m!Z&N5ew5sC1>c zf469%`w_(zp?i@|Tos{vsbQh}M8iV&X@;fzLhK_p=QP-7hjM_P*M%(EV1!LihU(3*8%(t|b3Six#@SsJJF{ zf77tg{bR#I_pc2L-47TRy8mrh=&tHiR1^CTHY{`>ZCL2uqI4y?V9`SN8H(#dmp@QM zS!f~Qq4=>DFLYmMSm?gou+aT@!$Nn%u+SYC7Cvt@EOcL^bn9&XEfyW|ZslT)=Gf2E z*ht8KnPDOS^@fG~cN!M*KWtdY-(^_H|Eggj|GS2T{GS;X@@-?s^#0zWqX^44#$7u0 z^eNNAu*$%NX=1|gn&%R_hl}}j_5uvA>GbOj5EeQuTgy?59`+panm?v{c*U9?VWEo& zz-zh{6htoO*Vzl=ag=?@$}N^QCJe86cWi_5Kwr$)*$c`+9phL}%Z%wBUU42a4noKM ziSw;gTgn?6;1%CnbfB(o&8y8<_rG7EUQy4XL!IZ@ud;09*{`O=IVK;vymd^e@HjL; z_posgI`SMJQ{o(x58XlO$a8#5NkANmp!>XatgL3+*za+k=NMe3BeyjwqptGoyZ5Ay zJo_%bTf*zRdA9jM%)41S@@(^i(6P)$ZsVM0C$G)155^lln7iQO)GG!zxR_sOFTn7c zj&AuLVWC4j9wKtuU&Sb!UvQH;*vNAoT$yIu$gMq=*vM^8%&?K?IA2y*`>i9-alYR= zbezO1^A6#whDe4+2a#i`f4S^T!A)AR@r9S+eSN*;C|mR?aN zFg}%vE7IWlsPu|{0gQf!D^9j?y*#}Z((9Ac>r>L}73uZL^!n8F`n2?lBWILzReHr+ zhrl8CIKX?=j&fJY@AtTW2-(0Jhx@5R=a#TeVzR3?^@_02#RT9repz2cUFlwhZaH5} zSIw(egoQ390I%^OM+!EFc-X+G(zG!lc*S}}Smwl^f4w^BwAcm3$s^ zLxD?NtY6s3Yi?L(<9u^NR^FKS!SAFmqOO+B4F!sai}`i-0t~M<&NptC%?$-A2QKE< z*$XiIPHn6PX^{_J^A_kHF6P(S3oyK<+soXLRR_DZA-$JKkpnOFk5BIr9;aS;>lNXl ziwVGMd=^KJCC_2Fszdxi>Bwcix`%b3u5Mp>xz%e- z7+&jHuLuiWOaNZv^%h~FL;QouKYDf5(#C}0HLqR~7P^=KyvFM-!a|4m9NV1D#i0SZ zMt!_-4}_6NcA1&?~}17ZZTjc)dkf=t3O`e=m8>6VpAs z;;en(_H-+&l!;u-ud^4mmB%@?maSK{lBbQ8l>!&**+Booo?Gh%Y2X#_xUSA)1u{r3=GWPa*4480aGEBMXW94; z7~XWyISyRRud^4map{sapQg#Mm%&)T-VECQd!1H^^ zBg{Lca^PZqoxK3VtBqyP)~l2b=Mb6}`QR0Ywkg zvw^z0wQ#BRBcy@fb-!A&pFo@E^ck8}DvJ5xs!rQu+4J=-+I*>vJf0hfccu+)52{P8 z|CkcPE50}BSa$w&Z_#nS^S~YJzyd)o=GWPa_T5~@$9Zhz(s!5G$gS@#v60*QvBXAh zZJrL>tTw-WCHwqo$NlO&HkO^+=s4fG=DkG+_UdO>6miKF~81UU?abMyKF{18@a8u#CMf^mW^EYKIgHKPv4!A6<>$$8TLNT zvu&WwR|>hzyGz?6pSsF!bFC|#pJI{)uXq>p)jg^s$LHEi9f#w5zHd-B$*ik`n6L8d zy9G#b=fwL;o995j`7uwln5T z&qkhe)G`}+tr=N1^6X1lHu9W5vTUHuACb#>t2}hvm)o4jM?Pnv{D78KSNEpATcG}v zr6aF>^fDWH)m4^_Jlj0WMxNt5%SN8_RlJhj=6SV8mW@1hOqb}$tM4wekyl+!X@h|J z3LW+g2US;j>A)r_4n@#CbmX~4t*rVs@*IOx;vAC?9dzWm_nA_8G5OG`jl9}pN`=Rv z0lJ5cdt1Mn66ctF=TlREMogLpGw-`jkN zjojKi!$z)co?#=;^&_i2a&7bD2hiqAY~@ZH@F(<=a zBA12n8&f#ErqfTkA}n+|x1&AwYC~sxb8gSFk>?tfW#b@p=o+PM98>+lE8b0; z7t*|RF{ka3SDP28{M4W#fF~gSra4IKAox7#?jZPG-+oVS3fM zWZC~k?$k!s{0ti?13qh?n3s)Z{};Ja8~QX96W`FOjy(P^GK&jrSb;b9IJ|^Uc+lq-__cSG@qk>)4~)m6bwvo9FbLy3|Hamo?6(f|tDOG7;vUt3 zex=_VgFX_cUi-_kenm;y#pzWq$Sa=(TPff^?{e>=BJ(cynJMSI%e|7yrSI;|oiD!6 z_p%FPuG>88SJvDDmrEvg)gNL?4=!-wx-22`ig=kXbNPQwHWP;i=zeD77UP37@XBvl zzn-S)kY1I@JqPehzBXjE+*3C8vPMp+Hc%JB{hrmamo@VLCmlKNy*?_&$K74)ciQH8 zt@S$h$XHo1hj6@~aytEe07S&8*F4MSE2PzQbbEYds^gJR>JXpfKDB^iW4r_oHuf@? z#FRhKamhWYV=rTHn(Ba_m6Za&#{4>afzJHqs{$7s^Xu#dHt_$Nk6iNohRgcZl=7mC znEbMH3%uWm9Us!d#i>`EhmG(%X_OP}?8F8oV;84ay&x~R@7wQkG3 z9M3U|jlMrN z-2289hB&yLkAs-g%PZgVi`vldvI9mM_?_6GWbESfsu$#4Wst`ho^nYszs_D@1N}9h zwR*}0$NV~bQ5$#nd|^sOp^TXP3Kk!7F~81U)W&}6*vp<{N_2Osln9baLl)}mptsDjI>k1#4shkJPCdybv_eq&b^ASu1ao!rNC)$$*KKyq>FHC(R<$26(qOZc2Shj6{02KRRb_b{Sq;^2N7+}{=4 zLxiOd?x(^1UBNwmSl-eH_tW4WKXb2X@VjDh>d7p-bNH|xxj6L}e(v0LUO0a1%nOGN zH=OLd;pe2Jm?Gh4cJCy#$VcPjG*0okb8@#Bo>jgOT1>y&@au@lB2TIY>vOO%XlRqYSSFp6PI02v3LOj>GR{tBZEC!n5jA`){0TP^{PO;aTmT6&~tJDYM4# z9yZk8Y8rtscn!bg%1hV;!qmljL)WbPAj#dn1qbDYLV%T5O!x4Lbsa%i=wbr!nvNH> zG>k6h5FhRXH6qpXjp2f$vd$@*5 z+i~Tm?U={Cma&&B`k3woP|hlO?RPQV!za**9+Q9!x+`1whU>7dtS9!rbymHw6di;UI z^_V*U;d*>I^&;s@FoMvjbN)>uP-pN~Z&~4Kf`#-M z>qpkQk{zDej>OJu-^}zG`>=DnD(CR|Sn8ZBEv^4N?zN1)T+tQr|BC9cHp@G#?lxu@;(M)fURXx7)zL!29F-sYBoA534Z_0Pf*!zTpLO7w7JW?_HleJa@;w>T>Eu zi{t&=x!vv`veNH%eYrD(Pe%CRb3WJC;ki3k!{Kv&I+mq2cE9(~e0#ZKO~U%6Zk2BK zzE(;Ju@;(MVJpA&INQeIdVKEOlq%bv!-M-3YG4hIgZpW4e^+pihbc=R+#jyThwCxM zGhBGC852H#_~B>Cq~6ZqvJ$6$2laONoKJNnyVu2f%L)(sCuL_Br&nC^x(|xH+qdWg z={DKM5p*G(re(eY>)CuU4Wr9KKU#JUFirEt>6I>X^#BZ;x|V9+j42FWaUM2wPoQP) zmyNsoe=E~eFL;F<37SLrUg|2Q{8}~&MZLzj2^`}0QdcqM4{Z>fA}n->e+Kzg@|rfL zdw9jm+B{50S>Q5lpgb)zrh9nByR;D_Z|G1?&N?z}W>IHdu{i7Ex2Wj9H@3Jplh`gagIa)ushmTXg6C0G2U7TL^g1iU8UB+6z zZ3```J=L>{E5gFMLRg%7O~1?bSVjKOg*NgW6EPW4j;TGnm@+@2 zXl_<-vGI;67XI$(@NJ}X`>K|?HybPN`XTjwk@4O?8@YU67S*t?L+a|WIqC4*<5Dty zA@$^Z+9TV>MY+wv**5M+8|!>@#P0aO|9WCmvfnl?%B?-Jb=;304)f7bSg0#JcS_$~ zP3Qa7m4{Opo)-4YCdxfV9@l(qT(Y1G<>FZZ`u5Yt(+|MLLDW^gZSJ>+%=LNoE8m9p z<$T)wBDY`Vc_x6TvzYo&=)gU{@BKi@z73o^==lk3#;Mo3TecUXr0nAKsu$!%zWw-4 zD75XJAGyHhjhHa}Uis?Yor+p;aS(!gc(o_*q3rfEAM$+y7wq8s3gkPx7TZPbg9{!! zw4Lld9SYX_X>fm6a1RlhCJyeW!Tnvq{mN>PKDeI-_jd*Nn0T%H;C>q1-xb^+eqRCV zizQplSGU4 zpA}B?2$vVrE4;RgUUwRv)$Up0A%8N5qwu-vg%(@ip=fBCln}bPun1$2JO-iCoODvlo<8C7+88UhsVz zmAq`EjS*9Mp@WWV$q&eD-NbYcuXx9Gl_o^q(4m}&d@gmh#6~$U8>_-TmW#bYM~OUi zz~MJV!n%nG@3zj62QKE<*$c`6jT^{onK9kND-LZ_u(_D-rsR<>CSM>A7xU}v1@W)} zuVuz`53hKqHn@;9FI~)`9Msuc=q4;Hrm)@GNQa5512$e1l!@};cWXmQB5&wW4*J(S zY16M|#)RP&hx#bkTugUU@<qPI zz^Ow#`Yyf$J1i?EyjvS#e#&l6ZM=j+w9J_9;T31)MVe`v4)>aY?}1Z?a?jb_c#KHYExW6m7hX_j_+)so1yMp`Op0%LvxxRIq5@doZ7H<%y{MwpJkEf{OJ!r&r8*W zafpl4j=OF5-IXKnW9-JR&*x5NzgSLIc$Qx6FL7EIiuJlZJnKv)D?GD*X7&Hn@OHbI z^H@gu%zDU3e~$FS#i>{Q>9nyBo>_h{eo-D=%&+p2*L_f=dkg+}ul#xBai7&by6%mV z!RF|WZll%hB)xHCe0GpfuB8se;_WPT^mA@(3gL0;aSx|vVn(x<5Qq0jfxLLDMBj2k zSe*Kulsl!g%0=4%`9p{JXOma%h>OWj$)7JT(!rmP3*yn+^({HN-4`3)+m}Sa_PQBt~Hjq}+(Jfa6!a|35^sBr3mGV={i!x&JYEAArfM0U8F_m&-x!CJL*~slD zpHcyy4$N1m(LMLwDHo*OO8~v1t}xE=k+IOl0l+;x(!$9j#|8Y+<(ROBuOgCMOnypU zarlU}a@TCTOG((p^a`)#V3~o7Q?KEAMfiQ?5es7)df-)G=GG+L`)p5tIpu|mQ*Y&@ zd$fh{U`v6xS$ZjHA$`Uphvr*MFENYZS>+3%#q_%kzitI?v7A|-L!9!?{F?m?y;6!( zerA1U@!4(o;Ug9`+u;6V z-NVKFDlhwS9~8f*Tg>Tr3$mAtyPe+2Msw2cj2okV>tr&9CiNH7{kS!7V(9#EWoxu~ zho;08yN&jGV=(NulJ;bnXdHG1XvubGs6n`*h(VIfg(2yB_he(E)3xkKi}2Qop&L#n z$IbOwdZdL%_+&624jP?7E2M#>U8;laCb7?CDWg%C9Pi+^nuF$gr`s8yErwC66t&R~ zL$GfdG`H6(LKo{A&lww?js5}=)YEoy2G#)0+EvIPI7qMzLsg0T=BMfF~RJYmNoHRG* zqVRBfG8y)gZex@TPjy-r3vQvLVSh4Cvh9wNJ0?kQmK}DXc64GZOYJD3F)<-Y?r@m3 zeV9LF)EQJaPCL0-Zp$4_*h6b&Z*+#q>1LONFW5&HLFhsk!AG9C8QPmew-ufg7&zHq ze?!t5Hzvs`oO>*qNjuVdx4*6$QEiwE&<{svN8@C!_OKmR`e!zVUN-=B>IS3MiDau8 zmKx@5_l6!H?(CzMjVzP}GSp)o_V^k)BGcJWE4bpjRb?PLIZ)PCvPFY^O!X3GLr4pARyZckVe@py%Zpyq98sQ5bKL3oLMmyUw))-4 zmPMeYFnY8-7OipjpKm~(krtX2MU9fyWJnWTXczMw;?jOJ1lD-Y!zDH5!e38m&q$3h&rG1lBhR2u9S{eBS{Nc8MPAPebsC#X1kKP!3sk>9Zirzm0ax^KEkQ=WLVirQ zliq+N(u61HlDw$RIKm1FIx!v(+6@|njaI)m=9A0qwg!{+Zl`7O!_%`;kQB4cHd|C2 zwdO1tj6L3r4U_g(rV@X>`-|e@W=on!+m>JOjI8gbch2b}-GDq|2dEwm_ zhNNAp{{DPYsKk0a3q?Zdwf)qhQUQ(OU~85Xc2I>>b)!C!_OzlCZfi5l&$B{MNqDCv zJWJKL`GsL6SUICnX7t)V{n`eMm0!c zK3neu3pQ+G>T4xfO;r!BNG?p~f|0n>qs7HSu}49<3tCVD@9t)45ybN^b`MqphBD8; zyre4$tz0 zVoJ=qe zB26ZH6-lyf)SzuglXky>V*+GT&k<)wRIdR$MB*85TMf|DCHVpVP%lUNegRjc2JD=N(@kR5qbdbpQfpX zCKh|{L!9gb=$RyGY+=78y#yUb$>#zZOw&Zs%fpw1z|%*L33fK3xs)r z^SRTM#krJH;)GS5u$mKAcfz8%Fs>PgUU9w>SVqy2R`l{Z(u$6> zq9d*7NGm$hijK6RBh8^wa-@|UX(dNm$t%y1R&u139BCy-TFH@Ca-=zC%Z{|NBdzR6 zD?8H4o()G@*^yRuq?H|MWk;H0qT)!aIMOPPw2C9G;z+A_b{%OIM_R>^R&k^`#Z?_? zRYzLYkydr2RUK(nM_Se6;z+AH(yER$Cu_}-R&%7)9BDO2TFsGGbEMTAX*G|ZBdz90 zbCT5^X>~_h-H}#zq}3g1bw^s=kydx4)jiIRwD};O=p#p3a->C$w8)Vb zInp9WS~RbN_W*0*&OjIUxj;9;#{C?E9Qcv!1=rqHlJ0(N>1Lg6IOA; zs!mwV39CC{(PCI}z7k|#;3RXT6&-0sM_SR5R&=Bl9ce{JTG5eKbflFWX%4-TBdz2} zD|vYxX(dNm$&praq?H_LB}ZD>k>*e-JJQOIw6Y_u?3L$8D?8H4j~`MleO+ht2@%_ja->D`oOQ*Y`v8Zo?sI|ZHefC+O<0@@ zOqch$Fxdsn1*I##6IOD<%1&6t39C9`H7Bg@ghg{PU+mX^|r> za->C$w8)VbInttee!2%JIY$9&USM%9P!{szPS|{ANjINY_61JXk`q>T!YWQ!)d{OP zVRa`gS_~^X(u$6>q9biSNOl2^w4x)e=twI%(u$6>q9d(1FO9c1i(*TT*pef*G_Q=? z(Ggbi!W^+BM{LOvTXMvf9I>T&v0U+GM_SpDR(7O0l*^8^vX{=0R(7P79cg7pTG^3S zaimooX%$DBL$BgUt2ojsUS3C9#gSHVq*WYgRYzLYkydr2IaI2Sw5lVm>PV}4#FpInrv5G{(ZWNQ)e4ks~c~q(zRj$dMM!bJ0CO+1mrm1*Xfr6Sn4r z6`iosT$pSD=7PjT*$JySVO1xr=7iOquxK%?c-#qdq!k@$MMs*K%#l`fq!k@$MMqlE zkydo1l^kg$M_S3@Tymt%2XU*F9BCy-TFH@Ca-@|UX{C8-ypvgMv$7+$?1(KpV$1W& zxQjZ%%3hcww(N*4J7UX@*z&wsuH=d%t>Q?lIMOPPG>3Ai#&5>4fq}3d0HLpBJ zTFsGGccj%FX>~_h-I3;)tvk}{jd#)t#_tF|2sp z33H?s9ce{JTG5f_Wp$(#9ce{JTG5eKbflFWX(dNm$>CRWq?H_LUNT2o$&praq?H_L zB}ZD>kydu3l^tnihjZDHHXpPT}aR~>0pFP$T;>PV|P(rS*h znj@{|NUJ&0YK}CAUd@qKbEMV0ypFV*BdzX8t2@%_jHjPFUFq ztIUPT7GN$&s8pS>niE!c!lK2n;&CU;kydo16&-0sM_SR5<`w5iD>~AOjj26 zInqiFmy#o`SR5IMU{Wc&t?%X%$CW#gSH-m&QAp#gSHZ#8w@#RYz>q5nFY{ zR_B%R#Nr66dSQ;(sw1{KFP1B>=18kK(rS*hnj@{|NUJ&09LhCETFp!6NUJ&0>W;L! zBdzX8t2@%_j;7CF))M_S}a^Qbt|B1c-}NQ>sn z)8)SAE%$SQ>2mLctvO*uC#>Xzm7TDP6IPuIlP$npkkG3+VRa`gS_~^5cfuTLMMqlE zkydo16&-0sN1A8Ckydo16&-0MM_S2|R&wl?9BCy-TFH@Ca-@01Inqjww2~vO>_{s+ z(#npsvcsk9NGm(i%8oQIt0S%KNGm(iDvq>@Bdy{{t2ojs4!?>ct>Q@Yk~z{UjPV|PoU4ws`5>M~s*bd(Bdt0wjdwDOlSs`GTXV$L9I-V=Y|Rl{ zbHvu>mGPYE2&;Kvj@a6~SgxqLBdzX8t2@%_j;7CF))N18{^krp}9qWQeK+}FM3el9Rw?wznTC#>j%m7K7$6IOA;s!mvK zE=;xnb3sD6?u12)Va4N4m?N#|NGm$hijK6RBdzF2D>~9VvyQZ)Bdz2}D>>3ij*XHd zt>j26Inqjww2~vuGvP=pInv6Gw6Y_u>_{s+cFT^mvLmhRNGm(iyy6^bWk*`Ykyde} zRUBy*M_R?4fq}3d0HAh;_kydl0)f~<>N7{T4&yY1oT5Vn$?_?I|$GRi7?ue~BV(X6Bx+Avk zh^;$f>+{NZIdFv4y|8()Tq%(wEpntqj;7CF))M_S}aiyUblWk*^xpH7$i$Xo8` z0@LN*30rf*icVO`2`f8c6(_9fgw>p|`dpZ70p@~4Mzk1KJnn=!(u$6>q9d*7NGm$h zijK6RBdzF2^Qbt|N{+OWBdz3=S8}A49BCy-TFH@Ca-@|UX`Wd}TG^3ScBGXZX=TSo z*^yRuq?H|MWk*`sk>;6jq*WYg6-Qddkyde}RUEq&M_R>^R&k_N9BE#0jTsz#(yETMsw2(I>PV|O(rS*hnj@{|NUJ&0YL2v;!>{H@t2xrVWRA4D zBdzX8t2@%_jWBH#|B1) zBlHs$S6z(ATblGm`U!=Ot|hc~tRUZzSiqNqQ$X=p0pIRP-iH*VY_(`;WiZp_O(PsXQ{q*oaAC&LysGkrbwX$~8Q@!GL3Fu!wt54Z zYqs04H9nD88RI7X6bOB-Kg-==G4%DZg-(zDs0(Z~=}U=^7RLQTe}LMiuYEmBG4utq z<3h7Aq#tjgS+qdkbl*BDCJOZR@dN5Q8}uE*$Z)hU>d?=-6t>ER(=;I$k~0Hn-r`E4 z3g~Q5IW&Kf$r0VP`dgq|IC8~R*X~I7jXl=8F&WcW&IX(dqZ9o}w@nofEmp-+wgNb%tfND!^O1Cm9n{kv%v!xJ*xixfm!|EYz2yW|C)MBPPFwhTESmVW2vq%P z>Sj_;XmoU(^oJg(WyTZ!HsQjlW_OYlnxn#KkhD4*9h%(N&vIJp#nktxFl6u3mlzK_ zG?7WJStC&S?VVD5!5JFft#M(!PrtT6@cuQflr3jl>f)s)y9MPSB4ij0!C(4T+Co8rVULzap)Uc0!ZL zkiYJIr+PH2)K_5_EjHuDJ@$n%wsi0iZ&Efo(b*XHj`Sv5N4uTT_}H;yGBI;FHwuCa z6kouvx}fh%#?D5MTKfp9^eBC~c+%iud~~DP9i{hD5DndSpK7ev9~1Xe=qH^KRexGc zqmghAOd1X5)1GW?os|KORG>u{G?TOpXc{Rp zmlivV3=EJiw|wft!y%2Aw0Nowty>bf)jUZft#_6xm>6s{JKeh>a5L#OsD_6}@WNAD zfyT^6=giT<5WikBN~p58y7UF%@|!MQ`kAS&gwUe8nH(*=cB@4{UPCkEW`Ed8Mz7n+ zthlWbK!U$s|IGMEdOZr8qlB03Q}l~PM_n_sa+cq5Rnr*epIp%89^cSD;h*KXo0Ku* z$h53Kc*FA;mxi%+t4)FWxZ&T1euj?LvUIdOcQU50av#&C{qC0VQkuYq z)THzyek0n&Vlw9mQ3m#Trm|V3as_fTHOHePH_IT9$&F`6ZhhPx$0%+cx%uifsnqk$ zmW~Ou`htmBS0F1RwnB->ip18BzuIhi$3|p4cfn zeI0!=!Zadfoak)QJTLnmuKO9SrFT{>JS?cNqc=&_2rg_4`&%j7fb49rr1f>=rn^~P z&m)cWK!-6|z~D_clKhKZ(8xPRh$EuxmZ}LNsT$FNWmB2%zhsk|jA@14D9Epc6?ltG zX0c0~?_BA6%g?Ck!rt%Ec97SEF!#2+1)6cx;MJJp&tiDc^2@ntYD#ETrY&Mvgb=uf zCdz;VE!7x=&+T@Dhj;K~N-1%!7fsgd_pY8j=ZDE=>(|Xl!@piNSI#WK^~)kbCVgf| z2}nj}dOiK9B7X31F-0Mr4sPY2Wn7F;qi^G19HXb6P2SVshbpasXyI;kC$!b#U#2@s z`yl?U(4(|=BHdNHT{_S`!e^JXTLkrws^wmE%WTr&F0`49=?5if#lrI$oz)CG>-3|K zx<^34+jGcFt$CKEq*2dLH7HwpCH0^VJ?!knjAlkB*}X~RqF?u+?G1?1EXW*Y9b>yF z+TNU`lt<}-GQH)IK)t(3bD2~Rq-x`D&yN|7>roGIc2Y0ey=kFlYj?AD(q~7Ra!6`W zY|x=Y#*}EQPrnq%Rwq5V=N>@6{zA`IO5SPx3D4u{LCj7n1TC@COli7sVC|^V0~>qE zOHaZya`qyMr;f0uag<16?}*Ey*tI zZ1vYzg8dvkMgI#{Bx)bTzH3()m;z^upCY9YNM(cGc(5ey&QWz8o> z+WGU|BRWegWn=PWxG{+a^VQZsU45bXMv%GLrk&~9$wDpU?c7c zBV`Lu7Pn^$wld3ZxqRNHH@jI4a~nNi9>LPUQ`c$w`P78o7~oS8>UPo>xW1(ZW&tlw z&dwg6vGPk4QN5yYJDr{M`5Bp>cvvYcILpxPI80(C2 z^40B}OuE> redmine_git.te + # Fall though and rebuild policy + else + exit 0 + fi + else + echo "No new avcs found" + exit 0 + fi + else + echo -e $USAGE + exit 1 + fi +elif [ $# -ge 2 ] ; then + echo -e $USAGE + exit 1 +fi + +echo "Building and Loading Policy" +set -x +make -f /usr/share/selinux/devel/Makefile +/usr/sbin/semodule -i redmine_git.pp + diff --git a/selinux/redmine_git.te b/selinux/redmine_git.te new file mode 100644 index 000000000..54b0a7aa1 --- /dev/null +++ b/selinux/redmine_git.te @@ -0,0 +1,69 @@ +policy_module(redmine_git,1.0.0) + +######################################## +# +# Declarations +# +require { + type httpd_t, httpd_sys_script_t, httpd_sys_script_exec_t; + type sudo_db_t; + type httpd_redmine_git_script_t; + class process { setrlimit setfscreate }; + class netlink_route_socket { write getattr read bind create nlmsg_read }; + class capability { setuid sys_resource setgid }; + class dir { getattr search write}; +} + +apache_content_template(redmine_git) + +permissive httpd_redmine_git_script_t; + +######################################## +# +# httpd_redmine_git_script local policy +# +######################################## + +manage_dirs_pattern(httpd_redmine_git_script_t, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t) +manage_files_pattern(httpd_redmine_git_script_t, httpd_redmine_git_script_rw_t, httpd_redmine_git_script_rw_t) + +domain_use_interactive_fds(httpd_redmine_git_script_t) + +files_read_etc_files(httpd_redmine_git_script_t) + +miscfiles_read_localization(httpd_redmine_git_script_t) + +# Allow our scripts to be called by redmine/apache +httpd_redmine_git_script_domtrans(httpd_sys_script_t) + +# Allow us to access to rest of redmine site +miscfiles_read_public_files(httpd_redmine_git_script_t) +miscfiles_manage_public_files(httpd_redmine_git_script_t) + +#============= httpd_redmine_git_script_t ============== +#Specific capabilities identified by audit2allow + +allow httpd_redmine_git_script_t self:capability audit_write; +allow httpd_redmine_git_script_t self:capability { setuid sys_resource setgid }; +allow httpd_redmine_git_script_t self:key write; +allow httpd_redmine_git_script_t self:netlink_audit_socket { write nlmsg_relay create read }; +allow httpd_redmine_git_script_t self:netlink_route_socket { write getattr read bind create nlmsg_read }; +allow httpd_redmine_git_script_t self:process setrlimit; +allow httpd_redmine_git_script_t sudo_db_t:dir { getattr search }; + +gitosis_read_lib_files(httpd_redmine_git_script_t) +gitosis_manage_lib_files(httpd_redmine_git_script_t) + +httpd_rw_stream_sockets(httpd_redmine_git_script_t) +kernel_read_kernel_sysctls(httpd_redmine_git_script_t) +logging_send_syslog_msg(httpd_redmine_git_script_t) + +# These seem to be needed for ssh.... Not sure why ssh needs +# to read and/or validate contexts... +allow httpd_redmine_git_script_t self:process setfscreate; +miscfiles_manage_cert_dirs(httpd_redmine_git_script_t) +miscfiles_manage_cert_files(httpd_redmine_git_script_t) +selinux_load_policy(httpd_redmine_git_script_t) +selinux_validate_context(httpd_redmine_git_script_t) +seutil_read_file_contexts(httpd_redmine_git_script_t) +seutil_search_default_contexts(httpd_redmine_git_script_t) diff --git a/tasks/selinux.rake b/tasks/selinux.rake new file mode 100644 index 000000000..92e1e4c7c --- /dev/null +++ b/tasks/selinux.rake @@ -0,0 +1,273 @@ +################################################################################ +# Rakefile for selinux installation for Redmine+Redmine_Git_Hosting Plugin # +# # +# This rakefile provides a variety of options for configuring the selinux # +# context for Redmine + Redmine_Git_Hosting Plugin. In addition to the usual # +# environment variables (such as RAIL_ENV), this rakefile has one additional # +# variable, ROOT_PATTERN. ROOT_PATTERN holds an optional regular expression # +# (not globbed filename) which describes the possible root locations for # +# redmine installations; note that such patterns must be quoted to avoid # +# attempts by the shell to expand them. If undefined, the rakefile will use # +# the Rails.root for the local installation. # +# # +# TOP-LEVEL TARGETS: # +# # +# These commands should be executed after altering the init.rb file as # +# described in the README.mkb file. Each target type comes in both "install" # +# and "remove" versions. In the following, the environment variables are # +# optional (of course). Default for ROOT_PATTERN is Rails.root # +# # +# 1) Build bin directory with customized scripts for redmine_git_hosting, # +# install new selinux policy, and install complete selinux context for # +# redmine+redmine_git_hosting plugin # +# # +# rake selinux:install RAILS_ENV=xxx ROOT_PATTERN="yyy" # +# rake selinux:remove RAILS_ENV=xxx ROOT_PATTERN="yyy" # +# # +# 2) Build bin directory with customized scripts for redmine_git_hosting, # +# install new selinux policy, and install selinux context for # +# redmine_git_hosting plugin (not for complete redmine installation). This # +# option assumes that the redmine installation (and plugin) code are # +# already labeled as "public_content_rw_t" except for dispatch.* files # +# which should be labeled as "httpd_sys_script_exec_t". # +# # +# rake selinux:redmine_git_hosting:install RAILS_ENV=xxx ROOT_PATTERN="yyy" # +# rake selinux:redmine_git_hosting:remove RAILS_ENV=xxx ROOT_PATTERN="yyy" # +# # +# 3) Build bin directory with customized scripts for redmine_git_hosting and # +# install new selinux policy. Do not install file contexts of any sort. # +# Proper labeling (done in some other way) should have all of redmine # +# (including plugins) labeled as "public_content_rw_t", with the exception # +# of public/dispatch.* (which should be labeled "httpd_sys_script_exec_t") # +# and vendor/plugins/redmine_git_hosting/bin(/.*) which should be labeled # +# with the new label "httpd_redmine_git_script_exec_t". # +# # +# rake selinux:redmine_git_hosting:install_scripts_and_policy RAILS_ENV=xxx ROOT_PATTERN="yyy" +# rake selinux:redmine_git_hosting:remove_scripts_and_policy RAILS_ENV=xxx ROOT_PATTERN="yyy" +# # +################################################################################ + +namespace :selinux do + desc "Configure selinux for Redmine and Redmine_Git_Hosting plugin" + task :install => [:environment,:install_contexts,"selinux:redmine_git_hosting:install"] do + end + + desc "Unconfigure selinux for Redmine and Redmine_Git_Hosting plugin" + task :remove => [:environment,"selinux:redmine_git_hosting:remove",:remove_contexts] do + end + + desc "Install selinux file contexts for redmine (without plugins)" + task :install_contexts => [:environment] do + roots = redmine_roots + root_pattern = redmine_root_pattern + puts "[Installing file contexts for redmine:" + + sh "semanage fcontext -a -t public_content_rw_t \"#{root_pattern}(/.*)?\"" + sh "semanage fcontext -a -t httpd_sys_script_exec_t \"#{root_pattern}/public/dispatch.*\"" + + roots.each do |path| + puts "Setting new context for redmine root instance at #{path}." + sh "restorecon -R -p #{path}" + end + puts "DONE.]" + end + + desc "Remove selinux file contexts for redmine (without plugins)" + task :remove_contexts => [:environment] do + roots = redmine_roots + root_pattern = redmine_root_pattern + puts "[Removing file contexts for redmine (ignoring errors):" + + sh "semanage fcontext -d \"#{root_pattern}(/.*)?\"" + sh "semanage fcontext -d \"#{root_pattern}/public/dispatch.*\"" + + roots.each do |path| + puts "Setting new context for redmine root instance at #{path}." + sh "restorecon -R -p #{path}" + end + puts "DONE.]" + end + + namespace :redmine_git_hosting do + desc "Install scripts, policy, and file context for redmine_git_hosting plugin." + task :install => [:environment,:install_scripts,:install_policy,:install_contexts] do + end + + desc "Remove scripts, policy, and file context for redmine_git_hosting plugin." + task :remove => [:environment,:remove_contexts,:remove_policy,:remove_scripts] do + end + + desc "Install scripts and policy for redmine_git_hosting plugin." + task :install_scripts_and_policy => [:environment,:install_scripts,:install_policy] do + end + + desc "Remove scripts and policy for redmine_git_hosting plugin." + task :remove_scripts_and_policy => [:environment,:remove_policy,:remove_scripts] do + end + + desc "Generate and install redmine_git_hosting shell scripts." + task :install_scripts => [:environment] do + puts "[Generating and installing redmine_git_hosting shell scripts:" + + plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting") + plugin_roots.each do |path| + if path != "#{Rails.root}/vendor/plugins/redmine_git_hosting" + # Have to call another rails environment. Keep default root in that environment + chdir File.expand_path("#{path}/../../..") do + print %x[rake selinux:redmine_git_hosting:install_scripts_helper] + end + else + Rake::Task["selinux:redmine_git_hosting:install_scripts_helper"].invoke + end + end + puts "DONE.]" + end + + desc "Helper function for generating and installing redmine_git_hosting shell scripts." + task :install_scripts_helper => [:environment] do + web_program = ENV['HTTPD'] || 'httpd' + web_user = ENV['WEB_USER'] || %x[ps aux | grep #{web_program} | sed "s/ .*$//" | sort -u | grep -v `whoami`].split("\n")[0] + GitHosting.web_user = web_user + + # Helper only executed in local environment + path = "#{Rails.root}/vendor/plugins/redmine_git_hosting" + print "Clearing out #{path}/bin directory..." + %x[rm -rf "#{path}/bin"] + puts "Success!" + print "Writing customized scripts to #{path}/bin directory..." + GitHosting.update_git_exec + puts "Success!" + end + + desc "Remove redmine_git_hosting shell scripts." + task :remove_scripts => [:environment] do + puts "[Deleting redmine_git_hosting shell scripts:" + plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting") + plugin_roots.each do |path| + sh "rm -rf #{path}/bin" + puts "Success!" + end + puts "DONE.]" + end + + desc "Install selinux tags and policy for redmine_git_hosting." + task :install_policy => [:environment] do + puts "[Installing selinux tags and policy for redmine_git_hosting:" + plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting") + sh "#{plugin_roots[0]}/selinux/redmine_git.sh" + puts "DONE.]" + end + + desc "Remove selinux tags and policy for redmine_git_hosting." + task :remove_policy => [:environment] do + puts "[Deleting selinux tags and policy for redmine_git_hosting." + sh "semodule -r redmine_git | true" + puts "DONE.]" + end + + desc "Install file contexts for redmine_git_hosting plugin." + task :install_contexts => [:environment] do + plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting") + plugin_root_pattern = redmine_root_pattern("vendor/plugins/redmine_git_hosting") + puts "[Installing file context for redmine_git_hosting plugin:" + sh "semanage fcontext -a -t httpd_redmine_git_script_exec_t \"#{plugin_root_pattern}/bin(/.*)?\" | true" + + plugin_roots.each do |path| + puts "Setting new context for plugin instance at #{path}." + sh "restorecon -R -p #{path}" + end + puts "DONE.]" + end + + desc "Remove file contexts for redmine_git_hosting plugin." + task :remove_contexts => [:environment] do + plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting") + plugin_root_pattern = redmine_root_pattern("vendor/plugins/redmine_git_hosting") + puts "[Deleting file context for redmine_git_hosting plugin (ignoring errors)." + sh "semanage fcontext -d \"#{plugin_root_pattern}/bin(/.*)?\" | true" + plugin_roots.each do |path| + puts "Setting new context for plugin instance at #{path}." + sh "restorecon -R -p #{path}" + end + puts "DONE.]" + end + end +end + +############################################################################# +# # +# Path support logic # +# # +############################################################################# +@@redmine_roots = {} +@@redmine_root_pattern = ENV['ROOT_PATTERN'] || Rails.root +@@find_maxdepth = 6 + +# Turn a regex file descriptor (file context) into a +# conservative (enclosing) globbed expression +# +# Grabbed this from /usr/sbin/fixfiles... +def regex_to_glob(in_regex) + # clobber anything after space char + my_regex = in_regex.gsub(/\s.*/,"") + my_regex = my_regex.gsub(%r|\(([/\w]+)\)\?|,"{\1,}") + my_regex = my_regex.gsub(%r|([/\w])\?|,"{\1,}") + my_regex = my_regex.gsub(%r|\?.*|,"*") + my_regex = my_regex.gsub(%r|\(.*|,"*") + my_regex = my_regex.gsub(%r|\[.*|,"*") + my_regex = my_regex.gsub(%r|\.\*.*|,"*") + my_regex = my_regex.gsub(%r|\.\+.*|,"*") + + return my_regex +end + +# Return a pattern for the root a redmine installation (as defined +# either by Rails.root or ROOT_PATTERN. +# +# Optional arguments are joined together with "/" as a path and +# appended to the end of the root pattern as described above +def redmine_root_pattern(*optionpath) + if optionpath.length > 0 + pathend = optionpath.join("/") + "#{@@redmine_root_pattern}/#{pathend}" + else + "#{@@redmine_root_pattern}" + end +end + +# Return an array of pointers to redmine directories as defined by +# the redmine_root_pattern (see above). +# +# When optional arguments are included, they are joined together with +# "/" and appended to the end of each redmine root path. +# +# Note that we use the @@redmine_roots hash to cache our results and +# thus avoid repeating work. +def redmine_roots(*optionpath) + if @@redmine_roots["/"].nil? + glob_pattern = regex_to_glob(redmine_root_pattern) + search_command = "find #{glob_pattern} -maxdepth #{@@find_maxdepth} -type d -regextype posix-extended -regex #{@@redmine_root_pattern} -prune" + if glob_pattern =~ /.*[\(\[\*\+\?].*/ + puts "Searching for directories matching \"#{@@redmine_root_pattern}\" (may take a bit):" + puts "#{search_command}" + end + new_roots=%x[#{search_command}].split("\n") + if new_roots.length == 0 + fail "Error: ROOT_PATTERN does not match any directories!" + end + @@redmine_roots["/"] = new_roots + end + if optionpath.length > 0 + pathend = optionpath.join("/") + if @@redmine_roots[pathend].nil? + subpaths = @@redmine_roots["/"].map{|myroot|"#{myroot}/#{pathend}"}.select{|dir|File.directory?(dir)} + if subpaths.length == 0 + fail "Error: ROOT_PATTERN/#{pathend} does not match any directories!" + end + @@redmine_roots[pathend] = subpaths + end + @@redmine_roots[pathend] + else + @@redmine_roots["/"] + end +end From 33aafdd2f9557d31346ddd6856c3b65353f00ab4 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Wed, 23 Nov 2011 16:46:00 -0800 Subject: [PATCH 002/107] Performance fix for redmine_git_hosting. This prevents a rediculous number of calls to update_repositories caused by the fact that redmine saves to the repository model for every commit that it examines. This patch also prevents multiple calls to update_repositories when adding groups to the membership of a project. You need to migrate plugins to take advantage of new indices: rake db:migrate_plugins --- app/models/git_hosting_observer.rb | 18 +++++-- ...0948_add_indexes_to_gitolite_public_key.rb | 11 +++++ init.rb | 8 ++++ .../patches/git_repository_patch.rb | 11 +++++ .../patches/members_controller_patch.rb | 48 +++++++++++++++++++ lib/git_hosting/patches/repository_patch.rb | 13 ++++- .../patches/sys_controller_patch.rb | 26 ++++++++++ 7 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb create mode 100644 lib/git_hosting/patches/members_controller_patch.rb create mode 100644 lib/git_hosting/patches/sys_controller_patch.rb diff --git a/app/models/git_hosting_observer.rb b/app/models/git_hosting_observer.rb index 8b8de5462..f1db69976 100644 --- a/app/models/git_hosting_observer.rb +++ b/app/models/git_hosting_observer.rb @@ -2,6 +2,7 @@ class GitHostingObserver < ActiveRecord::Observer observe :project, :user, :gitolite_public_key, :member, :role, :repository @@updating_active = true + @@updating_active_stack = 0 @@cached_project_updates = [] def reload_this_observer @@ -12,14 +13,25 @@ def reload_this_observer def self.set_update_active(is_active) - @@updating_active = is_active - if is_active + if !is_active + @@updating_active_stack += 1 + else + @@updating_active_stack -= 1 + if @@updating_active_stack < 0 + @@updating_active_stack = 0 + end + end + + if is_active && @@updating_active_stack == 0 if @@cached_project_updates.length > 0 @@cached_project_updates = @@cached_project_updates.flatten.uniq.compact GitHosting::update_repositories(@@cached_project_updates, false) + @@cached_project_updates = [] end + @@updating_active = true + else + @@updating_active = false end - @@cached_project_updates = [] end diff --git a/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb b/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb new file mode 100644 index 000000000..d5596275e --- /dev/null +++ b/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb @@ -0,0 +1,11 @@ +class AddIndexesToGitolitePublicKey < ActiveRecord::Migration + def self.up + add_index :gitolite_public_keys, :user_id + add_index :gitolite_public_keys, :identifier + end + + def self.down + remove_index :gitolite_public_keys, :user_id + remove_index :gitolite_public_keys, :identifier + end +end diff --git a/init.rb b/init.rb index 5567c40f2..f0337d979 100755 --- a/init.rb +++ b/init.rb @@ -66,6 +66,14 @@ require 'git_hosting/patches/git_repository_patch' Repository::Git.send(:include, GitHosting::Patches::GitRepositoryPatch) + require_dependency 'sys_controller' + require 'git_hosting/patches/sys_controller_patch' + SysController.send(:include, GitHosting::Patches::SysControllerPatch) + + require_dependency 'members_controller' + require 'git_hosting/patches/members_controller_patch' + MembersController.send(:include, GitHosting::Patches::MembersControllerPatch) + require_dependency 'git_hosting/patches/repository_cia_filters' end diff --git a/lib/git_hosting/patches/git_repository_patch.rb b/lib/git_hosting/patches/git_repository_patch.rb index fb9cf2ead..af081f1ff 100644 --- a/lib/git_hosting/patches/git_repository_patch.rb +++ b/lib/git_hosting/patches/git_repository_patch.rb @@ -9,6 +9,16 @@ def extra_report_last_commit_with_always_true true end + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + fetch_changesets_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end def self.included(base) base.class_eval do @@ -17,6 +27,7 @@ def self.included(base) begin base.send(:alias_method_chain, :report_last_commit, :always_true) base.send(:alias_method_chain, :extra_report_last_commit, :always_true) + base.send(:alias_method_chain, :fetch_changesets, :disable_update) rescue end diff --git a/lib/git_hosting/patches/members_controller_patch.rb b/lib/git_hosting/patches/members_controller_patch.rb new file mode 100644 index 000000000..af9d7d4cb --- /dev/null +++ b/lib/git_hosting/patches/members_controller_patch.rb @@ -0,0 +1,48 @@ +module GitHosting + module Patches + module MembersControllerPatch + def new_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + new_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def edit_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + edit_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + destroy_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + def self.included(base) + base.class_eval do + unloadable + end + begin + base.send(:alias_method_chain, :new, :disable_update) + base.send(:alias_method_chain, :edit, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end + end + end + end +end diff --git a/lib/git_hosting/patches/repository_patch.rb b/lib/git_hosting/patches/repository_patch.rb index 134915ca6..6c3695250 100644 --- a/lib/git_hosting/patches/repository_patch.rb +++ b/lib/git_hosting/patches/repository_patch.rb @@ -25,8 +25,17 @@ def factory_with_git_extra_init(klass_name, *args) end return new_repo end - end + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + fetch_changesets_without_disable_update + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + end def self.included(base) base.extend(ClassMethods) @@ -34,9 +43,9 @@ def self.included(base) unloadable class << self alias_method_chain :factory, :git_extra_init + alias_method_chain :fetch_changesets, :disable_update end end - end end end diff --git a/lib/git_hosting/patches/sys_controller_patch.rb b/lib/git_hosting/patches/sys_controller_patch.rb new file mode 100644 index 000000000..6b35c86e5 --- /dev/null +++ b/lib/git_hosting/patches/sys_controller_patch.rb @@ -0,0 +1,26 @@ +module GitHosting + module Patches + module SysControllerPatch + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + fetch_changesets_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + def self.included(base) + base.class_eval do + unloadable + end + begin + base.send(:alias_method_chain, :fetch_changesets, :disable_update) + rescue + end + end + end + end +end From 10bfd7dfeeb66c1681ec454a8e4c2c80914831a4 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sat, 26 Nov 2011 11:07:45 -0800 Subject: [PATCH 003/107] More performance improvements. This patch should remove all remaining need for recursive calls to update_repositories. Will log such calls as errors in upcoming major code fix... --- .../gitolite_public_keys_controller.rb | 4 ++ init.rb | 8 +++ .../patches/roles_controller_patch.rb | 36 +++++++++++ .../patches/users_controller_patch.rb | 59 +++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 lib/git_hosting/patches/roles_controller_patch.rb create mode 100644 lib/git_hosting/patches/users_controller_patch.rb diff --git a/app/controllers/gitolite_public_keys_controller.rb b/app/controllers/gitolite_public_keys_controller.rb index e11721d2b..1276f8471 100644 --- a/app/controllers/gitolite_public_keys_controller.rb +++ b/app/controllers/gitolite_public_keys_controller.rb @@ -10,9 +10,11 @@ def edit end def delete + GitHostingObserver.set_update_active(false) @gitolite_public_key[:active] = 0 @gitolite_public_key.save redirect_to url_for(:controller => 'my', :action => 'account') + GitHostingObserver.set_update_active(true) end def update @@ -25,6 +27,7 @@ def update end def create + GitHostingObserver.set_update_active(false) @gitolite_public_key = GitolitePublicKey.new(params[:public_key].merge(:user => @user)) if @gitolite_public_key.save flash[:notice] = l(:notice_public_key_added) @@ -32,6 +35,7 @@ def create @gitolite_public_key = GitolitePublicKey.new(:user => @user) end redirect_to url_for(:controller => 'my', :action => 'account') + GitHostingObserver.set_update_active(true) end protected diff --git a/init.rb b/init.rb index f0337d979..721cdbaca 100755 --- a/init.rb +++ b/init.rb @@ -74,6 +74,14 @@ require 'git_hosting/patches/members_controller_patch' MembersController.send(:include, GitHosting::Patches::MembersControllerPatch) + require_dependency 'users_controller' + require 'git_hosting/patches/users_controller_patch' + UsersController.send(:include, GitHosting::Patches::UsersControllerPatch) + + require_dependency 'roles_controller' + require 'git_hosting/patches/roles_controller_patch' + RolesController.send(:include, GitHosting::Patches::RolesControllerPatch) + require_dependency 'git_hosting/patches/repository_cia_filters' end diff --git a/lib/git_hosting/patches/roles_controller_patch.rb b/lib/git_hosting/patches/roles_controller_patch.rb new file mode 100644 index 000000000..33782df66 --- /dev/null +++ b/lib/git_hosting/patches/roles_controller_patch.rb @@ -0,0 +1,36 @@ +module GitHosting + module Patches + module RolesControllerPatch + def edit_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + edit_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + destroy_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def self.included(base) + base.class_eval do + unloadable + end + begin + base.send(:alias_method_chain, :edit, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end + end + end + end +end diff --git a/lib/git_hosting/patches/users_controller_patch.rb b/lib/git_hosting/patches/users_controller_patch.rb new file mode 100644 index 000000000..e5f31f6c4 --- /dev/null +++ b/lib/git_hosting/patches/users_controller_patch.rb @@ -0,0 +1,59 @@ +module GitHosting + module Patches + module UsersControllerPatch + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + create_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + update_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + destroy_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def edit_membership_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + edit_membership_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + def self.included(base) + base.class_eval do + unloadable + end + begin + base.send(:alias_method_chain, :create, :disable_update) + base.send(:alias_method_chain, :update, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + base.send(:alias_method_chain, :edit_membershipt, :disable_update) + rescue + end + end + end + end +end From db30d7eebf941804504d5d7b6797da12cf2ec070 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sat, 26 Nov 2011 22:20:00 -0800 Subject: [PATCH 004/107] Rewrite of primary gitolite interface for better resilience. The primary "update_repositories" function is now largely self-correcting against a variety of errors seen in the field. Also added a bunch of error checking on functions called-out to the shell. Among other things, this code attempts to reconnect the gitolite-admin public key when it gets deleted (can happen). Regular use of 'sys/fetch_changesets' will recorrect any errors in the public key directory and gitolite.conf file. For example, orphan keys that happen to be left behind in the public key directory will be removed, missing keys will be added, etc. One new piece of functionality is the 'recycle_bin' which is used to store deleted repositories immediately after they are deleted (allowing them to be recovered for up to 'gitRecycleExpireTime' hours after deletion). You need to migrate plugins because there are new plugin settings: rake db:migrate_plugins --- README.mkd | 72 ++- app/models/git_hosting_observer.rb | 21 +- app/models/git_hosting_settings_observer.rb | 94 ++- app/models/gitolite_public_key.rb | 10 + .../settings/_redmine_git_hosting.html.erb | 19 + config/locales/bg.yml | 5 +- config/locales/bs.yml | 5 +- config/locales/ca.yml | 5 +- config/locales/cs.yml | 5 +- config/locales/da.yml | 5 +- config/locales/de.yml | 5 +- config/locales/el.yml | 5 +- config/locales/en.yml | 5 +- config/locales/es.yml | 5 +- config/locales/fi.yml | 5 +- config/locales/fr.yml | 5 +- config/locales/gl.yml | 5 +- config/locales/he.yml | 5 +- config/locales/hu.yml | 5 +- config/locales/id.yml | 5 +- config/locales/it.yml | 5 +- config/locales/ja.yml | 5 +- config/locales/ko.yml | 5 +- config/locales/lt.yml | 5 +- config/locales/nl.yml | 5 +- config/locales/no.yml | 5 +- config/locales/pl.yml | 5 +- config/locales/pt-BR.yml | 3 + config/locales/pt.yml | 3 + config/locales/ro.yml | 5 +- config/locales/ru.yml | 5 +- config/locales/sk.yml | 5 +- config/locales/sl.yml | 5 +- config/locales/sr.yml | 5 +- config/locales/sv.yml | 5 +- config/locales/th.yml | 5 +- config/locales/tr.yml | 5 +- config/locales/uk.yml | 5 +- config/locales/vi.yml | 5 +- config/locales/zh-TW.yml | 5 +- config/locales/zh.yml | 5 +- .../20111123214911_add_settings_to_plugin.rb | 34 + init.rb | 10 +- lib/git_hosting.rb | 593 +++++++++++++----- .../patches/repositories_controller_patch.rb | 2 +- lib/git_hosting/patches/repository_patch.rb | 4 +- .../patches/sys_controller_patch.rb | 4 +- lib/gitolite_conf.rb | 50 +- lib/gitolite_recycle.rb | 112 ++++ tasks/gitolite.rake | 25 +- 50 files changed, 967 insertions(+), 259 deletions(-) create mode 100644 db/migrate/20111123214911_add_settings_to_plugin.rb create mode 100755 lib/gitolite_recycle.rb diff --git a/README.mkd b/README.mkd index dfae03514..d9fe779be 100755 --- a/README.mkd +++ b/README.mkd @@ -1,9 +1,9 @@ -# Redmine Git Hosting Plugin (v0.4.2) +# Redmine Git Hosting Plugin (v0.4.2x) A ChiliProject / Redmine plugin which makes configuring your own git hosting easy. This plugin allows straightforward management of gitolite and associated public keys, the git daemon, and integrates code from Scott Schacon's "grack" utility to provide Git Smart HTTP access. Git repositories are automatically created when the repository is created in -redmine. There is also an option to automatically create a git repository for a project, when the project is created. +Redmine. There is also an option to automatically create a git repository for a project, when the project is created. Caching functionality is also implemented to speed page-load times for viewing git repositories. @@ -41,6 +41,9 @@ Note that this guide refers to the "web server user" as the user under which Rai not always) the same as the user that runs the main web server. If you are running Rails under a different user, follow these instructions using that user, not the one for the main web server. +As of the most recent set of patches, this plugin is compatible with running multiple Redmine installations on the same server, each +with the same *or* different gitolite users/repositories. The later configuration (multiple Redmine installations, each with a different +gitolite installation) is particularly useful for a web-hosting scenario with independent developers. ## Step-By-Step configuration instructions @@ -101,12 +104,12 @@ variables are set correctly. To adjust these variables, open an editor and edit Starting on line 22 you will see the settings definitions you should edit. -The *httpServer* variable should be set to the url used to access your redmine site, e.g. www.my-own-personal-git-host-server.com. Note that if Redmine is not -installed in the site root this should include the path to your redmine root, e.g. www.my-own-personal-git-host-server.com/path/to/redmine +The *httpServer* variable should be set to the url used to access your Redmine site, e.g. www.my-own-personal-git-host-server.com. Note that if Redmine is not +installed in the site root this should include the path to your Redmine root, e.g. www.my-own-personal-git-host-server.com/path/to/redmine The *gitServer* will usually be the same as the the httpServer variable -- this is the server name to use to access the gitolite repositories via ssh. This should be -the hostname only, so this will be different from *httpServer* if Redmine is not installed in the site root. In other words, even if redmine is installed in +the hostname only, so this will be different from *httpServer* if Redmine is not installed in the site root. In other words, even if Redmine is installed in www.my-own-personal-git-host-server.com/path/to/redmine, *gitServer* should be set to www.my-own-personal-git-host-server.com @@ -146,12 +149,51 @@ you have both git and svn (or hg, or cvs etc.) repositories, this may cause prob *Delete Git Repository When Project Is Deleted* can be enabled to let this plugin control repository deletion as well as repository creation. By default, this feature is disabled and when a repository is deleted in ChiliProject / Redmine, it is not deleted in gitolite. This is a safety feature to prevent -the accidental loss of data. If this feature is enabled, the safety is turned off and the repository files will be permanently deleted when the Project/Repository is deleted in ChiliProject/Redmine. +the accidental loss of data. If this feature is enabled, the safety is turned off and the repository files will be deleted when the Project/Repository is +deleted in ChiliProject/Redmine. Note, however, that even when this feature is enabled, deleted repositories are placed into a "recycle_bin" for a configurable +amount of time (defaulting to 24 hours) and can be recovered by recreating the project in Redmine with the same Identifier. Details are placed in the log. + +The *gitRecycleBasePath* is the path *relative to the git user root* where deleted repositories are placed. This path should end in a path separator, e.g. '/'. +Deleted repositories are kept here for up to *gitRecycleExpireTime* hours (configurable, defaults to 24.0 hours). + +The *gitLockWaitTime* represents the amount of time that the plugin will wait in attempting to acquire its internal synchronization lock before giving up. +You probably will not need to change this value. *Show Checkout URLs* can be disabled to hide the git URL bar in the repository tab. It is enabled by default. See below in the "Caching" section of this readme for more information on caching and how the caching variables should be configured. +## Resychronization of gitolite configuration + +Whenever a Redmine `fetch_changesets()` operation is executed (i.e. `http://REDMINE_ROOT/sys/fetch_changesets?key=xxx`), this plugin will check the +gitolite keydir and configuration file for consistency. It will correct any errors that it finds. Further, regular execution of a fetch_changesets operation +will make sure that repositories placed in the recycle_bin (during delete operations) will be expired and removed. Since there still seem to be some +phantom synchronization problems, it is recommended that you execute `fetch_changesets()` regularly (every 15 to 30 minutes). + +Two rake tasks can additionally be used for resynchronization (although these are redundant with executing `fetch_changesets()` through other means). + +**(1)** To fixup the gitolite configuration file, fix errors, and delete expired files in the recycle_bin, execute: + + RAILS_ENV=production rake gitolite:update_repositories + +**(2)** To perform all the above operations while at the same time fetching changesets for all repositories, execute: + + RAILS_ENV=production rake gitolite:fetch_changes + +**Note that it is very important that these commands be run as *www-user* (or whatever your web server user happens to be), lest you get permission problems later.** +(The same is true of any `fetch_changesets()` operation initiated without using the web server, i.e. through the command line or from the cron daemon). + +## Interaction with non-Redmine gitolite users + +This plugin respects gitolite repositories that are managed outside of Redmine or managed by both Redmine and non-Redmine users: + +* When performing a *fetch_changesets()* operation, this plugin will delete and reestablish all keys that are of the form "redmine_", + since it considers these to be under its exclusive control. A special token, called "redmine_dummy_key", is used as a placeholder when no access + is granted for a given repository. +* Keys other than "redmine_" are left untouched and can be in projects by themselves or mixed in with projects managed by redmine. +* When a Redmine-managed project is deleted (with the *Delete Git Repository When Project Is Deleted* option enabled), its corresponding git repository + *will not be deleted/recycled* if there are non-Redmine keys in the gitolite.conf file. + ## A Note About PATH variables One major source of issues with this plugin is that Rails needs to be able to run both *sudo* and *git*. Specifically, these programs need to be in one of the directories specified by @@ -246,19 +288,19 @@ This library allows you to quickly deploy ChiliProject, with this plugin to an u chili\_test.sh script, modifying the variables in those scripts as desired. This library is still under development, so these instructions may need to be updated in the near future. -## Selinux Configuration for redmine +## Selinux Configuration for Redmine This plugin can be configured to run with selinux. We have included a rakefile in tasks/selinux.rake to assist with installing with selinux. You should start by editing init.rb and migrating as described above. Then, you -can execute one of the selinux rake tasks (from the redmine root). For instance, the simplest option installs -a selinux configuration for both redmine and the redmine_git_hosting plugin: +can execute one of the selinux rake tasks (from the Redmine root). For instance, the simplest option installs +a selinux configuration for both Redmine and the redmine_git_hosting plugin: rake selinux:install RAILS_ENV=production This will generate the redmine_git_hosting binaries in ./bin, install a selinux policy for these binaries (called -redmine_git.pp), then install a complete context for redmine as follows: +redmine_git.pp), then install a complete context for Redmine as follows: -**(1)** Most of redmine will be marked with "public_content_rw_t". +**(1)** Most of Redmine will be marked with "public_content_rw_t". **(2)** The dispatch files in Rails.root/public/dispatch.* will be marked with "httpd_sys_script_exec_t" @@ -266,7 +308,7 @@ redmine_git.pp), then install a complete context for redmine as follows: with "httpd_redmine_git_script_exec_t", which has been crafted to allow the sudo behavior required by these binaries. -Note that this rake file has additional options. For instance, you can specify redmine roots with regular +Note that this rake file has additional options. For instance, you can specify Redmine roots with regular expressions (not globbed expessions!) as follows (notice the use of double quotes): rake selinux:install RAILS_ENV=production ROOT_PATTERN="/source/.*/redmine" @@ -288,7 +330,8 @@ with apache and fcgi. Other configurations may require slight tweaking. This plugin has been primarily tested on Ubuntu Server 10.10 and 11.04 (32 and 64 bit) with ChiliProject v1.x, ChiliProject 2.0.0 and Redmine 1.2.1 with PostgreSQL as the database (July, 2011). It is possible that some -debugging will be necessary for other configurations. +debugging will be necessary for other configurations. Selinux configurations were tested under Redhat Enterprise Linux +version 6.x with apache and fcgi. ## Required gems @@ -308,7 +351,8 @@ This plugin is based largely on the Gitosis plugin by Jan Schulz-Hofen for http: were provided by github users untoldwind, tingar and ericpaulbishop. These updates were merged together and expanded upon by Eric Bishop to create this more comprehensive Git Hosting plugin. +Copyright (c) 2011 John Kubiatowicz (kubitron@cs.berkeley.edu) MIT License. + Copyright (c) 2010-2011 Eric Bishop (ericpaulbishop@gmail.com) MIT License. Copyright (c) 2009-2010 Jan Schulz-Hofen, ROCKET RENTALS GmbH (http://www.rocket-rentals.de). MIT License. - diff --git a/app/models/git_hosting_observer.rb b/app/models/git_hosting_observer.rb index f1db69976..9599a0b7c 100644 --- a/app/models/git_hosting_observer.rb +++ b/app/models/git_hosting_observer.rb @@ -3,6 +3,7 @@ class GitHostingObserver < ActiveRecord::Observer @@updating_active = true @@updating_active_stack = 0 + @@updating_active_flags = {} @@cached_project_updates = [] def reload_this_observer @@ -13,6 +14,12 @@ def reload_this_observer def self.set_update_active(is_active) + case is_active + when Symbol then @@updating_active_flags[is_active] = true + when Hash then @updating_active_flags.merge(is_active) + when Project then @@cached_project_updates << is_active + end + if !is_active @@updating_active_stack += 1 else @@ -23,10 +30,11 @@ def self.set_update_active(is_active) end if is_active && @@updating_active_stack == 0 - if @@cached_project_updates.length > 0 + if @@cached_project_updates.length > 0 || !@@updating_active_flags.empty? @@cached_project_updates = @@cached_project_updates.flatten.uniq.compact - GitHosting::update_repositories(@@cached_project_updates, false) + GitHosting::update_repositories(@@cached_project_updates, @@updating_active_flags) @@cached_project_updates = [] + @@updating_active_flags = {} end @@updating_active = true else @@ -34,11 +42,10 @@ def self.set_update_active(is_active) end end - def before_destroy(object) if object.is_a?(Repository::Git) if Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true" - GitHosting::update_repositories(object.project, true) + GitHosting::delete_repository(object.project) %x[#{GitHosting::git_user_runner} 'rm -rf #{object.url}' ] end GitHosting::clear_cache_for_project(object.project) @@ -63,7 +70,7 @@ def before_save(object) def after_save(object) update_repositories(object) end - + def after_destroy(object) if !object.is_a?(Repository::Git) @@ -74,9 +81,7 @@ def after_destroy(object) protected - def update_repositories(object) - projects = [] case object when Repository::Git then projects.push(object.project) @@ -87,7 +92,7 @@ def update_repositories(object) end if(projects.length > 0) if (@@updating_active) - GitHosting::update_repositories(projects, false) + GitHosting::update_repositories(projects) else @@cached_project_updates.concat(projects) end diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb index fcd16e9ae..884779cb6 100644 --- a/app/models/git_hosting_settings_observer.rb +++ b/app/models/git_hosting_settings_observer.rb @@ -1,14 +1,7 @@ class GitHostingSettingsObserver < ActiveRecord::Observer observe :setting - @@old_hook_debug = Setting.plugin_redmine_git_hosting['gitHooksDebug'] - @@old_hook_asynch = Setting.plugin_redmine_git_hosting['gitHooksAreAsynchronous'] - @@old_http_server = Setting.plugin_redmine_git_hosting['httpServer'] - @@old_git_user = Setting.plugin_redmine_git_hosting['gitUser'] - @@old_gitolite_identity = Setting.plugin_redmine_git_hosting['gitoliteIdentityFile'] - @@old_gitolite_publickey = Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile'] - @@old_repo_base = Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] - + @@old_valuehash = (Setting.plugin_redmine_git_hosting).clone def reload_this_observer observed_classes.each do |klass| @@ -16,34 +9,83 @@ def reload_this_observer end end + # There is a long-running bug in ActiveRecord::Observer that prevents us from + # returning from before_save() with false to signal verification failure. + # + # Thus, we can only silently refuse to perform bad changes and/or perform + # slight corrections to badly formatted values. def before_save(object) - if object.name == "plugin_redmine_git_hosting" && !GitHosting.bin_dir_writeable? - # If bin directory not alterable, don't alow changes to - # Git Username, or Gitolite public or private keys + # Only validate settings for our plugin + if object.name == "plugin_redmine_git_hosting" valuehash = object.value - valuehash['gitUser'] = @@old_git_user - valuehash['gitoliteIdentityFile'] = @@old_gitolite_identity - valuehash['gitoliteIdentityPublicKeyFile'] = @@old_gitolite_publickey - object.value = valuehash + if !GitHosting.bin_dir_writeable? + # If bin directory not alterable, don't allow changes to + # Git Username, or Gitolite public or private keys + valuehash['gitUser'] = @@old_valuehash['gitUser'] + valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile'] + valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile'] + end + + # Normalize Repository path, should be relative and end in '/' + if valuehash['gitRepositoryBasePath'] + normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRepositoryBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRepositoryBasePath'] = @@old_valuehash['gitRepositoryBasePath'] + end + end + + # Normalize Recycle bin path, should be relative and end in '/' + if valuehash['gitRecycleBasePath'] + normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRecycleBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRecycleBasePath'] = @@old_valuehash['gitRecycleBasePath'] + end + end + + # Exclude bad expire times (and exclude non-numbers) + if valuehash['gitRecycleExpireTime'] + if valuehash['gitRecycleExpireTime'].to_f > 0 + valuehash['gitRecycleExpireTime'] = "#{(valuehash['gitRecycleExpireTime'].to_f * 10).to_i / 10.0}" + else + valuehash['gitRecycleExpireTime'] = @@old_valuehash['gitRecycleExpireTime'] + end + end + + # Validate wait time > 0 (and exclude non-numbers) + if valuehash['gitLockWaitTime'] + if valuehash['gitLockWaitTime'].to_i > 0 + valuehash['gitLockWaitTime'] = "#{valuehash['gitLockWaitTime'].to_i}" + else + valuehash['gitLockWaitTime'] = @@old_valuehash['gitLockWaitTime'] + end + end + # Save back results + object.value = valuehash end end def after_save(object) + # Only perform after-actions on settings for our plugin if object.name == "plugin_redmine_git_hosting" + valuehash = object.value if GitHosting.bin_dir_writeable? %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] end - if @@old_repo_base != object.value['gitRepositoryBasePath'] + if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] GitHostingObserver.set_update_active(false) all_projects = Project.find(:all) all_projects.each do |p| if p.repository.is_a?(Repository::Git) r = p.repository repo_name= p.parent ? File.join(GitHosting::get_full_parent_path(p, true),p.identifier) : p.identifier - r.url = File.join(object.value['gitRepositoryBasePath'], "#{repo_name}.git") + r.url = File.join(valuehash['gitRepositoryBasePath'], "#{repo_name}.git") r.root_url = r.url r.save end @@ -51,24 +93,20 @@ def after_save(object) GitHostingObserver.set_update_active(true) end - if @@old_git_user != object.value['gitUser'] + if @@old_valuehash['gitUser'] != valuehash['gitUser'] GitHosting.setup_hooks - GitHosting.update_repositories( Project.find(:all), false) + GitHosting.update_repositories(:resync_all=>true) - elsif @@old_http_server != object.value['httpServer'] || @@old_hook_debug != object.value['gitHooksDebug'] || @@old_repo_base != object.value['gitRepositoryBasePath'] || @@old_hook_asynch != object.value['gitHooksAreAsynchronous'] + elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] || + @@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] || + @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] || + @@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous'] GitHosting.update_global_hook_params end - @@old_hook_debug = object.value['gitHooksDebug'] - @@old_hook_asynch = object.value['gitHooksAreAsynchronous'] - @@old_http_server = object.value['httpServer'] - @@old_git_user = object.value['gitUser'] - @@old_gitolite_identity = object.value['gitoliteIdentityFile'] - @@old_gitolite_publickey = object.value['gitoliteIdentityPublicKeyFile'] - @@old_repo_base = object.value['gitRepositoryBasePath'] + @@old_valuehash = (Setting.plugin_redmine_git_hosting).clone end end - end diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 1c36e36e1..809e4c5f2 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -32,4 +32,14 @@ def set_identifier end def to_s ; title ; end + + @@myregular = /^redmine_(.*)_\d*_\d*(.pub)?$/ + def self.ident_to_user_token(identifier) + result = @@myregular.match(identifier) + (result!=nil) ? result[1] : nil + end + + def self.user_to_user_token(user) + user.login.underscore.gsub(/[^0-9a-zA-Z\-]/,'_') + end end diff --git a/app/views/settings/_redmine_git_hosting.html.erb b/app/views/settings/_redmine_git_hosting.html.erb index 9e6527c84..dddb72792 100644 --- a/app/views/settings/_redmine_git_hosting.html.erb +++ b/app/views/settings/_redmine_git_hosting.html.erb @@ -48,6 +48,25 @@

+

+ + <%= text_field_tag("settings[gitRecycleBasePath]", @settings['gitRecycleBasePath'], :size => 60) %> +
+

+ +

+ + <%= text_field_tag("settings[gitRecycleExpireTime]", @settings['gitRecycleExpireTime'], :size => 10) %> +
+

+ +

+ + <%= text_field_tag("settings[gitLockWaitTime]", @settings['gitLockWaitTime'], :size => 10) %> +
+

+ +

<%= l(:label_git_cache_parameters)%>

diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/da.yml b/config/locales/da.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/de.yml b/config/locales/de.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/el.yml b/config/locales/el.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/en.yml b/config/locales/en.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/es.yml b/config/locales/es.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/he.yml b/config/locales/he.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/id.yml b/config/locales/id.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/it.yml b/config/locales/it.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/no.yml b/config/locales/no.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index e9989cd0e..96dc0a6df 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -7,6 +7,9 @@ pt-BR: label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' + label_git_recycle_base_path: 'Directório Recycle Bin base (Relativo à "home" do utilizador git)' + label_git_recycle_expire_time: 'expirar o tempo para Recycle Bin (em horas)' + label_git_lock_wait_time: 'Sincronização de bloqueio o tempo de espera (em segundos)' label_cannot_change_selinux: 'não pode ser mudada (selinux)' field_git_daemon: Git Daemon diff --git a/config/locales/pt.yml b/config/locales/pt.yml index f6db9e33d..cf033316f 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -7,6 +7,9 @@ pt: label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' + label_git_recycle_base_path: 'Directório Recycle Bin base (Relativo à "home" do utilizador git)' + label_git_recycle_expire_time: 'expirar o tempo para Recycle Bin (em horas)' + label_git_lock_wait_time: 'Sincronização de bloqueio o tempo de espera (em segundos)' label_cannot_change_selinux: 'não pode ser mudada (selinux)' field_git_daemon: Git Daemon diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/th.yml b/config/locales/th.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 723401ce1..b8c50fb92 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -6,7 +6,10 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) - label_git_repository_base_path: Git Repository Base Path (Relative to git user home) + label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) + label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) + label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) + label_git_lock_wait_time: Synchronization Lock Wait Time
(in seconds) label_cannot_change_selinux: Cannot be changed under selinux field_git_daemon: Git Daemon diff --git a/db/migrate/20111123214911_add_settings_to_plugin.rb b/db/migrate/20111123214911_add_settings_to_plugin.rb new file mode 100644 index 000000000..dc801779c --- /dev/null +++ b/db/migrate/20111123214911_add_settings_to_plugin.rb @@ -0,0 +1,34 @@ +class AddSettingsToPlugin < ActiveRecord::Migration + def self.up + begin + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitRecycleBasePath'] ||= 'recycle_bin/' + valuehash['gitRecycleExpireTime'] ||= '24.0' + valuehash['gitLockWaitTime'] ||= '10' + if (Setting.plugin_redmine_git_hosting != valuehash) + Setting.plugin_redmine_git_hosting = valuehash + say "Added redmine_git_hosting settings: 'gitRecycleBasePath', 'getRecycleExpireTime', 'getLockWaitTime'" + end + rescue + # ignore problems if plugin settings don't exist yet + end + end + + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitRecycleBasePath') + valuehash.delete('gitRecycleExpireTime') + valuehash.delete('gitLockWaitTime') + if (Setting.plugin_redmine_git_hosting != valuehash) + Setting.plugin_redmine_git_hosting = valuehash + say "Removed redmine_git_hosting settings: 'gitRecycleBasePath', 'getRecycleExpireTime', 'getLockWaitTime'" + end + Setting.plugin_redmine_git_hosting = valuehash + rescue + # ignore problems if table doesn't exist yet.... + end + end +end diff --git a/init.rb b/init.rb index 721cdbaca..485b3dc77 100755 --- a/init.rb +++ b/init.rb @@ -12,11 +12,15 @@ description 'Enables Redmine / ChiliProject to control hosting of git repositories' version '0.4.2' url 'https://github.com/ericpaulbishop/redmine_git_hosting' + settings :default => { - 'httpServer' => 'localhost', - 'gitServer' => 'localhost', - 'gitUser' => 'git', + 'httpServer' => "tessellation.cs.berkeley.edu/redmine", + 'gitServer' => 'tessellation.cs.berkeley.edu', + 'gitUser' => 'git-tess', 'gitRepositoryBasePath' => 'repositories/', + 'gitRecycleBasePath' => 'recycle_bin/', + 'gitRecycleExpireTime' => '24.0', + 'gitLockWaitTime' => '10', 'gitoliteIdentityFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa', 'gitoliteIdentityPublicKeyFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa.pub', 'allProjectsUseGit' => 'false', diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index 07aa7e0e3..829faa448 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -2,17 +2,34 @@ require 'net/ssh' require 'tempfile' require 'tmpdir' +require 'stringio' require 'gitolite_conf.rb' +require 'gitolite_recycle.rb' require 'git_adapter_hooks.rb' module GitHosting + LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) + REPOSITORY_IF_UNDEF = "repositories/" # In case settings not migrated (normally from settings) + # Used to register errors when pulling and pushing the conf file + class GitHostingException < StandardError + end + + # Time in seconds to wait before giving up on acquiring the lock + def self.lock_wait_time + Setting.plugin_redmine_git_hosting['gitLockWaitTime'].to_i || LOCK_WAIT_IF_UNDEF + end + + # Repository base path (relative to git user home directory) + def self.repository_base + Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] || REPOSITORY_IF_UNDEF + end + + @@logger = nil def self.logger - # it may be useful to redefine this for some debugging purposes - # but by default, we're just going to use the default Rails logger - return Rails.logger + @@logger ||= MyLogger.new end @@web_user = nil @@ -31,7 +48,6 @@ def self.git_user Setting.plugin_redmine_git_hosting['gitUser'] end - @@mirror_pubkey = nil def self.mirror_push_public_key if @@mirror_pubkey.nil? @@ -124,7 +140,7 @@ def self.repository_name project end def self.repository_path project - return File.join(Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'], repository_name(project)) + ".git" + return File.join(repository_base, repository_name(project)) + ".git" end def self.add_route_for_project(p) @@ -162,7 +178,7 @@ def self.get_bin_dir @@git_hosting_bin_dir ||= Rails.root.join("vendor/plugins/redmine_git_hosting/bin") if !File.directory?(@@git_hosting_bin_dir) - logger.error "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" + logger.info "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" %x[mkdir -p "#{@@git_hosting_bin_dir}"] %x[chmod 750 "#{@@git_hosting_bin_dir}"] %x[chown #{web_user} "#{@@git_hosting_bin_dir}"] @@ -195,14 +211,14 @@ def self.bin_dir_writeable?(*option) end def self.git_exec_path - return File.join(get_bin_dir(), "run_git_as_git_user") + return File.join(get_bin_dir, "run_git_as_git_user") end def self.gitolite_ssh_path - return File.join(get_bin_dir(), "gitolite_admin_ssh") + return File.join(get_bin_dir, "gitolite_admin_ssh") end def self.git_user_runner_path - return File.join(get_bin_dir(), "run_as_git_user") + return File.join(get_bin_dir, "run_as_git_user") end @@ -227,7 +243,7 @@ def self.git_user_runner def self.update_git_exec - logger.info "Setting up #{get_bin_dir()}" + logger.info "Setting up #{get_bin_dir}" gitolite_key=Setting.plugin_redmine_git_hosting['gitoliteIdentityFile'] File.open(gitolite_ssh_path(), "w") do |f| @@ -290,16 +306,14 @@ def self.update_git_exec File.chmod(0550, git_exec_path()) File.chmod(0550, gitolite_ssh_path()) File.chmod(0550, git_user_runner_path()) - %x[chown #{web_user} -R "#{@@git_hosting_bin_dir}"] + %x[chown #{web_user} -R "#{get_bin_dir}"] end - @@lock_file = nil def self.lock(retries) is_locked = false - local_dir = get_tmp_dir() if @@lock_file.nil? - @@lock_file=File.new(File.join(local_dir,'redmine_git_hosting_lock'),File::CREAT|File::RDONLY) + @@lock_file=File.new(File.join(get_tmp_dir,'redmine_git_hosting_lock'),File::CREAT|File::RDONLY) end while retries > 0 @@ -318,195 +332,451 @@ def self.unlock end end + def self.shell(command) + begin + my_command = "#{command} 2>&1" + result = %x[#{my_command}].chomp + code = $?.exitstatus + rescue Exception => e + result=e.message + code = -1 + end + if code != 0 + logger.error "Command failed (return #{code}): #{command}" + logger.error "#{result}" + raise GitHostingException, "Shell Error" + end + end + # Try to get a cloned version of gitolite-admin repository. + # + # This code tries to recover from a variety of errors which have been observed + # in the field, including a loss of the admin key and an empty top-level directory + # + # Return: false => have uncommitted changes + # true => directory on master + # + # This routine must only be called after acquisition of the lock + # + # John Kubiatowicz, 11/15/11 def self.clone_or_pull_gitolite_admin # clone/pull from admin repo - local_dir = get_tmp_dir() - if File.exists? "#{local_dir}/gitolite-admin" - logger.info "Fetching changes for #{local_dir}/gitolite-admin" - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' fetch] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' merge FETCH_HEAD] - else - logger.info "Cloning gitolite-admin repository" - %x[env GIT_SSH=#{gitolite_ssh()} git clone #{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}:gitolite-admin.git #{local_dir}/gitolite-admin] - end - %x[chmod 700 "#{local_dir}/gitolite-admin" ] - # Make sure we have our hooks setup - GitAdapterHooks.check_hooks_installed + repo_dir = File.join(get_tmp_dir,"gitolite-admin") + begin + if (File.exists? "#{repo_dir}") && (File.exists? "#{repo_dir}/.git") && (File.exists? "#{repo_dir}/keydir") && (File.exists? "#{repo_dir}/conf") + logger.info "Fetching changes from gitolite-admin repository to #{repo_dir}" + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' fetch] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' merge FETCH_HEAD] + + # unmerged changes=> non-empty return + return_val = %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' status --short].empty? + else + logger.info "Cloning gitolite-admin repository to #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] + shell %[env GIT_SSH=#{gitolite_ssh()} git clone #{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}:gitolite-admin.git #{repo_dir}] + return_val = true # on master, since fresh clone + end + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup + GitAdapterHooks.check_hooks_installed + + return return_val + rescue + # Hm.... perhaps we have some other sort of failure... + logger.error "Failure to access gitolite-admin repository. Attempting to fix..." + begin + logger.info " Reestablishing gitolite key" + shell %[cat #{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']} | #{GitHosting.git_user_runner} 'cat > ~/id_rsa.pub'] + shell %[#{GitHosting.git_user_runner} 'gl-setup ~/id_rsa.pub'] + shell %[#{GitHosting.git_user_runner} 'rm ~/id_rsa.pub'] + + logger.info " Deleting and recloning gitolite-admin to #{repo_dir}" + shell %[rm -r #{repo_dir}] unless !File.exists?(repo_dir) + shell %[env GIT_SSH=#{gitolite_ssh()} git clone #{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}:gitolite-admin.git #{repo_dir}] + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup + GitAdapterHooks.check_hooks_installed + logger.info "Successfully restablished access to gitolite-admin repository!" + rescue + logger.error "Failure again. Probably requires human intervention" + raise GitHostingException, "Gitolite-admine Clone Failure" + end + end end - def self.move_repository(old_name, new_name) - old_path = File.join(Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'], "#{old_name}.git") - new_path = File.join(Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'], "#{new_name}.git") + # Commit Changes to the gitolite-admin repository. This assumes that repository exists + # (i.e. that a clone_or_fetch_gitolite_admin has already be called). + # + # This routine must only be called after acquisition of the lock + # + # John Kubiatowicz, 11/15/11 + def self.commit_gitolite_admin(*args) + resyncing = args && args.first # create tmp dir, return cleanly if, for some reason, we don't have proper permissions - local_dir = get_tmp_dir() + repo_dir = File.join(get_tmp_dir,"gitolite-admin") + + # commit / push changes to gitolite admin repo + begin + if (!resyncing) + logger.info "Committing changes to gitolite-admin repository" + message = "Updated by Redmine" + else + logger.info "Committing corrections to gitolite-admin repository" + message = "Updated by Redmine: Corrections discovered during RESYNC_ALL" + end + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/gitolite.conf] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -a -m '#{message}'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' push ] + rescue + logger.error "Problems committing changes to gitolite-admin repository!! Probably requires human intervention" + raise GitHostingException, "Gitlite-admin Commit Failure" + end + end + def self.move_repository(old_name, new_name) #lock - if !lock(5) + if !lock(lock_wait_time) + logger.error "git_hosting: move_repository() exited without acquiring lock!" return end - - # Make sure we have gitoite-admin cloned - clone_or_pull_gitolite_admin - - # rename in conf file - conf = GitoliteConfig.new(File.join(local_dir, 'gitolite-admin', 'conf', 'gitolite.conf')) - conf.rename_repo( old_name, new_name ) - conf.save + begin + # Make sure we have gitoite-admin cloned + clone_or_pull_gitolite_admin - # physicaly move the repo BEFORE committing/pushing conf changes to gitolite admin repo - %x[#{git_user_runner} 'mkdir -p "#{new_path}"'] - %x[#{git_user_runner} 'rmdir "#{new_path}"'] - %x[#{git_user_runner} 'mv "#{old_path}" "#{new_path}"'] + old_path = File.join(repository_base, "#{old_name}.git") + new_path = File.join(repository_base, "#{new_name}.git") + + logger.warn "Adjusting position of repository from '#{old_name}' to '#{new_name}' in gitolite.conf" + + # rename in conf file + conf = GitoliteConfig.new(File.join(get_tmp_dir, 'gitolite-admin', 'conf', 'gitolite.conf')) + conf.rename_repo( old_name, new_name ) + conf.save + + logger.warn " Moving repository from '#{old_path}' to '#{new_path}' in gitolite repository" + + # physicaly move the repo BEFORE committing/pushing conf changes to gitolite admin repo + prefix = new_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' + if prefix + # Has subdirectory. Must construct destination directory + repo_prefix = File.join(repository_base, prefix) + GitHosting.shell %[#{git_user_runner} mkdir -p '#{repo_prefix}'] + end + shell %[#{git_user_runner} 'mv "#{old_path}" "#{new_path}"'] + + # If any empty directories left behind, try to delete them. Ignore failure. + old_prefix = old_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' + if old_prefix + repo_subpath = File.join(repository_base, old_prefix) + result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") + result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} + end + + # Commit / push changes to gitolite admin repo + commit_gitolite_admin + + rescue GitHostingException + logger.error "git_hosting: move_repository() failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: move_repository() failed" + end + # unlock + unlock() + end + + # Delete repository from specified project. + # + # We remove all redmine keys from the repository access rights. + # There are then three options: + # + # 1) The repo has non-redmine keys => we leave it alone + # 2) The repo has no keys left, but repository delete is not enabled + # => will leave repository alone with redmine_dummy_key + # 3) The repo has no keys left and repository delete is enabled + # => will delete repository + def self.delete_repository(project) + # Grab lock + if !lock(lock_wait_time) + logger.error "git_hosting: delete_repository() exited without acquiring lock!" + return + end - # commit / push changes to gitolite admin repo - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' add keydir/*] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' add conf/gitolite.conf] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' config user.email '#{Setting.mail_from}'] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' config user.name 'Redmine'] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' commit -a -m 'updated by Redmine' ] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' push ] + begin + # Make sure we have gitolite-admin cloned + clone_or_pull_gitolite_admin - # unlock + repo_name = repository_name(project) + + conf = GitoliteConfig.new(File.join(get_tmp_dir, 'gitolite-admin', 'conf', 'gitolite.conf')) + + # Kill off redmine keys + conf.delete_redmine_keys repo_name + + if Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true" + if conf.repo_has_no_keys? repo_name + logger.warn "Deleting repository '#{repo_name}' from gitolite.conf" + conf.delete_repo repo_name + GitoliteRecycle.move_repository_to_recycle repo_name + else + logger.warn "Repository '#{repo_name}' not deleted from gitolite.conf (non-redmine keys present and preserved)" + end + else + logger.warn "Deleting all redmine keys for repository '#{repo_name}' from gitolite.conf" + end + + conf.save + + # Commit / push changes to gitolite admin repo + commit_gitolite_admin + + rescue GitHostingException + logger.error "git_hosting: delete_repository() failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: delete_repository() failed" + end unlock() - end + # Update keys for all members of projects of interest + # + # This code is entirely self-correcting for keys owned by users of the specified + # projects. It should work regardless of the history of steps that got us here. + # + # Note that this code has changed from the original. Now, we look at all keys owned + # by users in the specified projects to make sure that they are still live. We + # do this with a single pass through the keydir and do not rely on the "inactive" + # status to tell us that a key should be deleted. The reason is that weird + # synchronization issues (not entirely understood) can cause phantom keys to get left + # in the keydir which can really mess up gitolite. + # + # Also, when performing :resync_all, if the 'deleteGitRepositories' setting is 'true', + # then we will remove repositories in the gitolite.conf file that are identifiable as + # "redmine managed" (because they have one or more keys of the right form) but which + # are nolonger live for some reason (probably because the project was deleted). + # + # John Kubiatowicz, 11/15/11 + # + # Usage: + # + # 1) update_repositories(project) => update for specified project + # 2) update_repositories([list of projects]) => update all projects + # 3) update_repositories(:flag1=>true, :flag2 => false) + # + # Current flags: + # :resync_all => go through all redmine-maintained gitolite repos, + # clean up keydir, delete unused keys, clean up gitolite.conf @@recursionCheck = false - def self.update_repositories(projects, is_repo_delete) - + def self.update_repositories(*args) + flags = {} + args.each {|arg| flags.merge!(arg) if arg.is_a?(Hash)} + if flags[:resync_all] + logger.info "Executing RESYNC_ALL operation on gitolite configuration" + projects = Project.active.has_module(:repository).find(:all, :include => :repository) + else + projects = args.flatten.select{|p| p.is_a?(Project)} + end + git_projects = projects.uniq.select{|p| p.repository.is_a?(Repository::Git) } + return if git_projects.empty? if(defined?(@@recursionCheck)) if(@@recursionCheck) + # This shouldn't happen any more -- log as error + logger.error "git_hosting: update_repositories() exited with positive recursionCheck flag!" return end - end + end @@recursionCheck = true - logger.debug "Updating repositories..." - projects = (projects.is_a?(Array) ? projects : [projects]) - - - # Don't bother doing anything if none of the projects we've been handed have a Git repository - unless projects.detect{|p| p.repository.is_a?(Repository::Git) }.nil? - - + # Grab actual lock + if !lock(lock_wait_time) + logger.error "git_hosting: update_repositories() exited without acquiring lock!" + @@recursionCheck = false + return + end - #lock - if !lock(5) - @@recursionCheck = false - return + begin + # Make sure we have gitoite-admin cloned. + on_master = clone_or_pull_gitolite_admin + + # Get directory for the gitolite-admin + repo_dir = File.join(get_tmp_dir,"gitolite-admin") + + # Flag to indicate whether repo has changed. If we have uncommited changes, we will commit later. + changed = !on_master + + # logger.info "Updating keydirectory for projects: #{git_projects.join ', '}" + + keydir = File.join(repo_dir,"keydir") + old_keyhash = {} + Dir.foreach(keydir) do |keyfile| + user_token = GitolitePublicKey.ident_to_user_token(keyfile) + if !user_token.nil? + old_keyhash[user_token] ||= [] + old_keyhash[user_token] << keyfile + end + end + + git_projects.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq.each do |cur_user| + active_keys = cur_user.gitolite_public_keys.active || [] + + # Remove old keys that happen to be left around + cur_token = GitolitePublicKey.user_to_user_token(cur_user) + old_keynames = old_keyhash[cur_token] || [] + cur_keynames = active_keys.map{|key| "#{key.identifier}.pub"} + (old_keynames - cur_keynames).each do |keyname| + filename = File.join(keydir,"#{keyname}") + logger.warn "Removing gitolite key: #{keyname}" + %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] + changed = true + end + + # Remove inactive keys (will already be deleted by above code) + cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} + + # Add missing keys to the keydir + active_keys.each do |key| + keyname = "#{key.identifier}.pub" + unless old_keynames.index(keyname) + filename = File.join(keydir,"#{keyname}") + logger.info "Adding gitolite key: #{keyname}" + File.open(filename, 'w') {|f| f.write(key.key.gsub(/\n/,'')) } + changed = true + end + end + + # In preparation for resync_all, below + old_keyhash.delete(cur_token) + end + + # Remove keys for deleted users + if flags[:resync_all] + # All keys left in old_keyhash should be for users nolonger authorized for gitolite repos + old_keyhash.each_value do |keyname| + filename = File.join(keydir,"#{keyname}") + logger.warn "Removing orphan gitolite key: #{keyname}" + %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] + changed = true + end end - - - # Make sure we have gitoite-admin cloned - clone_or_pull_gitolite_admin - - - local_dir = get_tmp_dir() - conf = GitoliteConfig.new(File.join(local_dir, 'gitolite-admin', 'conf', 'gitolite.conf')) + conf = GitoliteConfig.new(File.join(repo_dir, 'conf', 'gitolite.conf')) orig_repos = conf.all_repos new_repos = [] new_projects = [] - changed = false - - projects.select{|p| p.repository.is_a?(Repository::Git)}.each do |project| - - repo_name = repository_name(project) - - #check for delete -- if delete we can just - #delete repo, and ignore updating users/public keys - if is_repo_delete - if Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true" - conf.delete_repo(repo_name) - end - else - #check whether we're adding a new repo - if orig_repos[ repo_name ] == nil - changed = true - add_route_for_project(project) - new_repos.push repo_name - new_projects.push project - - end - - - # fetch users - users = project.member_principals.map(&:user).compact.uniq - write_users = users.select{ |user| user.allowed_to?( :commit_access, project ) } - read_users = users.select{ |user| user.allowed_to?( :view_changesets, project ) && !user.allowed_to?( :commit_access, project ) } - - # write key files - users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| - filename = File.join(local_dir, 'gitolite-admin/keydir',"#{key.identifier}.pub") - unless File.exists? filename - File.open(filename, 'w') {|f| f.write(key.key.gsub(/\n/,'')) } - changed = true - end - end - - # delete inactives - users.map{|u| u.gitolite_public_keys.inactive}.flatten.compact.uniq.each do |key| - filename = File.join(local_dir, 'gitolite-admin/keydir',"#{key.identifier}.pub") - if File.exists? filename - %x[git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' rm keydir/#{key.identifier}.pub] - changed = true - GitolitePublicKey.destroy(key.id) - end - end - - # update users - read_user_keys = [] - write_user_keys = [] - read_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| - read_user_keys.push key.identifier - end - write_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| - write_user_keys.push key.identifier - end - - #git daemon - if (project.repository.extra.git_daemon == 1 || project.repository.extra.git_daemon == nil ) && project.is_public - read_user_keys.push "daemon" - end - - conf.set_read_user repo_name, read_user_keys - conf.set_write_user repo_name, write_user_keys + + # Regenerate configuration file for projects of interest + # logger.info "Updating gitolite.conf for projects: #{git_projects.join ', '}" + git_projects.each do |proj| + repo_name = repository_name(proj) + + #check whether we're adding a new repo + if orig_repos[ repo_name ] == nil + changed = true + add_route_for_project(proj) + new_repos.push repo_name + new_projects.push proj + + # Attempt to recover repository from recycle_bin, if present + GitoliteRecycle.recover_repository_if_present repo_name + end + + # fetch users + users = proj.member_principals.map(&:user).compact.uniq + write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } + read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } + + # update users + read_user_keys = [] + write_user_keys = [] + read_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| + read_user_keys.push key.identifier + end + write_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| + write_user_keys.push key.identifier + end + + #git daemon + if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public + read_user_keys.push "daemon" end + + # Remove previous redmine keys, then add new keys + # By doing things this way, we leave non-redmine keys alone + conf.delete_redmine_keys repo_name + conf.add_read_user repo_name, read_user_keys + conf.add_write_user repo_name, write_user_keys + + # This is in preparation for full resync (below) + orig_repos.delete repo_name end + + # If resyncing, check for orphan repositories which still have redmine keys... + # At this point, orig_repos contains all repositories in original gitolite.conf + # which are not part of an active redmine project. There are four possibilities: + # + # 1) These repos do not have redmine keys => we leave them alone + # 2) They have both redmine keys and other (non-redmine) keys => remove redmine keys + # 3) They have only redmine keys, but repository delete is not enabled + # => remove redmine keys (will leave redmine_dummy_key when we save) + # 4) They have only redmine keys and repository delete is enabled => delete repository + # + # Finally, delete expired files from recycle bin. + if flags[:resync_all] + orig_repos.each_key do |repo_name| + if conf.is_redmine_repo? repo_name + # First, delete redmine keys for this repository + conf.delete_redmine_keys repo_name + if (Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true") && (conf.repo_has_no_keys? repo_name) + logger.warn "Deleting orphan repository '#{repo_name}' from gitolite.conf" + conf.delete_repo repo_name + GitoliteRecycle.move_repository_to_recycle repo_name + else + logger.info "Deleting redmine keys for orphan repository '#{repo_name}' from gitolite.conf" + end + end + end + GitoliteRecycle.delete_expired_files + end if conf.changed? conf.save changed = true end - + if changed - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' add keydir/*] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' add conf/gitolite.conf] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' config user.email '#{Setting.mail_from}'] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' config user.name 'Redmine'] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' commit -a -m 'updated by Redmine' ] - %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{local_dir}/gitolite-admin/.git' --work-tree='#{local_dir}/gitolite-admin' push ] + # Have changes. Commit / push changes to gitolite admin repo + commit_gitolite_admin flags[:resync_all] end - + # Set post recieve hooks for new projects # We need to do this AFTER push, otherwise necessary repos may not be created yet if new_projects.length > 0 GitAdapterHooks.setup_hooks(new_projects) end - unlock() - end - @@recursionCheck = false + rescue GitHostingException + logger.error "git_hosting: update_repositories() failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: update_repositories() failed" + end + unlock() + @@recursionCheck = false end - - def self.clear_cache_for_project(project) + def self.clear_cache_for_project(project) if project.is_a?(Project) project = project.identifier end @@ -539,5 +809,24 @@ def self.update_global_hook_params unlock() end end + + class MyLogger + # Prefix to error messages + ERROR_PREFIX = "***> " + + # For errors, add our prefix to all messages + def error(*progname, &block) + if block_given? + Rails.logger.error(*progname) { "#{ERROR_PREFIX}#{yield}".gsub(/\n/,"\n#{ERROR_PREFIX}") } + else + Rails.logger.error "#{ERROR_PREFIX}#{progname}".gsub(/\n/,"\n#{ERROR_PREFIX}") + end + end + + # Handle everything else with base object + def method_missing(m, *args, &block) + Rails.logger.send m, *args, &block + end + end end diff --git a/lib/git_hosting/patches/repositories_controller_patch.rb b/lib/git_hosting/patches/repositories_controller_patch.rb index 72e71c999..e4935d9bd 100644 --- a/lib/git_hosting/patches/repositories_controller_patch.rb +++ b/lib/git_hosting/patches/repositories_controller_patch.rb @@ -42,7 +42,7 @@ def edit_with_scm_settings end end - GitHosting.update_repositories(@project, false) if !@project.repository.nil? + GitHosting.update_repositories(@project) if !@project.repository.nil? GitHosting.setup_hooks(@project) if !@project.repository.nil? else diff --git a/lib/git_hosting/patches/repository_patch.rb b/lib/git_hosting/patches/repository_patch.rb index 6c3695250..0fc6cc62e 100644 --- a/lib/git_hosting/patches/repository_patch.rb +++ b/lib/git_hosting/patches/repository_patch.rb @@ -32,8 +32,8 @@ def fetch_changesets_with_disable_update # Do actual update fetch_changesets_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); + # Reenable updates to perform a sync of all projects + GitHostingObserver.set_update_active(:resync_all); end end diff --git a/lib/git_hosting/patches/sys_controller_patch.rb b/lib/git_hosting/patches/sys_controller_patch.rb index 6b35c86e5..cf473c949 100644 --- a/lib/git_hosting/patches/sys_controller_patch.rb +++ b/lib/git_hosting/patches/sys_controller_patch.rb @@ -8,8 +8,8 @@ def fetch_changesets_with_disable_update # Do actual update fetch_changesets_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); + # Perform the updating process on all projects + GitHostingObserver.set_update_active(:resync_all); end def self.included(base) diff --git a/lib/gitolite_conf.rb b/lib/gitolite_conf.rb index 1a57a3e88..3279ff7e8 100755 --- a/lib/gitolite_conf.rb +++ b/lib/gitolite_conf.rb @@ -1,10 +1,16 @@ module GitHosting class GitoliteConfig + DUMMY_REDMINE_KEY="redmine_dummy_key" + def initialize file_path @path = file_path load end + def logger + return GitHosting.logger + end + def save File.open(@path, "w") do |f| f.puts content @@ -39,6 +45,29 @@ def rename_repo old_name, new_name end end + # A repository is a "redmine" repository if it has redmine keys or no keys + # (latter case is checked, since we end up adding the DUMMY_REDMINE_KEY to + # a repository with no keys anyway.... + def is_redmine_repo? repo_name + repository(repo_name).rights.detect {|perm, users| users.detect {|key| is_redmine_key? key}} || (repo_has_no_keys? repo_name) + end + + def delete_redmine_keys repo_name + return if !@repositories[repo_name] + + repository(repo_name).rights.each do |perm, users| + users.delete_if {|key| is_redmine_key? key} + end + end + + def repo_has_no_keys? repo_name + !repository(repo_name).rights.detect {|perm, users| users.length > 0} + end + + def is_redmine_key? keyname + (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY) ? true : false + end + def changed? @original_content != content end @@ -51,7 +80,6 @@ def all_repos return repos end - private def load @original_content = [] @@ -81,14 +109,15 @@ def repository repo_name def content content = [] - # To facilitate creation of repos, even when no users are defined - # always define at least one user -- specifically the admin - # user which has rights to modify gitolite-admin and control - # all repos. Since the gitolite-admin user can grant anyone - # any permission anyway, this isn't really a security risk. - # If no users are defined, this ensures the repo actually - # gets created, hence it's necessary. - admin_user = @repositories["gitolite-admin"].rights["RW+".to_sym][0] + # If no gitolite-admin user, something seriously wrong. Add it in with id_rsa. + # + # If this doesn't work for some reason, will be corrected at later time by + # gl-setup run. + if @repositories["gitolite-admin"].nil? + content << "repo\tgitolite-admin" + content << "tRW+\t=\tid_rsa" + content << "" + end @repositories.each do |repo, rights| content << "repo\t#{repo}" has_users=false @@ -99,7 +128,8 @@ def content end end if !has_users - content << "\tR\t=\t#{admin_user}" + # If no users, use dummy key to make sure repo created + content << "\tR\t=\t#{DUMMY_REDMINE_KEY}" end content << "" end diff --git a/lib/gitolite_recycle.rb b/lib/gitolite_recycle.rb new file mode 100755 index 000000000..e140054d2 --- /dev/null +++ b/lib/gitolite_recycle.rb @@ -0,0 +1,112 @@ +module GitHosting + # This class implements a basic recycle bit for repositories deleted from the gitolite repository + # + # Whenever repositories are deleted, we rename them and place them in the recycle_bin. + # Assuming that GitoliteRecycle.delete_expired_files is called regularly, files in the recycle_bin + # older than 'preserve_time' will be deleted. Both the path for the recycle_bin and the preserve_time + # are settable as settings. + # + # John Kubiatowicz, 11/21/11 + class GitoliteRecycle + TRASH_DIR_SEP = "__" # Separator character(s) used to replace '/' in name + + RECYCLE_BIN_IF_UNDEF = "recycle_bin/" # In case settings not migrated (normally from settings) + PRESERVE_TIME_IF_UNDEF = 1440 # In case settings not migrated (normally from settings) + + def self.logger + return GitHosting.logger + end + + # Repository base path (relative to git user home directory) + def self.repository_base + Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] + end + + # Recycle bin base path (relative to git user home directory) + def self.recycle_bin + Setting.plugin_redmine_git_hosting['gitRecycleBasePath'] || RECYCLE_BIN_IF_UNDEF + end + + # Recycle preservation time (in minutes) + def self.preserve_time + (Setting.plugin_redmine_git_hosting['gitRecycleExpireTime'].to_f * 60).to_i || PRESERVE_TIME_IF_UNDEF + end + + # Scan through the recyclebin and delete files older than 'preserve_time' minutes + # This can fail silently if, for instance, the recycle_bin doesn't exist. That is the intended behavior. + def self.delete_expired_files + result = %x[#{GitHosting.git_user_runner} 'find #{recycle_bin} -type d -regex '.*\.git' -cmin +#{preserve_time} -prune -print'].chomp.split("\n") + if result.length > 0 + logger.warn "Garbage-collecting expired file#{(result.length != 1) ? "s" : ""} from recycle bin:" + result.each do |filename| + begin + GitHosting.shell %[#{GitHosting.git_user_runner} rm -r #{filename}] + logger.warn " Deleting #{filename}" + rescue + logger.error "GitoliteRecycle.delete_expired_files() failed trying to delete repository #{filename}!" + end + end + + # Optionally remove recycle_bin (but only if empty). Ignore error if non-empty + %x[#{GitHosting.git_user_runner} rmdir #{recycle_bin}] + end + end + + def self.move_repository_to_recycle repo_name + repo_path = File.join(repository_base, repo_name) + new_path = File.join(recycle_bin,"#{Time.now.to_i.to_s}#{TRASH_DIR_SEP}#{name_to_recycle_name(repo_name)}") + begin + GitHosting.shell %[#{GitHosting.git_user_runner} mkdir -p '#{recycle_bin}'] + GitHosting.shell %[#{GitHosting.git_user_runner} mv '#{repo_path}.git' '#{new_path}.git'] + logger.warn " Moving '#{repo_name}.git' from gitolite repository => '#{new_path}.git'. Will remain for at least #{preserve_time/60.0} hours" + # If any empty directories left behind, try to delete them. Ignore failure. + old_prefix = repo_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' + if old_prefix + repo_subpath = File.join(repository_base, old_prefix) + result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") + result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} + end + rescue + logger.error "Attempt to move repository '#{repo_name}.git' to recycle bin failed" + end + end + + def self.recover_repository_if_present repo_name + # Pull up any matching repositories. Sort them (beginning is representation of time) + myregex = File.join(recycle_bin,"[0-9]+#{TRASH_DIR_SEP}#{name_to_recycle_name(repo_name)}.git") + files = %x[#{GitHosting.git_user_runner} find '#{recycle_bin}' -type d -regex '#{myregex}' -prune].chomp.split("\n").sort {|x,y| y <=> x } + if files.length > 0 + # Found something! + logger.warn "Restoring '#{repo_name}.git' to gitolite repository from recycle bin (#{files.first})" + begin + prefix = repo_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' + if prefix + repo_prefix = File.join(repository_base, prefix) + # Has subdirectory. Must reconstruct directory + GitHosting.shell %[#{GitHosting.git_user_runner} mkdir -p '#{repo_prefix}'] + end + repo_path = File.join(repository_base, repo_name) + GitHosting.shell %[#{GitHosting.git_user_runner} mv '#{files.first}' '#{repo_path}.git'] + + # Optionally remove recycle_bin (but only if empty). Ignore error if non-empty + %x[#{GitHosting.git_user_runner} rmdir #{recycle_bin}] + return true + rescue + logger.error "Attempt to recover '#{repo_name}.git' failed" + return false + end + else + false + end + end + + # This routine takes a name and turns it into a name for the recycle bit, + # where we have a 1-level directory full of deleted repositories which + # we keep for 'preserve_time'. + def self.name_to_recycle_name repo_name + new_trash_name = "#{repo_name}".gsub(/\//,"#{TRASH_DIR_SEP}") + end + + + end +end diff --git a/tasks/gitolite.rake b/tasks/gitolite.rake index e15d136d3..8b3b1715f 100644 --- a/tasks/gitolite.rake +++ b/tasks/gitolite.rake @@ -1,11 +1,26 @@ +# There are two tasks here of interest: gitolite:update_repositories and gitolite:fetch_changesets. +# The second includes the first (since fetching of changesets causes updating of gitolite config). +# +# As of the most recent release, either of these will complete resynchronize the gitolite configuration +# and can thus be used to recover from errors that might have been introduced by sychronization errors. +# +# Specifically: +# +# 1) Resynchronize gitolite configuration (fix keys directory and configuration). Also, expire +# repositories in the recycle_bin if time. +# +# rake gitolite:update_repositories RAILS_ENV=xxx +# +# 2) Fetch all changesets for repositories and then rescynronize gitolite configuration (as in #1) +# +# rake gitolite:fetch_changes RAILS_ENV=xxx +# namespace :gitolite do - desc "update gitolite repositories" + desc "Update all gitolite repositories" task :update_repositories => [:environment] do - projects = Project.active - puts "Updating repositories for projects #{projects.join(' ')}" - GitHosting.update_repositories(projects, false) + GitHosting.update_repositories(:resync_all => true) end - desc "fetch commits from gitolite repositories" + desc "Fetch commits from gitolite repositories/update gitolite configuration" task :fetch_changes => [:environment] do Repository.fetch_changesets end From 01e8b32412eaceefe87bfdbbf51448f1b6bdff82 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sun, 27 Nov 2011 09:49:47 -0800 Subject: [PATCH 005/107] Missed a performance case/source of recursion complaints in the log. This is not a major issue (or even correctness issue), but will cause complaints of recursion in the log. --- app/controllers/gitolite_public_keys_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/gitolite_public_keys_controller.rb b/app/controllers/gitolite_public_keys_controller.rb index 1276f8471..131d2af40 100644 --- a/app/controllers/gitolite_public_keys_controller.rb +++ b/app/controllers/gitolite_public_keys_controller.rb @@ -18,12 +18,14 @@ def delete end def update + GitHostingObserver.set_update_active(false) if @gitolite_public_key.update_attributes(params[:public_key]) flash[:notice] = l(:notice_public_key_updated) redirect_to url_for(:controller => 'my', :action => 'account') else render :action => 'edit' end + GitHostingObserver.set_update_active(true) end def create From fffa1fd579161ffb4071f975b4476120f1fcdd4b Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Tue, 3 Jan 2012 21:05:53 -0800 Subject: [PATCH 006/107] Update of selinux policy for latest version of selinux package. Also change rakefile to install binary policy (redmine_git.pp) instead of compiling source policy (redmine_git.te) before every installation. New rake task added to recompile policy if necessary: rake selinux:redmine_git_hosting:build_policy --- selinux/README | 14 +++++++++++--- selinux/redmine_git.pp | Bin 127822 -> 128153 bytes selinux/redmine_git.te | 10 ++++++++-- tasks/selinux.rake | 10 ++++++++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/selinux/README b/selinux/README index a877fb20b..b57a919ec 100644 --- a/selinux/README +++ b/selinux/README @@ -52,15 +52,23 @@ Somewhat less far-reaching options include: # install new selinux policy, and install selinux context for # the redmine_git_hosting plugin - rake selinux:redmine_git_hosting:install + rake selinux:redmine_git_hosting:install RAILS_ENV=production -Finally, for those who are hand-crafting their own file context: +For those who are hand-crafting their own file context: # Build bin directory with customized scripts for redmine_git_hosting # and install new selinux policy. No file contexts will be # installed (so that you must do customization afterwards). - rake selinux:redmine_git_hosting:install_scripts_and_policy + rake selinux:redmine_git_hosting:install_scripts_and_policy RAILS_ENV=production + +Finally, to rebuild the policy file (redmine_git.pp) from source (redmine_git.te), +you can type: + + # Rebuild redmine_git_hosting selinux policy from source + + rake selinux:redmine_git_hosting:build_policy RAILS_ENV=production + diff --git a/selinux/redmine_git.pp b/selinux/redmine_git.pp index bfe21e6945a9e61a536b6baf50f119f2006732a9..80895f79f0f1bb1e8a076256f6c605b863778ce1 100644 GIT binary patch delta 4706 zcmb7He^k^}7M}|;4loSE4~GF}U`*UF1KlDmrD28{hWW*SyPH4QWccZ(61Xt6*0xBe z&34<G)`np=Lk895pE?b?Gu+J0?R^q1N_of8M!e^{HoMBc&k-(Ok z;}J?`Dt%See6A!iRf_diI%4pUISytC?xa0_U7-R>Xri0&)^ArGr8H^R@jnY_+Ni=2!NCDZYkB?@j4zD6txmRJDEXfP|3^|jvSMkxw+ z2S`O`iuQv4zBU`ds#)f62Paf0pf+Gh% zDpup};<<|7d3|aEZ=fyd&}mPCslv$vb~B!ktI=Rl;z@flsx3N{JCYzC2c{;)Q^xT z#}xw^9FA^&pt;6Z@AWCL)s%_9cPU|-P~YNOh@Bo??DB@?J}EmhDBU{DcPrp_p;76M zg)&aXigPZ`_k1Ct!#G*8naMXD-P|N`dM=IO1>u`Q>o4W&XR{s!c~rXCAxVRgR#Nm7 zQhIWP%O^?_z(#wU#@0Uy2~oTjVugU`x}GOWN02%MQF@d(+oJ-zK*~Lt;N-KZ#bJ{U zQVbbP5#nQ{m`Gwhe(!08Xnv%y<242Q$6C-}SLs4|C?pYZK1il7>&N(}EEj(Mq08-F?XXIjqhu* z_lOH$Kaz^75f5rNk{)@~Y}AH$zUH7Hq|jc1DH~HdJuhk_D3&zM>u&UKGvN8nR>5a% z%EFG+RklT)RI9*m!fmv6(s~U6u9EwI*jR>Xc5z;#cf+Y!^ih# zVc%90jXJ#PuXZsuyD~vVX7WOkiLr1)VQ~2eh}B=t8WzJ z(%q>j?{v}l2YmIAWV3X)9!K94GUkt_uFaiZ1@)MAP#40Y7s(bc*)gL5i7HK+F`?ZLtm#0RLEeClzc;ux~;!}Bg^3nJ% z?Og~`DTm_?QnBH^iTs?yE_P=Gq~hpd1#UiU!P6V{7;&Unlxf9!H{O4=8h0E?L*w6T zMfv{WO+5}Cx8o-tXenZDG4e8&co#l#NROT&8ca7e_AZ-O?+Z;NTzGE&IzkF@Z$!JGRvH;G1c z9OK0a6&hrv3f(HIf_G0|lg5j@CheWZi|+wkf5X-I_*pFu{HFx}ayA9~&bjcR0TN5>)gsm$5Mw%*2i>h3vyPN_%b`)QSS}oT}yrj{`0i9S;wx8ZUe| zHWnZJy|^5BCLYd3O`83*OwRWEAdijCq*qh!SMp~yWUz8Ny*!p_pi0&F)wNa!qR>!~ z%sO;%nC*(8w{iDH1;2$^M-1rLH+pXL^-mZXv&d*9kBlyzcM5{7oz4xu`w0V@Wi_+i zOb;*~j@*q3mXSk`?;kK!U$|$;gt=R?*ia5E`H|gTUH59{&m)h)x)gTCz*P2W4%~fx zJL=*|Hm^`pnZjCZAZITWf`dibU@DVa;CWCm z^9o3oMEfl8QV1YVa<6_VRX)h$WH z7BVVAk~t(Pl_X*#=PSrMt(?5gBT0!Q;Q@fTEVBY?S6F==U|9jpk6&d{r=We05!3bD2>Ono1AG$^|L(6A;JJQ{Z6Wt!hY?gC#(}O_PCj z-%GK(u$)dhQ7QaMIZXcos{Um5{tAej2)6$S=;{3dn9CkpNwF86KVM1F6_leMLT^Or zmN|1i4F@kf*|+tSg>QJ$ha!W@IJ#ZIiWKQ$#FDi6P7_)^Qz+8(lPQ)IAv#2d)p=w) zJ9e`d_dpIs#ZS~w7PRVI2h0zw5S#-y<$!qnhFKBOqim<$eedPhrqr|fNBZvfz3<-p z?t8y({X8xHNm|bDhVS?uR4SF4ev|08^`Cok?p=&Wt1Y;9aW>RqcC|S-yso7+bZgVv z(ArzVp@@dWuEt)M87JB@K!;0g)_m2JiWDNNR^Hg&7K+@qrY)TilVqtXI7ijCzO6;D zG6|W7`yEC4_NL~x_O{kggi9|FCOdtp7`LV4Zr2q2w=Ekc2kkw4SM)MSn==Ql5<3n%^C63el**Hc zPdbZXwotb@H5rlBYeEqnS-tqHLq?jZM6{|SQZGnO$EDP7WCxV?*GQ(|=P)kR6CF$T7-Z}Wb+d#5kd1n!n zuKh(QVjxqcv?~kxJh^C~M!C&n$D0EdO8g`!7G^cxT)0#~^SpLE>z%J{3Pp-YF_SFK z(^Bp@!p$xMAFmTG_32=d(CGB#z!b5WsILI75ws6|c^C^?kWxay>)6D5APr~vGoXmG zv)0|Vt|ihQifGB`T%mBgUk8^6Xs7>L$mUSyjo}+3vOLpx5phwC7OoI7jWwAF0ZXRn zbJ5=m1)Z8PO=^_!4Nq9NmN0zZHCYqMBUeEY`OH8DWD4;_bd)HqtjSD4zAumsPTJ69 zJP^o(bVW3yofpPJcJvB~?+q)0dTg95W1}ujKK`An{zmJQCESno{z8@NUWl zwGXS*ftC$zf_!{&$+h4Y!7Z%44C;jZuWPeFBht#l?5j0ErJ#5Ew0NdA7p4kcVVxeo zq0&0)a`7H#3HEH!#_p{9#>TW(D8#OA6E*GZZU=t(Co}eJYsLQK0X(zKNN_Kn-ewZC z;*SHk?Sv8IScB?gCX$gW#~(l5N^Xhp>i_rdK2PuzfmJG=>4 z@{QY#N%n(i+MX|La=(gyZl`015zDvNV9ucy^uAey(XC$m@-w6G!Phtj1kQbp;%4EG z@L2F6lbR>kg2nL?tbAk|_UtOb(~mgVU^z^QJ-_n?$&M|2+yGp#1mou{7=6Oj?Mr3x zOqhnzogR*3*^?j9YQeInIBvoI^I~PesWhohK4u!DwD9qg1djcW%*6g(icK7Ru>v#5 zT(XOSb0utO7EQ`eo_-at+h@jTuUD+`7+$+Gx$*+f{?bFPVtw!Hq9j6s85g{n|r_iFFz2(mSNr(q)s1 zl~3)(jF$xiSH1jEiYnEBbB+{HLkY7A9*h5ZKS;;u#pBPn($9?F{?(7A`8h zp4eW1WrzF;+(U=3ncb64yR7_M4Q|+O!mdMfn(5?oIF3HajP`1AFkU4Q-;q&CK2I*z z`XNi)YX5Pc>;&sE_lOcV(D0TC8;(ATuA@`1^6xe=sCWp;gCluxsgGGuf6DU%az*&% z4)-`c{U0niwAmBu|Hm_c@pl`=l;V?4nQ%0n)SZC6&&;PGJ5vn7@TpeJI5j#vFA^U& zJTI2rxaA;j+|o(|Fo44}Kxc1ljaff95A-~#eYZJ<-eGvx=4Q26^0<@)n~DEcype60 zN+pS-mPO0KMl;TY;m35bji25O82ZkLn$LHOGpUq(e9O4lT{Co@1gg)NS$QTsXB=k=DY=Wr zQ&}`svPb3kIWOJ+v+<_rQm$uUmW)dfYs|jLR80#N$lP%dS25};Ty5B zGCgGQ?#aUEVhdP*I+QW(L}~<{4GyhLRMsb2Op@ms;CB7;fBk28I89~a<6Je=2PSxR z0*ZZ}*WpcdSt(NtDeU1}VLfYDPH&qsBR%~uf6*+iO7@@;mg$d=v#?XcVja}TbiJ}Y z9bjRXSfNS3{EIQ3?zaNTtvAveab%Ep&*y|f_Q$DQesGL@>7`trzk%4vS&6)iLdv13 zk|~Vt)Uuq}6x{gj)oIwkJh*zyQLB>J|JJI%3f8dx#q|D2be2wFJeM9xFOG|Tk#v$$ z&R&q6wNcn%JNVgsHYi{jb~wcJc5t(gt00f9vcnPyRoW=W?{LQVZpz#CDl)yoLm~~; z#OSFahPN7uB=j36m|1Hzc)4&MJ5Wtl2r{#o4tSWYaFN_;2khXGFrZ8I{HnM{e z)J)@o>$r6@J4i-G>6LCtPbk&6DTq7#kk59xAt)ieNBYWRYe%4W5|EkMyoyGTSJ6=W z6lf)fPWd5+z2>7j2&l}jKwTW_3=sF6ADTJT$_z`$dAx?y1aw(IfmV&sE{xC`f(n;% zreOzz^!?YlIUjqzl27HooSocvVF|Q>?2X}J-uj>#V$}MjeO+~MzlNP@qNFTbN}1ig zjG!J4J-wVD^)iA!T}seO4*5BEK!IQ`+p&~(Zsq*$dZ=Ztu4jxFw(#6J1{vUwL>F1N;RnmCZ^@^Kw3CPOoSAat*rE{gZ^b4>Z^-#@v zR*=7dVn9TEcLPPFzaA_T=8>GA9a#n;<_jf=FrQf)poQf$5@DbLENKf#sz$LO^6(z< zlH6TE0$%Bkn@tmVVvD%;DGSpykCDHUl*yu-pp-*KddAZWd>ReINo++sWQ`$qJ^KU1Xzw`UOtz;ToYFFX z?z4i`R2t#vmeusdrrZSVbeMe8+4izv#~5fr2hq3!ZtU0M+fU3DH;0ru+`$J|(m>od U%tvY#^m%Uv>xRBfXCNp2zdwhul>h($ diff --git a/selinux/redmine_git.te b/selinux/redmine_git.te index 54b0a7aa1..36c73d2d8 100644 --- a/selinux/redmine_git.te +++ b/selinux/redmine_git.te @@ -8,10 +8,13 @@ require { type httpd_t, httpd_sys_script_t, httpd_sys_script_exec_t; type sudo_db_t; type httpd_redmine_git_script_t; + type httpd_redmine_git_script_exec_t; + type gitosis_var_lib_t; class process { setrlimit setfscreate }; class netlink_route_socket { write getattr read bind create nlmsg_read }; class capability { setuid sys_resource setgid }; - class dir { getattr search write}; + class dir { getattr search write write rename create reparent rmdir }; + class lnk_file unlink; } apache_content_template(redmine_git) @@ -51,10 +54,13 @@ allow httpd_redmine_git_script_t self:netlink_route_socket { write getattr read allow httpd_redmine_git_script_t self:process setrlimit; allow httpd_redmine_git_script_t sudo_db_t:dir { getattr search }; +# Capabilities required to manage gitolite repositories +allow httpd_redmine_git_script_t gitosis_var_lib_t:dir { rename create reparent rmdir }; +allow httpd_redmine_git_script_t gitosis_var_lib_t:lnk_file unlink; gitosis_read_lib_files(httpd_redmine_git_script_t) gitosis_manage_lib_files(httpd_redmine_git_script_t) -httpd_rw_stream_sockets(httpd_redmine_git_script_t) +apache_rw_stream_sockets(httpd_redmine_git_script_t) kernel_read_kernel_sysctls(httpd_redmine_git_script_t) logging_send_syslog_msg(httpd_redmine_git_script_t) diff --git a/tasks/selinux.rake b/tasks/selinux.rake index 92e1e4c7c..3eae4cfd5 100644 --- a/tasks/selinux.rake +++ b/tasks/selinux.rake @@ -153,8 +153,14 @@ namespace :selinux do desc "Install selinux tags and policy for redmine_git_hosting." task :install_policy => [:environment] do puts "[Installing selinux tags and policy for redmine_git_hosting:" - plugin_roots = redmine_roots("vendor/plugins/redmine_git_hosting") - sh "#{plugin_roots[0]}/selinux/redmine_git.sh" + sh "semodule -i #{Rails.root}/vendor/plugins/redmine_git_hosting/selinux/redmine_git.pp" + puts "DONE.]" + end + + desc "Build and install selinux tags and policy for redmine_git_hosting." + task :build_policy => [:environment] do + puts "[Building and installing selinux policy for redmine_git_hosting:" + sh "#{Rails.root}/vendor/plugins/redmine_git_hosting/selinux/redmine_git.sh" puts "DONE.]" end From 2f1c213c0ce41d024ad2b86585e4dea99c25b2c7 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Tue, 10 Jan 2012 23:15:43 -0800 Subject: [PATCH 007/107] Continuing rewrite of redmine_git_hosting to rationalize path handling. It is important to remember to migrate_plugin settings, since there are new settings and one setting has changed a bit (i.e. httpServer should nolonger include a rails_root path). Thus, make sure to: rake db:migrate_plugins This commit includes a number of changes. First and foremost, the update_repositories() function now handles deletes and repository moves as well as all other changes. Consequently, :RESYNC_ALL will be able to recover from a variety of problems involving movement of repositories resulting from project parent changes. Note that changes in the parent for a subtree of projects works much better than before. Second, this commit adds new settings which allow (1) redmine-managed repositories to be focused in a specific subdirectory of the gitolite repository, (2) allow repositories to be named either hierarchically (default) or concentrated in a single directory (flat) independent of project parents, and (3) an extra path parameter which can be added to the http URL for smart http access of repositories. Third, added the "access" box at the top of the settings page which shows explicitly how settings affect access parameters. Random improvements include (1) rewrite of routes for smart HTTP to better handle changes in parentage (and avoid need to change routes as project parents change); (2) changes in settings now immediately trigger resulting changes in state (before, the settings cache got in the way); (3) all of the path functions (repository positions, ssh access, http access) have been concentrated into a small number of functions at the top of git_hosting.rb, rather than spread throughout the code; (4) continuing the changes started with the original performance fixes, cleaned up observer behavior triggered by changes in project membership and settings. --- README.mkd | 64 ++- app/controllers/git_http_controller.rb | 114 +++-- app/models/git_hosting_observer.rb | 73 ++- app/models/git_hosting_settings_observer.rb | 76 ++- app/views/projects/_git_urls.erb | 21 +- app/views/repositories/_git_urls.erb | 57 ++- app/views/settings/_display_access.html.erb | 19 + .../settings/_redmine_git_hosting.html.erb | 74 +-- assets/javascripts/git_url_display.js | 6 +- assets/stylesheets/application.css | 21 +- config/locales/bg.yml | 21 +- config/locales/bs.yml | 21 +- config/locales/ca.yml | 21 +- config/locales/cs.yml | 21 +- config/locales/da.yml | 21 +- config/locales/de.yml | 21 +- config/locales/el.yml | 21 +- config/locales/en.yml | 21 +- config/locales/es.yml | 21 +- config/locales/fi.yml | 21 +- config/locales/fr.yml | 21 +- config/locales/gl.yml | 21 +- config/locales/he.yml | 21 +- config/locales/hu.yml | 21 +- config/locales/id.yml | 21 +- config/locales/it.yml | 21 +- config/locales/ja.yml | 21 +- config/locales/ko.yml | 21 +- config/locales/lt.yml | 21 +- config/locales/nl.yml | 21 +- config/locales/no.yml | 21 +- config/locales/pl.yml | 21 +- config/locales/pt-BR.yml | 23 +- config/locales/pt.yml | 22 +- config/locales/ro.yml | 21 +- config/locales/ru.yml | 21 +- config/locales/sk.yml | 21 +- config/locales/sl.yml | 21 +- config/locales/sr.yml | 21 +- config/locales/sv.yml | 21 +- config/locales/th.yml | 21 +- config/locales/tr.yml | 21 +- config/locales/uk.yml | 21 +- config/locales/vi.yml | 21 +- config/locales/zh-TW.yml | 21 +- config/locales/zh.yml | 21 +- config/routes.rb | 33 +- ...20111220055819_add_settings_to_plugin_2.rb | 51 ++ init.rb | 13 +- lib/git_adapter_hooks.rb | 13 +- lib/git_hosting.rb | 466 ++++++++++-------- .../patches/projects_controller_patch.rb | 75 +-- .../patches/repositories_controller_patch.rb | 11 +- lib/gitolite_conf.rb | 25 + lib/gitolite_recycle.rb | 34 +- 55 files changed, 1461 insertions(+), 544 deletions(-) create mode 100644 app/views/settings/_display_access.html.erb create mode 100644 db/migrate/20111220055819_add_settings_to_plugin_2.rb diff --git a/README.mkd b/README.mkd index d9fe779be..69528e6c1 100755 --- a/README.mkd +++ b/README.mkd @@ -1,4 +1,4 @@ -# Redmine Git Hosting Plugin (v0.4.2x) +# Redmine Git Hosting Plugin (v0.4.3x) A ChiliProject / Redmine plugin which makes configuring your own git hosting easy. This plugin allows straightforward management of gitolite and associated public keys, the git daemon, and integrates code from Scott Schacon's "grack" utility @@ -99,40 +99,53 @@ root directory: **(6)** It is best to set several plugin variables BEFORE you run the db:migrate\_plugins task in step 7. In particular it is important -that the *httpServer*, *gitServer*, *gitUser*, *gitRepositoryBasePath*, *gitoliteIdentityFile* and *gitoliteIdentityPublicKeyFile* -variables are set correctly. To adjust these variables, open an editor and edit [redmine_rails_root]/vendor/plugins/redmine_git_hosting/init.rb file. -Starting on line 22 you will see the settings definitions you should edit. +that the *httpServer*, *gitServer*, *gitUser*, *gitoliteIdentityFile* and *gitoliteIdentityPublicKeyFile* +variables are set correctly. Others that should be set include *gitRepositoryBasePath*, *gitRedmineSubdir*, and *gitRepositoryHierarchy*; however, +the default values for these variables should be sufficient for most installations. +To adjust these variables, open an editor and edit [redmine_rails_root]/vendor/plugins/redmine_git_hosting/init.rb file. +Starting on line 22, you will see the settings definitions you should edit. -The *httpServer* variable should be set to the url used to access your Redmine site, e.g. www.my-own-personal-git-host-server.com. Note that if Redmine is not -installed in the site root this should include the path to your Redmine root, e.g. www.my-own-personal-git-host-server.com/path/to/redmine +The *httpServer* variable should be set to the hostname which will be used to access your Redmine site, e.g. www.my-own-personal-git-host-server.com. This variable +may optionally include a port using the ':portnum' syntax, i.e. www.my-own-person-git-host-server.com:8000. Unlike earlier versions of this plugin, this variable should +*not* include the path to your Redmine root. +The *gitServer* variable should be set to the hostname which will be used to access the gitolite repositories via ssh. In most configurations, this +variable will be identical to the *httpServer*, except for the fact that *gitServer* will never include an optional port number. -The *gitServer* will usually be the same as the the httpServer variable -- this is the server name to use to access the gitolite repositories via ssh. This should be -the hostname only, so this will be different from *httpServer* if Redmine is not installed in the site root. In other words, even if Redmine is installed in -www.my-own-personal-git-host-server.com/path/to/redmine, *gitServer* should be set to www.my-own-personal-git-host-server.com - - -The *gitUser* is the user under which gitolite is installed +The *gitUser* is the user under which gitolite is installed. +If you followed the above directions you will not need to modify the *gitoliteIdentityFile* or *gitoliteIdentityPublicKeyFile* variables -- these specify +the path to the private/public key files for accessing the gitolite admin repository. -The *gitRepositoryBasePath* is the path *relative to the git user root* where the repositories are located. This should always end in a file separator, e.g. '/'. -Since gitolite always uses repositories/ as the default place for repositories you probably shouldn't have to change this. +Although you can change the following three variables, their default values provide for a very reasonable installation: +The *gitRepositoryBasePath* is the path *relative to the git user root* where the repositories are located. This should always be non-empty and should end +in a file separator, e.g. '/'. Since gitolite always uses repositories/ as the default place for repositories you probably shouldn't have to change this. -If you followed the above directions you will not need to modify the *gitoliteIdentityFile* or *gitoliteIdentityPublicKeyFile* variables -- these specify -the path to the private/public key files for accessing the gitolite admin repository. +The *gitRedmineSubdir* is an optional subdirectory under the *gitRepositoryBasePath* which can be used for all plugin-managed repositories. Its default value +is the empty string (no special subdirectory). If you choose to set it, make sure that the resulting path ends in a file separator, e.g. '/'. +The *gitRepositoryHierarchy* variable is a boolean value which denotes whether or not the plugin-managed repositories are placed into a hierarchy that +mirrors the project hierarchy. Its value is either 'true' (default) or 'false'. These variables can be modified at a later time in the Administration => Plugins => Redmine Git Hosting Plugin configuration page. However to ensure that the database migration from your existing repositories goes smoothly it is best to modify these variables now. +As an example of the significance of the previous three variables, suppose that project-3 is a child of project-2 which is a child of project-1. +Assume *gitRepositoryBasePath* == "repository/" and *gitRedmineSubdir* == "projects". When *gitRepositoryHierachy* is 'true', project-3.git will be stored in +repository/projects/project-1/project-2/project-3.git, which will further be reflected in the ssh access URL of repository/projects/project-1/project-2/project-3.git. +In contrast, when *gitRepositoryHierarchy* is 'false', project-3.git will be stored directly under repository/projects -- regardless of the number and identity of +any parents that it might have. Note that the top of the settings page (Administration => Plugins => Redmine Git Hosting Plugin configuration page) provides +information about how your chosen configuration affects the storage locations and URLs for accessing projects. **(7)** Run the rake db:migrate\_plugins task to update the database. You will need to do this once for every rails environment you have configured (e.g. production, development, testing). For the production environment run: RAILS_ENV=production rake db:migrate_plugins +At this point, if you wish to utilize selinux to protect your installation, you should follow the instructions given in the "Selinux" section, below. + **(8)** Unless you want to access your repositories exclusively via Smart HTTP users will need to set a public key to connect via SSH. To do this, open a browser, login to ChiliProject/Redmine and follow the "My Account" Link in the upper right-hand corner of the page. The right-hand column contains controls for adding your public key(s). @@ -143,6 +156,9 @@ do not re-use the key you set as the gitolite admin key. **(9)** The plugin is now configured, but you may now want to set some additional settings on the Administration => Plugins => Redmine Git Hosting Plugin page. +The *gitLockWaitTime* represents the amount of time that the plugin will wait in attempting to acquire its internal synchronization lock before giving up. +You probably will not need to change this value. + *Automatically Initialize Git Repositories For New Projects* can be enabled to automatically create a new git repository every time you create a new project. You won't have to create the project, and then create the repository -- this will be done all it one step. However, if you have both git and svn (or hg, or cvs etc.) repositories, this may cause problems so it is disabled by default. @@ -153,11 +169,8 @@ the accidental loss of data. If this feature is enabled, the safety is turned of deleted in ChiliProject/Redmine. Note, however, that even when this feature is enabled, deleted repositories are placed into a "recycle_bin" for a configurable amount of time (defaulting to 24 hours) and can be recovered by recreating the project in Redmine with the same Identifier. Details are placed in the log. -The *gitRecycleBasePath* is the path *relative to the git user root* where deleted repositories are placed. This path should end in a path separator, e.g. '/'. -Deleted repositories are kept here for up to *gitRecycleExpireTime* hours (configurable, defaults to 24.0 hours). - -The *gitLockWaitTime* represents the amount of time that the plugin will wait in attempting to acquire its internal synchronization lock before giving up. -You probably will not need to change this value. +The *Git Recycle Bin Base Path* is the path *relative to the git user root* where deleted repositories are placed. This path should end in a path separator, +e.g. '/'. Deleted repositories are kept here for up to *gitRecycleExpireTime* hours (configurable, defaults to 24.0 hours). *Show Checkout URLs* can be disabled to hide the git URL bar in the repository tab. It is enabled by default. @@ -323,7 +336,14 @@ change options, then place your system back into enforcing mode. Alternatively, file and reinstall the plugin. Under normal operation, you will get one selinux complaint about /bin/touch in your log each time that you visit the plugin settings page. -This rakefile and selinux configuration has been primarily tested on Redhat Enterprise Linux version 6.x +One final comment: The selinux policy exists in binary form as selinux/redmine_git.pp. Should this policy +need to be rebuilt, an additional rake task exists which will build the policy from selinux/redmine_git.te: + + rake selinux:redmine_git_hosting:build_policy + +This task can be followed by the selinux:install task. + +The rakefile and selinux configuration has been primarily tested on Redhat Enterprise Linux version 6.x with apache and fcgi. Other configurations may require slight tweaking. ## Tested Configurations diff --git a/app/controllers/git_http_controller.rb b/app/controllers/git_http_controller.rb index b5faaba1a..cd42f97a6 100644 --- a/app/controllers/git_http_controller.rb +++ b/app/controllers/git_http_controller.rb @@ -10,71 +10,83 @@ class GitHttpController < ApplicationController before_filter :authenticate - def index - p1 = params[:p1] - p2 = params[:p2] - p3 = params[:p3] - proj_id = params[:id] - - - @git_http_repo_path = (params[:path]).gsub(/\.git$/, "") - - reqfile = p2 == "" ? p1 : ( p3 == "" ? p1 + "/" + p2 : p1 + "/" + p2 + "/" + p3); - - if p1 == "git-upload-pack" - service_rpc("upload-pack") - elsif p1 == "git-receive-pack" - service_rpc("receive-pack") - elsif p1 == "info" && p2 == "refs" - get_info_refs(reqfile) - elsif p1 == "HEAD" - get_text_file(reqfile) - elsif p1 == "objects" && p2 == "info" - if p3 != packs - get_text_file(reqfile) - else - get_info_packs(reqfile) - end - elsif p1 == "objects" && p2 != "pack" - get_loose_object(reqfile) - elsif p1 == "objects" && p2 == "pack" && p3.match(/\.pack$/) - get_pack_file(reqfile) - elsif p1 == "objects" && p2 == "pack" && p3.match(/\.idx$/) - get_idx_file(reqfile) - else - render_not_found - end - + if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) + proj_id = proj_path_split[2] + project = Project.find_by_identifier(proj_id) + @git_http_repo_path = project_path = proj_path_split[1] + + if GitHosting.http_access_url(project) == project_path + p1 = params[:path][0] || "" + p2 = params[:path][1] || "" + p3 = params[:path][2] || "" + + # Full requested path from .git repo + reqfile = params[:path].join('/') + + # git http protocol + if p1 == "git-upload-pack" + service_rpc("upload-pack") + elsif p1 == "git-receive-pack" + service_rpc("receive-pack") + elsif p1 == "info" && p2 == "refs" + get_info_refs(reqfile) + elsif p1 == "HEAD" + get_text_file(reqfile) + elsif p1 == "objects" && p2 == "info" + if p3 != "packs" + get_text_file(reqfile) + else + get_info_packs(reqfile) + end + elsif p1 == "objects" && p2 != "pack" + get_loose_object(reqfile) + elsif p1 == "objects" && p2 == "pack" && p3.match(/\.pack$/) + get_pack_file(reqfile) + elsif p1 == "objects" && p2 == "pack" && p3.match(/\.idx$/) + get_idx_file(reqfile) + else + render_not_found + end + else + # repository URL doesn't match + render_not_found + end + return + end + # Wrong prefix or :base doesn't end in .git or doesn't at least have one char in base name + render_not_found end private def authenticate - is_push = params[:p1] == "git-receive-pack" + is_push = (params[:path][0] == "git-receive-pack") query_valid = false authentication_valid = true - project = Project.find(params[:id]) - repository = project != nil ? project.repository : nil - if(project != nil && repository !=nil) - if repository.extra[:git_http] == 2 || (repository.extra[:git_http] == 1 && is_ssl?) - query_valid = true - allow_anonymous_read = project.is_public - if is_push || (!allow_anonymous_read) - authentication_valid = false - authenticate_or_request_with_http_basic do |login, password| - user = User.find_by_login(login); - if user.is_a?(User) - if user.allowed_to?( :commit_access, project ) || ((!is_push) && user.allowed_to?( :view_changesets, project )) - authentication_valid = user.check_password?(password) + if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) + project = Project.find_by_identifier(proj_path_split[2]) + repository = project != nil ? project.repository : nil + if(project != nil && repository !=nil) + if repository.extra[:git_http] == 2 || (repository.extra[:git_http] == 1 && is_ssl?) + query_valid = true + allow_anonymous_read = project.is_public + if is_push || (!allow_anonymous_read) + authentication_valid = false + authenticate_or_request_with_http_basic do |login, password| + user = User.find_by_login(login); + if user.is_a?(User) + if user.allowed_to?( :commit_access, project ) || ((!is_push) && user.allowed_to?( :view_changesets, project )) + authentication_valid = user.check_password?(password) + end end + authentication_valid end - authentication_valid end end end - end + end #if authentication failed, error already rendered #so, just render case where user queried a project diff --git a/app/models/git_hosting_observer.rb b/app/models/git_hosting_observer.rb index 9599a0b7c..d254e8e1f 100644 --- a/app/models/git_hosting_observer.rb +++ b/app/models/git_hosting_observer.rb @@ -13,45 +13,40 @@ def reload_this_observer end - def self.set_update_active(is_active) - case is_active - when Symbol then @@updating_active_flags[is_active] = true - when Hash then @updating_active_flags.merge(is_active) - when Project then @@cached_project_updates << is_active - end - - if !is_active + def self.set_update_active(*is_active) + if !is_active || !is_active.first @@updating_active_stack += 1 else - @@updating_active_stack -= 1 - if @@updating_active_stack < 0 - @@updating_active_stack = 0 - end - end - - if is_active && @@updating_active_stack == 0 - if @@cached_project_updates.length > 0 || !@@updating_active_flags.empty? + is_active.each do |item| + case item + when Symbol then @@updating_active_flags[item] = true + when Hash then @@updating_active_flags.merge!(item) + when Project then @@cached_project_updates |= [item] + end + end + + # If about to transition to zero and have something to run, do it + if @@updating_active_stack == 1 && (@@cached_project_updates.length > 0 || !@@updating_active_flags.empty?) @@cached_project_updates = @@cached_project_updates.flatten.uniq.compact GitHosting::update_repositories(@@cached_project_updates, @@updating_active_flags) - @@cached_project_updates = [] - @@updating_active_flags = {} - end - @@updating_active = true - else - @@updating_active = false - end - end + @@cached_project_updates = [] + @@updating_active_flags = {} + end - def before_destroy(object) - if object.is_a?(Repository::Git) - if Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true" - GitHosting::delete_repository(object.project) - %x[#{GitHosting::git_user_runner} 'rm -rf #{object.url}' ] - end - GitHosting::clear_cache_for_project(object.project) + # Wait until after running update_repositories before releasing + @@updating_active_stack -= 1 + if @@updating_active_stack < 0 + @@updating_active_stack = 0 + end end + @@updating_active = (@@updating_active_stack == 0) end - + + # Register args for updating and then do it without allowing recursive calls + def self.bracketed_update_repositories(*args) + set_update_active(false) + set_update_active(*args) + end def after_create(object) if not object.is_a?(Project) @@ -73,15 +68,18 @@ def after_save(object) def after_destroy(object) - if !object.is_a?(Repository::Git) - update_repositories(object) + if object.is_a?(Repository::Git) + update_repositories(object,:delete=>true) + GitHosting::clear_cache_for_project(object.project) + else + update_repositories(object) end end protected - def update_repositories(object) + def update_repositories(object,*flags) projects = [] case object when Repository::Git then projects.push(object.project) @@ -90,11 +88,12 @@ def update_repositories(object) when Member then projects.push(object.project) when Role then projects = object.members.map(&:project).flatten.uniq.compact end - if(projects.length > 0) + if (projects.length > 0) if (@@updating_active) - GitHosting::update_repositories(projects) + GitHosting::update_repositories(projects,*flags) else @@cached_project_updates.concat(projects) + @@updating_active_flags.merge!(*flags) unless flags.empty? end end end diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb index 884779cb6..e80e36156 100644 --- a/app/models/git_hosting_settings_observer.rb +++ b/app/models/git_hosting_settings_observer.rb @@ -25,7 +25,37 @@ def before_save(object) valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile'] valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile'] end + + # Server should not include any path components + if valuehash['gitServer'] + normalizedServer = valuehash['gitServer'].lstrip.rstrip.split('/').first + if (normalizedServer == '') + valuehash['gitServer'] = @@old_valuehash['gitServer'] + else + valuehash['gitServer'] = normalizedServer + end + end + + # Server should not include any path components + if valuehash['httpServer'] + normalizedServer = valuehash['httpServer'].lstrip.rstrip.split('/').first + if (normalizedServer == '') + valuehash['httpServer'] = @@old_valuehash['httpServer'] + else + valuehash['httpServer'] = normalizedServer + end + end + # Normalize http repository subdirectory path, should be either empty or relative and end in '/' + if valuehash['httpServerSubdir'] + normalizedFile = File.expand_path(valuehash['httpServerSubdir'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['httpServerSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['httpServerSubdir'] = '' + end + end + # Normalize Repository path, should be relative and end in '/' if valuehash['gitRepositoryBasePath'] normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/") @@ -36,6 +66,16 @@ def before_save(object) end end + # Normalize Redmine Subdirectory path, should be either empty or relative and end in '/' + if valuehash['gitRedmineSubdir'] + normalizedFile = File.expand_path(valuehash['gitRedmineSubdir'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRedmineSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRedmineSubdir'] = '' + end + end + # Normalize Recycle bin path, should be relative and end in '/' if valuehash['gitRecycleBasePath'] normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/") @@ -72,41 +112,43 @@ def after_save(object) # Only perform after-actions on settings for our plugin if object.name == "plugin_redmine_git_hosting" valuehash = object.value + + # Settings cache doesn't seem to invalidate symbolic versions of Settings immediately, + # so, any use of Setting.plugin_redmine_git_hosting[] by things called during this + # callback will be outdated.... True for at least some versions of redmine plugin... + # + # John Kubiatowicz 12/21/2011 + if Setting.respond_to?(:check_cache) + # Clear out all cached settings. + Setting.check_cache + end if GitHosting.bin_dir_writeable? %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] end - if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] - GitHostingObserver.set_update_active(false) - all_projects = Project.find(:all) - all_projects.each do |p| - if p.repository.is_a?(Repository::Git) - r = p.repository - repo_name= p.parent ? File.join(GitHosting::get_full_parent_path(p, true),p.identifier) : p.identifier - r.url = File.join(valuehash['gitRepositoryBasePath'], "#{repo_name}.git") - r.root_url = r.url - r.save - end - end - GitHostingObserver.set_update_active(true) + if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] || + @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || + @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || + @@old_valuehash['gitUser'] != valuehash['gitUser'] + # Need to update everyone! + GitHostingObserver.bracketed_update_repositories(:resync_all) end if @@old_valuehash['gitUser'] != valuehash['gitUser'] GitHosting.setup_hooks - GitHosting.update_repositories(:resync_all=>true) elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] || @@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] || - @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] || + @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || + @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || @@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous'] GitHosting.update_global_hook_params end - - @@old_valuehash = (Setting.plugin_redmine_git_hosting).clone + @@old_valuehash = valuehash.clone end end end diff --git a/app/views/projects/_git_urls.erb b/app/views/projects/_git_urls.erb index be2121e58..fa9c4388c 100644 --- a/app/views/projects/_git_urls.erb +++ b/app/views/projects/_git_urls.erb @@ -10,17 +10,22 @@ <% end %> From b50d99374ed2bf2f8d2f53c3e94e2dad059319d1 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Mon, 21 May 2012 15:03:25 -0500 Subject: [PATCH 043/107] Refactor extracting repo info for hooks --- app/controllers/gitolite_hooks_controller.rb | 114 ++++++++++++++----- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index 8a02590a3..91aad0c7b 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -12,6 +12,80 @@ def stub render(:code => 404) end + def post_receive_payloads(refs, project=nil) + project ||= @project + payloads = [] + refs.each do |ref| + oldhead, newhead, refname = ref.split(',') + + # Only pay attention to branch updates + next if not refname.match(/refs\/heads\//) + branch = refname.gsub('refs/heads/', '') + + if newhead.match(/^0{40}$/) + # Deleting a branch + GitHosting.logger.debug "Deleting branch \"#{branch}\"" + next + elsif oldhead.match(/^0{40}$/) + # Creating a branch + GitHosting.logger.debug "Creating branch \"#{branch}\"" + range = newhead + else + range = "#{oldhead}..#{newhead}" + end + + revisions_in_range = %x[#{GitHosting.git_exec} --git-dir='#{GitHosting.repository_path(project)}' rev-list --reverse #{range}] + #GitHosting.logger.debug "Revisions in Range: #{revisions.split().join(' ')}" + + commits = [] + revisions_in_range.split().each do |rev| + revision = project.repository.find_changeset_by_name(rev.strip) + commit = { + :id => revision.revision, + :url => "", + :author => revision.author, + :message => "", + :timestamp => "", + :added => [], + :modified => [], + :removed => [] + } + revision.changes.each do |change| + if change.action == "M" + commit[:modified] << change.path + elsif change.action == "A" + commit[:added] << change.path + elsif change.action == "D" + commit[:removed] << change.path + end + end + commits << commit + end + + payloads << { + :before => oldhead, + :after => newhead, + :ref => refname, + :commits => commits, + :repository => { + :description => "", + :fork => "", + :homepage => "", + :name => "", + :open_issues => "", + :owner => { + :email => "", + :name => "" + }, + :private => true, + :url => "", + :watchers => 0 + } + } + end + payloads + end + def post_receive if not @project.repository.extra.validate_encoded_time(params[:clear_time], params[:encoded_time]) @@ -47,46 +121,28 @@ def post_receive output.flush } if @project.repository_mirrors.any? + payloads = post_receive_payloads(params[:refs]) + GitHosting.logger.info "Payloads - #{payloads.to_json}" + # Notify CIA #Thread.abort_on_exception = true - Thread.new(@project, params[:refs]) {|project, refs| + Thread.new(@project, payloads) {|project, payloads| GitHosting.logger.debug "Notifying CIA" output.write("Notifying CIA\n") output.flush #GitHosting.logger.debug "REFS #{refs}" - refs.each {|ref| - oldhead, newhead, refname = ref.split(',') - - # Only pay attention to branch updates - next if not refname.match(/refs\/heads\//) - - branch = refname.gsub('refs/heads/', '') - - if newhead.match(/^0{40}$/) - # Deleting a branch - GitHosting.logger.debug "Deleting branch \"#{branch}\"" - next - elsif oldhead.match(/^0{40}$/) - # Creating a branch - GitHosting.logger.debug "Creating branch \"#{branch}\"" - range = newhead - else - range = "#{oldhead}..#{newhead}" - end - - revisions = %x[#{GitHosting.git_exec} --git-dir='#{GitHosting.repository_path(@project)}' rev-list --reverse #{range}] - #GitHosting.logger.debug "Revisions in Range: #{revisions.split().join(' ')}" - - revisions.split().each{|rev| - revision = project.repository.find_changeset_by_name(rev.strip) - #GitHosting.logger.debug "Revision Found: #{revision.revision}" + payloads.each do |payload| + branch = payload[:ref].gsub('refs/heads/', '') + payload[:commits].each do |commit| + revision = project.repository.find_changeset_by_name(commit[:id]) next if project.repository.cia_notifications.notified?(revision) # Already notified about this commit GitHosting.logger.info "Notifying CIA: Branch => #{branch} REVISION => #{revision.revision}" CiaNotificationMailer.deliver_notification(revision, branch) project.repository.cia_notifications.notified(revision) - } - } + end + end + } if !params[:refs].nil? && @project.repository.extra.notify_cia==1 }, :layout => false end From fdf2669cd3eee9237d9558ffe676fc03d6f7166c Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Mon, 21 May 2012 16:50:55 -0500 Subject: [PATCH 044/107] Fill out post-receive url payload --- app/controllers/gitolite_hooks_controller.rb | 27 ++++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index 91aad0c7b..e5dfedeec 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -42,10 +42,13 @@ def post_receive_payloads(refs, project=nil) revision = project.repository.find_changeset_by_name(rev.strip) commit = { :id => revision.revision, - :url => "", - :author => revision.author, - :message => "", - :timestamp => "", + :url => url_for_revision(revision), + :author => { + :name => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), + :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') + }, + :message => revision.comments, + :timestamp => revision.committed_on, :added => [], :modified => [], :removed => [] @@ -68,16 +71,17 @@ def post_receive_payloads(refs, project=nil) :ref => refname, :commits => commits, :repository => { - :description => "", - :fork => "", - :homepage => "", - :name => "", - :open_issues => "", + :description => project.description, + :fork => false, + :forks => 0, + :homepage => project.homepage, + :name => project.identifier, + :open_issues => project.issues.open.length, :owner => { :email => "", :name => "" }, - :private => true, + :private => !project.is_public, :url => "", :watchers => 0 } @@ -122,7 +126,8 @@ def post_receive } if @project.repository_mirrors.any? payloads = post_receive_payloads(params[:refs]) - GitHosting.logger.info "Payloads - #{payloads.to_json}" + + # Post to each post-receive URL here # Notify CIA #Thread.abort_on_exception = true From 4f79cf980ec8ac9fd20e7c3c618dba0894700a5e Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Mon, 21 May 2012 16:51:13 -0500 Subject: [PATCH 045/107] Only count response code 200 as success in post-receive hook --- contrib/hooks/post-receive.redmine_gitolite.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/hooks/post-receive.redmine_gitolite.rb b/contrib/hooks/post-receive.redmine_gitolite.rb index 27dcd7d6c..b41336515 100755 --- a/contrib/hooks/post-receive.redmine_gitolite.rb +++ b/contrib/hooks/post-receive.redmine_gitolite.rb @@ -44,7 +44,7 @@ def run_query(url_str, params, with_https) req.set_form_data(params) response = http.request(req) do |response| response.read_body do |body_frag| - success = true + success = response.code.to_i == 200 ? true : false log(body_frag, false, false) end end From c30210beb4eb32dc6f9a8aeaced6777606f8e28d Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Mon, 21 May 2012 21:55:34 -0500 Subject: [PATCH 046/107] POST to post-receive URLs and add en translations --- app/controllers/gitolite_hooks_controller.rb | 96 ++++++++++++-------- config/locales/en.yml | 7 ++ 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index e5dfedeec..f32787ca7 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -12,6 +12,8 @@ def stub render(:code => 404) end + # Returns an array of GitHub post-receive hook style hashes + # http://help.github.com/post-receive-hooks/ def post_receive_payloads(refs, project=nil) project ||= @project payloads = [] @@ -41,49 +43,55 @@ def post_receive_payloads(refs, project=nil) revisions_in_range.split().each do |rev| revision = project.repository.find_changeset_by_name(rev.strip) commit = { - :id => revision.revision, - :url => url_for_revision(revision), - :author => { - :name => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), - :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') + "id" => revision.revision, + "url" => url_for(:controller => "repositories", :action => "revision", + :id => project, :rev => rev, :only_path => false, + :host => Setting['host_name'], :protocol => Setting['protocol'] + ), + "author" => { + "name" => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), + "email" => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') }, - :message => revision.comments, - :timestamp => revision.committed_on, - :added => [], - :modified => [], - :removed => [] + "message" => revision.comments, + "timestamp" => revision.committed_on, + "added" => [], + "modified" => [], + "removed" => [] } revision.changes.each do |change| if change.action == "M" - commit[:modified] << change.path + commit["modified"] << change.path elsif change.action == "A" - commit[:added] << change.path + commit["added"] << change.path elsif change.action == "D" - commit[:removed] << change.path + commit["removed"] << change.path end end commits << commit end payloads << { - :before => oldhead, - :after => newhead, - :ref => refname, - :commits => commits, - :repository => { - :description => project.description, - :fork => false, - :forks => 0, - :homepage => project.homepage, - :name => project.identifier, - :open_issues => project.issues.open.length, - :owner => { - :email => "", - :name => "" + "before" => oldhead, + "after" => newhead, + "ref" => refname, + "commits" => commits, + "repository" => { + "description" => project.description, + "fork" => false, + "forks" => 0, + "homepage" => project.homepage, + "name" => project.identifier, + "open_issues" => project.issues.open.length, + "owner" => { + "name" => Setting["app_title"], + "email" => Setting["mail_from"] }, - :private => !project.is_public, - :url => "", - :watchers => 0 + "private" => !project.is_public, + "url" => url_for(:controller => "repositories", :action => "show", + :id => project, :only_path => false, + :host => Setting["host_name"], :protocol => Setting["protocol"] + ), + "watchers" => 0 } } end @@ -125,9 +133,26 @@ def post_receive output.flush } if @project.repository_mirrors.any? - payloads = post_receive_payloads(params[:refs]) + payloads = [] + if @project.repository.extra.notify_cia == 1 or @project.repository_post_receive_urls.any? + payloads = post_receive_payloads(params[:refs]) + end - # Post to each post-receive URL here + # Post to each post-receive URL + @project.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each do |prurl| + msg = "Posting #{payloads.length} post-receive payloads to #{prurl.url} ... " + GitHosting.logger.debug msg + output.write msg + output.flush + uri = URI(prurl.url) + payloads.each do |payload| + res = Net::HTTP.post_form(uri, {"payload" => payload}) + output.write res.is_a?(Net::HTTPSuccess) ? "[success] " : "[failure] " + output.flush + end + output.write "done\n" + output.flush + end if @project.repository_post_receive_urls.any? # Notify CIA #Thread.abort_on_exception = true @@ -135,12 +160,11 @@ def post_receive GitHosting.logger.debug "Notifying CIA" output.write("Notifying CIA\n") output.flush - #GitHosting.logger.debug "REFS #{refs}" payloads.each do |payload| - branch = payload[:ref].gsub('refs/heads/', '') - payload[:commits].each do |commit| - revision = project.repository.find_changeset_by_name(commit[:id]) + branch = payload["ref"].gsub("refs/heads/", "") + payload["commits"].each do |commit| + revision = project.repository.find_changeset_by_name(commit["id"]) next if project.repository.cia_notifications.notified?(revision) # Already notified about this commit GitHosting.logger.info "Notifying CIA: Branch => #{branch} REVISION => #{revision.revision}" CiaNotificationMailer.deliver_notification(revision, branch) diff --git a/config/locales/en.yml b/config/locales/en.yml index 9f40e84d0..cb518cad1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -97,6 +97,13 @@ en: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL button_push: Push button_push_title: "Push To Mirror" From 419c5b4a9bd214678d792e2f95d24b6b44a98179 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Tue, 22 May 2012 01:57:30 -0500 Subject: [PATCH 047/107] Adding post-receive URL modes --- app/models/repository_post_receive_url.rb | 8 ++++++++ app/views/repository_post_receive_urls/_form.html.erb | 1 + db/migrate/2012052200000_add_post_receive_url_modes.rb | 9 +++++++++ 3 files changed, 18 insertions(+) create mode 100644 db/migrate/2012052200000_add_post_receive_url_modes.rb diff --git a/app/models/repository_post_receive_url.rb b/app/models/repository_post_receive_url.rb index 032b65c67..bdbddea6a 100644 --- a/app/models/repository_post_receive_url.rb +++ b/app/models/repository_post_receive_url.rb @@ -12,6 +12,14 @@ class RepositoryPostReceiveUrl < ActiveRecord::Base named_scope :active, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_ACTIVE}} named_scope :inactive, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_INACTIVE}} + validates_inclusion_of :mode, :in => [:github, :get] + def mode + read_attribute(:mode).to_sym rescue nil + end + def mode= (value) + write_attribute(:mode, (value.to_sym && value.to_sym.to_s rescue nil)) + end + def to_s return File.join("#{project.identifier}-#{url}") end diff --git a/app/views/repository_post_receive_urls/_form.html.erb b/app/views/repository_post_receive_urls/_form.html.erb index a2b1bd099..eaef09275 100644 --- a/app/views/repository_post_receive_urls/_form.html.erb +++ b/app/views/repository_post_receive_urls/_form.html.erb @@ -4,5 +4,6 @@

<%= f.text_field :url, :required => true, :size => 65 %>

<%= f.check_box :active %>

+

<%= f.select :mode, [['Github POST', 'github'], ['Empty GET', 'get']] %>

diff --git a/db/migrate/2012052200000_add_post_receive_url_modes.rb b/db/migrate/2012052200000_add_post_receive_url_modes.rb new file mode 100644 index 000000000..87b29ca4b --- /dev/null +++ b/db/migrate/2012052200000_add_post_receive_url_modes.rb @@ -0,0 +1,9 @@ +class AddPostReceiveUrlModes < ActiveRecord::Migration + def self.up + add_column :repository_post_receive_urls, :mode, :string, :default => "github" + end + + def self.down + remove_column :repository_post_receive_urls, :mode + end +end \ No newline at end of file From ac97f06950f0547d27a8be4b1ac5873ad89cf3b5 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Tue, 22 May 2012 02:20:02 -0500 Subject: [PATCH 048/107] Add GET style hook and missing translation --- app/controllers/gitolite_hooks_controller.rb | 6 +++++- app/views/repository_post_receive_urls/_form.html.erb | 2 +- config/locales/en.yml | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index f32787ca7..d378d0c45 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -146,7 +146,11 @@ def post_receive output.flush uri = URI(prurl.url) payloads.each do |payload| - res = Net::HTTP.post_form(uri, {"payload" => payload}) + if prurl.mode == :github + res = Net::HTTP.post_form(uri, {"payload" => payload}) + else + res = Net::HTTP.get_response(uri) + end output.write res.is_a?(Net::HTTPSuccess) ? "[success] " : "[failure] " output.flush end diff --git a/app/views/repository_post_receive_urls/_form.html.erb b/app/views/repository_post_receive_urls/_form.html.erb index eaef09275..c1eb6ad07 100644 --- a/app/views/repository_post_receive_urls/_form.html.erb +++ b/app/views/repository_post_receive_urls/_form.html.erb @@ -4,6 +4,6 @@

<%= f.text_field :url, :required => true, :size => 65 %>

<%= f.check_box :active %>

-

<%= f.select :mode, [['Github POST', 'github'], ['Empty GET', 'get']] %>

+

<%= f.select :mode, [['Github-style POST', :github], ['Empty GET request', :get]] %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index cb518cad1..76b65a37b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -104,6 +104,7 @@ en: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" From 5f0d62202a57cc0c61624505ef58017429f8d710 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sun, 8 Jul 2012 19:41:48 -0700 Subject: [PATCH 049/107] Some fixes to previous merge Fix syntax error in gitolite_hoooks_controller.rb, turn payload into json. Addition of changes to missing non-english locals. --- app/controllers/gitolite_hooks_controller.rb | 10 +++++----- config/locales/bg.yml | 8 ++++++++ config/locales/bs.yml | 8 ++++++++ config/locales/ca.yml | 8 ++++++++ config/locales/cs.yml | 8 ++++++++ config/locales/da.yml | 8 ++++++++ config/locales/de.yml | 8 ++++++++ config/locales/el.yml | 8 ++++++++ config/locales/es.yml | 8 ++++++++ config/locales/fi.yml | 8 ++++++++ config/locales/fr.yml | 8 ++++++++ config/locales/gl.yml | 8 ++++++++ config/locales/he.yml | 8 ++++++++ config/locales/hu.yml | 8 ++++++++ config/locales/id.yml | 8 ++++++++ config/locales/it.yml | 8 ++++++++ config/locales/ja.yml | 8 ++++++++ config/locales/ko.yml | 8 ++++++++ config/locales/lt.yml | 8 ++++++++ config/locales/nl.yml | 8 ++++++++ config/locales/no.yml | 8 ++++++++ config/locales/pl.yml | 8 ++++++++ config/locales/pt-BR.yml | 8 ++++++++ config/locales/pt.yml | 8 ++++++++ config/locales/ro.yml | 8 ++++++++ config/locales/ru.yml | 8 ++++++++ config/locales/sk.yml | 8 ++++++++ config/locales/sl.yml | 8 ++++++++ config/locales/sr.yml | 8 ++++++++ config/locales/sv.yml | 8 ++++++++ config/locales/th.yml | 8 ++++++++ config/locales/tr.yml | 8 ++++++++ config/locales/uk.yml | 8 ++++++++ config/locales/vi.yml | 8 ++++++++ config/locales/zh-TW.yml | 8 ++++++++ config/locales/zh.yml | 8 ++++++++ 36 files changed, 285 insertions(+), 5 deletions(-) diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index d378d0c45..5e4ffa5da 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -139,24 +139,24 @@ def post_receive end # Post to each post-receive URL - @project.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each do |prurl| + @project.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each {|prurl| msg = "Posting #{payloads.length} post-receive payloads to #{prurl.url} ... " GitHosting.logger.debug msg output.write msg output.flush uri = URI(prurl.url) - payloads.each do |payload| + payloads.each {|payload| if prurl.mode == :github - res = Net::HTTP.post_form(uri, {"payload" => payload}) + res = Net::HTTP.post_form(uri, {"payload" => payload.to_json}) else res = Net::HTTP.get_response(uri) end output.write res.is_a?(Net::HTTPSuccess) ? "[success] " : "[failure] " output.flush - end + } output.write "done\n" output.flush - end if @project.repository_post_receive_urls.any? + } if @project.repository_post_receive_urls.any? # Notify CIA #Thread.abort_on_exception = true diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 63ad782e4..4874e660d 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -97,6 +97,14 @@ bg: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 123b8792f..d538a6add 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -97,6 +97,14 @@ bs: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 233a1b3eb..a4422cba5 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -97,6 +97,14 @@ ca: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 8d164a8f8..339c6b91d 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -97,6 +97,14 @@ cs: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/da.yml b/config/locales/da.yml index 17e563f99..4463d1e1e 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -97,6 +97,14 @@ da: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/de.yml b/config/locales/de.yml index 402817ec5..268082810 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -97,6 +97,14 @@ de: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/el.yml b/config/locales/el.yml index b05ee6d80..cebbce77a 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -97,6 +97,14 @@ el: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/es.yml b/config/locales/es.yml index 5c5ad557d..ca0287ce7 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -97,6 +97,14 @@ es: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/fi.yml b/config/locales/fi.yml index edd75d0f4..74bd491a2 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -97,6 +97,14 @@ fi: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0729d0341..66d610487 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -97,6 +97,14 @@ fr: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/gl.yml b/config/locales/gl.yml index a72d964a6..d1cc43e87 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -97,6 +97,14 @@ gl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/he.yml b/config/locales/he.yml index fc5dd5156..506b85769 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -97,6 +97,14 @@ he: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/hu.yml b/config/locales/hu.yml index cee16f1d6..fb975da9b 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -97,6 +97,14 @@ mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/id.yml b/config/locales/id.yml index 3fed64dbe..517b66576 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -97,6 +97,14 @@ id: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/it.yml b/config/locales/it.yml index 1da49dbdc..c5768e5c6 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -97,6 +97,14 @@ it: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ja.yml b/config/locales/ja.yml index a5b39f84e..29b704cb3 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -97,6 +97,14 @@ ja: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 170a5ec98..d1eaddb4c 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -97,6 +97,14 @@ ko: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/lt.yml b/config/locales/lt.yml index a76985484..bb65623a7 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -97,6 +97,14 @@ lt: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index a316f7606..a476c6317 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -97,6 +97,14 @@ nl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/no.yml b/config/locales/no.yml index e1bd72835..a78a53afe 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -97,6 +97,14 @@ mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index ff42b29dc..a9cf04ac6 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -97,6 +97,14 @@ pl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 7033058aa..63f0d25bb 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -95,6 +95,14 @@ pt-BR: mirror_notice_created: O Espelho foi criado com sucesso mirror_notice_updated: O Espelho foi actualizado com sucesso mirror_notice_create_failed: Houve um erro na criação do espelho + label_post_receive_url_create: Criar URL Pós-receber do Repositório + label_post_receive_url_delete: Apagar URL Pós-receber do Repositório + label_post_receive_url_edit: Editar URL Pós-receber do Repositório + post_receive_url_notice_deleted: A URL Pós-receber foi apagado com sucesso + post_receive_url_notice_created: A URL Pós-receber foi criado com sucesso + post_receive_url_notice_updated: A URL Pós-receber foi actualizado com sucesso + post_receive_url_notice_create_failed: Houve um erro na criação a URL pós-receber + field_mode: Modo button_push: Push button_push_title: '"Push" Para o Espelho' diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 1cd5c9e09..65d13594f 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -95,6 +95,14 @@ pt: mirror_notice_created: O Espelho foi criado com sucesso mirror_notice_updated: O Espelho foi actualizado com sucesso mirror_notice_create_failed: Houve um erro na criação do espelho + label_post_receive_url_create: Criar URL Pós-receber do Repositório + label_post_receive_url_delete: Apagar URL Pós-receber do Repositório + label_post_receive_url_edit: Editar URL Pós-receber do Repositório + post_receive_url_notice_deleted: A URL Pós-receber foi apagado com sucesso + post_receive_url_notice_created: A URL Pós-receber foi criado com sucesso + post_receive_url_notice_updated: A URL Pós-receber foi actualizado com sucesso + post_receive_url_notice_create_failed: Houve um erro na criação a URL pós-receber + field_mode: Modo button_push: Push button_push_title: '"Push" Para o Espelho' diff --git a/config/locales/ro.yml b/config/locales/ro.yml index c99e6c668..8f189b34f 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -97,6 +97,14 @@ ro: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 2de09662a..b46c33481 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -97,6 +97,14 @@ ru: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 90e759cf6..496573859 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -97,6 +97,14 @@ sk: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 3af213732..20c8cb373 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -97,6 +97,14 @@ sl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 1c5fd819f..57f645b01 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -97,6 +97,14 @@ sr: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 1412847cd..9e40cb72a 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -97,6 +97,14 @@ sv: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/th.yml b/config/locales/th.yml index 732979c00..1974ae849 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -97,6 +97,14 @@ th: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/tr.yml b/config/locales/tr.yml index a4b93fbe2..4af0679fd 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -97,6 +97,14 @@ tr: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 73a919bd9..61c49a5d3 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -97,6 +97,14 @@ uk: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 12ec7ea98..3244c4c8d 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -97,6 +97,14 @@ vi: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 9fcf74cef..26f31f91e 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -97,6 +97,14 @@ mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/zh.yml b/config/locales/zh.yml index a76282b8a..600ee580f 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -97,6 +97,14 @@ zh: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + label_post_receive_url_create: Create Repository Post-receive URL + label_post_receive_url_delete: Delete Repository Post-receive URL + label_post_receive_url_edit: Edit Repository Post-receive URL + post_receive_url_notice_deleted: Post-receive URL was successfully deleted + post_receive_url_notice_created: Post-receive URL was successfully created + post_receive_url_notice_updated: Post-receive URL was successfully updated + post_receive_url_notice_create_failed: Failed to create Post-receive URL + field_mode: Mode button_push: Push button_push_title: "Push To Mirror" From ca73c30c0f443ab0dcb299a5d1ee4d3a611cfad6 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sun, 8 Jul 2012 17:05:57 -0700 Subject: [PATCH 050/107] Added new parameter 'gitForceHooksUpdate'. When true, will replace existing hooks if they don't match our hooks. Default value == true. --- .../settings/_redmine_git_hosting.html.erb | 6 ++++ config/locales/bg.yml | 1 + config/locales/bs.yml | 1 + config/locales/ca.yml | 1 + config/locales/cs.yml | 1 + config/locales/da.yml | 1 + config/locales/de.yml | 1 + config/locales/el.yml | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + config/locales/fi.yml | 1 + config/locales/fr.yml | 1 + config/locales/gl.yml | 1 + config/locales/he.yml | 1 + config/locales/hu.yml | 1 + config/locales/id.yml | 1 + config/locales/it.yml | 1 + config/locales/ja.yml | 1 + config/locales/ko.yml | 1 + config/locales/lt.yml | 1 + config/locales/nl.yml | 1 + config/locales/no.yml | 1 + config/locales/pl.yml | 1 + config/locales/pt-BR.yml | 3 +- config/locales/pt.yml | 3 +- config/locales/ro.yml | 1 + config/locales/ru.yml | 1 + config/locales/sk.yml | 1 + config/locales/sl.yml | 1 + config/locales/sr.yml | 1 + config/locales/sv.yml | 1 + config/locales/th.yml | 1 + config/locales/tr.yml | 1 + config/locales/uk.yml | 1 + config/locales/vi.yml | 1 + config/locales/zh-TW.yml | 1 + config/locales/zh.yml | 1 + ...20120708070841_add_settings_to_plugin_4.rb | 30 +++++++++++++++++++ init.rb | 3 +- lib/git_adapter_hooks.rb | 18 +++++++++-- 40 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20120708070841_add_settings_to_plugin_4.rb diff --git a/app/views/settings/_redmine_git_hosting.html.erb b/app/views/settings/_redmine_git_hosting.html.erb index d1c9b98d6..964bd18d9 100644 --- a/app/views/settings/_redmine_git_hosting.html.erb +++ b/app/views/settings/_redmine_git_hosting.html.erb @@ -149,6 +149,12 @@

+

+ + <%= select_tag("settings[gitForceHooksUpdate]", options_for_select([[l(:label_enabled), 'true'], [l(:label_disabled), 'false']], @settings['gitForceHooksUpdate'])) %> +
+

+

<%= image_tag((GitHosting.check_hooks_installed==true ? 'true.png' : 'exclamation.png'), :style => "vertical-align:bottom;") %> diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 4874e660d..90369c87a 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -53,6 +53,7 @@ bg: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/bs.yml b/config/locales/bs.yml index d538a6add..a07ee8281 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -53,6 +53,7 @@ bs: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/ca.yml b/config/locales/ca.yml index a4422cba5..f3a4623e6 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -53,6 +53,7 @@ ca: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 339c6b91d..355cf0e7a 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -53,6 +53,7 @@ cs: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/da.yml b/config/locales/da.yml index 4463d1e1e..964ab7e01 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -53,6 +53,7 @@ da: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/de.yml b/config/locales/de.yml index 268082810..432794079 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -53,6 +53,7 @@ de: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/el.yml b/config/locales/el.yml index cebbce77a..b1904bddb 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -53,6 +53,7 @@ el: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/en.yml b/config/locales/en.yml index 76b65a37b..ee42d882c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -53,6 +53,7 @@ en: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/es.yml b/config/locales/es.yml index ca0287ce7..e9a0f885f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -53,6 +53,7 @@ es: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 74bd491a2..9424829fa 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -53,6 +53,7 @@ fi: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 66d610487..8ecf752aa 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -53,6 +53,7 @@ fr: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/gl.yml b/config/locales/gl.yml index d1cc43e87..35face782 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -53,6 +53,7 @@ gl: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/he.yml b/config/locales/he.yml index 506b85769..84ccc999d 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -53,6 +53,7 @@ he: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/hu.yml b/config/locales/hu.yml index fb975da9b..bed9f8065 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -53,6 +53,7 @@ label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/id.yml b/config/locales/id.yml index 517b66576..77a9d5690 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -53,6 +53,7 @@ id: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/it.yml b/config/locales/it.yml index c5768e5c6..0ef334525 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -53,6 +53,7 @@ it: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 29b704cb3..648739795 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -53,6 +53,7 @@ ja: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/ko.yml b/config/locales/ko.yml index d1eaddb4c..8fad159ab 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -53,6 +53,7 @@ ko: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/lt.yml b/config/locales/lt.yml index bb65623a7..2a3bd2660 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -53,6 +53,7 @@ lt: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/nl.yml b/config/locales/nl.yml index a476c6317..80bb52710 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -53,6 +53,7 @@ nl: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/no.yml b/config/locales/no.yml index a78a53afe..1b18c2219 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -53,6 +53,7 @@ label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/pl.yml b/config/locales/pl.yml index a9cf04ac6..ffa1e846b 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -53,6 +53,7 @@ pl: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 63f0d25bb..3ec1c398c 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -50,7 +50,8 @@ pt-BR: label_git_hook_header: Hooks dos Repositórios label_git_hook_debug: Correr os hooks em mode Debug - label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_hooks_are_asynchronous: Run hooks de forma assíncrona + label_git_force_hooks_update: Força hooks a ser atualizado label_hook_installed: '"Hook" instalado' label_public_keys: 'Chaves públicas' diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 65d13594f..2bf7d381b 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -50,7 +50,8 @@ pt: label_git_hook_header: Hooks dos Repositórios label_git_hook_debug: Correr os hooks em mode Debug - label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_hooks_are_asynchronous: Run hooks de forma assíncrona + label_git_force_hooks_update: Força hooks a ser atualizado label_hook_installed: '"Hook" instalado' label_public_keys: 'Chaves públicas' diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 8f189b34f..ea02ac671 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -53,6 +53,7 @@ ro: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b46c33481..60ea86c82 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -53,6 +53,7 @@ ru: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 496573859..0c0eed31a 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -53,6 +53,7 @@ sk: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 20c8cb373..7df5290f2 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -53,6 +53,7 @@ sl: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 57f645b01..ce2b7bf4b 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -53,6 +53,7 @@ sr: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 9e40cb72a..fc35e0b08 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -53,6 +53,7 @@ sv: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/th.yml b/config/locales/th.yml index 1974ae849..5b514b609 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -53,6 +53,7 @@ th: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 4af0679fd..efeed1e4a 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -53,6 +53,7 @@ tr: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 61c49a5d3..095daa8a2 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -53,6 +53,7 @@ uk: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 3244c4c8d..5cb6ddd2a 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -53,6 +53,7 @@ vi: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 26f31f91e..207029ef2 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -53,6 +53,7 @@ label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 600ee580f..a79e42948 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -53,6 +53,7 @@ zh: label_git_hook_header: Repository Hooks label_git_hook_debug: Run hooks in Debug mode label_git_hooks_are_asynchronous: Run hooks asynchronously + label_git_force_hooks_update: Force hooks to be Updated label_hook_installed: Hook installed label_public_keys: Public keys diff --git a/db/migrate/20120708070841_add_settings_to_plugin_4.rb b/db/migrate/20120708070841_add_settings_to_plugin_4.rb new file mode 100644 index 000000000..08c2d3e89 --- /dev/null +++ b/db/migrate/20120708070841_add_settings_to_plugin_4.rb @@ -0,0 +1,30 @@ +class AddSettingsToPlugin4 < ActiveRecord::Migration + def self.up + begin + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitForceHooksUpdate'] ||= 'true' + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Added redmine_git_hosting settings: 'gitForceHooksUpdate'." + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if plugin settings don't exist yet + end + end + + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitForceHooksUpdate') + + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Removed redmine_git_hosting settings: 'gitForceHooksUpdate'." + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if table doesn't exist yet.... + end + end +end diff --git a/init.rb b/init.rb index 2b78bd1f7..2421f3455 100755 --- a/init.rb +++ b/init.rb @@ -38,7 +38,8 @@ 'gitHooksDebug' => 'false', 'gitHooksAreAsynchronous' => 'true', 'gitTempDataDir' => '/tmp/redmine_git_hosting/', - 'gitScriptDir' => '' + 'gitScriptDir' => '', + 'gitForceHooksUpdate' => 'true' }, :partial => 'redmine_git_hosting' project_module :repository do diff --git a/lib/git_adapter_hooks.rb b/lib/git_adapter_hooks.rb index 395c81342..bc39840cb 100755 --- a/lib/git_adapter_hooks.rb +++ b/lib/git_adapter_hooks.rb @@ -45,10 +45,24 @@ def self.check_hooks_installed @@check_hooks_installed_cached = true return @@check_hooks_installed_cached else - error_msg = "\"post-receive\" is alreay present but it's not ours!" + error_msg = "\"post-receive\" is already present but it's not ours!" logger.warn error_msg - @@check_hooks_installed_stamp = Time.new @@check_hooks_installed_cached = error_msg + if Setting.plugin_redmine_git_hosting['gitForceHooksUpdate']!='false' + begin + logger.info "Restoring \"post-receive\" hook since forceInstallHook==true" + install_hook("post-receive.redmine_gitolite.rb") + logger.info "\"post-receive.redmine_gitolite\ installed" + logger.info "Running \"gl-setup\" on the gitolite install..." + GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] + update_global_hook_params + logger.info "Finished installing hooks in the gitolite install..." + @@check_hooks_installed_cached = true + rescue + logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" + end + end + @@check_hooks_installed_stamp = Time.new return @@check_hooks_installed_cached end end From 12d51e76c4621a7287726a83b20179b5feb2a181 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sat, 14 Jul 2012 13:54:24 -0700 Subject: [PATCH 051/107] Remove control characters from key text. --- app/models/gitolite_public_key.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 1ab33ee83..6f250e455 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -13,6 +13,7 @@ class GitolitePublicKey < ActiveRecord::Base validate :has_not_been_changed before_validation :set_identifier + before_validation :remove_control_characters def has_not_been_changed unless new_record? @@ -45,6 +46,11 @@ def reset_identifier self.identifier end + # Remove control characters from key + def remove_control_characters + self.key=key.gsub(/[\a\r\n\t]/,'') + end + def to_s ; title ; end @@myregular = /^redmine_(.*)_\d*_\d*(.pub)?$/ From e9f4b2922bd7dd4aaec40aedbb263286cec1bc9a Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Mon, 16 Jul 2012 14:06:16 -0700 Subject: [PATCH 052/107] Updated ModalBox to latest version. --- assets/javascripts/modalbox/lib/builder.js | 136 - assets/javascripts/modalbox/lib/effects.js | 614 ++-- assets/javascripts/modalbox/lib/prototype.js | 2883 +++++++++++------ .../javascripts/modalbox/lib/scriptaculous.js | 8 +- assets/javascripts/modalbox/lib/unittest.js | 25 +- assets/javascripts/modalbox/modalbox.js | 474 ++- assets/stylesheets/modalbox/modalbox.css | 61 +- 7 files changed, 2498 insertions(+), 1703 deletions(-) delete mode 100644 assets/javascripts/modalbox/lib/builder.js mode change 100755 => 100644 assets/javascripts/modalbox/modalbox.js diff --git a/assets/javascripts/modalbox/lib/builder.js b/assets/javascripts/modalbox/lib/builder.js deleted file mode 100644 index 5b4ce87d2..000000000 --- a/assets/javascripts/modalbox/lib/builder.js +++ /dev/null @@ -1,136 +0,0 @@ -// script.aculo.us builder.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 - -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -var Builder = { - NODEMAP: { - AREA: 'map', - CAPTION: 'table', - COL: 'table', - COLGROUP: 'table', - LEGEND: 'fieldset', - OPTGROUP: 'select', - OPTION: 'select', - PARAM: 'object', - TBODY: 'table', - TD: 'table', - TFOOT: 'table', - TH: 'table', - THEAD: 'table', - TR: 'table' - }, - // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, - // due to a Firefox bug - node: function(elementName) { - elementName = elementName.toUpperCase(); - - // try innerHTML approach - var parentTag = this.NODEMAP[elementName] || 'div'; - var parentElement = document.createElement(parentTag); - try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 - parentElement.innerHTML = "<" + elementName + ">"; - } catch(e) {} - var element = parentElement.firstChild || null; - - // see if browser added wrapping tags - if(element && (element.tagName.toUpperCase() != elementName)) - element = element.getElementsByTagName(elementName)[0]; - - // fallback to createElement approach - if(!element) element = document.createElement(elementName); - - // abort if nothing could be created - if(!element) return; - - // attributes (or text) - if(arguments[1]) - if(this._isStringOrNumber(arguments[1]) || - (arguments[1] instanceof Array) || - arguments[1].tagName) { - this._children(element, arguments[1]); - } else { - var attrs = this._attributes(arguments[1]); - if(attrs.length) { - try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707 - parentElement.innerHTML = "<" +elementName + " " + - attrs + ">"; - } catch(e) {} - element = parentElement.firstChild || null; - // workaround firefox 1.0.X bug - if(!element) { - element = document.createElement(elementName); - for(attr in arguments[1]) - element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; - } - if(element.tagName.toUpperCase() != elementName) - element = parentElement.getElementsByTagName(elementName)[0]; - } - } - - // text, or array of children - if(arguments[2]) - this._children(element, arguments[2]); - - return element; - }, - _text: function(text) { - return document.createTextNode(text); - }, - - ATTR_MAP: { - 'className': 'class', - 'htmlFor': 'for' - }, - - _attributes: function(attributes) { - var attrs = []; - for(attribute in attributes) - attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) + - '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'"') + '"'); - return attrs.join(" "); - }, - _children: function(element, children) { - if(children.tagName) { - element.appendChild(children); - return; - } - if(typeof children=='object') { // array can hold nodes and text - children.flatten().each( function(e) { - if(typeof e=='object') - element.appendChild(e) - else - if(Builder._isStringOrNumber(e)) - element.appendChild(Builder._text(e)); - }); - } else - if(Builder._isStringOrNumber(children)) - element.appendChild(Builder._text(children)); - }, - _isStringOrNumber: function(param) { - return(typeof param=='string' || typeof param=='number'); - }, - build: function(html) { - var element = this.node('div'); - $(element).update(html.strip()); - return element.down(); - }, - dump: function(scope) { - if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope - - var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " + - "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " + - "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+ - "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+ - "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+ - "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/); - - tags.each( function(tag){ - scope[tag] = function() { - return Builder.node.apply(Builder, [tag].concat($A(arguments))); - } - }); - } -} diff --git a/assets/javascripts/modalbox/lib/effects.js b/assets/javascripts/modalbox/lib/effects.js index 70d07526c..b8c0259f5 100644 --- a/assets/javascripts/modalbox/lib/effects.js +++ b/assets/javascripts/modalbox/lib/effects.js @@ -1,4 +1,4 @@ -// script.aculo.us effects.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 +// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008 // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: @@ -13,17 +13,17 @@ // returns self (or first argument) if not convertable String.prototype.parseColor = function() { var color = '#'; - if(this.slice(0,4) == 'rgb(') { + if (this.slice(0,4) == 'rgb(') { var cols = this.slice(4,this.length-1).split(','); var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); } else { - if(this.slice(0,1) == '#') { - if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if(this.length==7) color = this.toLowerCase(); + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); } } - return(color.length==7 ? color : (arguments[0] || this)); -} + return (color.length==7 ? color : (arguments[0] || this)); +}; /*--------------------------------------------------------------------------*/ @@ -32,7 +32,7 @@ Element.collectTextNodes = function(element) { return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); -} +}; Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { @@ -40,18 +40,18 @@ Element.collectTextNodesIgnoreClass = function(element, className) { ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); -} +}; Element.setContentZoom = function(element, percent) { element = $(element); element.setStyle({fontSize: (percent/100) + 'em'}); - if(Prototype.Browser.WebKit) window.scrollBy(0,0); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; -} +}; Element.getInlineOpacity = function(element){ return $(element).style.opacity || ''; -} +}; Element.forceRerendering = function(element) { try { @@ -64,31 +64,63 @@ Element.forceRerendering = function(element) { /*--------------------------------------------------------------------------*/ -Array.prototype.call = function() { - var args = arguments; - this.each(function(f){ f.apply(this, args) }); -} - -/*--------------------------------------------------------------------------*/ - var Effect = { _elementDoesNotExistError: { name: 'ElementDoesNotExistError', message: 'The specified DOM element does not exist, but is required for this effect to operate' }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + ((pos % (1/pulses)) * pulses).round() == 0 ? + ((pos * pulses * 2) - (pos * pulses * 2).floor()) : + 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) + ); + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, tagifyText: function(element) { - if(typeof Builder == 'undefined') - throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); - var tagifyStyle = 'position:relative'; - if(Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; element = $(element); $A(element.childNodes).each( function(child) { - if(child.nodeType==3) { + if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( - Builder.node('span',{style: tagifyStyle}, + new Element('span', {style: tagifyStyle}).update( character == ' ' ? String.fromCharCode(160) : character), child); }); @@ -98,8 +130,8 @@ var Effect = { }, multiple: function(element, effect) { var elements; - if(((typeof element == 'object') || - (typeof element == 'function')) && + if (((typeof element == 'object') || + Object.isFunction(element)) && (element.length)) elements = element; else @@ -108,7 +140,7 @@ var Effect = { var options = Object.extend({ speed: 0.1, delay: 0.0 - }, arguments[2] || {}); + }, arguments[2] || { }); var masterDelay = options.delay; $A(elements).each( function(element, index) { @@ -125,51 +157,17 @@ var Effect = { effect = (effect || 'appear').toLowerCase(); var options = Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } - }, arguments[2] || {}); + }, arguments[2] || { }); Effect[element.visible() ? Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); } }; -var Effect2 = Effect; // deprecated - -/* ------------- transitions ------------- */ - -Effect.Transitions = { - linear: Prototype.K, - sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; - }, - reverse: function(pos) { - return 1-pos; - }, - flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; - return (pos > 1 ? 1 : pos); - }, - wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; - }, - pulse: function(pos, pulses) { - pulses = pulses || 5; - return ( - Math.round((pos % (1/pulses)) * pulses) == 0 ? - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : - 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) - ); - }, - none: function(pos) { - return 0; - }, - full: function(pos) { - return 1; - } -}; +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; /* ------------- core effects ------------- */ -Effect.ScopedQueue = Class.create(); -Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { +Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; this.interval = null; @@ -180,7 +178,7 @@ Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { add: function(effect) { var timestamp = new Date().getTime(); - var position = (typeof effect.options.queue == 'string') ? + var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; switch(position) { @@ -203,15 +201,15 @@ Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { effect.startOn += timestamp; effect.finishOn += timestamp; - if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); - if(!this.interval) + if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, remove: function(effect) { this.effects = this.effects.reject(function(e) { return e==effect }); - if(this.effects.length == 0) { + if (this.effects.length == 0) { clearInterval(this.interval); this.interval = null; } @@ -226,29 +224,15 @@ Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { Effect.Queues = { instances: $H(), get: function(queueName) { - if(typeof queueName != 'string') return queueName; + if (!Object.isString(queueName)) return queueName; - if(!this.instances[queueName]) - this.instances[queueName] = new Effect.ScopedQueue(); - - return this.instances[queueName]; + return this.instances.get(queueName) || + this.instances.set(queueName, new Effect.ScopedQueue()); } -} +}; Effect.Queue = Effect.Queues.get('global'); -Effect.DefaultOptions = { - transition: Effect.Transitions.sinoidal, - duration: 1.0, // seconds - fps: 100, // 100= assume 66fps max. - sync: false, // true for combining - from: 0.0, - to: 1.0, - delay: 0.0, - queue: 'parallel' -} - -Effect.Base = function() {}; -Effect.Base.prototype = { +Effect.Base = Class.create({ position: null, start: function(options) { function codeForEvent(options,eventName){ @@ -257,8 +241,8 @@ Effect.Base.prototype = { (options[eventName] ? 'this.options.'+eventName+'(this);' : '') ); } - if(options.transition === false) options.transition = Effect.Transitions.linear; - this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + if (options && options.transition === false) options.transition = Effect.Transitions.linear; + this.options = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { }); this.currentFrame = 0; this.state = 'idle'; this.startOn = this.options.delay*1000; @@ -268,61 +252,60 @@ Effect.Base.prototype = { this.totalFrames = this.options.fps*this.options.duration; eval('this.render = function(pos){ '+ - 'if(this.state=="idle"){this.state="running";'+ - codeForEvent(options,'beforeSetup')+ + 'if (this.state=="idle"){this.state="running";'+ + codeForEvent(this.options,'beforeSetup')+ (this.setup ? 'this.setup();':'')+ - codeForEvent(options,'afterSetup')+ - '};if(this.state=="running"){'+ + codeForEvent(this.options,'afterSetup')+ + '};if (this.state=="running"){'+ 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+ 'this.position=pos;'+ - codeForEvent(options,'beforeUpdate')+ + codeForEvent(this.options,'beforeUpdate')+ (this.update ? 'this.update(pos);':'')+ - codeForEvent(options,'afterUpdate')+ + codeForEvent(this.options,'afterUpdate')+ '}}'); this.event('beforeStart'); - if(!this.options.sync) - Effect.Queues.get(typeof this.options.queue == 'string' ? + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).add(this); }, loop: function(timePos) { - if(timePos >= this.startOn) { - if(timePos >= this.finishOn) { + if (timePos >= this.startOn) { + if (timePos >= this.finishOn) { this.render(1.0); this.cancel(); this.event('beforeFinish'); - if(this.finish) this.finish(); + if (this.finish) this.finish(); this.event('afterFinish'); return; } var pos = (timePos - this.startOn) / this.totalTime, - frame = Math.round(pos * this.totalFrames); - if(frame > this.currentFrame) { + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { this.render(pos); this.currentFrame = frame; } } }, cancel: function() { - if(!this.options.sync) - Effect.Queues.get(typeof this.options.queue == 'string' ? + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, event: function(eventName) { - if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); - if(this.options[eventName]) this.options[eventName](this); + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); }, inspect: function() { var data = $H(); for(property in this) - if(typeof this[property] != 'function') data[property] = this[property]; + if (!Object.isFunction(this[property])) data.set(property, this[property]); return '#'; } -} +}); -Effect.Parallel = Class.create(); -Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { +Effect.Parallel = Class.create(Effect.Base, { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); @@ -335,35 +318,45 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { effect.render(1.0); effect.cancel(); effect.event('beforeFinish'); - if(effect.finish) effect.finish(position); + if (effect.finish) effect.finish(position); effect.event('afterFinish'); }); } }); -Effect.Event = Class.create(); -Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { initialize: function() { - var options = Object.extend({ - duration: 0 - }, arguments[0] || {}); - this.start(options); + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); }, update: Prototype.emptyFunction }); -Effect.Opacity = Class.create(); -Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { +Effect.Opacity = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); - if(!this.element) throw(Effect._elementDoesNotExistError); + if (!this.element) throw(Effect._elementDoesNotExistError); // make this work on IE on elements without 'layout' - if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); var options = Object.extend({ from: this.element.getOpacity() || 0.0, to: 1.0 - }, arguments[1] || {}); + }, arguments[1] || { }); this.start(options); }, update: function(position) { @@ -371,36 +364,30 @@ Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { } }); -Effect.Move = Class.create(); -Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { +Effect.Move = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); - if(!this.element) throw(Effect._elementDoesNotExistError); + if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ x: 0, y: 0, mode: 'relative' - }, arguments[1] || {}); + }, arguments[1] || { }); this.start(options); }, setup: function() { - // Bug in Opera: Opera returns the "real" position of a static element or - // relative element that does not have top/left explicitly set. - // ==> Always set top and left for position relative elements in your stylesheets - // (to 0 if you do not need them) this.element.makePositioned(); this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); this.originalTop = parseFloat(this.element.getStyle('top') || '0'); - if(this.options.mode == 'absolute') { - // absolute movement, so we need to calc deltaX and deltaY + if (this.options.mode == 'absolute') { this.options.x = this.options.x - this.originalLeft; this.options.y = this.options.y - this.originalTop; } }, update: function(position) { this.element.setStyle({ - left: Math.round(this.options.x * position + this.originalLeft) + 'px', - top: Math.round(this.options.y * position + this.originalTop) + 'px' + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' }); } }); @@ -408,30 +395,29 @@ Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { // for backwards compatibility Effect.MoveBy = function(element, toTop, toLeft) { return new Effect.Move(element, - Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); }; -Effect.Scale = Class.create(); -Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { +Effect.Scale = Class.create(Effect.Base, { initialize: function(element, percent) { this.element = $(element); - if(!this.element) throw(Effect._elementDoesNotExistError); + if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, - scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleMode: 'box', // 'box' or 'contents' or { } with provided values scaleFrom: 100.0, scaleTo: percent - }, arguments[2] || {}); + }, arguments[2] || { }); this.start(options); }, setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; this.elementPositioning = this.element.getStyle('position'); - this.originalStyle = {}; + this.originalStyle = { }; ['top','left','width','height','fontSize'].each( function(k) { this.originalStyle[k] = this.element.style[k]; }.bind(this)); @@ -441,7 +427,7 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%','pt'].each( function(fontSizeType) { - if(fontSize.indexOf(fontSizeType)>0) { + if (fontSize.indexOf(fontSizeType)>0) { this.fontSize = parseFloat(fontSize); this.fontSizeType = fontSizeType; } @@ -450,62 +436,61 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; this.dims = null; - if(this.options.scaleMode=='box') + if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; - if(/^content/.test(this.options.scaleMode)) + if (/^content/.test(this.options.scaleMode)) this.dims = [this.element.scrollHeight, this.element.scrollWidth]; - if(!this.dims) + if (!this.dims) this.dims = [this.options.scaleMode.originalHeight, this.options.scaleMode.originalWidth]; }, update: function(position) { var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if(this.options.scaleContent && this.fontSize) + if (this.options.scaleContent && this.fontSize) this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); }, finish: function(position) { - if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); }, setDimensions: function(height, width) { - var d = {}; - if(this.options.scaleX) d.width = Math.round(width) + 'px'; - if(this.options.scaleY) d.height = Math.round(height) + 'px'; - if(this.options.scaleFromCenter) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { var topd = (height - this.dims[0])/2; var leftd = (width - this.dims[1])/2; - if(this.elementPositioning == 'absolute') { - if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; - if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; } else { - if(this.options.scaleY) d.top = -topd + 'px'; - if(this.options.scaleX) d.left = -leftd + 'px'; + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; } } this.element.setStyle(d); } }); -Effect.Highlight = Class.create(); -Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { +Effect.Highlight = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); - if(!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); this.start(options); }, setup: function() { // Prevent executing on elements not in the layout flow - if(this.element.getStyle('display')=='none') { this.cancel(); return; } + if (this.element.getStyle('display')=='none') { this.cancel(); return; } // Disable background image during the effect - this.oldStyle = {}; + this.oldStyle = { }; if (!this.options.keepBackgroundImage) { this.oldStyle.backgroundImage = this.element.getStyle('background-image'); this.element.setStyle({backgroundImage: 'none'}); } - if(!this.options.endcolor) + if (!this.options.endcolor) this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); - if(!this.options.restorecolor) + if (!this.options.restorecolor) this.options.restorecolor = this.element.getStyle('background-color'); // init color calculations this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); @@ -513,7 +498,7 @@ Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), }, update: function(position) { this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ - return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); }, finish: function() { this.element.setStyle(Object.extend(this.oldStyle, { @@ -522,30 +507,21 @@ Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), } }); -Effect.ScrollTo = Class.create(); -Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { - initialize: function(element) { - this.element = $(element); - this.start(arguments[1] || {}); - }, - setup: function() { - Position.prepare(); - var offsets = Position.cumulativeOffset(this.element); - if(this.options.offset) offsets[1] += this.options.offset; - var max = window.innerHeight ? - window.height - window.innerHeight : - document.body.scrollHeight - - (document.documentElement.clientHeight ? - document.documentElement.clientHeight : document.body.clientHeight); - this.scrollStart = Position.deltaY; - this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; - }, - update: function(position) { - Position.prepare(); - window.scrollTo(Position.deltaX, - this.scrollStart + (position*this.delta)); - } -}); +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(), + max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1] > max ? max : elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()) } + ); +}; /* ------------- combination effects ------------- */ @@ -553,14 +529,15 @@ Effect.Fade = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); var options = Object.extend({ - from: element.getOpacity() || 1.0, - to: 0.0, - afterFinishInternal: function(effect) { - if(effect.options.to!=0) return; - effect.element.hide().setStyle({opacity: oldOpacity}); - }}, arguments[1] || {}); + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); return new Effect.Opacity(element,options); -} +}; Effect.Appear = function(element) { element = $(element); @@ -573,9 +550,9 @@ Effect.Appear = function(element) { }, beforeSetup: function(effect) { effect.element.setOpacity(effect.options.from).show(); - }}, arguments[1] || {}); + }}, arguments[1] || { }); return new Effect.Opacity(element,options); -} +}; Effect.Puff = function(element) { element = $(element); @@ -597,9 +574,9 @@ Effect.Puff = function(element) { }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } - }, arguments[1] || {}) + }, arguments[1] || { }) ); -} +}; Effect.BlindUp = function(element) { element = $(element); @@ -611,9 +588,9 @@ Effect.BlindUp = function(element) { afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } - }, arguments[1] || {}) + }, arguments[1] || { }) ); -} +}; Effect.BlindDown = function(element) { element = $(element); @@ -630,8 +607,8 @@ Effect.BlindDown = function(element) { afterFinishInternal: function(effect) { effect.element.undoClipping(); } - }, arguments[1] || {})); -} + }, arguments[1] || { })); +}; Effect.SwitchOff = function(element) { element = $(element); @@ -652,8 +629,8 @@ Effect.SwitchOff = function(element) { } }) } - }, arguments[1] || {})); -} + }, arguments[1] || { })); +}; Effect.DropOut = function(element) { element = $(element); @@ -672,29 +649,35 @@ Effect.DropOut = function(element) { afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); } - }, arguments[1] || {})); -} + }, arguments[1] || { })); +}; Effect.Shake = function(element) { element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left') }; - return new Effect.Move(element, - { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { new Effect.Move(effect.element, - { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, - { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, - { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, - { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, - { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); }}) }}) }}) }}) }}) }}); -} +}; Effect.SlideDown = function(element) { element = $(element).cleanWhitespace(); @@ -710,7 +693,7 @@ Effect.SlideDown = function(element) { afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); - if(window.opera) effect.element.setStyle({top: ''}); + if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { @@ -720,23 +703,25 @@ Effect.SlideDown = function(element) { afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } - }, arguments[1] || {}) + }, arguments[1] || { }) ); -} +}; Effect.SlideUp = function(element) { element = $(element).cleanWhitespace(); var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); return new Effect.Scale(element, window.opera ? 0 : 1, Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'box', scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, - beforeStartInternal: function(effect) { + afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); - if(window.opera) effect.element.setStyle({top: ''}); + if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); }, afterUpdateInternal: function(effect) { @@ -744,12 +729,12 @@ Effect.SlideUp = function(element) { (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); - effect.element.down().undoPositioned(); + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } - }, arguments[1] || {}) + }, arguments[1] || { }) ); -} +}; // Bug in opera makes the TD containing this element expand for a instance after finish Effect.Squish = function(element) { @@ -762,7 +747,7 @@ Effect.Squish = function(element) { effect.element.hide().undoClipping(); } }); -} +}; Effect.Grow = function(element) { element = $(element); @@ -771,7 +756,7 @@ Effect.Grow = function(element) { moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full - }, arguments[1] || {}); + }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, @@ -836,7 +821,7 @@ Effect.Grow = function(element) { ) } }); -} +}; Effect.Shrink = function(element) { element = $(element); @@ -845,7 +830,7 @@ Effect.Shrink = function(element) { moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none - }, arguments[1] || {}); + }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, @@ -890,11 +875,11 @@ Effect.Shrink = function(element) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); -} +}; Effect.Pulsate = function(element) { element = $(element); - var options = arguments[1] || {}; + var options = arguments[1] || { }; var oldOpacity = element.getInlineOpacity(); var transition = options.transition || Effect.Transitions.sinoidal; var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; @@ -903,7 +888,7 @@ Effect.Pulsate = function(element) { Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); -} +}; Effect.Fold = function(element) { element = $(element); @@ -923,46 +908,43 @@ Effect.Fold = function(element) { afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); } }); - }}, arguments[1] || {})); + }}, arguments[1] || { })); }; -Effect.Morph = Class.create(); -Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { +Effect.Morph = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); - if(!this.element) throw(Effect._elementDoesNotExistError); + if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ - style: {} - }, arguments[1] || {}); - if (typeof options.style == 'string') { - if(options.style.indexOf(':') == -1) { - var cssText = '', selector = '.' + options.style; - $A(document.styleSheets).reverse().each(function(styleSheet) { - if (styleSheet.cssRules) cssRules = styleSheet.cssRules; - else if (styleSheet.rules) cssRules = styleSheet.rules; - $A(cssRules).reverse().each(function(rule) { - if (selector == rule.selectorText) { - cssText = rule.style.cssText; - throw $break; - } - }); - if (cssText) throw $break; + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; }); - this.style = cssText.parseStyle(); - options.afterFinishInternal = function(effect){ + options.afterFinishInternal = function(effect) { effect.element.addClassName(effect.options.style); effect.transforms.each(function(transform) { - if(transform.style != 'opacity') - effect.element.style[transform.style] = ''; + effect.element.style[transform.style] = ''; }); } - } else this.style = options.style.parseStyle(); - } else this.style = $H(options.style) + } + } this.start(options); }, + setup: function(){ function parseColor(color){ - if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ return parseInt( color.slice(i*2+1,i*2+3), 16 ) @@ -971,14 +953,14 @@ Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { this.transforms = this.style.map(function(pair){ var property = pair[0], value = pair[1], unit = null; - if(value.parseColor('#zzzzzz') != '#zzzzzz') { + if (value.parseColor('#zzzzzz') != '#zzzzzz') { value = value.parseColor(); unit = 'color'; - } else if(property == 'opacity') { + } else if (property == 'opacity') { value = parseFloat(value); - if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); - } else if(Element.CSS_LENGTH.test(value)) { + } else if (Element.CSS_LENGTH.test(value)) { var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); value = parseFloat(components[1]); unit = (components.length == 3) ? components[2] : null; @@ -1002,7 +984,7 @@ Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { }); }, update: function(position) { - var style = {}, transform, i = this.transforms.length; + var style = { }, transform, i = this.transforms.length; while(i--) style[(transform = this.transforms[i]).style] = transform.unit=='color' ? '#'+ @@ -1012,24 +994,25 @@ Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + (Math.round(transform.originalValue[2]+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : - transform.originalValue + Math.round( - ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); this.element.setStyle(style, true); } }); -Effect.Transform = Class.create(); -Object.extend(Effect.Transform.prototype, { +Effect.Transform = Class.create({ initialize: function(tracks){ this.tracks = []; - this.options = arguments[1] || {}; + this.options = arguments[1] || { }; this.addTracks(tracks); }, addTracks: function(tracks){ tracks.each(function(track){ - var data = $H(track).values().first(); + track = $H(track); + var data = track.values().first(); this.tracks.push($H({ - ids: $H(track).keys().first(), + ids: track.keys().first(), effect: Effect.Morph, options: { style: data } })); @@ -1039,8 +1022,9 @@ Object.extend(Effect.Transform.prototype, { play: function(){ return new Effect.Parallel( this.tracks.map(function(track){ - var elements = [$(track.ids) || $$(track.ids)].flatten(); - return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); }).flatten(), this.options ); @@ -1060,35 +1044,79 @@ Element.CSS_PROPERTIES = $w( Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; +String.__parseStyleElement = document.createElement('div'); String.prototype.parseStyle = function(){ - var element = document.createElement('div'); - element.innerHTML = '

'; - var style = element.childNodes[0].style, styleRules = $H(); + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
'; + style = String.__parseStyleElement.childNodes[0].style; + } Element.CSS_PROPERTIES.each(function(property){ - if(style[property]) styleRules[property] = style[property]; + if (style[property]) styleRules.set(property, style[property]); }); - if(Prototype.Browser.IE && this.indexOf('opacity') > -1) { - styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]; - } + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + return styleRules; }; -Element.morph = function(element, style) { - new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); - return element; +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +}; + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element) + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } }; -['getInlineOpacity','forceRerendering','setContentZoom', - 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( - function(f) { Element.Methods[f] = Element[f]; } +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + } + } ); -Element.Methods.visualEffect = function(element, effect, options) { - s = effect.dasherize().camelize(); - effect_class = s.charAt(0).toUpperCase() + s.substring(1); - new Effect[effect_class](element, options); - return $(element); -}; +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); -Element.addMethods(); \ No newline at end of file +Element.addMethods(Effect.Methods); diff --git a/assets/javascripts/modalbox/lib/prototype.js b/assets/javascripts/modalbox/lib/prototype.js index 5d2100fac..8613914b1 100644 --- a/assets/javascripts/modalbox/lib/prototype.js +++ b/assets/javascripts/modalbox/lib/prototype.js @@ -1,57 +1,114 @@ -/* Prototype JavaScript framework, version 1.5.1 +/* Prototype JavaScript framework, version 1.6.0.1 * (c) 2005-2007 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * -/*--------------------------------------------------------------------------*/ + *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.5.1', + Version: '1.6.0.1', Browser: { IE: !!(window.attachEvent && !window.opera), Opera: !!window.opera, WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1 + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) }, BrowserFeatures: { XPath: !!document.evaluate, ElementExtensions: !!window.HTMLElement, SpecificElementExtensions: - (document.createElement('div').__proto__ !== - document.createElement('form').__proto__) + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ }, - ScriptFragment: ']*>([\u0001-\uFFFF]*?)', - JSONFilter: /^\/\*-secure-\s*(.*)\s*\*\/\s*$/, + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, K: function(x) { return x } -} +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + +/* Based on Alex Arnell's inheritance implementation. */ var Class = { create: function() { - return function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { this.initialize.apply(this, arguments); } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; } -} +}; -var Abstract = new Object(); +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value, value = Object.extend((function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method), { + valueOf: function() { return method }, + toString: function() { return method.toString() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; Object.extend = function(destination, source) { - for (var property in source) { + for (var property in source) destination[property] = source[property]; - } return destination; -} +}; Object.extend(Object, { inspect: function(object) { try { - if (object === undefined) return 'undefined'; + if (Object.isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : object.toString(); } catch (e) { @@ -62,24 +119,35 @@ Object.extend(Object, { toJSON: function(object) { var type = typeof object; - switch(type) { + switch (type) { case 'undefined': case 'function': case 'unknown': return; case 'boolean': return object.toString(); } + if (object === null) return 'null'; if (object.toJSON) return object.toJSON(); - if (object.ownerDocument === document) return; + if (Object.isElement(object)) return; + var results = []; for (var property in object) { var value = Object.toJSON(object[property]); - if (value !== undefined) + if (!Object.isUndefined(value)) results.push(property.toJSON() + ': ' + value); } + return '{' + results.join(', ') + '}'; }, + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + keys: function(object) { var keys = []; for (var property in object) @@ -95,55 +163,99 @@ Object.extend(Object, { }, clone: function(object) { - return Object.extend({}, object); + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object && object.constructor === Array; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; } }); -Function.prototype.bind = function() { - var __method = this, args = $A(arguments), object = args.shift(); - return function() { - return __method.apply(object, args.concat($A(arguments))); - } -} +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, -Function.prototype.bindAsEventListener = function(object) { - var __method = this, args = $A(arguments), object = args.shift(); - return function(event) { - return __method.apply(object, [event || window.event].concat(args)); - } -} + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } }, - succ: function() { - return this + 1; + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } }, - times: function(iterator) { - $R(0, this, true).each(iterator); - return this; + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); }, - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } }, - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; } }); +Function.prototype.defer = Function.prototype.delay.curry(0.01); + Date.prototype.toJSON = function() { - return '"' + this.getFullYear() + '-' + - (this.getMonth() + 1).toPaddedString(2) + '-' + - this.getDate().toPaddedString(2) + 'T' + - this.getHours().toPaddedString(2) + ':' + - this.getMinutes().toPaddedString(2) + ':' + - this.getSeconds().toPaddedString(2) + '"'; + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; }; var Try = { @@ -155,17 +267,22 @@ var Try = { try { returnValue = lambda(); break; - } catch (e) {} + } catch (e) { } } return returnValue; } -} +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; /*--------------------------------------------------------------------------*/ -var PeriodicalExecuter = Class.create(); -PeriodicalExecuter.prototype = { +var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; @@ -178,6 +295,10 @@ PeriodicalExecuter.prototype = { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, + execute: function() { + this.callback(this); + }, + stop: function() { if (!this.timer) return; clearInterval(this.timer); @@ -188,13 +309,13 @@ PeriodicalExecuter.prototype = { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; - this.callback(this); + this.execute(); } finally { this.currentlyExecuting = false; } } } -} +}); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); @@ -228,7 +349,7 @@ Object.extend(String.prototype, { sub: function(pattern, replacement, count) { replacement = this.gsub.prepareReplacement(replacement); - count = count === undefined ? 1 : count; + count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; @@ -238,14 +359,14 @@ Object.extend(String.prototype, { scan: function(pattern, iterator) { this.gsub(pattern, iterator); - return this; + return String(this); }, truncate: function(length, truncation) { length = length || 30; - truncation = truncation === undefined ? '...' : truncation; + truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? - this.slice(0, length - truncation.length) + truncation : this; + this.slice(0, length - truncation.length) + truncation : String(this); }, strip: function() { @@ -279,7 +400,7 @@ Object.extend(String.prototype, { }, unescapeHTML: function() { - var div = document.createElement('div'); + var div = new Element('div'); div.innerHTML = this.stripTags(); return div.childNodes[0] ? (div.childNodes.length > 1 ? $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : @@ -288,16 +409,16 @@ Object.extend(String.prototype, { toQueryParams: function(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return {}; + if (!match) return { }; - return match[1].split(separator || '&').inject({}, function(hash, pair) { + return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()); var value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { - if (hash[key].constructor != Array) hash[key] = [hash[key]]; + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; @@ -316,9 +437,7 @@ Object.extend(String.prototype, { }, times: function(count) { - var result = ''; - for (var i = 0; i < count; i++) result += this; - return result; + return count < 1 ? '' : new Array(count + 1).join(this); }, camelize: function() { @@ -364,11 +483,17 @@ Object.extend(String.prototype, { return this.sub(filter || Prototype.JSONFilter, '#{1}'); }, + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + evalJSON: function(sanitize) { var json = this.unfilterJSON(); try { - if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json))) - return eval('(' + json + ')'); + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); }, @@ -392,6 +517,10 @@ Object.extend(String.prototype, { blank: function() { return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); } }); @@ -405,10 +534,10 @@ if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.proto }); String.prototype.gsub.prepareReplacement = function(replacement) { - if (typeof replacement == 'function') return replacement; + if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; -} +}; String.prototype.parseQuery = String.prototype.toQueryParams; @@ -419,28 +548,47 @@ Object.extend(String.prototype.escapeHTML, { with (String.prototype.escapeHTML) div.appendChild(text); -var Template = Class.create(); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; -Template.prototype = { +var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); - this.pattern = pattern || Template.Pattern; + this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + return this.template.gsub(this.pattern, function(match) { - var before = match[1]; + if (object == null) return ''; + + var before = match[1] || ''; if (before == '\\') return match[2]; - return before + String.interpret(object[match[3]]); - }); + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }.bind(this)); } -} +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; -var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); +var $break = { }; var Enumerable = { - each: function(iterator) { + each: function(iterator, context) { var index = 0; + iterator = iterator.bind(context); try { this._each(function(value) { iterator(value, index++); @@ -451,40 +599,45 @@ var Enumerable = { return this; }, - eachSlice: function(number, iterator) { + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var index = -number, slices = [], array = this.toArray(); while ((index += number) < array.length) slices.push(array.slice(index, index+number)); - return slices.map(iterator); + return slices.collect(iterator, context); }, - all: function(iterator) { + all: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!(iterator || Prototype.K)(value, index); + result = result && !!iterator(value, index); if (!result) throw $break; }); return result; }, - any: function(iterator) { + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!(iterator || Prototype.K)(value, index)) + if (result = !!iterator(value, index)) throw $break; }); return result; }, - collect: function(iterator) { + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; this.each(function(value, index) { - results.push((iterator || Prototype.K)(value, index)); + results.push(iterator(value, index)); }); return results; }, - detect: function(iterator) { + detect: function(iterator, context) { + iterator = iterator.bind(context); var result; this.each(function(value, index) { if (iterator(value, index)) { @@ -495,7 +648,8 @@ var Enumerable = { return result; }, - findAll: function(iterator) { + findAll: function(iterator, context) { + iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (iterator(value, index)) @@ -504,17 +658,24 @@ var Enumerable = { return results; }, - grep: function(pattern, iterator) { + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + this.each(function(value, index) { - var stringValue = value.toString(); - if (stringValue.match(pattern)) - results.push((iterator || Prototype.K)(value, index)); - }) + if (filter.match(value)) + results.push(iterator(value, index)); + }); return results; }, include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + var found = false; this.each(function(value) { if (value == object) { @@ -526,14 +687,15 @@ var Enumerable = { }, inGroupsOf: function(number, fillWith) { - fillWith = fillWith === undefined ? null : fillWith; + fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); }, - inject: function(memo, iterator) { + inject: function(memo, iterator, context) { + iterator = iterator.bind(context); this.each(function(value, index) { memo = iterator(memo, value, index); }); @@ -547,30 +709,33 @@ var Enumerable = { }); }, - max: function(iterator) { + max: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { - value = (iterator || Prototype.K)(value, index); - if (result == undefined || value >= result) + value = iterator(value, index); + if (result == null || value >= result) result = value; }); return result; }, - min: function(iterator) { + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var result; this.each(function(value, index) { - value = (iterator || Prototype.K)(value, index); - if (result == undefined || value < result) + value = iterator(value, index); + if (result == null || value < result) result = value; }); return result; }, - partition: function(iterator) { + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - ((iterator || Prototype.K)(value, index) ? + (iterator(value, index) ? trues : falses).push(value); }); return [trues, falses]; @@ -578,13 +743,14 @@ var Enumerable = { pluck: function(property) { var results = []; - this.each(function(value, index) { + this.each(function(value) { results.push(value[property]); }); return results; }, - reject: function(iterator) { + reject: function(iterator, context) { + iterator = iterator.bind(context); var results = []; this.each(function(value, index) { if (!iterator(value, index)) @@ -593,7 +759,8 @@ var Enumerable = { return results; }, - sortBy: function(iterator) { + sortBy: function(iterator, context) { + iterator = iterator.bind(context); return this.map(function(value, index) { return {value: value, criteria: iterator(value, index)}; }).sort(function(left, right) { @@ -608,7 +775,7 @@ var Enumerable = { zip: function() { var iterator = Prototype.K, args = $A(arguments); - if (typeof args.last() == 'function') + if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); @@ -624,46 +791,42 @@ var Enumerable = { inspect: function() { return '#'; } -} +}; Object.extend(Enumerable, { map: Enumerable.collect, find: Enumerable.detect, select: Enumerable.findAll, + filter: Enumerable.findAll, member: Enumerable.include, - entries: Enumerable.toArray + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any }); -var $A = Array.from = function(iterable) { +function $A(iterable) { if (!iterable) return []; - if (iterable.toArray) { - return iterable.toArray(); - } else { - var results = []; - for (var i = 0, length = iterable.length; i < length; i++) - results.push(iterable[i]); - return results; - } + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; } if (Prototype.Browser.WebKit) { - $A = Array.from = function(iterable) { + function $A(iterable) { if (!iterable) return []; - if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && - iterable.toArray) { - return iterable.toArray(); - } else { - var results = []; - for (var i = 0, length = iterable.length; i < length; i++) - results.push(iterable[i]); - return results; - } + if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && + iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; } } +Array.from = $A; + Object.extend(Array.prototype, Enumerable); -if (!Array.prototype._reverse) - Array.prototype._reverse = Array.prototype.reverse; +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { @@ -692,7 +855,7 @@ Object.extend(Array.prototype, { flatten: function() { return this.inject([], function(array, value) { - return array.concat(value && value.constructor == Array ? + return array.concat(Object.isArray(value) ? value.flatten() : [value]); }); }, @@ -704,12 +867,6 @@ Object.extend(Array.prototype, { }); }, - indexOf: function(object) { - for (var i = 0, length = this.length; i < length; i++) - if (this[i] == object) return i; - return -1; - }, - reverse: function(inline) { return (inline !== false ? this : this.toArray())._reverse(); }, @@ -726,6 +883,12 @@ Object.extend(Array.prototype, { }); }, + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + clone: function() { return [].concat(this); }, @@ -742,15 +905,35 @@ Object.extend(Array.prototype, { var results = []; this.each(function(object) { var value = Object.toJSON(object); - if (value !== undefined) results.push(value); + if (!Object.isUndefined(value)) results.push(value); }); return '[' + results.join(', ') + ']'; } }); +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + Array.prototype.toArray = Array.prototype.clone; function $w(string) { + if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } @@ -760,7 +943,7 @@ if (Prototype.Browser.Opera){ var array = []; for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); for (var i = 0, length = arguments.length; i < length; i++) { - if (arguments[i].constructor == Array) { + if (Object.isArray(arguments[i])) { for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { @@ -768,136 +951,135 @@ if (Prototype.Browser.Opera){ } } return array; - } + }; } -var Hash = function(object) { - if (object instanceof Hash) this.merge(object); - else Object.extend(this, object || {}); -}; - -Object.extend(Hash, { - toQueryString: function(obj) { - var parts = []; - parts.add = arguments.callee.addPair; +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, - this.prototype._each.call(obj, function(pair) { - if (!pair.key) return; - var value = pair.value; + succ: function() { + return this + 1; + }, - if (value && typeof value == 'object') { - if (value.constructor == Array) value.each(function(value) { - parts.add(pair.key, value); - }); - return; - } - parts.add(pair.key, value); - }); + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, - return parts.join('&'); + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; }, - toJSON: function(object) { - var results = []; - this.prototype._each.call(object, function(pair) { - var value = Object.toJSON(pair.value); - if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); - }); - return '{' + results.join(', ') + '}'; + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; } }); -Hash.toQueryString.addPair = function(key, value, prefix) { - key = encodeURIComponent(key); - if (value === undefined) this.push(key); - else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); -} +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; -Object.extend(Hash.prototype, Enumerable); -Object.extend(Hash.prototype, { - _each: function(iterator) { - for (var key in this) { - var value = this[key]; - if (value && value == Hash.prototype[key]) continue; +var Hash = Class.create(Enumerable, (function() { - var pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - }, + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } - keys: function() { - return this.pluck('key'); - }, + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, - values: function() { - return this.pluck('value'); - }, + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, - merge: function(hash) { - return $H(hash).inject(this, function(mergedHash, pair) { - mergedHash[pair.key] = pair.value; - return mergedHash; - }); - }, + set: function(key, value) { + return this._object[key] = value; + }, - remove: function() { - var result; - for(var i = 0, length = arguments.length; i < length; i++) { - var value = this[arguments[i]]; - if (value !== undefined){ - if (result === undefined) result = value; - else { - if (result.constructor != Array) result = [result]; - result.push(value) - } - } - delete this[arguments[i]]; - } - return result; - }, + get: function(key) { + return this._object[key]; + }, - toQueryString: function() { - return Hash.toQueryString(this); - }, + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, - inspect: function() { - return '#'; - }, + toObject: function() { + return Object.clone(this._object); + }, - toJSON: function() { - return Hash.toJSON(this); - } -}); + keys: function() { + return this.pluck('key'); + }, -function $H(object) { - if (object instanceof Hash) return object; - return new Hash(object); -}; + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, -// Safari iterates over shadowed properties -if (function() { - var i = 0, Test = function(value) { this.key = value }; - Test.prototype.key = 'foo'; - for (var property in new Test('bar')) i++; - return i > 1; -}()) Hash.prototype._each = function(iterator) { - var cache = []; - for (var key in this) { - var value = this[key]; - if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; - cache.push(key); - var pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); + clone: function() { + return new Hash(this); + } } -}; -ObjectRange = Class.create(); -Object.extend(ObjectRange.prototype, Enumerable); -Object.extend(ObjectRange.prototype, { +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { initialize: function(start, end, exclusive) { this.start = start; this.end = end; @@ -923,7 +1105,7 @@ Object.extend(ObjectRange.prototype, { var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); -} +}; var Ajax = { getTransport: function() { @@ -935,7 +1117,7 @@ var Ajax = { }, activeRequestCount: 0 -} +}; Ajax.Responders = { responders: [], @@ -955,10 +1137,10 @@ Ajax.Responders = { dispatch: function(callback, request, transport, json) { this.each(function(responder) { - if (typeof responder[callback] == 'function') { + if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); - } catch (e) {} + } catch (e) { } } }); } @@ -967,42 +1149,38 @@ Ajax.Responders = { Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ - onCreate: function() { - Ajax.activeRequestCount++; - }, - onComplete: function() { - Ajax.activeRequestCount--; - } + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } }); -Ajax.Base = function() {}; -Ajax.Base.prototype = { - setOptions: function(options) { +Ajax.Base = Class.create({ + initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', - parameters: '' - } - Object.extend(this.options, options || {}); + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); - if (typeof this.options.parameters == 'string') + + if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); } -} - -Ajax.Request = Class.create(); -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; +}); -Ajax.Request.prototype = Object.extend(new Ajax.Base(), { +Ajax.Request = Class.create(Ajax.Base, { _complete: false, - initialize: function(url, options) { + initialize: function($super, url, options) { + $super(options); this.transport = Ajax.getTransport(); - this.setOptions(options); this.request(url); }, @@ -1019,7 +1197,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { this.parameters = params; - if (params = Hash.toQueryString(params)) { + if (params = Object.toQueryString(params)) { // when GET, append parameters to URL if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; @@ -1028,14 +1206,14 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } try { - if (this.options.onCreate) this.options.onCreate(this.transport); - Ajax.Responders.dispatch('onCreate', this, this.transport); + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); - if (this.options.asynchronous) - setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); @@ -1083,7 +1261,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; - if (typeof extras.push == 'function') + if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else @@ -1095,33 +1273,39 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { }, success: function() { - return !this.transport.status - || (this.transport.status >= 200 && this.transport.status < 300); + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } }, respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState]; - var transport = this.transport, json = this.evalJSON(); + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; - (this.options['on' + this.transport.status] + (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(transport, json); + || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } - var contentType = this.getHeader('Content-type'); - if (contentType && contentType.strip(). - match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) - this.evalResponse(); + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); } try { - (this.options['on' + state] || Prototype.emptyFunction)(transport, json); - Ajax.Responders.dispatch('on' + state, this, transport, json); + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } @@ -1134,14 +1318,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { getHeader: function(name) { try { - return this.transport.getResponseHeader(name); - } catch (e) { return null } - }, - - evalJSON: function() { - try { - var json = this.getHeader('X-JSON'); - return json ? json.evalJSON() : null; + return this.transport.getResponseHeader(name) || null; } catch (e) { return null } }, @@ -1159,57 +1336,126 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } }); -Ajax.Updater = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; -Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { - initialize: function(container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); } - this.transport = Ajax.getTransport(); - this.setOptions(options); + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, - var onComplete = this.options.onComplete || Prototype.emptyFunction; - this.options.onComplete = (function(transport, param) { - this.updateContent(); - onComplete(transport, param); - }).bind(this); + status: 0, + statusText: '', - this.request(url); + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } }, - updateContent: function() { - var receiver = this.container[this.success() ? 'success' : 'failure']; - var response = this.transport.responseText; + getHeader: Ajax.Request.prototype.getHeader, - if (!this.options.evalScripts) response = response.stripScripts(); + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, - if (receiver = $(receiver)) { - if (this.options.insertion) - new this.options.insertion(receiver, response); - else - receiver.update(response); - } + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, - if (this.success()) { - if (this.onComplete) - setTimeout(this.onComplete.bind(this), 10); - } - } + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } }); -Ajax.PeriodicalUpdater = Class.create(); -Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { - initialize: function(container, url, options) { - this.setOptions(options); +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); - this.updater = {}; + this.updater = { }; this.container = container; this.url = url; @@ -1227,15 +1473,14 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, - updateComplete: function(request) { + updateComplete: function(response) { if (this.options.decay) { - this.decay = (request.responseText == this.lastText ? + this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); - this.lastText = request.responseText; + this.lastText = response.responseText; } - this.timer = setTimeout(this.onTimerEvent.bind(this), - this.decay * this.frequency * 1000); + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { @@ -1248,7 +1493,7 @@ function $(element) { elements.push($(arguments[i])); return elements; } - if (typeof element == 'string') + if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } @@ -1259,65 +1504,51 @@ if (Prototype.BrowserFeatures.XPath) { var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(query.snapshotItem(i)); + results.push(Element.extend(query.snapshotItem(i))); return results; }; - - document.getElementsByClassName = function(className, parentElement) { - var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; - return document._getElementsByXPath(q, parentElement); - } - -} else document.getElementsByClassName = function(className, parentElement) { - var children = ($(parentElement) || document.body).getElementsByTagName('*'); - var elements = [], child; - for (var i = 0, length = children.length; i < length; i++) { - child = children[i]; - if (Element.hasClassName(child, className)) - elements.push(Element.extend(child)); - } - return elements; -}; +} /*--------------------------------------------------------------------------*/ -if (!window.Element) var Element = {}; - -Element.extend = function(element) { - var F = Prototype.BrowserFeatures; - if (!element || !element.tagName || element.nodeType == 3 || - element._extended || F.SpecificElementExtensions || element == window) - return element; - - var methods = {}, tagName = element.tagName, cache = Element.extend.cache, - T = Element.Methods.ByTag; - - // extend methods for all tags (Safari doesn't need this) - if (!F.ElementExtensions) { - Object.extend(methods, Element.Methods), - Object.extend(methods, Element.Methods.Simulated); - } - - // extend methods for specific tags - if (T[tagName]) Object.extend(methods, T[tagName]); - - for (var property in methods) { - var value = methods[property]; - if (typeof value == 'function' && !(property in element)) - element[property] = cache.findOrStore(value); - } - - element._extended = Prototype.emptyFunction; - return element; -}; +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} -Element.extend.cache = { - findOrStore: function(value) { - return this[value] = this[value] || function() { - return value.apply(null, [this].concat($A(arguments))); +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); } - } -}; + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); +}).call(window); + +Element.cache = { }; Element.Methods = { visible: function(element) { @@ -1346,28 +1577,78 @@ Element.Methods = { return element; }, - update: function(element, html) { - html = typeof html == 'undefined' ? '' : html.toString(); - $(element).innerHTML = html.stripScripts(); - setTimeout(function() {html.evalScripts()}, 10); + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); return element; }, - replace: function(element, html) { + replace: function(element, content) { element = $(element); - html = typeof html == 'undefined' ? '' : html.toString(); - if (element.outerHTML) { - element.outerHTML = html.stripScripts(); - } else { + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); var range = element.ownerDocument.createRange(); - range.selectNodeContents(element); - element.parentNode.replaceChild( - range.createContextualFragment(html.stripScripts()), element); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); } - setTimeout(function() {html.evalScripts()}, 10); + return element; }, + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); @@ -1393,7 +1674,7 @@ Element.Methods = { }, descendants: function(element) { - return $A($(element).getElementsByTagName('*')).each(Element.extend); + return $(element).getElementsBySelector("*"); }, firstDescendant: function(element) { @@ -1423,7 +1704,7 @@ Element.Methods = { }, match: function(element, selector) { - if (typeof selector == 'string') + if (Object.isString(selector)) selector = new Selector(selector); return selector.match($(element)); }, @@ -1432,56 +1713,86 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = element.ancestors(); - return expression ? Selector.findElement(ancestors, expression, index) : - ancestors[index || 0]; + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); - var descendants = element.descendants(); - return expression ? Selector.findElement(descendants, expression, index) : - descendants[index || 0]; + return Object.isNumber(expression) ? element.descendants()[expression] : + element.select(expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = element.previousSiblings(); - return expression ? Selector.findElement(previousSiblings, expression, index) : - previousSiblings[index || 0]; + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = element.nextSiblings(); - return expression ? Selector.findElement(nextSiblings, expression, index) : - nextSiblings[index || 0]; + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); }, - getElementsBySelector: function() { + select: function() { var args = $A(arguments), element = $(args.shift()); return Selector.findChildElements(element, args); }, - getElementsByClassName: function(element, className) { - return document.getElementsByClassName(className, element); + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { - if (!element.attributes) return null; - var t = Element._attributeTranslations; + var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - var attribute = element.attributes[name]; - return attribute ? attribute.nodeValue : null; + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } } return element.getAttribute(name); }, + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + getHeight: function(element) { return $(element).getDimensions().height; }, @@ -1497,39 +1808,28 @@ Element.Methods = { hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; - if (elementClassName.length == 0) return false; - if (elementClassName == className || - elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) - return true; - return false; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; - Element.classNames(element).add(className); + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; - Element.classNames(element).remove(className); + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; - Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); - return element; - }, - - observe: function() { - Event.observe.apply(Event, arguments); - return $A(arguments).first(); - }, - - stopObserving: function() { - Event.stopObserving.apply(Event, arguments); - return $A(arguments).first(); + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); }, // removes whitespace-only text node children @@ -1551,14 +1851,29 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); + var originalAncestor = ancestor; + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (element.sourceIndex && !Prototype.Browser.Opera) { + var e = element.sourceIndex, a = ancestor.sourceIndex, + nextAncestor = ancestor.nextSibling; + if (!nextAncestor) { + do { ancestor = ancestor.parentNode; } + while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); + } + if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); + } + while (element = element.parentNode) - if (element == ancestor) return true; + if (element == originalAncestor) return true; return false; }, scrollTo: function(element) { element = $(element); - var pos = Position.cumulativeOffset(element); + var pos = element.cumulativeOffset(); window.scrollTo(pos[0], pos[1]); return element; }, @@ -1579,16 +1894,20 @@ Element.Methods = { return $(element).getStyle('opacity'); }, - setStyle: function(element, styles, camelized) { + setStyle: function(element, styles) { element = $(element); - var elementStyle = element.style; - + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]) + if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? - (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : - (camelized ? property : property.camelize())] = styles[property]; + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; return element; }, @@ -1655,8 +1974,8 @@ Element.Methods = { makeClipping: function(element) { element = $(element); if (element._overflow) return element; - element._overflow = element.style.overflow || 'auto'; - if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, @@ -1667,28 +1986,234 @@ Element.Methods = { element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; } }; +Element.Methods.identify.counter = 1; + Object.extend(Element.Methods, { - childOf: Element.Methods.descendantOf, + getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + if (Prototype.Browser.Opera) { - Element.Methods._getStyle = Element.Methods.getStyle; - Element.Methods.getStyle = function(element, style) { - switch(style) { - case 'left': - case 'top': - case 'right': - case 'bottom': - if (Element._getStyle(element, 'position') == 'static') return null; - default: return Element._getStyle(element, style); + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } } - }; + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); } + else if (Prototype.Browser.IE) { + $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position != 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -1703,126 +2228,318 @@ else if (Prototype.Browser.IE) { if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset'+style.capitalize()] + 'px'; + return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { - style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; - style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + + style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; - // IE is missing .innerHTML support for TABLE-related elements - Element.Methods.update = function(element, html) { - element = $(element); - html = typeof html == 'undefined' ? '' : html.toString(); - var tagName = element.tagName.toUpperCase(); - if (['THEAD','TBODY','TR','TD'].include(tagName)) { - var div = document.createElement('div'); - switch (tagName) { - case 'THEAD': - case 'TBODY': - div.innerHTML = '' + html.stripScripts() + '
'; - depth = 2; - break; - case 'TR': - div.innerHTML = '' + html.stripScripts() + '
'; - depth = 3; - break; - case 'TD': - div.innerHTML = '
' + html.stripScripts() + '
'; - depth = 4; + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } } - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - depth.times(function() { div = div.firstChild }); - $A(div.childNodes).each(function(node) { element.appendChild(node) }); - } else { - element.innerHTML = html.stripScripts(); } - setTimeout(function() { html.evalScripts() }, 10); - return element; - } -} -else if (Prototype.Browser.Gecko) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; }; -} -Element._attributeTranslations = { - names: { - colspan: "colSpan", - rowspan: "rowSpan", - valign: "vAlign", - datetime: "dateTime", - accesskey: "accessKey", - tabindex: "tabIndex", - enctype: "encType", - maxlength: "maxLength", - readonly: "readOnly", - longdesc: "longDesc" - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - var node = element.getAttributeNode('title'); - return node.specified ? node.nodeValue : null; + Element._attributeTranslations.write = { + names: Object.clone(Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if (document.createElement('div').outerHTML) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
', 1], + TBODY: ['', '
', 2], + TR: ['', '
', 3], + TD: ['
', '
', 4], + SELECT: ['', 1] } }; (function() { - Object.extend(this, { - href: this._getAttr, - src: this._getAttr, - type: this._getAttr, - disabled: this._flag, - checked: this._flag, - readonly: this._flag, - multiple: this._flag + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD }); -}).call(Element._attributeTranslations.values); +}).call(Element._insertionTranslations); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { - var t = Element._attributeTranslations, node; - attribute = t.names[attribute] || attribute; - node = $(element).getAttributeNode(attribute); + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); return node && node.specified; } }; -Element.Methods.ByTag = {}; +Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { - window.HTMLElement = {}; + document.createElement('div').__proto__) { + window.HTMLElement = { }; window.HTMLElement.prototype = document.createElement('div').__proto__; Prototype.BrowserFeatures.ElementExtensions = true; } +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + Element.hasAttribute = function(element, attribute) { if (element.hasAttribute) return element.hasAttribute(attribute); return Element.Methods.Simulated.hasAttribute(element, attribute); @@ -1847,26 +2564,26 @@ Element.addMethods = function(methods) { methods = arguments[1]; } - if (!tagName) Object.extend(Element.Methods, methods || {}); + if (!tagName) Object.extend(Element.Methods, methods || { }); else { - if (tagName.constructor == Array) tagName.each(extend); + if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = {}; + Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; - var cache = Element.extend.cache; for (var property in methods) { var value = methods[property]; + if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) - destination[property] = cache.findOrStore(value); + destination[property] = value.methodize(); } } @@ -1890,7 +2607,7 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - window[klass] = {}; + window[klass] = { }; window[klass].prototype = document.createElement(tagName).__proto__; return window[klass]; } @@ -1903,169 +2620,84 @@ Element.addMethods = function(methods) { if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); - if (typeof klass == "undefined") continue; + if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; -}; - -var Toggle = { display: Element.toggle }; - -/*--------------------------------------------------------------------------*/ - -Abstract.Insertion = function(adjacency) { - this.adjacency = adjacency; -} - -Abstract.Insertion.prototype = { - initialize: function(element, content) { - this.element = $(element); - this.content = content.stripScripts(); - - if (this.adjacency && this.element.insertAdjacentHTML) { - try { - this.element.insertAdjacentHTML(this.adjacency, this.content); - } catch (e) { - var tagName = this.element.tagName.toUpperCase(); - if (['TBODY', 'TR'].include(tagName)) { - this.insertContent(this.contentFromAnonymousTable()); - } else { - throw e; - } - } - } else { - this.range = this.element.ownerDocument.createRange(); - if (this.initializeRange) this.initializeRange(); - this.insertContent([this.range.createContextualFragment(this.content)]); - } - - setTimeout(function() {content.evalScripts()}, 10); - }, - - contentFromAnonymousTable: function() { - var div = document.createElement('div'); - div.innerHTML = '' + this.content + '
'; - return $A(div.childNodes[0].childNodes[0].childNodes); - } -} - -var Insertion = new Object(); - -Insertion.Before = Class.create(); -Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { - initializeRange: function() { - this.range.setStartBefore(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.parentNode.insertBefore(fragment, this.element); - }).bind(this)); - } -}); - -Insertion.Top = Class.create(); -Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(true); - }, - - insertContent: function(fragments) { - fragments.reverse(false).each((function(fragment) { - this.element.insertBefore(fragment, this.element.firstChild); - }).bind(this)); - } -}); - -Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { - initializeRange: function() { - this.range.selectNodeContents(this.element); - this.range.collapse(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.appendChild(fragment); - }).bind(this)); - } -}); -Insertion.After = Class.create(); -Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { - initializeRange: function() { - this.range.setStartAfter(this.element); - }, - - insertContent: function(fragments) { - fragments.each((function(fragment) { - this.element.parentNode.insertBefore(fragment, - this.element.nextSibling); - }).bind(this)); - } -}); - -/*--------------------------------------------------------------------------*/ - -Element.ClassNames = Class.create(); -Element.ClassNames.prototype = { - initialize: function(element) { - this.element = $(element); - }, - - _each: function(iterator) { - this.element.className.split(/\s+/).select(function(name) { - return name.length > 0; - })._each(iterator); - }, + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; - set: function(className) { - this.element.className = className; +document.viewport = { + getDimensions: function() { + var dimensions = { }; + var B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : + (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + }); + return dimensions; }, - add: function(classNameToAdd) { - if (this.include(classNameToAdd)) return; - this.set($A(this).concat(classNameToAdd).join(' ')); + getWidth: function() { + return this.getDimensions().width; }, - remove: function(classNameToRemove) { - if (!this.include(classNameToRemove)) return; - this.set($A(this).without(classNameToRemove).join(' ')); + getHeight: function() { + return this.getDimensions().height; }, - toString: function() { - return $A(this).join(' '); + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; - -Object.extend(Element.ClassNames.prototype, Enumerable); /* Portions of the Selector class are derived from Jack Slocum’s DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ -var Selector = Class.create(); - -Selector.prototype = { +var Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); this.compileMatcher(); }, + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + return false; + + return true; + }, + compileMatcher: function() { - // Selectors with namespaced attributes can't use the XPath version - if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + if (this.shouldUseXPath()) return this.compileXPathMatcher(); var e = this.expression, ps = Selector.patterns, h = Selector.handlers, c = Selector.criteria, le, p, m; if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; return; + this.matcher = Selector._cache[e]; + return; } + this.matcher = ["this.matcher = function(root) {", "var r = root, h = Selector.handlers, c = false, n;"]; @@ -2074,7 +2706,7 @@ Selector.prototype = { for (var i in ps) { p = ps[i]; if (m = e.match(p)) { - this.matcher.push(typeof c[i] == 'function' ? c[i](m) : + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : new Template(c[i]).evaluate(m)); e = e.replace(m[0], ''); break; @@ -2089,7 +2721,7 @@ Selector.prototype = { compileXPathMatcher: function() { var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; + x = Selector.xpath, le, m; if (Selector._cache[e]) { this.xpath = Selector._cache[e]; return; @@ -2100,7 +2732,7 @@ Selector.prototype = { le = e; for (var i in ps) { if (m = e.match(ps[i])) { - this.matcher.push(typeof x[i] == 'function' ? x[i](m) : + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m)); e = e.replace(m[0], ''); break; @@ -2119,7 +2751,39 @@ Selector.prototype = { }, match: function(element) { - return this.findElements(document).include(element); + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; }, toString: function() { @@ -2129,10 +2793,10 @@ Selector.prototype = { inspect: function() { return "#"; } -}; +}); Object.extend(Selector, { - _cache: {}, + _cache: { }, xpath: { descendant: "//*", @@ -2146,15 +2810,19 @@ Object.extend(Selector, { }, className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", id: "[@id='#{1}']", - attrPresence: "[@#{1}]", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, attr: function(m) { + m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; return new Template(Selector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { var h = Selector.xpath.pseudos[m[1]]; if (!h) return ''; - if (typeof h === 'function') return h(m); + if (Object.isFunction(h)) return h(m); return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { @@ -2176,14 +2844,14 @@ Object.extend(Selector, { 'enabled': "[not(@disabled)]", 'not': function(m) { var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, m, v; + x = Selector.xpath, le, v; var exclusion = []; while (e && le != e && (/\S/).test(e)) { le = e; for (var i in p) { if (m = e.match(p[i])) { - v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m); + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); exclusion.push("(" + v.substring(1, v.length - 1) + ")"); e = e.replace(m[0], ''); break; @@ -2241,7 +2909,7 @@ Object.extend(Selector, { m[3] = (m[5] || m[6]); return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); }, - pseudo: function(m) { + pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); }, @@ -2263,9 +2931,34 @@ Object.extend(Selector, { tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, attrPresence: /^\[([\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return Selector.operators[matches[2]](nodeValue, matches[3]); + } }, handlers: { @@ -2297,7 +2990,7 @@ Object.extend(Selector, { parentNode._counted = true; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - node = nodes[i]; + var node = nodes[i]; if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; } } else { @@ -2329,7 +3022,7 @@ Object.extend(Selector, { child: function(nodes) { var h = Selector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, children = [], child; child = node.childNodes[j]; j++) + for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); } return results; @@ -2364,7 +3057,7 @@ Object.extend(Selector, { // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { - tagName = tagName.toUpperCase(); + var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { @@ -2377,14 +3070,15 @@ Object.extend(Selector, { if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() == tagName) results.push(node); + if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, id: function(nodes, root, id, combinator) { var targetNode = $(id), h = Selector.handlers; - if (!nodes && root == document) return targetNode ? [targetNode] : []; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; if (nodes) { if (combinator) { if (combinator == 'child') { @@ -2424,6 +3118,7 @@ Object.extend(Selector, { }, attrPresence: function(nodes, root, attr) { + if (!nodes) nodes = root.getElementsByTagName("*"); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); @@ -2592,14 +3287,15 @@ Object.extend(Selector, { }, findElement: function(elements, expression, index) { - if (typeof expression == 'number') { + if (Object.isNumber(expression)) { index = expression; expression = false; } return Selector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { - var exprs = expressions.join(','), expressions = []; + var exprs = expressions.join(','); + expressions = []; exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { expressions.push(m[1].strip()); }); @@ -2612,6 +3308,16 @@ Object.extend(Selector, { } }); +if (Prototype.Browser.IE) { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + Selector.handlers.concat = function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }; +} + function $$() { return Selector.findChildElements(document, $A(arguments)); } @@ -2621,13 +3327,19 @@ var Form = { return form; }, - serializeElements: function(elements, getHash) { - var data = elements.inject({}, function(result, element) { + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { - var key = element.name, value = $(element).getValue(); - if (value != null) { - if (key in result) { - if (result[key].constructor != Array) result[key] = [result[key]]; + key = element.name; value = $(element).getValue(); + if (value != null && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; @@ -2636,13 +3348,13 @@ var Form = { return result; }); - return getHash ? data : Hash.toQueryString(data); + return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { - serialize: function(form, getHash) { - return Form.serializeElements(Form.getElements(form), getHash); + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { @@ -2684,9 +3396,15 @@ Form.Methods = { }, findFirstElement: function(form) { - return $(form).getElements().find(function(element) { - return element.type != 'hidden' && !element.disabled && - ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); }); }, @@ -2697,22 +3415,23 @@ Form.Methods = { }, request: function(form, options) { - form = $(form), options = Object.clone(options || {}); + form = $(form), options = Object.clone(options || { }); - var params = options.parameters; + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { - if (typeof params == 'string') params = params.toQueryParams(); + if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; - return new Ajax.Request(form.readAttribute('action'), options); + return new Ajax.Request(action, options); } -} +}; /*--------------------------------------------------------------------------*/ @@ -2726,7 +3445,7 @@ Form.Element = { $(element).select(); return element; } -} +}; Form.Element.Methods = { serialize: function(element) { @@ -2734,9 +3453,9 @@ Form.Element.Methods = { if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { - var pair = {}; + var pair = { }; pair[element.name] = value; - return Hash.toQueryString(pair); + return Object.toQueryString(pair); } } return ''; @@ -2748,6 +3467,13 @@ Form.Element.Methods = { return Form.Element.Serializers[method](element); }, + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + clear: function(element) { $(element).value = ''; return element; @@ -2762,9 +3488,9 @@ Form.Element.Methods = { try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) + !['button', 'reset', 'submit'].include(element.type))) element.select(); - } catch (e) {} + } catch (e) { } return element; }, @@ -2780,7 +3506,7 @@ Form.Element.Methods = { element.disabled = false; return element; } -} +}; /*--------------------------------------------------------------------------*/ @@ -2790,27 +3516,44 @@ var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { - input: function(element) { + input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': - return Form.Element.Serializers.inputSelector(element); + return Form.Element.Serializers.inputSelector(element, value); default: - return Form.Element.Serializers.textarea(element); + return Form.Element.Serializers.textarea(element, value); } }, - inputSelector: function(element) { - return element.checked ? element.value : null; + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; }, - textarea: function(element) { - return element.value; + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; }, - select: function(element) { - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); + select: function(element, index) { + if (Object.isUndefined(index)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } }, selectOne: function(element) { @@ -2833,45 +3576,34 @@ Form.Element.Serializers = { // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } -} +}; /*--------------------------------------------------------------------------*/ -Abstract.TimedObserver = function() {} -Abstract.TimedObserver.prototype = { - initialize: function(element, frequency, callback) { - this.frequency = frequency; +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); this.element = $(element); - this.callback = callback; - this.lastValue = this.getValue(); - this.registerCallback(); }, - registerCallback: function() { - setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - onTimerEvent: function() { + execute: function() { var value = this.getValue(); - var changed = ('string' == typeof this.lastValue && 'string' == typeof value - ? this.lastValue != value : String(this.lastValue) != String(value)); - if (changed) { + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } -} +}); -Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { +Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); -Form.Observer = Class.create(); -Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { +Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } @@ -2879,8 +3611,7 @@ Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { /*--------------------------------------------------------------------------*/ -Abstract.EventObserver = function() {} -Abstract.EventObserver.prototype = { +Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; @@ -2901,7 +3632,7 @@ Abstract.EventObserver.prototype = { }, registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback.bind(this)); + Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { @@ -2917,24 +3648,20 @@ Abstract.EventObserver.prototype = { } } } -} +}); -Form.Element.EventObserver = Class.create(); -Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); -Form.EventObserver = Class.create(); -Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { +Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); -if (!window.Event) { - var Event = new Object(); -} +if (!window.Event) var Event = { }; Object.extend(Event, { KEY_BACKSPACE: 8, @@ -2950,100 +3677,339 @@ Object.extend(Event, { KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, + KEY_INSERT: 45, - element: function(event) { - return $(event.target || event.srcElement); - }, - - isLeftClick: function(event) { - return (((event.which) && (event.which == 1)) || - ((event.button) && (event.button == 1))); - }, - - pointerX: function(event) { - return event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)); - }, - - pointerY: function(event) { - return event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)); - }, + cache: { }, - stop: function(event) { - if (event.preventDefault) { - event.preventDefault(); - event.stopPropagation(); - } else { - event.returnValue = false; - event.cancelBubble = true; + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; } - }, + return Element.extend(element); + } +}); - // find the first node with the given tagName, starting from the - // node the event was triggered on; traverses the DOM upwards - findElement: function(event, tagName) { - var element = Event.element(event); - while (element.parentNode && (!element.tagName || - (element.tagName.toUpperCase() != tagName.toUpperCase()))) - element = element.parentNode; - return element; - }, +Event.Methods = (function() { + var isButton; - observers: false, + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; - _observeAndCache: function(element, name, observer, useCapture) { - if (!this.observers) this.observers = []; - if (element.addEventListener) { - this.observers.push([element, name, observer, useCapture]); - element.addEventListener(name, observer, useCapture); - } else if (element.attachEvent) { - this.observers.push([element, name, observer, useCapture]); - element.attachEvent('on' + name, observer); - } - }, + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; - unloadCache: function() { - if (!Event.observers) return; - for (var i = 0, length = Event.observers.length; i < length; i++) { - Event.stopObserving.apply(this, Event.observers[i]); - Event.observers[i][0] = null; - } - Event.observers = false; - }, + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } - observe: function(element, name, observer, useCapture) { - element = $(element); - useCapture = useCapture || false; + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, - if (name == 'keypress' && - (Prototype.Browser.WebKit || element.attachEvent)) - name = 'keydown'; + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, - Event._observeAndCache(element, name, observer, useCapture); - }, + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, - stopObserving: function(element, name, observer, useCapture) { - element = $(element); - useCapture = useCapture || false; + pointer: function(event) { + return { + x: event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)), + y: event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)) + }; + }, - if (name == 'keypress' && - (Prototype.Browser.WebKit || element.attachEvent)) - name = 'keydown'; + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, - if (element.removeEventListener) { - element.removeEventListener(name, observer, useCapture); - } else if (element.detachEvent) { - try { - element.detachEvent('on' + name, observer); - } catch (e) {} + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._eventID) return element._eventID; + arguments.callee.id = arguments.callee.id || 1; + return element._eventID = ++arguments.callee.id; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false }); -/* prevent memory leaks in IE */ -if (Prototype.Browser.IE) - Event.observe(window, 'unload', Event.unloadCache, false); +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + document.loaded = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write("'); + document.write(' <% end %> <% end %> diff --git a/app/views/repository_mirrors/_form.html.erb b/app/views/repository_mirrors/_form.html.erb index 6598915a0..e1bac8800 100644 --- a/app/views/repository_mirrors/_form.html.erb +++ b/app/views/repository_mirrors/_form.html.erb @@ -1,8 +1,36 @@ -<%= error_messages_for 'repository_mirrors' %> +
<%= error_messages_for 'mirror' %>

<%= f.text_field :url, :required => true, :size => 65 %>

-

<%= f.check_box :active %>

+

<%= f.select :push_mode, options_for_select([[l(:label_full_mirror),0],[l(:label_forced_update),1],[l(:label_fast_forward),2]], @mirror.push_mode), :label => :label_push_mode %> +

+ + diff --git a/app/views/repository_mirrors/create.html.erb b/app/views/repository_mirrors/create.html.erb index b3eafd8e3..f2f61846e 100644 --- a/app/views/repository_mirrors/create.html.erb +++ b/app/views/repository_mirrors/create.html.erb @@ -1,5 +1,5 @@ <% if not @is_xhr %>

<%=l(:label_mirror_create)%>

<% end %> -<% labelled_form_for :repository_mirrors, @mirror, +<% labelled_remote_form_for :repository_mirrors, @mirror, :url => { :controller => 'repository_mirrors', :action => 'create', :project_id => @project, :id => @mirror.id }, :html => {:method => :post, :class => 'tabular'} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff --git a/app/views/repository_mirrors/edit.html.erb b/app/views/repository_mirrors/edit.html.erb index cc9b3b066..cd936c5d4 100644 --- a/app/views/repository_mirrors/edit.html.erb +++ b/app/views/repository_mirrors/edit.html.erb @@ -1,5 +1,5 @@ <% if not @is_xhr %>

<%=l(:label_mirror_edit)%>

<% end %> -<% labelled_form_for :repository_mirrors, @mirror, +<% labelled_remote_form_for :repository_mirrors, @mirror, :url => { :controller => 'repository_mirrors', :action => 'update', :project_id => @project, :id => @mirror.id }, :html => {:method => :post, :class => 'tabular'} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff --git a/app/views/repository_mirrors/form_error.rjs b/app/views/repository_mirrors/form_error.rjs new file mode 100644 index 000000000..f235542e3 --- /dev/null +++ b/app/views/repository_mirrors/form_error.rjs @@ -0,0 +1,3 @@ +page.replace_html 'validation_messages', (error_messages_for 'mirror') +page << "if (typeof(Modalbox) != 'undefined' ) { Modalbox.resizeToContent(); }" + diff --git a/app/views/repository_post_receive_urls/_form.html.erb b/app/views/repository_post_receive_urls/_form.html.erb index c1eb6ad07..2cb62cdd4 100644 --- a/app/views/repository_post_receive_urls/_form.html.erb +++ b/app/views/repository_post_receive_urls/_form.html.erb @@ -1,9 +1,9 @@ -<%= error_messages_for 'repository_post_receive_urls' %> +
<%= error_messages_for 'prurl' %>

<%= f.text_field :url, :required => true, :size => 65 %>

-

<%= f.check_box :active %>

<%= f.select :mode, [['Github-style POST', :github], ['Empty GET request', :get]] %>

+

<%= f.check_box :active %>

diff --git a/app/views/repository_post_receive_urls/create.html.erb b/app/views/repository_post_receive_urls/create.html.erb index 37d6ac103..678aaac64 100644 --- a/app/views/repository_post_receive_urls/create.html.erb +++ b/app/views/repository_post_receive_urls/create.html.erb @@ -1,5 +1,5 @@ <% if not @is_xhr %>

<%=l(:label_post_receive_url_create)%>

<% end %> -<% labelled_form_for :repository_post_receive_urls, @prurl, +<% labelled_remote_form_for :repository_post_receive_urls, @prurl, :url => { :controller => 'repository_post_receive_urls', :action => 'create', :project_id => @project, :id => @prurl.id }, :html => {:method => :post, :class => 'tabular'} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff --git a/app/views/repository_post_receive_urls/edit.html.erb b/app/views/repository_post_receive_urls/edit.html.erb index 983f6805c..3d0e7e135 100644 --- a/app/views/repository_post_receive_urls/edit.html.erb +++ b/app/views/repository_post_receive_urls/edit.html.erb @@ -1,5 +1,5 @@ <% if not @is_xhr %>

<%=l(:label_post_receive_url_edit)%>

<% end %> -<% labelled_form_for :repository_post_receive_urls, @prurl, +<% labelled_remote_form_for :repository_post_receive_urls, @prurl, :url => { :controller => 'repository_post_receive_urls', :action => 'update', :project_id => @project, :id => @prurl.id }, :html => {:method => :post, :class => 'tabular'} do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> diff --git a/app/views/repository_post_receive_urls/form_error.rjs b/app/views/repository_post_receive_urls/form_error.rjs new file mode 100644 index 000000000..179467295 --- /dev/null +++ b/app/views/repository_post_receive_urls/form_error.rjs @@ -0,0 +1,3 @@ +page.replace_html 'validation_messages', (error_messages_for 'prurl') +page << "if (typeof(Modalbox) != 'undefined' ) { Modalbox.resizeToContent(); }" + diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 90369c87a..7701441a6 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -30,7 +30,7 @@ bg: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ bg: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ bg: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/bs.yml b/config/locales/bs.yml index a07ee8281..279e0e616 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -30,7 +30,7 @@ bs: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ bs: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ bs: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ca.yml b/config/locales/ca.yml index f3a4623e6..e2d0adfd3 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -30,7 +30,7 @@ ca: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ ca: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ ca: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 355cf0e7a..4f3c5adc6 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -30,7 +30,7 @@ cs: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ cs: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ cs: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/da.yml b/config/locales/da.yml index 964ab7e01..6b797f629 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -30,7 +30,7 @@ da: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ da: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ da: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/de.yml b/config/locales/de.yml index 432794079..64e088275 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -30,7 +30,7 @@ de: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ de: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ de: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/el.yml b/config/locales/el.yml index b1904bddb..a0fa718f5 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -30,7 +30,7 @@ el: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ el: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ el: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/en.yml b/config/locales/en.yml index ee42d882c..c8a36cb86 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,7 +30,7 @@ en: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ en: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ en: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/es.yml b/config/locales/es.yml index e9a0f885f..4844d058b 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -30,7 +30,7 @@ es: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ es: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ es: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 9424829fa..b3a323170 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -30,7 +30,7 @@ fi: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ fi: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ fi: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8ecf752aa..6fe36dede 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -30,7 +30,7 @@ fr: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ fr: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ fr: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 35face782..3af46f5d3 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -30,7 +30,7 @@ gl: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ gl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ gl: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/he.yml b/config/locales/he.yml index 84ccc999d..5692bde83 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -30,7 +30,7 @@ he: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ he: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ he: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/hu.yml b/config/locales/hu.yml index bed9f8065..2cbc404a5 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -30,7 +30,7 @@ field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/id.yml b/config/locales/id.yml index 77a9d5690..34382c724 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -30,7 +30,7 @@ id: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ id: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ id: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/it.yml b/config/locales/it.yml index 0ef334525..2bf6ed9a9 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -30,7 +30,7 @@ it: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ it: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ it: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 648739795..634b4fd0b 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -30,7 +30,7 @@ ja: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ ja: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ ja: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 8fad159ab..5151be9f4 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -30,7 +30,7 @@ ko: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ ko: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ ko: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 2a3bd2660..79f4c0678 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -30,7 +30,7 @@ lt: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ lt: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ lt: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 80bb52710..24f027996 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -30,7 +30,7 @@ nl: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ nl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ nl: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/no.yml b/config/locales/no.yml index 1b18c2219..4f1fbb874 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -30,7 +30,7 @@ field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index ffa1e846b..8f65385a9 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -30,7 +30,7 @@ pl: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ pl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ pl: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 3ec1c398c..9e89af42f 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -96,13 +96,32 @@ pt-BR: mirror_notice_created: O Espelho foi criado com sucesso mirror_notice_updated: O Espelho foi actualizado com sucesso mirror_notice_create_failed: Houve um erro na criação do espelho + mirror_notice_update_failed: Houve um erro ao atualizar o espelho + label_push_mode: Espelho modo de atualização + label_full_mirror: Espelhando + label_forced_update: Atualização forçada + label_fast_forward: Fast Forward (não forçada) + label_inactive: Inativo + label_mirror: Espelho + label_forced: Forçado + label_unforced: Não Forçada + all_references: Todos os Refs + all_branches: Todos os ramos + all_tags: Todas as Tags + explicit: Explícito + field_include_all_branches: Atualize todos os Ramos + field_include_all_tags: Atualize todas Tags + field_explicit_refspec: Especificação de referência explícita label_post_receive_url_create: Criar URL Pós-receber do Repositório label_post_receive_url_delete: Apagar URL Pós-receber do Repositório label_post_receive_url_edit: Editar URL Pós-receber do Repositório post_receive_url_notice_deleted: A URL Pós-receber foi apagado com sucesso post_receive_url_notice_created: A URL Pós-receber foi criado com sucesso post_receive_url_notice_updated: A URL Pós-receber foi actualizado com sucesso - post_receive_url_notice_create_failed: Houve um erro na criação a URL pós-receber + post_receive_url_notice_create_failed: Houve um erro na criação a pós-receber URL + post_receive_url_notice_update_failed: Houve um erro ao atualizar o URL pós-receber + label_github_post: GitHub POST + label_empty_get: Vazio GET field_mode: Modo button_push: Push diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 2bf7d381b..5da01f38c 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -96,13 +96,32 @@ pt: mirror_notice_created: O Espelho foi criado com sucesso mirror_notice_updated: O Espelho foi actualizado com sucesso mirror_notice_create_failed: Houve um erro na criação do espelho + mirror_notice_update_failed: Houve um erro ao atualizar o espelho + label_push_mode: Espelho modo de atualização + label_full_mirror: Espelhando + label_forced_update: Atualização forçada + label_fast_forward: Fast Forward (não forçada) + label_inactive: Inativo + label_mirror: Espelho + label_forced: Forçado + label_unforced: Não Forçada + all_references: Todos os Refs + all_branches: Todos os ramos + all_tags: Todas as Tags + explicit: Explícito + field_include_all_branches: Atualize todos os Ramos + field_include_all_tags: Atualize todas Tags + field_explicit_refspec: Especificação de referência explícita label_post_receive_url_create: Criar URL Pós-receber do Repositório label_post_receive_url_delete: Apagar URL Pós-receber do Repositório label_post_receive_url_edit: Editar URL Pós-receber do Repositório post_receive_url_notice_deleted: A URL Pós-receber foi apagado com sucesso post_receive_url_notice_created: A URL Pós-receber foi criado com sucesso post_receive_url_notice_updated: A URL Pós-receber foi actualizado com sucesso - post_receive_url_notice_create_failed: Houve um erro na criação a URL pós-receber + post_receive_url_notice_create_failed: Houve um erro na criação a pós-receber URL + post_receive_url_notice_update_failed: Houve um erro ao atualizar o URL pós-receber + label_github_post: GitHub POST + label_empty_get: Vazio GET field_mode: Modo button_push: Push diff --git a/config/locales/ro.yml b/config/locales/ro.yml index ea02ac671..8ed42de28 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -30,7 +30,7 @@ ro: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ ro: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ ro: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 60ea86c82..fb09db4c5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -30,7 +30,7 @@ ru: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ ru: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ ru: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 0c0eed31a..3fa172f50 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -30,7 +30,7 @@ sk: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ sk: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ sk: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 7df5290f2..c1b59d78c 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -30,7 +30,7 @@ sl: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ sl: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ sl: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sr.yml b/config/locales/sr.yml index ce2b7bf4b..7e93c1536 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -30,7 +30,7 @@ sr: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ sr: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ sr: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/sv.yml b/config/locales/sv.yml index fc35e0b08..a41bce90a 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -30,7 +30,7 @@ sv: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ sv: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ sv: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/th.yml b/config/locales/th.yml index 5b514b609..8714e2357 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -30,7 +30,7 @@ th: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ th: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ th: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/tr.yml b/config/locales/tr.yml index efeed1e4a..70aa2103a 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -30,7 +30,7 @@ tr: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ tr: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ tr: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 095daa8a2..4d43e6086 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -30,7 +30,7 @@ uk: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ uk: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ uk: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 5cb6ddd2a..466a2b6eb 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -30,7 +30,7 @@ vi: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ vi: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ vi: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 207029ef2..ac651375f 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -30,7 +30,7 @@ field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/config/locales/zh.yml b/config/locales/zh.yml index a79e42948..70cb33940 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -30,7 +30,7 @@ zh: field_git_daemon: Git Daemon field_git_http: Git Smart HTTP field_notify_cia: Notify CIA.vc - + label_git_show_urls: "Show checkout URL's" label_enabled: Enabled @@ -98,6 +98,22 @@ zh: mirror_notice_created: Mirror was successfully created mirror_notice_updated: Mirror was successfully updated mirror_notice_create_failed: Failed to create mirror + mirror_notice_update_failed: Failed to update mirror + label_push_mode: Mirror update mode + label_full_mirror: Complete Mirroring + label_forced_update: Force Update Remote + label_fast_forward: Fast Forward (Unforced) + label_inactive: Inactive + label_mirror: Mirror + label_forced: Forced + label_unforced: Unforced + all_references: All Refs + all_branches: All Branches + all_tags: All Tags + explicit: Explicit + field_include_all_branches: Push all Branches + field_include_all_tags: Push all Tags + field_explicit_refspec: Explicit Reference Spec label_post_receive_url_create: Create Repository Post-receive URL label_post_receive_url_delete: Delete Repository Post-receive URL label_post_receive_url_edit: Edit Repository Post-receive URL @@ -105,7 +121,10 @@ zh: post_receive_url_notice_created: Post-receive URL was successfully created post_receive_url_notice_updated: Post-receive URL was successfully updated post_receive_url_notice_create_failed: Failed to create Post-receive URL + post_receive_url_notice_update_failed: Failed to update Post-receive URL field_mode: Mode + label_github_post: GitHub POST + label_empty_get: Empty GET button_push: Push button_push_title: "Push To Mirror" diff --git a/db/migrate/20120710204007_add_repository_mirror_fields.rb b/db/migrate/20120710204007_add_repository_mirror_fields.rb new file mode 100644 index 000000000..1bd104890 --- /dev/null +++ b/db/migrate/20120710204007_add_repository_mirror_fields.rb @@ -0,0 +1,15 @@ +class AddRepositoryMirrorFields < ActiveRecord::Migration + def self.up + add_column :repository_mirrors, :push_mode, :integer, :default => 0 + add_column :repository_mirrors, :include_all_branches, :boolean, :default => false + add_column :repository_mirrors, :include_all_tags, :boolean, :default => false + add_column :repository_mirrors, :explicit_refspec, :string, :default => "" + end + + def self.down + remove_column :repository_mirrors, :push_mode + remove_column :repository_mirrors, :include_all_branches + remove_column :repository_mirrors, :include_all_tags + remove_column :repository_mirrors, :explicit_refspec + end +end From c1aeff655903411c37c6cea5a7d89d2b89bca309 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Fri, 27 Jul 2012 11:55:04 -0700 Subject: [PATCH 054/107] Fixed bug in post-receive hook which prevented multi-ref notification. This was due to a bug in ruby 1.8.7's Net::HTTP::Post.set_form_data. --- .../hooks/post-receive.redmine_gitolite.rb | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/contrib/hooks/post-receive.redmine_gitolite.rb b/contrib/hooks/post-receive.redmine_gitolite.rb index b41336515..d0f51b779 100755 --- a/contrib/hooks/post-receive.redmine_gitolite.rb +++ b/contrib/hooks/post-receive.redmine_gitolite.rb @@ -28,8 +28,25 @@ def get_http_params(rgh_vars) params end +# Need to do this ourselves, because 1.8.7 ruby is broken +def set_form_data(request, params, sep = '&') + request.body = params.map {|k,v| + if v.instance_of?(Array) + v.map {|e| "#{urlencode(k.to_s)}=#{urlencode(e.to_s)}"}.join(sep) + else + "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" + end + }.join(sep) + + request.content_type = 'application/x-www-form-urlencoded' +end + +def urlencode(str) + str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) } +end + def run_query(url_str, params, with_https) - url_str = (with_https ? "https://" : "http://" ) + url_str.gsub(/^http[s]*:\/\//, "") + url_str = (with_https ? "https://" : "http://" ) + url_str.gsub(/^http[s]*:\/\//, "") success = false begin url = URI.parse(url_str) @@ -41,7 +58,7 @@ def run_query(url_str, params, with_https) http.verify_mode = OpenSSL::SSL::VERIFY_NONE end req = Net::HTTP::Post.new(url.request_uri) - req.set_form_data(params) + set_form_data(req,params) response = http.request(req) do |response| response.read_body do |body_frag| success = response.code.to_i == 200 ? true : false @@ -78,7 +95,7 @@ def run_query(url_str, params, with_https) # Let's read the refs passed to us refs = [] -$<.each do |line| +$<.each do |line| r = line.chomp.strip.split refs.push( [ r[0].to_s, r[1].to_s, r[2].to_s ].join(",") ) end From e158d6cb5a9082b3e3948916c969945b89b15f1e Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Thu, 19 Jul 2012 09:38:13 -0700 Subject: [PATCH 055/107] Rearrange dependencies to prevent failure in testing. --- init.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init.rb b/init.rb index 2421f3455..b0638f276 100755 --- a/init.rb +++ b/init.rb @@ -1,7 +1,5 @@ # coding: utf-8 require 'redmine' -require_dependency 'principal' -require_dependency 'user' require File.join(File.dirname(__FILE__), 'app', 'models', 'git_repository_extra') require File.join(File.dirname(__FILE__), 'app', 'models', 'git_cia_notification') @@ -54,6 +52,8 @@ require "dispatcher" Dispatcher.to_prepare :redmine_git_patches do + require_dependency 'principal' + require_dependency 'user' require_dependency 'git_hosting' require_dependency 'projects_controller' From 008e02992db386470b8eecd0e0a07417e7be2409 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Thu, 26 Jul 2012 23:36:18 -0700 Subject: [PATCH 056/107] Added the ability to use an alternate gitolite configuration file. New parameters gitConfigFile and gitConfigHasAdminKey set this feature. The gitConfigFile is relative to the conf/ directory. The gitConfigHasAdminKey parameter states whether or not the plugin will maintain the administrative key in the gitConfigFile (forced to true for default config file of gitolite.conf. --- README.mkd | 12 +- app/models/git_hosting_settings_observer.rb | 334 ++-- .../settings/_redmine_git_hosting.html.erb | 10 + config/locales/bg.yml | 4 + config/locales/bs.yml | 4 + config/locales/ca.yml | 4 + config/locales/cs.yml | 4 + config/locales/da.yml | 4 + config/locales/de.yml | 4 + config/locales/el.yml | 4 + config/locales/en.yml | 4 + config/locales/es.yml | 4 + config/locales/fi.yml | 4 + config/locales/fr.yml | 4 + config/locales/gl.yml | 4 + config/locales/he.yml | 4 + config/locales/hu.yml | 4 + config/locales/id.yml | 4 + config/locales/it.yml | 4 + config/locales/ja.yml | 4 + config/locales/ko.yml | 4 + config/locales/lt.yml | 4 + config/locales/nl.yml | 4 + config/locales/no.yml | 4 + config/locales/pl.yml | 4 + config/locales/pt-BR.yml | 4 + config/locales/pt.yml | 4 + config/locales/ro.yml | 4 + config/locales/ru.yml | 4 + config/locales/sk.yml | 4 + config/locales/sl.yml | 4 + config/locales/sr.yml | 4 + config/locales/sv.yml | 4 + config/locales/th.yml | 4 + config/locales/tr.yml | 4 + config/locales/uk.yml | 4 + config/locales/vi.yml | 4 + config/locales/zh-TW.yml | 4 + config/locales/zh.yml | 4 + ...20120724211806_add_settings_to_plugin_5.rb | 33 + init.rb | 26 +- lib/git_hosting.rb | 1435 +++++++++-------- lib/gitolite_conf.rb | 224 +-- 43 files changed, 1232 insertions(+), 986 deletions(-) create mode 100644 db/migrate/20120724211806_add_settings_to_plugin_5.rb diff --git a/README.mkd b/README.mkd index 0d82747d0..160ca8aab 100755 --- a/README.mkd +++ b/README.mkd @@ -131,8 +131,8 @@ root directory: **(6)** It is best to set several plugin variables BEFORE you run the db:migrate\_plugins task in step (7). In particular it is important that the **gitScriptDir**, **gitUser**, **httpServer**, **gitServer**, **gitoliteIdentityFile** and **gitoliteIdentityPublicKeyFile** -variables are set correctly. Others that could be set include **gitRepositoryBasePath**, **gitRedmineSubdir**, and **gitRepositoryHierarchy**; however, -the default values for these variables should be sufficient for most installations. +variables are set correctly. Others that could be set include **gitConfigFile**, **gitConfigHasAdminKey**, **gitRepositoryBasePath**, +**gitRedmineSubdir**, and **gitRepositoryHierarchy**; however, the default values for these variables should be sufficient for most installations. To perform initial adjustment of settings, open an editor and edit 'REDMINE_ROOT/vendor/plugins/redmine_git_hosting/init.rb' file. Starting on line 17, you will see the settings definitions you should edit. @@ -143,7 +143,7 @@ default location should be sufficient for most installations.* An *absolute* path can be utilized to place scripts anywhere in the filesystem. For instance, if you would prefer for the scripts to be placed in the /tmp directory to mimic the behavior of prior versions of plugin, you can set **gitScriptDir** to the same directory as **gitTemporaryDir** (i.e. '/tmp/redmine_git_hosting/' by - default). Note that the script directory must be executable (thereby preventing scripts from being placed in the /tmp directory in some systems). + default). Note that the script directory must be executable (thereby preventing scripts from being placed in the /tmp directory some systems). If non-empty, the **gitScriptDir** path should end in a path separator, e.g. '/'. In step (8), below, you will build the scripts and decide whether they can be modifiable by the plugin while it is running. @@ -164,6 +164,12 @@ If you followed the above directions you will not need to modify the **gitoliteI the path to the private/public key files for accessing the gitolite admin repository. Further, although you can change the following three variables, their default values provide for a very reasonable installation: +* The **gitConfigFile** parameter specifies the gitolite configuration file used by Redmine for Redmine-managed repositories. This file (or path) is relative +to the gitolite conf/ directory. The default value of "gitolite.conf" is sufficient for most configurations. If you choose to change this parameter, you will need +to place a corresponding "include" statement in gitolite.conf. Note that **gitConfigHasAdminKey** may be set to false for a non-standard gitolite.conf file. +When this parameter is false, the plugin will assume that the administrative key is in the main gitolite.conf file; when true, the plugin will attempt to maintain +the administrative key in the Redmine-managed gitolite config file. + * The **gitRepositoryBasePath** is the path *relative to the git user root* where the repositories are located. This should always be non-empty and should end in a file separator, e.g. '/'. Since gitolite always uses repositories/ as the default place for repositories you probably shouldn't have to change this. diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb index 9e666f727..fbab2c2e5 100644 --- a/app/models/git_hosting_settings_observer.rb +++ b/app/models/git_hosting_settings_observer.rb @@ -9,169 +9,187 @@ def reload_this_observer end end - # There is a long-running bug in ActiveRecord::Observer that prevents us from - # returning from before_save() with false to signal verification failure. - # - # Thus, we can only silently refuse to perform bad changes and/or perform - # slight corrections to badly formatted values. - def before_save(object) - # Only validate settings for our plugin - if object.name == "plugin_redmine_git_hosting" - valuehash = object.value - if !GitHosting.bin_dir_writeable? - # If bin directory not alterable, don't allow changes to - # Script directory, Git Username, or Gitolite public or private keys - valuehash['gitScriptDir'] = @@old_valuehash['gitScriptDir'] - valuehash['gitUser'] = @@old_valuehash['gitUser'] - valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile'] - valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile'] - elsif valuehash['gitScriptDir'] && (valuehash['gitScriptDir'] != @@old_valuehash['gitScriptDir']) + # There is a long-running bug in ActiveRecord::Observer that prevents us from + # returning from before_save() with false to signal verification failure. + # + # Thus, we can only silently refuse to perform bad changes and/or perform + # slight corrections to badly formatted values. + def before_save(object) + # Only validate settings for our plugin + if object.name == "plugin_redmine_git_hosting" + valuehash = object.value + if !GitHosting.bin_dir_writeable? + # If bin directory not alterable, don't allow changes to + # Script directory, Git Username, or Gitolite public or private keys + valuehash['gitScriptDir'] = @@old_valuehash['gitScriptDir'] + valuehash['gitUser'] = @@old_valuehash['gitUser'] + valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile'] + valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile'] + elsif valuehash['gitScriptDir'] && (valuehash['gitScriptDir'] != @@old_valuehash['gitScriptDir']) # Remove old bin directory and scripts, since about to change directory - %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] - - # Script directory either absolute or relative to redmine root - stripped = valuehash['gitScriptDir'].lstrip.rstrip - normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components - if (normalizedFile == "/") - # Assume that we are relative bin directory ("/" and "" => "") - valuehash['gitScriptDir'] = "" - elsif (stripped[0,1] != "/") - valuehash['gitScriptDir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitScriptDir'] = normalizedFile + "/" # Add trailing '/' - end - elsif valuehash['gitUser'] != @@old_valuehash['gitUser'] || - valuehash['gitoliteIdentityFile'] != @@old_valuehash['gitoliteIdentityFile'] || - valuehash['gitoliteIdentityPublicKeyFile'] != @@old_valuehash['gitoliteIdentityPublicKeyFile'] + %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] + + # Script directory either absolute or relative to redmine root + stripped = valuehash['gitScriptDir'].lstrip.rstrip + normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components + if (normalizedFile == "/") + # Assume that we are relative bin directory ("/" and "" => "") + valuehash['gitScriptDir'] = "" + elsif (stripped[0,1] != "/") + valuehash['gitScriptDir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitScriptDir'] = normalizedFile + "/" # Add trailing '/' + end + elsif valuehash['gitUser'] != @@old_valuehash['gitUser'] || + valuehash['gitoliteIdentityFile'] != @@old_valuehash['gitoliteIdentityFile'] || + valuehash['gitoliteIdentityPublicKeyFile'] != @@old_valuehash['gitoliteIdentityPublicKeyFile'] # Remove old scripts, since about to change content (leave directory alone) - %x[ rm -f '#{ GitHosting.get_bin_dir }*' ] - end - - # Temp directory must be absolute and not-empty - if valuehash['gitTempDataDir'] && (valuehash['gitTempDataDir'] != @@old_valuehash['gitTempDataDir']) - # Remove old tmp directory, since about to change - %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] - - stripped = valuehash['gitTempDataDir'].lstrip.rstrip - normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components - if (normalizedFile == "/" || stripped[0,1] != "/") - # Don't allow either root-level (absolute) or relative - valuehash['gitTempDataDir'] = "/tmp/redmine_git_hosting/" - else - valuehash['gitTempDataDir'] = normalizedFile + "/" # Add trailing '/' - end - end - - # Server should not include any path components. Also, ports should be numeric. - if valuehash['gitServer'] - normalizedServer = valuehash['gitServer'].lstrip.rstrip.split('/').first - if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) - valuehash['gitServer'] = @@old_valuehash['gitServer'] - else - valuehash['gitServer'] = normalizedServer - end - end - - # Server should not include any path components. Also, ports should be numeric. - if valuehash['httpServer'] - normalizedServer = valuehash['httpServer'].lstrip.rstrip.split('/').first - if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) - valuehash['httpServer'] = @@old_valuehash['httpServer'] - else - valuehash['httpServer'] = normalizedServer - end - end - - # Normalize http repository subdirectory path, should be either empty or relative and end in '/' - if valuehash['httpServerSubdir'] - normalizedFile = File.expand_path(valuehash['httpServerSubdir'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['httpServerSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['httpServerSubdir'] = '' - end - end - - # Normalize Repository path, should be relative and end in '/' - if valuehash['gitRepositoryBasePath'] - normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitRepositoryBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitRepositoryBasePath'] = @@old_valuehash['gitRepositoryBasePath'] - end - end - - # Normalize Redmine Subdirectory path, should be either empty or relative and end in '/' - if valuehash['gitRedmineSubdir'] - normalizedFile = File.expand_path(valuehash['gitRedmineSubdir'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitRedmineSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitRedmineSubdir'] = '' - end - end - - # Normalize Recycle bin path, should be relative and end in '/' - if valuehash['gitRecycleBasePath'] - normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitRecycleBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitRecycleBasePath'] = @@old_valuehash['gitRecycleBasePath'] - end - end - - # Exclude bad expire times (and exclude non-numbers) + %x[ rm -f '#{ GitHosting.get_bin_dir }*' ] + end + + # Temp directory must be absolute and not-empty + if valuehash['gitTempDataDir'] && (valuehash['gitTempDataDir'] != @@old_valuehash['gitTempDataDir']) + # Remove old tmp directory, since about to change + %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + + stripped = valuehash['gitTempDataDir'].lstrip.rstrip + normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components + if (normalizedFile == "/" || stripped[0,1] != "/") + # Don't allow either root-level (absolute) or relative + valuehash['gitTempDataDir'] = "/tmp/redmine_git_hosting/" + else + valuehash['gitTempDataDir'] = normalizedFile + "/" # Add trailing '/' + end + end + + # Server should not include any path components. Also, ports should be numeric. + if valuehash['gitServer'] + normalizedServer = valuehash['gitServer'].lstrip.rstrip.split('/').first + if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) + valuehash['gitServer'] = @@old_valuehash['gitServer'] + else + valuehash['gitServer'] = normalizedServer + end + end + + # Server should not include any path components. Also, ports should be numeric. + if valuehash['httpServer'] + normalizedServer = valuehash['httpServer'].lstrip.rstrip.split('/').first + if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) + valuehash['httpServer'] = @@old_valuehash['httpServer'] + else + valuehash['httpServer'] = normalizedServer + end + end + + # Normalize http repository subdirectory path, should be either empty or relative and end in '/' + if valuehash['httpServerSubdir'] + normalizedFile = File.expand_path(valuehash['httpServerSubdir'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['httpServerSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['httpServerSubdir'] = '' + end + end + + # Normalize Config File + if valuehash['gitConfigFile'] + # Must be relative! + normalizedFile = File.expand_path(valuehash['gitConfigFile'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitConfigFile'] = normalizedFile[1..-1] # Clobber leading '/' + else + valuehash['gitConfigFile'] = GitHosting::GitoliteConfig::PRIMARY_CONF_FILE + end + + # Repair key must be true if default path + if valuehash['gitConfigFile'] == GitHosting::GitoliteConfig::PRIMARY_CONF_FILE + valuehash['gitConfigHasAdminKey'] = 'true' + end + end + + # Normalize Repository path, should be relative and end in '/' + if valuehash['gitRepositoryBasePath'] + normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRepositoryBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRepositoryBasePath'] = @@old_valuehash['gitRepositoryBasePath'] + end + end + + # Normalize Redmine Subdirectory path, should be either empty or relative and end in '/' + if valuehash['gitRedmineSubdir'] + normalizedFile = File.expand_path(valuehash['gitRedmineSubdir'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRedmineSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRedmineSubdir'] = '' + end + end + + # Normalize Recycle bin path, should be relative and end in '/' + if valuehash['gitRecycleBasePath'] + normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRecycleBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRecycleBasePath'] = @@old_valuehash['gitRecycleBasePath'] + end + end + + # Exclude bad expire times (and exclude non-numbers) if valuehash['gitRecycleExpireTime'] - if valuehash['gitRecycleExpireTime'].to_f > 0 - valuehash['gitRecycleExpireTime'] = "#{(valuehash['gitRecycleExpireTime'].to_f * 10).to_i / 10.0}" - else - valuehash['gitRecycleExpireTime'] = @@old_valuehash['gitRecycleExpireTime'] - end - end - - # Validate wait time > 0 (and exclude non-numbers) - if valuehash['gitLockWaitTime'] - if valuehash['gitLockWaitTime'].to_i > 0 - valuehash['gitLockWaitTime'] = "#{valuehash['gitLockWaitTime'].to_i}" - else - valuehash['gitLockWaitTime'] = @@old_valuehash['gitLockWaitTime'] - end - end - # Save back results - object.value = valuehash - end - end - + if valuehash['gitRecycleExpireTime'].to_f > 0 + valuehash['gitRecycleExpireTime'] = "#{(valuehash['gitRecycleExpireTime'].to_f * 10).to_i / 10.0}" + else + valuehash['gitRecycleExpireTime'] = @@old_valuehash['gitRecycleExpireTime'] + end + end + + # Validate wait time > 0 (and exclude non-numbers) + if valuehash['gitLockWaitTime'] + if valuehash['gitLockWaitTime'].to_i > 0 + valuehash['gitLockWaitTime'] = "#{valuehash['gitLockWaitTime'].to_i}" + else + valuehash['gitLockWaitTime'] = @@old_valuehash['gitLockWaitTime'] + end + end + # Save back results + object.value = valuehash + end + end + def after_save(object) - # Only perform after-actions on settings for our plugin + # Only perform after-actions on settings for our plugin if object.name == "plugin_redmine_git_hosting" - valuehash = object.value - + valuehash = object.value + # Settings cache doesn't seem to invalidate symbolic versions of Settings immediately, - # so, any use of Setting.plugin_redmine_git_hosting[] by things called during this - # callback will be outdated.... True for at least some versions of redmine plugin... - # - # John Kubiatowicz 12/21/2011 + # so, any use of Setting.plugin_redmine_git_hosting[] by things called during this + # callback will be outdated.... True for at least some versions of redmine plugin... + # + # John Kubiatowicz 12/21/2011 if Setting.respond_to?(:check_cache) - # Clear out all cached settings. - Setting.check_cache - end + # Clear out all cached settings. + Setting.check_cache + end if @@old_valuehash['gitScriptDir'] != valuehash['gitScriptDir'] || - @@old_valuehash['gitUser'] != valuehash['gitUser'] || - @@old_valuehash['gitoliteIdentityFile'] != valuehash['gitoliteIdentityFile'] || - @@old_valuehash['gitoliteIdentityPublicKeyFile'] != valuehash['gitoliteIdentityPublicKeyFile'] - # Need to update scripts - GitHosting.update_git_exec - end + @@old_valuehash['gitUser'] != valuehash['gitUser'] || + @@old_valuehash['gitoliteIdentityFile'] != valuehash['gitoliteIdentityFile'] || + @@old_valuehash['gitoliteIdentityPublicKeyFile'] != valuehash['gitoliteIdentityPublicKeyFile'] + # Need to update scripts + GitHosting.update_git_exec + end if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] || - @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || - @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || - @@old_valuehash['gitUser'] != valuehash['gitUser'] - # Need to update everyone! + @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || + @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || + @@old_valuehash['gitUser'] != valuehash['gitUser'] || + @@old_valuehash['gitConfigFile'] != valuehash['gitConfigFile'] || + @@old_valuehash['gitConfigHasAdminKey'] != valuehash['gitConfigHasAdminKey'] + # Need to update everyone! GitHostingObserver.bracketed_update_repositories(:resync_all) end @@ -179,15 +197,15 @@ def after_save(object) GitHosting.setup_hooks - elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] || - @@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] || - @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || - @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || - @@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous'] + elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] || + @@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] || + @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || + @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || + @@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous'] GitHosting.update_global_hook_params end - @@old_valuehash = valuehash.clone + @@old_valuehash = valuehash.clone end end end diff --git a/app/views/settings/_redmine_git_hosting.html.erb b/app/views/settings/_redmine_git_hosting.html.erb index 964bd18d9..15af1b864 100644 --- a/app/views/settings/_redmine_git_hosting.html.erb +++ b/app/views/settings/_redmine_git_hosting.html.erb @@ -39,6 +39,16 @@

<%= l(:label_gitolite_repository_settings)%>

+

+ + <%= text_field_tag("settings[gitConfigFile]", "./#{@settings['gitConfigFile']||GitHosting::GitoliteConfig::PRIMARY_CONF_FILE}", :size => 60) %> +
+

+

+ + <%= select_tag("settings[gitConfigHasAdminKey]", options_for_select([[l(:label_true), 'true'], [l(:label_false), 'false']], @settings['gitConfigHasAdminKey'])) %> +
+

<%= text_field_tag("settings[gitRepositoryBasePath]", "./#{@settings['gitRepositoryBasePath']}", :size => 60) %> diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 7701441a6..bac4065ef 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -6,6 +6,8 @@ bg: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ bg: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 279e0e616..7158f7e3d 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -6,6 +6,8 @@ bs: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ bs: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/ca.yml b/config/locales/ca.yml index e2d0adfd3..ed842f7a9 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -6,6 +6,8 @@ ca: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ ca: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 4f3c5adc6..e1fa96dde 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -6,6 +6,8 @@ cs: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ cs: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/da.yml b/config/locales/da.yml index 6b797f629..973630c8d 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -6,6 +6,8 @@ da: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ da: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/de.yml b/config/locales/de.yml index 64e088275..5e5a4b949 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -6,6 +6,8 @@ de: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ de: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/el.yml b/config/locales/el.yml index a0fa718f5..be4529f04 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -6,6 +6,8 @@ el: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ el: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/en.yml b/config/locales/en.yml index c8a36cb86..cb36bc86c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -6,6 +6,8 @@ en: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ en: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/es.yml b/config/locales/es.yml index 4844d058b..573b7f209 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -6,6 +6,8 @@ es: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ es: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/fi.yml b/config/locales/fi.yml index b3a323170..d63be5e6d 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -6,6 +6,8 @@ fi: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ fi: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 6fe36dede..4d2ac0a49 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -6,6 +6,8 @@ fr: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ fr: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 3af46f5d3..d03af4042 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -6,6 +6,8 @@ gl: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ gl: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/he.yml b/config/locales/he.yml index 5692bde83..02252b0e6 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -6,6 +6,8 @@ he: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ he: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 2cbc404a5..e6c37c6cc 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -6,6 +6,8 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/id.yml b/config/locales/id.yml index 34382c724..8e63e11b1 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -6,6 +6,8 @@ id: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ id: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/it.yml b/config/locales/it.yml index 2bf6ed9a9..6be58efd4 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -6,6 +6,8 @@ it: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ it: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 634b4fd0b..0e6ae9c4f 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -6,6 +6,8 @@ ja: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ ja: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 5151be9f4..6cb43503e 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -6,6 +6,8 @@ ko: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ ko: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 79f4c0678..1f621b474 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -6,6 +6,8 @@ lt: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ lt: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 24f027996..a1a64f9cc 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -6,6 +6,8 @@ nl: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ nl: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/no.yml b/config/locales/no.yml index 4f1fbb874..5b01171c6 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -6,6 +6,8 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 8f65385a9..8c2fc822a 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -6,6 +6,8 @@ pl: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ pl: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 9e89af42f..d36ceb9bd 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -6,6 +6,8 @@ pt-BR: label_git_user: Nome de Utilizador Git label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) + label_gitolite_config_file: 'Arquivo de configuração Gitolite
(relativo ao diretório conf)' + label_gitolite_config_has_admin_key: 'Arquivo de configuração Gitolite tem admin chave' label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' label_git_recycle_base_path: 'Directório Recycle Bin base (Relativo à "home" do utilizador git)' label_git_recycle_expire_time: 'expirar o tempo para Recycle Bin (em horas)' @@ -35,6 +37,8 @@ pt-BR: label_git_show_urls: "Exibir os URL's de checkout" label_enabled: Habilitado label_disabled: Desabilitado + label_true: "Verdadeiro" + label_false: "Falso" label_flat: Plano label_hierarchical: 'Hierárquico' label_https_only: Apenas HTTPS diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 5da01f38c..c420e5a0d 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -6,6 +6,8 @@ pt: label_git_user: Nome de Utilizador Git label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) + label_gitolite_config_file: Arquivo de configuração Gitolite
(relativo ao diretório conf) + label_gitolite_config_has_admin_key: Arquivo de configuração Gitolite tem admin chave label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' label_git_recycle_base_path: 'Directório Recycle Bin base (Relativo à "home" do utilizador git)' label_git_recycle_expire_time: 'expirar o tempo para Recycle Bin (em horas)' @@ -35,6 +37,8 @@ pt: label_git_show_urls: "Exibir os URL's de checkout" label_enabled: Habilitado label_disabled: Desabilitado + label_true: "Verdadeiro" + label_false: "Falso" label_flat: Plano label_hierarchical: 'Hierárquico' label_https_only: Apenas HTTPS diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 8ed42de28..6c2978bc4 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -6,6 +6,8 @@ ro: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ ro: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/ru.yml b/config/locales/ru.yml index fb09db4c5..cd6852f21 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -6,6 +6,8 @@ ru: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ ru: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 3fa172f50..0c18347e9 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -6,6 +6,8 @@ sk: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ sk: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/sl.yml b/config/locales/sl.yml index c1b59d78c..5ea76e91d 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -6,6 +6,8 @@ sl: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ sl: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 7e93c1536..1b7388206 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -6,6 +6,8 @@ sr: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ sr: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/sv.yml b/config/locales/sv.yml index a41bce90a..bc039bd38 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -6,6 +6,8 @@ sv: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ sv: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/th.yml b/config/locales/th.yml index 8714e2357..4621d24a1 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -6,6 +6,8 @@ th: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ th: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 70aa2103a..752100704 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -6,6 +6,8 @@ tr: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ tr: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 4d43e6086..a84293b29 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -6,6 +6,8 @@ uk: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ uk: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 466a2b6eb..6dd5c2963 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -6,6 +6,8 @@ vi: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ vi: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index ac651375f..61031f104 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -6,6 +6,8 @@ label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 70cb33940..d86b3eba9 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -6,6 +6,8 @@ zh: label_git_user: Git Username label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Gitolite SSH Identity File (Private Key) + label_gitolite_config_file: Gitolite Configuration File
(Relative to conf directory) + label_gitolite_config_has_admin_key: Gitolite Configuration File has Admin Key label_git_repository_base_path: Git Repository Base Path
(Relative to git user home) label_git_recycle_base_path: Git Recycle Bin Base Path
(Relative to git user home) label_git_recycle_expire_time: Expire Time for Repositories in Recycle Bin
(in hours) @@ -35,6 +37,8 @@ zh: label_git_show_urls: "Show checkout URL's" label_enabled: Enabled label_disabled: Disabled + label_true: "True" + label_false: "False" label_flat: Flat label_hierarchical: Hierarchical label_https_only: HTTPS Only diff --git a/db/migrate/20120724211806_add_settings_to_plugin_5.rb b/db/migrate/20120724211806_add_settings_to_plugin_5.rb new file mode 100644 index 000000000..89efb0861 --- /dev/null +++ b/db/migrate/20120724211806_add_settings_to_plugin_5.rb @@ -0,0 +1,33 @@ +class AddSettingsToPlugin5 < ActiveRecord::Migration + def self.up + begin + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitConfigFile'] ||= 'gitolite.conf' + valuehash['gitConfigHasAdminKey'] || 'true' + + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Added redmine_git_hosting settings: 'gitConfigFile', 'gitConfigHasAdminKey'" + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if plugin settings don't exist yet + end + end + + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitConfigFile') + valuehash.delete('gitConfigHasAdminKey') + + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Removed redmine_git_hosting settings: 'gitConfigFile', 'gitConfigHasAdminKey" + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if table doesn't exist yet.... + end + end +end diff --git a/init.rb b/init.rb index b0638f276..4084f9e52 100755 --- a/init.rb +++ b/init.rb @@ -13,21 +13,23 @@ settings :default => { 'httpServer' => 'localhost', - 'httpServerSubdir' => '', + 'httpServerSubdir' => '', 'gitServer' => 'localhost', 'gitUser' => 'git', + 'gitConfigPath' => 'gitolite.conf', # Redmine gitolite config file + 'gitConfigHasAdminKey' => 'true', # Conf file should have admin key 'gitRepositoryBasePath' => 'repositories/', - 'gitRedmineSubdir' => '', - 'gitRepositoryHierarchy' => 'true', - 'gitRecycleBasePath' => 'recycle_bin/', - 'gitRecycleExpireTime' => '24.0', - 'gitLockWaitTime' => '10', + 'gitRedmineSubdir' => '', + 'gitRepositoryHierarchy' => 'true', + 'gitRecycleBasePath' => 'recycle_bin/', + 'gitRecycleExpireTime' => '24.0', + 'gitLockWaitTime' => '10', 'gitoliteIdentityFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa', 'gitoliteIdentityPublicKeyFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa.pub', 'allProjectsUseGit' => 'false', - 'gitDaemonDefault' => '1', # Default is Daemon enabled + 'gitDaemonDefault' => '1', # Default is Daemon enabled 'gitHttpDefault' => '1', # Default is HTTP_ONLY - 'gitNotifyCIADefault' => '0', # Default is CIA Notification disabled + 'gitNotifyCIADefault' => '0', # Default is CIA Notification disabled 'deleteGitRepositories' => 'false', 'gitRepositoriesShowUrl' => 'true', 'gitCacheMaxTime' => '-1', @@ -35,9 +37,9 @@ 'gitCacheMaxSize' => '16', 'gitHooksDebug' => 'false', 'gitHooksAreAsynchronous' => 'true', - 'gitTempDataDir' => '/tmp/redmine_git_hosting/', + 'gitTempDataDir' => '/tmp/redmine_git_hosting/', 'gitScriptDir' => '', - 'gitForceHooksUpdate' => 'true' + 'gitForceHooksUpdate' => 'true' }, :partial => 'redmine_git_hosting' project_module :repository do @@ -45,8 +47,8 @@ permission :view_repository_mirrors, :repository_mirrors => :index permission :edit_repository_mirrors, :repository_mirrors => :edit permission :create_repository_post_receive_urls, :repository_post_receive_urls => :create - permission :view_repository_post_receive_urls, :repository_post_receive_urls => :index - permission :edit_repository_post_receive_urls, :repository_post_receive_urls => :edit + permission :view_repository_post_receive_urls, :repository_post_receive_urls => :index + permission :edit_repository_post_receive_urls, :repository_post_receive_urls => :edit end end require "dispatcher" diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index 872234aca..5c52e3c55 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -10,65 +10,70 @@ module GitHosting - LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) + LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) REPOSITORY_IF_UNDEF = "repositories/" # In case settings not migrated (normally from settings) REDMINE_SUBDIR = "" # In case settings not migrated (normally from settings) REDMINE_HIERARCHICAL = "true" # In case settings not migrated (normally from settings) - HTTP_SERVER_SUBDIR = "" # In case settings not migrated (normally from settings) + HTTP_SERVER_SUBDIR = "" # In case settings not migrated (normally from settings) TEMP_DATA_DIR = "/tmp/redmine_git_hosting" # In case settings not migrated (normally from settings) - SCRIPT_DIR = "" # In case settings not migrated (normally from settings) + SCRIPT_DIR = "" # In case settings not migrated (normally from settings) SCRIPT_PARENT = "bin" - + # Used to register errors when pulling and pushing the conf file - class GitHostingException < StandardError - end - - # Time in seconds to wait before giving up on acquiring the lock - def self.lock_wait_time - Setting.plugin_redmine_git_hosting['gitLockWaitTime'].to_i || LOCK_WAIT_IF_UNDEF - end - - # Repository base path (relative to git user home directory) - def self.repository_base - Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] || REPOSITORY_IF_UNDEF - end - - # Redmine subdirectory path (relative to Repository base path - def self.repository_redmine_subdir - Setting.plugin_redmine_git_hosting['gitRedmineSubdir'] || REDMINE_SUBDIR - end - - # Redmine repositories in hierarchy - def self.repository_hierarchy - (Setting.plugin_redmine_git_hosting['gitRepositoryHierarchy'] || REDMINE_HIERARCHICAL) != "false" - end + class GitHostingException < StandardError + end + + # Time in seconds to wait before giving up on acquiring the lock + def self.lock_wait_time + Setting.plugin_redmine_git_hosting['gitLockWaitTime'].to_i || LOCK_WAIT_IF_UNDEF + end + + # Configuration file (relative to git conf directory) + def self.gitolite_conf + GitoliteConfig.gitolite_conf + end + + # Repository base path (relative to git user home directory) + def self.repository_base + Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] || REPOSITORY_IF_UNDEF + end + + # Redmine subdirectory path (relative to Repository base path + def self.repository_redmine_subdir + Setting.plugin_redmine_git_hosting['gitRedmineSubdir'] || REDMINE_SUBDIR + end + + # Redmine repositories in hierarchy + def self.repository_hierarchy + (Setting.plugin_redmine_git_hosting['gitRepositoryHierarchy'] || REDMINE_HIERARCHICAL) != "false" + end def self.http_server_subdir - Setting.plugin_redmine_git_hosting['httpServerSubdir'] || HTTP_SERVER_SUBDIR - end - - # This is the file portion of the url used when talking through ssh to the repository. - def self.git_access_url project - return "#{repository_name(project)}" - end - - # This is the relative portion of the url (below the rails_root) used when talking through httpd to the repository - # Note that this differs from the git_access_url in not including 'repository_redmine_subdir' as part of the path. - def self.http_access_url project - return "#{http_server_subdir}#{redmine_name(project)}" - end - - # Server path (minus protocol) - def self.my_root_url - # Remove any path from httpServer in case they are leftover from previous installations. - # No trailing /. - my_root_path = Redmine::Utils::relative_url_root - File.join(Setting.plugin_redmine_git_hosting['httpServer'][/^[^\/]*/],my_root_path,"/")[0..-2] - end - - @@logger = nil + Setting.plugin_redmine_git_hosting['httpServerSubdir'] || HTTP_SERVER_SUBDIR + end + + # This is the file portion of the url used when talking through ssh to the repository. + def self.git_access_url project + return "#{repository_name(project)}" + end + + # This is the relative portion of the url (below the rails_root) used when talking through httpd to the repository + # Note that this differs from the git_access_url in not including 'repository_redmine_subdir' as part of the path. + def self.http_access_url project + return "#{http_server_subdir}#{redmine_name(project)}" + end + + # Server path (minus protocol) + def self.my_root_url + # Remove any path from httpServer in case they are leftover from previous installations. + # No trailing /. + my_root_path = Redmine::Utils::relative_url_root + File.join(Setting.plugin_redmine_git_hosting['httpServer'][/^[^\/]*/],my_root_path,"/")[0..-2] + end + + @@logger = nil def self.logger - @@logger ||= MyLogger.new + @@logger ||= MyLogger.new end @@web_user = nil @@ -79,9 +84,9 @@ def self.web_user return @@web_user end - def self.web_user=(setuser) - @@web_user = setuser - end + def self.web_user=(setuser) + @@web_user = setuser + end def self.git_user Setting.plugin_redmine_git_hosting['gitUser'] @@ -96,11 +101,11 @@ def self.mirror_push_public_key %x[ #{GitHosting.git_user_runner} 'chmod 600 ~/.ssh/gitolite_admin_id_rsa' ] %x[ #{GitHosting.git_user_runner} 'chmod 644 ~/.ssh/gitolite_admin_id_rsa.pub' ] - pubk = ( %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}' ] ).chomp.strip + pubk = ( %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}' ] ).chomp.strip git_user_dir = ( %x[ #{GitHosting.git_user_runner} "cd ~ ; pwd" ] ).chomp.strip %x[ #{GitHosting.git_user_runner} 'echo "#{pubk}" > ~/.ssh/gitolite_admin_id_rsa.pub ' ] %x[ echo '#!/bin/sh' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/run_gitolite_admin_ssh'] - %x[ echo 'exec ssh -o BatchMode=yes -o StrictHostKeyChecking=no -i #{git_user_dir}/.ssh/gitolite_admin_id_rsa "$@"' | #{GitHosting.git_user_runner} "cat >> ~/.ssh/run_gitolite_admin_ssh" ] + %x[ echo 'exec ssh -o BatchMode=yes -o StrictHostKeyChecking=no -i #{git_user_dir}/.ssh/gitolite_admin_id_rsa "$@"' | #{GitHosting.git_user_runner} "cat >> ~/.ssh/run_gitolite_admin_ssh" ] %x[ #{GitHosting.git_user_runner} 'chmod 644 ~/.ssh/gitolite_admin_id_rsa.pub' ] %x[ #{GitHosting.git_user_runner} 'chmod 600 ~/.ssh/gitolite_admin_id_rsa'] %x[ #{GitHosting.git_user_runner} 'chmod 700 ~/.ssh/run_gitolite_admin_ssh'] @@ -151,7 +156,7 @@ def self.sudo_web_to_git_user @@sudo_web_to_git_user_stamp = Time.new return @@sudo_web_to_git_user_cached end - test = %x[#{GitHosting.git_user_runner} echo "yes"] + test = %x[#{GitHosting.git_user_runner} echo "yes"] if test.match(/yes/) @@sudo_web_to_git_user_cached = true @@sudo_web_to_git_user_stamp = Time.new @@ -164,9 +169,9 @@ def self.sudo_web_to_git_user end def self.get_full_parent_path(project, is_file_path) - return "" if !project.parent || !repository_hierarchy + return "" if !project.parent || !repository_hierarchy parent_parts = []; - p = project + p = project while p.parent parent_id = p.parent.identifier.to_s parent_parts.unshift(parent_id) @@ -175,24 +180,24 @@ def self.get_full_parent_path(project, is_file_path) return is_file_path ? File.join(parent_parts) : parent_parts.join("/") end - def self.redmine_name(project) - return File.expand_path(File.join("./",get_full_parent_path(project, false),project.identifier),"/")[1..-1] - end + def self.redmine_name(project) + return File.expand_path(File.join("./",get_full_parent_path(project, false),project.identifier),"/")[1..-1] + end def self.repository_name(project) - return File.expand_path(File.join("./",repository_redmine_subdir,get_full_parent_path(project, false),project.identifier),"/")[1..-1] + return File.expand_path(File.join("./",repository_redmine_subdir,get_full_parent_path(project, false),project.identifier),"/")[1..-1] end def self.repository_path projectId - repo_name = projectId.is_a?(String) ? projectId : repository_name(projectId) + repo_name = projectId.is_a?(String) ? projectId : repository_name(projectId) return File.join(repository_base, repo_name) + ".git" end - @@git_hosting_tmp_dir = nil - @@previous_git_tmp_dir = nil + @@git_hosting_tmp_dir = nil + @@previous_git_tmp_dir = nil def self.get_tmp_dir - tmp_dir = (Setting.plugin_redmine_git_hosting['gitTempDataDir'] || TEMP_DATA_DIR) - if (@@previous_git_tmp_dir != tmp_dir) + tmp_dir = (Setting.plugin_redmine_git_hosting['gitTempDataDir'] || TEMP_DATA_DIR) + if (@@previous_git_tmp_dir != tmp_dir) @@previous_git_tmp_dir = tmp_dir - @@git_hosting_tmp_dir = File.join(tmp_dir,git_user) + "/" + @@git_hosting_tmp_dir = File.join(tmp_dir,git_user) + "/" end if !File.directory?(@@git_hosting_tmp_dir) %x[mkdir -p "#{@@git_hosting_tmp_dir}"] @@ -202,56 +207,56 @@ def self.get_tmp_dir return @@git_hosting_tmp_dir end - @@git_hosting_bin_dir = nil - @@previous_git_script_dir = nil + @@git_hosting_bin_dir = nil + @@previous_git_script_dir = nil def self.get_bin_dir - script_dir = Setting.plugin_redmine_git_hosting['gitScriptDir'] || SCRIPT_DIR - if @@previous_git_script_dir != script_dir - @@previous_git_script_dir = script_dir - @@git_bin_dir_writeable = nil - - # Directory for binaries includes 'SCRIPT_PARENT' at the end. - # Further, absolute path adds additional 'git_user' component for multi-gitolite installations. - if script_dir[0,1] == "/" - @@git_hosting_bin_dir = File.join(script_dir,git_user,SCRIPT_PARENT) + "/" - else - @@git_hosting_bin_dir = Rails.root.join("vendor/plugins/redmine_git_hosting",script_dir,SCRIPT_PARENT).to_s+"/" - end - end - if !File.directory?(@@git_hosting_bin_dir) - logger.info "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" - %x[mkdir -p "#{@@git_hosting_bin_dir}"] - %x[chmod 750 "#{@@git_hosting_bin_dir}"] - %x[chown #{web_user} "#{@@git_hosting_bin_dir}"] - - if !File.directory?(@@git_hosting_bin_dir) - logger.error "Cannot create bin directory: #{@@git_hosting_bin_dir}" - end - end + script_dir = Setting.plugin_redmine_git_hosting['gitScriptDir'] || SCRIPT_DIR + if @@previous_git_script_dir != script_dir + @@previous_git_script_dir = script_dir + @@git_bin_dir_writeable = nil + + # Directory for binaries includes 'SCRIPT_PARENT' at the end. + # Further, absolute path adds additional 'git_user' component for multi-gitolite installations. + if script_dir[0,1] == "/" + @@git_hosting_bin_dir = File.join(script_dir,git_user,SCRIPT_PARENT) + "/" + else + @@git_hosting_bin_dir = Rails.root.join("vendor/plugins/redmine_git_hosting",script_dir,SCRIPT_PARENT).to_s+"/" + end + end + if !File.directory?(@@git_hosting_bin_dir) + logger.info "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" + %x[mkdir -p "#{@@git_hosting_bin_dir}"] + %x[chmod 750 "#{@@git_hosting_bin_dir}"] + %x[chown #{web_user} "#{@@git_hosting_bin_dir}"] + + if !File.directory?(@@git_hosting_bin_dir) + logger.error "Cannot create bin directory: #{@@git_hosting_bin_dir}" + end + end return @@git_hosting_bin_dir end - + @@git_bin_dir_writeable = nil - def self.bin_dir_writeable?(*option) - @@git_bin_dir_writeable = nil if option.length > 0 && option[0] == :reset + def self.bin_dir_writeable?(*option) + @@git_bin_dir_writeable = nil if option.length > 0 && option[0] == :reset if @@git_bin_dir_writeable == nil - mybindir = get_bin_dir - mytestfile = "#{mybindir}/writecheck" - if (!File.directory?(mybindir)) - @@git_bin_dir_writeable = false - else - %x[touch "#{mytestfile}"] - if (!File.exists?("#{mytestfile}")) - @@git_bin_dir_writeable = false - else - %x[rm "#{mytestfile}"] - @@git_bin_dir_writeable = true - end + mybindir = get_bin_dir + mytestfile = "#{mybindir}/writecheck" + if (!File.directory?(mybindir)) + @@git_bin_dir_writeable = false + else + %x[touch "#{mytestfile}"] + if (!File.exists?("#{mytestfile}")) + @@git_bin_dir_writeable = false + else + %x[rm "#{mytestfile}"] + @@git_bin_dir_writeable = true + end end - end + end @@git_bin_dir_writeable - end - + end + def self.git_exec_path return File.join(get_bin_dir, "run_git_as_git_user") end @@ -341,10 +346,10 @@ def self.update_git_exec f.puts '{' f.puts ' $command =~ s/\\\\/\\\\\\\\/g;' # Previous line turns \; => \\; - # If old sudo, turn \\; => "\\;" to protect ';' from loss as command separator during eval - if sudo_version < sudo_version_switch - f.puts ' $command =~ s/(\\\\\\\\;)/"$1"/g;' - end + # If old sudo, turn \\; => "\\;" to protect ';' from loss as command separator during eval + if sudo_version < sudo_version_switch + f.puts ' $command =~ s/(\\\\\\\\;)/"$1"/g;' + end f.puts ' $command =~ s/"/\\\\"/g;' f.puts ' exec("sudo -u ' + git_user + ' -i eval \"$command\"");' f.puts '}' @@ -364,7 +369,7 @@ def self.lock(retries) end while retries > 0 - is_locked = @@lock_file.flock(File::LOCK_EX|File::LOCK_NB) + is_locked = @@lock_file.flock(File::LOCK_EX|File::LOCK_NB) retries-=1 if (!is_locked) && retries > 0 sleep 1 @@ -379,678 +384,680 @@ def self.unlock end end - def self.shell(command) - begin - my_command = "#{command} 2>&1" - result = %x[#{my_command}].chomp - code = $?.exitstatus - rescue Exception => e + def self.shell(command) + begin + my_command = "#{command} 2>&1" + result = %x[#{my_command}].chomp + code = $?.exitstatus + rescue Exception => e result=e.message - code = -1 - end - if code != 0 - logger.error "Command failed (return #{code}): #{command}" - message = " "+result.split("\n").join("\n ") - logger.error message - raise GitHostingException, "Shell Error" - end - end - - # Try to get a cloned version of gitolite-admin repository. - # - # This code tries to recover from a variety of errors which have been observed - # in the field, including a loss of the admin key and an empty top-level directory - # - # Return: false => have uncommitted changes - # true => directory on master - # - # This routine must only be called after acquisition of the lock - # - # John Kubiatowicz, 11/15/11 - # - # This routine will no-longer merge in changes, since this can cause weird behavior - # when interacting with cron-jobs that clean up /tmp. - # - # John Kubiatowicz, 04/23/12 - # + code = -1 + end + if code != 0 + logger.error "Command failed (return #{code}): #{command}" + message = " "+result.split("\n").join("\n ") + logger.error message + raise GitHostingException, "Shell Error" + end + end + + # Try to get a cloned version of gitolite-admin repository. + # + # This code tries to recover from a variety of errors which have been observed + # in the field, including a loss of the admin key and an empty top-level directory + # + # Return: false => have uncommitted changes + # true => directory on master + # + # This routine must only be called after acquisition of the lock + # + # John Kubiatowicz, 11/15/11 + # + # This routine will no-longer merge in changes, since this can cause weird behavior + # when interacting with cron-jobs that clean up /tmp. + # + # John Kubiatowicz, 04/23/12 + # def self.clone_or_pull_gitolite_admin(resync_all_flag) # clone/pull from admin repo - repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) - - # If preexisting directory exists, try to clone and merge.... - if (File.exists? "#{repo_dir}") && (File.exists? "#{repo_dir}/.git") && (File.exists? "#{repo_dir}/keydir") && (File.exists? "#{repo_dir}/conf") - begin - logger.info "Fetching changes from gitolite-admin repository to #{repo_dir}" - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' fetch] + repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) + + # If preexisting directory exists, try to clone and merge.... + if (File.exists? "#{repo_dir}") && (File.exists? "#{repo_dir}/.git") && (File.exists? "#{repo_dir}/keydir") && (File.exists? "#{repo_dir}/conf") + begin + logger.info "Fetching changes from gitolite-admin repository to #{repo_dir}" + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' fetch] shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' merge FETCH_HEAD] - # unmerged changes=> non-empty return - return_val = %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' status --short].empty? + # unmerged changes=> non-empty return + return_val = %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' status --short].empty? - if (return_val) - shell %[chmod 700 "#{repo_dir}" ] - # Make sure we have our hooks setup + if (return_val) + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup GitAdapterHooks.check_hooks_installed - return return_val - else - # The attempt to merge can cause a weird failure mode when interacting with cron jobs that clean out old - # files in /tmp. The issue is that keys in the keydir can go idle and get deleted. Then, when we merge we - # create an admin repo minus those keys (including the admin key!). Only a RESYNC_ALL operation will - # actually fix. Thus, we never return "have uncommitted changes", but instead fail the merge and reclone. - # - # 04/23/12 - # --KUBI-- - logger.error "Seems to be unmerged changes! Going to delete and reclone for safety." - logger.error "May need to execute RESYNC_ALL to fix whatever caused pending changes." unless resync_all_flag - end - rescue - logger.error "Repository fetch and merge failed -- trying to delete and reclone repository." - end - end - - begin - logger.info "Cloning gitolite-admin repository to #{repo_dir}" - shell %[rm -rf "#{repo_dir}"] - shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] - shell %[chmod 700 "#{repo_dir}" ] - # Make sure we have our hooks setup + return return_val + else + # The attempt to merge can cause a weird failure mode when interacting with cron jobs that clean out old + # files in /tmp. The issue is that keys in the keydir can go idle and get deleted. Then, when we merge we + # create an admin repo minus those keys (including the admin key!). Only a RESYNC_ALL operation will + # actually fix. Thus, we never return "have uncommitted changes", but instead fail the merge and reclone. + # + # 04/23/12 + # --KUBI-- + logger.error "Seems to be unmerged changes! Going to delete and reclone for safety." + logger.error "May need to execute RESYNC_ALL to fix whatever caused pending changes." unless resync_all_flag + end + rescue + logger.error "Repository fetch and merge failed -- trying to delete and reclone repository." + end + end + + begin + logger.info "Cloning gitolite-admin repository to #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] + shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup GitAdapterHooks.check_hooks_installed - return true # On master (fresh clone) - rescue - begin - # Try to repair admin access. - fixup_gitolite_admin - - logger.info "Recloning gitolite-admin repository to #{repo_dir}" - shell %[rm -rf "#{repo_dir}"] - shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] - shell %[chmod 700 "#{repo_dir}" ] - # Make sure we have our hooks setup + return true # On master (fresh clone) + rescue + begin + # Try to repair admin access. + fixup_gitolite_admin + + logger.info "Recloning gitolite-admin repository to #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] + shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup GitAdapterHooks.check_hooks_installed - return true # On master (fresh clone) - rescue - logger.error "Cannot clone administrative repository. Requires human intervention!!!" - end - end + return true # On master (fresh clone) + rescue + logger.error "Cannot clone administrative repository. Requires human intervention!!!" + end + end + end + + # Recover from failure to clone repository. + # + # This routine attempts to recover from a failure to clone by reestablishing the gitolite + # key. It does so by directly cloning the gitolite-admin repository and editing the configuration + # file (gitolite.conf). If we ever try to allow gitolite services on a separate server from Redmine, + # we will have to turn this into a stand-alone script. + # + # Ideally, we have gitolite >= 2.0.3 so that we have 'gl-admin-push'. If not, we try to use gl-setup + # which has some quirks and is not as good. + # + # We try to: + # (1) figure out what the proper name is for the access key by first looking in the conf file, then + # looking for a matching keyname in the keydir. + # (2) delete any keys in the keydir that match our key + # (3) reestablish the keyname in the conf file and the key in the keydir + # (4) push the result back to the admin repo. + # + # Most of this activity is all done as the git user, hence the long command lines. Only parsing of the + # conf file is done as the redmine user (hence the need for the separate "tmp_conf_dir". + # + # Return: on success, returns "Success!". On Failure, throws a GitHostingException. + # + # Consider this the "nuclear" option.... + def self.fixup_gitolite_admin + unless GitoliteConfig.has_admin_key? + raise GitHostingException, "Cannot repair Admin Key: Key not managed by Redmine!" + end + logger.warn "Attempting to restore repository access key:" + begin + repo_dir = File.join(Dir.tmpdir,"fixrepo",git_user,GitHosting::GitoliteConfig::ADMIN_REPO) + conf_file = File.join(repo_dir,"conf",gitolite_conf) + keydir = File.join(repo_dir, 'keydir') + + tmp_conf_dir = File.join(Dir.tmpdir,"fixconf",git_user) + tmp_conf_file = File.join(tmp_conf_dir,gitolite_conf) + + logger.warn " Cloning administrative repo directly as #{git_user} in #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] if File.exists?(repo_dir) + admin_repo = "#{GitHosting.repository_base}/#{GitHosting::GitoliteConfig::ADMIN_REPO}" + shell %[#{GitHosting.git_user_runner} git clone #{admin_repo} #{repo_dir}] + + # Load up existing conf file + shell %[mkdir -p #{tmp_conf_dir}] + shell %[#{GitHosting.git_user_runner} 'cat #{conf_file}' | cat > #{tmp_conf_file}] + conf = GitoliteConfig.new(tmp_conf_file) + + # copy key into home directory... + shell %[cat #{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']} | #{GitHosting.git_user_runner} 'cat > ~/id_rsa.pub'] + + # Locate any keys that match new key -- save first one + admin_key_matches = %x[#{GitHosting.git_user_runner} 'find #{keydir} -type f -exec cmp -s ~/id_rsa.pub {} \\; -print'].chomp.split("\n").map do |name| + my_basename = File.basename(name,".pub") + shell %[#{GitHosting.git_user_runner} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{my_basename}.pub] + my_basename + end + + unless admin_key_matches.empty? + oldkeys = admin_key_matches.join(", ") + logger.warn " Deleting old keys in keydir that match gitoliteIdentityPublicKeyFile: #{oldkeys}" + end + + # Grab admin key name out of conf file (if it exists) + my_admin_key = conf.get_admin_key + + # Try to deduce administrative key name first from conf file, then from keydir, then use default. + # Remove all extraneous ".pub" from end, in case something crept through. + new_admin_key_name = (/^(.*?)(\.pub)*$/.match(my_admin_key || admin_key_matches.first || GitHosting::GitoliteConfig::DEFAULT_ADMIN_KEY_NAME))[1] + if my_admin_key + logger.warn " Using admin key name from conf file: '#{new_admin_key_name}'" + else + logger.warn " Using '#{new_admin_key_name}' as the admin key name" + end + + conf.set_admin_key new_admin_key_name + conf.save + shell %[cat #{tmp_conf_file} | #{GitHosting.git_user_runner} 'cat > #{conf_file}'] + shell %[#{GitHosting.git_user_runner} 'mv ~/id_rsa.pub #{keydir}/#{new_admin_key_name}.pub'] + + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/#{gitolite_conf}"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -m 'Updated by Redmine: Emergency repair of gitolite admin key'"] + begin + logger.warn " Pushing fixes using gl-admin-push" + shell %[#{GitHosting.git_user_runner} "cd #{repo_dir}; gl-admin-push -f"] + logger.warn "Successfully reestablished gitolite admin key!" + rescue + logger.error "gl-admin-push failed (pre 2.0.3 gitolite?). Trying 'gl-setup #{keydir}/#{new_admin_key_name}.pub'" + shell %[#{GitHosting.git_user_runner} "gl-setup #{keydir}/#{new_admin_key_name}.pub"] + logger.warn "Hopefully we have successfully reestablished gitolite admin key." + end + %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] + %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] + "Success!" + rescue + logger.error "Failed to reestablish gitolite admin key." + %x[#{GitHosting.git_user_runner} 'rm -f ~/id_rsa.pub'] + %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] + %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] + raise GitHostingException, "Failure to Repair Gitolite Admin Key" + end end - # Recover from failure to clone repository. - # - # This routine attempts to recover from a failure to clone by reestablishing the gitolite - # key. It does so by directly cloning the gitolite-admin repository and editing the gitolite.conf - # file. If we ever try to allow gitolite services on a separate server from Redmine, we will - # have to turn this into a stand-alone script. - # - # Ideally, we have gitolite >= 2.0.3 so that we have 'gl-admin-push'. If not, we try to use gl-setup - # which has some quirks and is not as good. - # - # We try to: - # (1) figure out what the proper name is for the access key by first looking in the conf file, then - # looking for a matching keyname in the keydir. - # (2) delete any keys in the keydir that match our key - # (3) reestablish the keyname in the conf file and the key in the keydir - # (4) push the result back to the admin repo. - # - # Most of this activity is all done as the git user, hence the long command lines. Only parsing of the - # conf file is done as the redmine user (hence the need for the separate "tmp_conf_dir". - # - # Return: on success, returns "Success!". On Failure, throws a GitHostingException. - # - # Consider this the "nuclear" option.... - def self.fixup_gitolite_admin - logger.warn "Attempting to restore repository access key:" - begin - repo_dir = File.join(Dir.tmpdir,"fixrepo",git_user,GitHosting::GitoliteConfig::ADMIN_REPO) - conf_file = File.join(repo_dir,"conf","gitolite.conf") - keydir = File.join(repo_dir, 'keydir') - - tmp_conf_dir = File.join(Dir.tmpdir,"fixconf",git_user) - tmp_conf_file = File.join(tmp_conf_dir,"gitolite.conf") - - logger.warn " Cloning administrative repo directly as #{git_user} in #{repo_dir}" - shell %[rm -rf "#{repo_dir}"] if File.exists?(repo_dir) - admin_repo = "#{GitHosting.repository_base}/#{GitHosting::GitoliteConfig::ADMIN_REPO}" - shell %[#{GitHosting.git_user_runner} git clone #{admin_repo} #{repo_dir}] - - # Load up existing conf file - shell %[mkdir -p #{tmp_conf_dir}] - shell %[#{GitHosting.git_user_runner} 'cat #{conf_file}' | cat > #{tmp_conf_file}] - conf = GitoliteConfig.new(tmp_conf_file) - - # copy key into home directory... - shell %[cat #{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']} | #{GitHosting.git_user_runner} 'cat > ~/id_rsa.pub'] - - # Locate any keys that match new key -- save first one - admin_key_matches = %x[#{GitHosting.git_user_runner} 'find #{keydir} -type f -exec cmp -s ~/id_rsa.pub {} \\; -print'].chomp.split("\n").map do |name| - my_basename = File.basename(name,".pub") - shell %[#{GitHosting.git_user_runner} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{my_basename}.pub] - my_basename - end - - unless admin_key_matches.empty? - oldkeys = admin_key_matches.join(", ") - logger.warn " Deleting old keys in keydir that match gitoliteIdentityPublicKeyFile: #{oldkeys}" - end - - # Grab admin key name out of conf file (if it exists) - my_admin_key = conf.get_admin_key - - # Try to deduce administrative key name first from conf file, then from keydir, then use default. - # Remove all extraneous ".pub" from end, in case something crept through. - new_admin_key_name = (/^(.*?)(\.pub)*$/.match(my_admin_key || admin_key_matches.first || GitHosting::GitoliteConfig::DEFAULT_ADMIN_KEY_NAME))[1] - if my_admin_key - logger.warn " Using admin key name from conf file: '#{new_admin_key_name}'" - else - logger.warn " Using '#{new_admin_key_name}' as the admin key name" - end - - conf.set_admin_key new_admin_key_name - conf.save - shell %[cat #{tmp_conf_file} | #{GitHosting.git_user_runner} 'cat > #{conf_file}'] - shell %[#{GitHosting.git_user_runner} 'mv ~/id_rsa.pub #{keydir}/#{new_admin_key_name}.pub'] - - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/gitolite.conf"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -m 'Updated by Redmine: Emergency repair of gitolite admin key'"] - begin - logger.warn " Pushing fixes using gl-admin-push" - shell %[#{GitHosting.git_user_runner} "cd #{repo_dir}; gl-admin-push -f"] - logger.warn "Successfully reestablished gitolite admin key!" - rescue - logger.error "gl-admin-push failed (pre 2.0.3 gitolite?). Trying 'gl-setup #{keydir}/#{new_admin_key_name}.pub'" - shell %[#{GitHosting.git_user_runner} "gl-setup #{keydir}/#{new_admin_key_name}.pub"] - logger.warn "Hopefully we have successfully reestablished gitolite admin key." - end - %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] - %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] - "Success!" - rescue - logger.error "Failed to reestablish gitolite admin key." - %x[#{GitHosting.git_user_runner} 'rm -f ~/id_rsa.pub'] - %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] - %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] - raise GitHostingException, "Failure to Repair Gitolite Admin Key" - end - end - - # Commit Changes to the gitolite-admin repository. This assumes that repository exists - # (i.e. that a clone_or_fetch_gitolite_admin has already be called). - # - # This routine must only be called after acquisition of the lock - # - # John Kubiatowicz, 11/15/11 - def self.commit_gitolite_admin(*args) + # Commit Changes to the gitolite-admin repository. This assumes that repository exists + # (i.e. that a clone_or_fetch_gitolite_admin has already be called). + # + # This routine must only be called after acquisition of the lock + # + # John Kubiatowicz, 11/15/11 + def self.commit_gitolite_admin(*args) resyncing = args && args.first # create tmp dir, return cleanly if, for some reason, we don't have proper permissions - repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) + repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) # commit / push changes to gitolite admin repo - begin + begin if (!resyncing) - logger.info "Committing changes to gitolite-admin repository" - message = "Updated by Redmine" - else - logger.info "Committing corrections to gitolite-admin repository" - message = "Updated by Redmine: Corrections discovered during RESYNC_ALL" - end + logger.info "Committing changes to gitolite-admin repository" + message = "Updated by Redmine" + else + logger.info "Committing corrections to gitolite-admin repository" + message = "Updated by Redmine: Corrections discovered during RESYNC_ALL" + end shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/gitolite.conf] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/#{gitolite_conf}] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'] shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -a -m '#{message}'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -a -m '#{message}'] shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' push ] - rescue - logger.error "Problems committing changes to gitolite-admin repository!! Probably requires human intervention" - raise GitHostingException, "Gitlite-admin Commit Failure" - end - end - - # Update keys for all members of projects of interest - # - # This code is entirely self-correcting for keys owned by users of the specified - # projects. It should work regardless of the history of steps that got us here. - # - # Note that this code has changed from the original. Now, we look at all keys owned - # by users in the specified projects to make sure that they are still live. We - # do this with a single pass through the keydir and do not rely on the "inactive" - # status to tell us that a key should be deleted. The reason is that weird - # synchronization issues (not entirely understood) can cause phantom keys to get left - # in the keydir which can really mess up gitolite. - # - # As of the latest release, we also recover from a variety of aborted MOVE and DELETE - # operations. Further, we better handle movement of complete trees of projects. - # - # Also, when performing :resync_all, if the 'deleteGitRepositories' setting is 'true', - # then we will remove repositories in the gitolite.conf file that are identifiable as - # "redmine managed" (because they have one or more keys of the right form) but which - # are nolonger live for some reason (probably because the project was deleted). - # - # John Kubiatowicz, 01/04/12 - # - # Usage: - # - # 1) update_repositories(project) => update for specified project - # 2) update_repositories([list of projects]) => update all projects - # 3) update_repositories(:flag1=>true, :flag2 => false) - # - # Current flags: - # :resync_all => go through all redmine-maintained gitolite repos, - # clean up keydir, delete unused keys, clean up gitolite.conf - # :delete => Clean up keydir, delete unused keys, remove redmine-maintaind - # gitolite entries and repositories unassociated with live projects. - # Unlike :resync_all, do not fix up live projects - # :descendants => for every given project, update self and all decendants - # :archive => Project is being archived -- remove keys from gitolite.conf, - # and possibly keydir if not used by any other project - # + rescue + logger.error "Problems committing changes to gitolite-admin repository!! Probably requires human intervention" + raise GitHostingException, "Gitlite-admin Commit Failure" + end + end + + # Update keys for all members of projects of interest + # + # This code is entirely self-correcting for keys owned by users of the specified + # projects. It should work regardless of the history of steps that got us here. + # + # Note that this code has changed from the original. Now, we look at all keys owned + # by users in the specified projects to make sure that they are still live. We + # do this with a single pass through the keydir and do not rely on the "inactive" + # status to tell us that a key should be deleted. The reason is that weird + # synchronization issues (not entirely understood) can cause phantom keys to get left + # in the keydir which can really mess up gitolite. + # + # As of the latest release, we also recover from a variety of aborted MOVE and DELETE + # operations. Further, we better handle movement of complete trees of projects. + # + # Also, when performing :resync_all, if the 'deleteGitRepositories' setting is 'true', + # then we will remove repositories in the configuration file (gitolite.conf) that are + # identifiable as "redmine managed" (because they have one or more keys of the right form) + # but which are nolonger live for some reason (probably because the project was deleted). + # + # John Kubiatowicz, 01/04/12 + # + # Usage: + # + # 1) update_repositories(project) => update for specified project + # 2) update_repositories([list of projects]) => update all projects + # 3) update_repositories(:flag1=>true, :flag2 => false) + # + # Current flags: + # :resync_all => go through all redmine-maintained gitolite repos, + # clean up keydir, delete unused keys, clean up gitolite.conf + # :delete => Clean up keydir, delete unused keys, remove redmine-maintaind + # gitolite entries and repositories unassociated with live projects. + # Unlike :resync_all, do not fix up live projects + # :descendants => for every given project, update self and all decendants + # :archive => Project is being archived -- remove keys from gitolite.conf, + # and possibly keydir if not used by any other project + # @@recursionCheck = false def self.update_repositories(*args) - flags = {} - args.each {|arg| flags.merge!(arg) if arg.is_a?(Hash)} - if flags[:resync_all] - logger.info "Executing RESYNC_ALL operation on gitolite configuration" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - elsif flags[:delete] - # When delete, want to recompute users, so need to go through all projects - logger.info "Executing DELETE operation (resync keys, remove dead repositories)" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - elsif flags[:archive] - # When archive, want to recompute users, so need to go through all projects - logger.info "Executing ARCHIVE operation (remove keys)" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - elsif flags[:descendants] - if Project.method_defined?(:self_and_descendants) - projects = (args.flatten.select{|p| p.is_a?(Project)}).collect{|p| p.self_and_descendants}.flatten - else - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - end - else - projects = args.flatten.select{|p| p.is_a?(Project)} - end + flags = {} + args.each {|arg| flags.merge!(arg) if arg.is_a?(Hash)} + if flags[:resync_all] + logger.info "Executing RESYNC_ALL operation on gitolite configuration" + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + elsif flags[:delete] + # When delete, want to recompute users, so need to go through all projects + logger.info "Executing DELETE operation (resync keys, remove dead repositories)" + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + elsif flags[:archive] + # When archive, want to recompute users, so need to go through all projects + logger.info "Executing ARCHIVE operation (remove keys)" + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + elsif flags[:descendants] + if Project.method_defined?(:self_and_descendants) + projects = (args.flatten.select{|p| p.is_a?(Project)}).collect{|p| p.self_and_descendants}.flatten + else + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + end + else + projects = args.flatten.select{|p| p.is_a?(Project)} + end git_projects = projects.uniq.select{|p| p.repository.is_a?(Repository::Git) } - return if git_projects.empty? + return if git_projects.empty? if(defined?(@@recursionCheck)) if(@@recursionCheck) - # This shouldn't happen any more -- log as error - logger.error "git_hosting: update_repositories() exited with positive recursionCheck flag!" + # This shouldn't happen any more -- log as error + logger.error "git_hosting: update_repositories() exited with positive recursionCheck flag!" return end - end + end @@recursionCheck = true # Grab actual lock if !lock(lock_wait_time) - logger.error "git_hosting: update_repositories() exited without acquiring lock!" + logger.error "git_hosting: update_repositories() exited without acquiring lock!" @@recursionCheck = false return end - begin - # Make sure we have gitoite-admin cloned. - # If have uncommitted changes, reflect in "changed" flag. + begin + # Make sure we have gitoite-admin cloned. + # If have uncommitted changes, reflect in "changed" flag. changed = !clone_or_pull_gitolite_admin(flags[:resync_all]) - # Get directory for the gitolite-admin - repo_dir = File.join(get_tmp_dir,"gitolite-admin") - - # logger.info "Updating keydirectory for projects: #{git_projects.join ', '}" - keydir = File.join(repo_dir,"keydir") - old_keyhash = {} - Dir.foreach(keydir) do |keyfile| - user_token = GitolitePublicKey.ident_to_user_token(keyfile) - if !user_token.nil? - old_keyhash[user_token] ||= [] - old_keyhash[user_token] << keyfile - end - end - - # Collect relevant users into hash with user as key and activity (in some active project) as value - git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq.each do |cur_user| - active_keys = cur_user.gitolite_public_keys.active || [] - - # Remove old keys that happen to be left around + # Get directory for the gitolite-admin + repo_dir = File.join(get_tmp_dir,"gitolite-admin") + + # logger.info "Updating keydirectory for projects: #{git_projects.join ', '}" + keydir = File.join(repo_dir,"keydir") + old_keyhash = {} + Dir.foreach(keydir) do |keyfile| + user_token = GitolitePublicKey.ident_to_user_token(keyfile) + if !user_token.nil? + old_keyhash[user_token] ||= [] + old_keyhash[user_token] << keyfile + end + end + + # Collect relevant users into hash with user as key and activity (in some active project) as value + git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq.each do |cur_user| + active_keys = cur_user.gitolite_public_keys.active || [] + + # Remove old keys that happen to be left around cur_token = GitolitePublicKey.user_to_user_token(cur_user) # Current filenames - old_keynames = old_keyhash[cur_token] || [] - cur_keynames = [] - - # Get list of active keys that SHOULD be in the keydir - active_keys.each do |key| - key_id = key.identifier - key_token = GitolitePublicKey.ident_to_user_token(key_id) - if key_token != cur_token - # Rare case -- user login changed. Fix it. - key_id = key.reset_identifier - - # Add all key filenames with this (incorrect) token into the set of names - old_keynames += (old_keyhash[key_token] || []) - old_keyhash.delete(key_token) - end - cur_keynames << "#{key_id}.pub" - end - - (old_keynames - cur_keynames).each do |keyname| - filename = File.join(keydir,"#{keyname}") - logger.warn "Removing gitolite key: #{keyname}" - %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] + old_keynames = old_keyhash[cur_token] || [] + cur_keynames = [] + + # Get list of active keys that SHOULD be in the keydir + active_keys.each do |key| + key_id = key.identifier + key_token = GitolitePublicKey.ident_to_user_token(key_id) + if key_token != cur_token + # Rare case -- user login changed. Fix it. + key_id = key.reset_identifier + + # Add all key filenames with this (incorrect) token into the set of names + old_keynames += (old_keyhash[key_token] || []) + old_keyhash.delete(key_token) + end + cur_keynames << "#{key_id}.pub" + end + + (old_keynames - cur_keynames).each do |keyname| + filename = File.join(keydir,"#{keyname}") + logger.warn "Removing gitolite key: #{keyname}" + %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] changed = true - end - - # Remove inactive keys (will already be deleted by above code) - cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} - - # Add missing keys to the keydir - active_keys.each do |key| - keyname = "#{key.identifier}.pub" - unless old_keynames.index(keyname) - filename = File.join(keydir,"#{keyname}") - logger.info "Adding gitolite key: #{keyname}" + end + + # Remove inactive keys (will already be deleted by above code) + cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} + + # Add missing keys to the keydir + active_keys.each do |key| + keyname = "#{key.identifier}.pub" + unless old_keynames.index(keyname) + filename = File.join(keydir,"#{keyname}") + logger.info "Adding gitolite key: #{keyname}" File.open(filename, 'w') {|f| f.write(key.key.gsub(/\n/,'')) } changed = true end - end - - # In preparation for resync_all, below - old_keyhash.delete(cur_token) - end + end + + # In preparation for resync_all, below + old_keyhash.delete(cur_token) + end # Remove keys for deleted users - orphanString=flags[:resync_all] ? "orphan " : "" - if flags[:resync_all] || flags[:delete] || flags[:archive] - # All keys left in old_keyhash should be for users nolonger authorized for gitolite repos + orphanString=flags[:resync_all] ? "orphan " : "" + if flags[:resync_all] || flags[:delete] || flags[:archive] + # All keys left in old_keyhash should be for users nolonger authorized for gitolite repos old_keyhash.each_value do |keyset| - keyset.each do |keyname| - filename = File.join(keydir,"#{keyname}") - logger.warn "Removing #{orphanString}gitolite key: #{keyname}" - %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] + keyset.each do |keyname| + filename = File.join(keydir,"#{keyname}") + logger.warn "Removing #{orphanString}gitolite key: #{keyname}" + %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] changed = true - end - end + end + end end - conf = GitoliteConfig.new(File.join(repo_dir, 'conf', 'gitolite.conf')) - - - # Current redmine repositories (basename=>[repo_name1,repo_name2]) - redmine_repos = conf.redmine_repo_map - - # The set of actual repositories (basename=>[repo_name1,repo_name2]) - actual_repos = GitoliteConfig.gitolite_repository_map - - # Set of all entries in gitolite.conf file (name1=>1, name2=>2) - total_entries = conf.all_repos - - # Projects for which we want to update hooks - new_projects = [] - - # Regenerate configuration file for projects of interest - # Also, try to match up actual repositories with projects (being somewhat conservative - # when a project might be out of control of redmine. - # - # Note that we go through all projects, including archived ones, since we may need to - # find orphaned repos. Archived projects get left with a "ARCHIVED_REDMINE_KEY". - git_projects.each do |proj| - repo_name = repository_name(proj) - - # Common case: these are nil or lists of one element. - my_entries = redmine_repos[proj.identifier] - my_repos = actual_repos[proj.identifier] - - # We have one or more gitolite.conf entries with the right base name. Pick one with - # closest name (will pick one with 'repo_name' if it exists. - closest_entry = closest_path(my_entries,repo_name) - if !closest_entry - # CREATION case. - if !total_entries[repo_name] - logger.warn "Creating new entry '#{repo_name}' in gitolite.conf" - else - logger.warn "Utilizing existing non-redmine entry '#{repo_name}' in gitolite.conf" - end - elsif closest_entry != repo_name - # MOVE case. - logger.warn "Moving entry '#{closest_entry}' to '#{repo_name}' in gitolite.conf." - conf.rename_repo(closest_entry,repo_name) - else - # NORMAL case. Have entry with correct name. - if !my_repos.index(repo_name) - logger.warn "Missing or misnamed repository for existing gitolite entry '#{repo_name}'." - end - end - new_projects << proj unless my_repos.index(closest_entry) # Reinit hooks unless NORMAL or MOVE case - my_entries.delete closest_entry # Claimed this one => don't need to delete later - - if my_repos.empty? - # This is the normal CREATION case. No repositories with matching basenames - # Attempt to recover repository from recycle_bin, if present. Else, create new repository. - if !GitoliteRecycle.recover_repository_if_present repo_name - logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" - end - elsif my_repos.index(closest_entry) - # We have a repository that matches the entry we used above. Move this one to match if necessary - # If closest_entry == repo_name, this is a NORMAL case (do nothing!) - # If closest_entry != repo_name, this is the MOVE case. - move_physical_repo(closest_entry,repo_name) if closest_entry != repo_name - elsif my_repos.index(repo_name) - # Existing repo with right name. We know that there wasn't a corresponding gitolite.conf entry.... - logger.warn " Using existing repository: '#{repository_path(repo_name)}'" - else - # Of the repos in my_repo with a matching base name, only steal away those not already controlled - # by gitolite.conf. The one reasonable case here is if (for some reason) a move was properly - # executed in gitolite.conf but the repo didn't get moved. - closest_repo = closest_path((my_repos - total_entries.keys),repo_name) - if !closest_repo - logger.error "One or more repositories with matching base name '#{proj.identifier}' exist, but already have entries in gitolite.conf" - logger.error "They are: #{my_repos.join(', ')}" - # Attempt to recover repository from recycle_bin, if present. Else, create new repository. - if !GitoliteRecycle.recover_repository_if_present repo_name - logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" - end - else - logger.warn " Claiming orphan repository '#{repository_path(closest_repo)}' in gitolite repository." - move_physical_repo(closest_repo,repo_name) - end - end - - # Update repository url and root_url if necessary + conf = GitoliteConfig.new(File.join(repo_dir, 'conf', gitolite_conf)) + + # Current redmine repositories (basename=>[repo_name1,repo_name2]) + redmine_repos = conf.redmine_repo_map + + # The set of actual repositories (basename=>[repo_name1,repo_name2]) + actual_repos = GitoliteConfig.gitolite_repository_map + + # Set of all entries in gitolite.conf file (name1=>1, name2=>2) + total_entries = conf.all_repos + + # Projects for which we want to update hooks + new_projects = [] + + # Regenerate configuration file for projects of interest + # Also, try to match up actual repositories with projects (being somewhat conservative + # when a project might be out of control of redmine. + # + # Note that we go through all projects, including archived ones, since we may need to + # find orphaned repos. Archived projects get left with a "ARCHIVED_REDMINE_KEY". + git_projects.each do |proj| + repo_name = repository_name(proj) + + # Common case: these are nil or lists of one element. + my_entries = redmine_repos[proj.identifier] + my_repos = actual_repos[proj.identifier] + + # We have one or more gitolite.conf entries with the right base name. Pick one with + # closest name (will pick one with 'repo_name' if it exists. + closest_entry = closest_path(my_entries,repo_name) + if !closest_entry + # CREATION case. + if !total_entries[repo_name] + logger.warn "Creating new entry '#{repo_name}' in #{gitolite_conf}" + else + logger.warn "Utilizing existing non-redmine entry '#{repo_name}' in #{gitolite_conf}" + end + elsif closest_entry != repo_name + # MOVE case. + logger.warn "Moving entry '#{closest_entry}' to '#{repo_name}' in #{gitolite_conf}." + conf.rename_repo(closest_entry,repo_name) + else + # NORMAL case. Have entry with correct name. + if !my_repos.index(repo_name) + logger.warn "Missing or misnamed repository for existing gitolite entry '#{repo_name}'." + end + end + new_projects << proj unless my_repos.index(closest_entry) # Reinit hooks unless NORMAL or MOVE case + my_entries.delete closest_entry # Claimed this one => don't need to delete later + + if my_repos.empty? + # This is the normal CREATION case. No repositories with matching basenames + # Attempt to recover repository from recycle_bin, if present. Else, create new repository. + if !GitoliteRecycle.recover_repository_if_present repo_name + logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" + end + elsif my_repos.index(closest_entry) + # We have a repository that matches the entry we used above. Move this one to match if necessary + # If closest_entry == repo_name, this is a NORMAL case (do nothing!) + # If closest_entry != repo_name, this is the MOVE case. + move_physical_repo(closest_entry,repo_name) if closest_entry != repo_name + elsif my_repos.index(repo_name) + # Existing repo with right name. We know that there wasn't a corresponding gitolite.conf entry.... + logger.warn " Using existing repository: '#{repository_path(repo_name)}'" + else + # Of the repos in my_repo with a matching base name, only steal away those not already controlled + # by gitolite.conf. The one reasonable case here is if (for some reason) a move was properly + # executed in gitolite.conf but the repo didn't get moved. + closest_repo = closest_path((my_repos - total_entries.keys),repo_name) + if !closest_repo + logger.error "One or more repositories with matching base name '#{proj.identifier}' exist, but already have entries in gitolite.conf" + logger.error "They are: #{my_repos.join(', ')}" + # Attempt to recover repository from recycle_bin, if present. Else, create new repository. + if !GitoliteRecycle.recover_repository_if_present repo_name + logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" + end + else + logger.warn " Claiming orphan repository '#{repository_path(closest_repo)}' in gitolite repository." + move_physical_repo(closest_repo,repo_name) + end + end + + # Update repository url and root_url if necessary myrepo = proj.repository - target_url = repository_path(proj) - if myrepo.url != target_url || myrepo.root_url != target_url - # logger.warn " Updating internal access path to '#{target_url}'." - myrepo.url = myrepo.root_url = target_url - proj.repository.save - end - - # If this is an active (non-archived) project, then update gitolite entry. Add GIT_DAEMON_KEY. - if proj.active? + target_url = repository_path(proj) + if myrepo.url != target_url || myrepo.root_url != target_url + # logger.warn " Updating internal access path to '#{target_url}'." + myrepo.url = myrepo.root_url = target_url + proj.repository.save + end + + # If this is an active (non-archived) project, then update gitolite entry. Add GIT_DAEMON_KEY. + if proj.active? # fetch users users = proj.member_principals.map(&:user).compact.uniq write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } - + # update users read_user_keys = [] write_user_keys = [] read_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| read_user_keys.push key.identifier - end + end write_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| write_user_keys.push key.identifier end - + #git daemon support - if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public + if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public read_user_keys.push GitoliteConfig::GIT_DAEMON_KEY end - + # Remove previous redmine keys, then add new keys - # By doing things this way, we leave non-redmine keys alone - # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys - # (to be put back as above, when appropriate). - conf.delete_redmine_keys repo_name + # By doing things this way, we leave non-redmine keys alone + # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys + # (to be put back as above, when appropriate). + conf.delete_redmine_keys repo_name conf.add_read_user repo_name, read_user_keys conf.add_write_user repo_name, write_user_keys - # If no redmine keys, mark with dummy key - if (read_user_keys+write_user_keys).empty? - conf.mark_with_dummy_key repo_name - end - else - # Must be an archived project! Clear out redmine keys. Mark as an archived project. - conf.delete_redmine_keys repo_name - conf.mark_archived repo_name - end + # If no redmine keys, mark with dummy key + if (read_user_keys+write_user_keys).empty? + conf.mark_with_dummy_key repo_name + end + else + # Must be an archived project! Clear out redmine keys. Mark as an archived project. + conf.delete_redmine_keys repo_name + conf.mark_archived repo_name + end end - + # If resyncing or deleting, check for orphan repositories which still have redmine keys... - # At this point, redmine_repos contains all repositories in original gitolite.conf - # which have redmine keys but are not part of an active redmine project. - # There are three possibilities: - # - # 1) They have both redmine keys and other (non-redmine) keys => remove redmine keys - # 2) They have only redmine keys, but repository delete is not enabled - # => remove redmine keys (will leave redmine_dummy_key when we save) - # 3) They have only redmine keys and repository delete is enabled => delete repository + # At this point, redmine_repos contains all repositories in original gitolite.conf + # which have redmine keys but are not part of an active redmine project. + # There are three possibilities: + # + # 1) They have both redmine keys and other (non-redmine) keys => remove redmine keys + # 2) They have only redmine keys, but repository delete is not enabled + # => remove redmine keys (will leave redmine_dummy_key when we save) + # 3) They have only redmine keys and repository delete is enabled => delete repository if flags[:resync_all] || flags[:delete] - if flags[:delete] - # Get rid of all live repos from redmine_repos - proj_ids = git_projects.map{|proj| proj.identifier} - redmine_repos.delete_if{|basename,values| proj_ids.index(basename)} - end - redmine_repos.values.flatten.each do |repo_name| - # First, delete redmine keys for this repository - conf.delete_redmine_keys repo_name - if (Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true") - if conf.repo_has_no_keys? repo_name - logger.warn "Deleting #{orphanString}entry '#{repo_name}' from gitolite.conf" - conf.delete_repo repo_name - GitoliteRecycle.move_repository_to_recycle repo_name - else - logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in gitolite.conf" - if git_repository_exists? repo_name - logger.info " Not removing #{repo_name}.git from gitolite repository, because non-redmine keys remain." - end - end - else - logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in gitolite.conf" - end - end - # Delete expired files from recycle bin. - GitoliteRecycle.delete_expired_files - end + if flags[:delete] + # Get rid of all live repos from redmine_repos + proj_ids = git_projects.map{|proj| proj.identifier} + redmine_repos.delete_if{|basename,values| proj_ids.index(basename)} + end + redmine_repos.values.flatten.each do |repo_name| + # First, delete redmine keys for this repository + conf.delete_redmine_keys repo_name + if (Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true") + if conf.repo_has_no_keys? repo_name + logger.warn "Deleting #{orphanString}entry '#{repo_name}' from #{gitolite_conf}" + conf.delete_repo repo_name + GitoliteRecycle.move_repository_to_recycle repo_name + else + logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" + if git_repository_exists? repo_name + logger.info " Not removing #{repo_name}.git from gitolite repository, because non-redmine keys remain." + end + end + else + logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" + end + end + # Delete expired files from recycle bin. + GitoliteRecycle.delete_expired_files + end if conf.changed? conf.save changed = true end - + if changed - # Have changes. Commit / push changes to gitolite admin repo + # Have changes. Commit / push changes to gitolite admin repo commit_gitolite_admin flags[:resync_all] end - + # Set post receive hooks for new projects (or check all repositories on :resync_all). # We need to do this AFTER push, otherwise necessary repos may not be created yet. - GitAdapterHooks.setup_hooks_params((flags[:resync_all]||flags[:resync_hooks]) ? git_projects : new_projects) - - rescue GitHostingException - logger.error "git_hosting: update_repositories() failed" - rescue => e - logger.error e.message - logger.error e.backtrace[0..4].join("\n") - logger.error "git_hosting: update_repositories() failed" - end + GitAdapterHooks.setup_hooks_params((flags[:resync_all]||flags[:resync_hooks]) ? git_projects : new_projects) + + rescue GitHostingException + logger.error "git_hosting: update_repositories() failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: update_repositories() failed" + end unlock() @@recursionCheck = false end - # This routine moves a repository in the gitolite repository structure. - def self.move_physical_repo(old_name,new_name) - begin - logger.warn " Moving gitolite repository from '#{old_name}.git' to '#{new_name}.git'" - - if git_repository_exists? new_name - logger.error "Repository already exists at #{new_name}.git! Moving to recycle bin to avoid overwrite." - GitoliteRecycle.move_repository_to_recycle new_name - end - - # physicaly move the repo BEFORE committing/pushing conf changes to gitolite admin repo - prefix = new_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' - if prefix - # Has subdirectory. Must construct destination directory - repo_prefix = File.join(repository_base, prefix) - GitHosting.shell %[#{git_user_runner} mkdir -p '#{repo_prefix}'] - end - old_path = repository_path(old_name) - new_path = repository_path(new_name) - shell %[#{git_user_runner} 'mv "#{old_path}" "#{new_path}"'] - - # If any empty directories left behind, try to delete them. Ignore failure. - old_prefix = old_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' - if old_prefix - repo_subpath = File.join(repository_base, old_prefix) - result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") - result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} - end - rescue GitHostingException - logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" - rescue => e - logger.error e.message - logger.error e.backtrace[0..4].join("\n") - logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" - end - end - - # Check to see if the given repository exists or not... - # Need to work a bit, since we have to su to figure it out... - def self.git_repository_exists?(repo_name) - file_exists?(repository_path(repo_name)) - end - - # Check to see if the given file exists off the git user's homedirectory. - # Need to work a bit, since we have to su to figure it out... - def self.file_exists?(filename) - (%x[#{GitHosting.git_user_runner} test -r '#{filename}' && echo 'yes' || echo 'no']).match(/yes/) ? true : false - end - - # Takes a list of path names and a path name and attempts to find the item in the list that matches - # in the most components. Assume at least one element in list - def self.closest_path(path_list,target_path) - path_list.sort do |x, y| - # sort by length of match, then dictionary - lmatchx = longest_match_length(x,target_path) - lmatchy = longest_match_length(y,target_path) - if (lmatchx < lmatchy) - -1 - elsif (lmatchy < lmatchx) - 1 - else - # in case of equivalence, want earliest alphabetical - y <=> x - end - end.last - end - - # return the number of characters that match between str1 and str2 - def self.longest_match_length(str1,str2) - for result in 0..str1.length-1 - # note that str2[result]=nil if beyond end of string, which works fine! - if str1[result] != str2[result] - return result - end - end - str1.length - end - - def self.clear_cache_for_project(project) + # This routine moves a repository in the gitolite repository structure. + def self.move_physical_repo(old_name,new_name) + begin + logger.warn " Moving gitolite repository from '#{old_name}.git' to '#{new_name}.git'" + + if git_repository_exists? new_name + logger.error "Repository already exists at #{new_name}.git! Moving to recycle bin to avoid overwrite." + GitoliteRecycle.move_repository_to_recycle new_name + end + + # physicaly move the repo BEFORE committing/pushing conf changes to gitolite admin repo + prefix = new_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' + if prefix + # Has subdirectory. Must construct destination directory + repo_prefix = File.join(repository_base, prefix) + GitHosting.shell %[#{git_user_runner} mkdir -p '#{repo_prefix}'] + end + old_path = repository_path(old_name) + new_path = repository_path(new_name) + shell %[#{git_user_runner} 'mv "#{old_path}" "#{new_path}"'] + + # If any empty directories left behind, try to delete them. Ignore failure. + old_prefix = old_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' + if old_prefix + repo_subpath = File.join(repository_base, old_prefix) + result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") + result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} + end + rescue GitHostingException + logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" + end + end + + # Check to see if the given repository exists or not... + # Need to work a bit, since we have to su to figure it out... + def self.git_repository_exists?(repo_name) + file_exists?(repository_path(repo_name)) + end + + # Check to see if the given file exists off the git user's homedirectory. + # Need to work a bit, since we have to su to figure it out... + def self.file_exists?(filename) + (%x[#{GitHosting.git_user_runner} test -r '#{filename}' && echo 'yes' || echo 'no']).match(/yes/) ? true : false + end + + # Takes a list of path names and a path name and attempts to find the item in the list that matches + # in the most components. Assume at least one element in list + def self.closest_path(path_list,target_path) + path_list.sort do |x, y| + # sort by length of match, then dictionary + lmatchx = longest_match_length(x,target_path) + lmatchy = longest_match_length(y,target_path) + if (lmatchx < lmatchy) + -1 + elsif (lmatchy < lmatchx) + 1 + else + # in case of equivalence, want earliest alphabetical + y <=> x + end + end.last + end + + # return the number of characters that match between str1 and str2 + def self.longest_match_length(str1,str2) + for result in 0..str1.length-1 + # note that str2[result]=nil if beyond end of string, which works fine! + if str1[result] != str2[result] + return result + end + end + str1.length + end + + def self.clear_cache_for_project(project) if project.is_a?(Project) project = project.identifier end @@ -1084,23 +1091,23 @@ def self.update_global_hook_params end end - class MyLogger - # Prefix to error messages - ERROR_PREFIX = "***> " - - # For errors, add our prefix to all messages - def error(*progname, &block) - if block_given? - Rails.logger.error(*progname) { "#{ERROR_PREFIX}#{yield}".gsub(/\n/,"\n#{ERROR_PREFIX}") } - else - Rails.logger.error "#{ERROR_PREFIX}#{progname}".gsub(/\n/,"\n#{ERROR_PREFIX}") - end - end - - # Handle everything else with base object - def method_missing(m, *args, &block) - Rails.logger.send m, *args, &block - end - end + class MyLogger + # Prefix to error messages + ERROR_PREFIX = "***> " + + # For errors, add our prefix to all messages + def error(*progname, &block) + if block_given? + Rails.logger.error(*progname) { "#{ERROR_PREFIX}#{yield}".gsub(/\n/,"\n#{ERROR_PREFIX}") } + else + Rails.logger.error "#{ERROR_PREFIX}#{progname}".gsub(/\n/,"\n#{ERROR_PREFIX}") + end + end + + # Handle everything else with base object + def method_missing(m, *args, &block) + Rails.logger.send m, *args, &block + end + end end diff --git a/lib/gitolite_conf.rb b/lib/gitolite_conf.rb index 1dc44d61d..5edd7ba18 100755 --- a/lib/gitolite_conf.rb +++ b/lib/gitolite_conf.rb @@ -1,25 +1,43 @@ module GitHosting class GitoliteConfig - DUMMY_REDMINE_KEY="redmine_dummy_key" + DUMMY_REDMINE_KEY="redmine_dummy_key" ARCHIVED_REDMINE_KEY="redmine_archived_project" - GIT_DAEMON_KEY="daemon" - ADMIN_REPO = "gitolite-admin" - DEFAULT_ADMIN_KEY_NAME = "id_rsa" + GIT_DAEMON_KEY="daemon" + ADMIN_REPO = "gitolite-admin" + PRIMARY_CONF_FILE = "gitolite.conf" + DEFAULT_ADMIN_KEY_NAME = "id_rsa" + + def self.gitolite_conf + Setting.plugin_redmine_git_hosting['gitConfigFile'] || PRIMARY_CONF_FILE + end + + def self.default_conf? + gitolite_conf == PRIMARY_CONF_FILE + end + + # Need admin key if conf file is default file or if there was an admin key. + def self.has_admin_key? + Setting.plugin_redmine_git_hosting['gitConfigHasAdminKey'] != 'false' || default_conf? + end def initialize file_path @path = file_path load end - def logger + def logger return GitHosting.logger end def save - File.open(@path, "w") do |f| - f.puts content + begin + File.open(@path, "w") do |f| + f.puts content + end + @original_content = content + rescue => e + GitHosting.logger.error "Error trying to write config file: #{e.to_s}" end - @original_content = content end def add_write_user repo_name, users @@ -39,21 +57,21 @@ def set_read_user repo_name, users end def mark_with_dummy_key repo_name - add_read_user repo_name, [DUMMY_REDMINE_KEY] - end + add_read_user repo_name, [DUMMY_REDMINE_KEY] + end def mark_archived repo_name - add_read_user repo_name, [ARCHIVED_REDMINE_KEY] - end + add_read_user repo_name, [ARCHIVED_REDMINE_KEY] + end - # Grab admin key (assuming it exists) - def get_admin_key - return (repository(ADMIN_REPO).get "RW+").first - end + # Grab admin key (assuming it exists) + def get_admin_key + return (repository(ADMIN_REPO).get "RW+").first + end - def set_admin_key new_name - repository(ADMIN_REPO).set "RW+", new_name - end + def set_admin_key new_name + repository(ADMIN_REPO).set "RW+", new_name + end def delete_repo repo_name @repositories.delete(repo_name) @@ -66,35 +84,35 @@ def rename_repo old_name, new_name end end - # A repository is a "redmine" repository if it has redmine keys or no keys - # (latter case is checked, since we end up adding the DUMMY_REDMINE_KEY to - # a repository with no keys anyway.... - def is_redmine_repo? repo_name - repository(repo_name).rights.detect {|perm, users| users.detect {|key| is_redmine_key? key}} || (repo_has_no_keys? repo_name) - end + # A repository is a "redmine" repository if it has redmine keys or no keys + # (latter case is checked, since we end up adding the DUMMY_REDMINE_KEY to + # a repository with no keys anyway.... + def is_redmine_repo? repo_name + repository(repo_name).rights.detect {|perm, users| users.detect {|key| is_redmine_key? key}} || (repo_has_no_keys? repo_name) + end - # Delete all of the redmine keys from a repository - # In addition, if there are any redmine keys, delete the GIT_DAEMON_KEY as well, - # since we assume this under control of redmine server. - def delete_redmine_keys repo_name + # Delete all of the redmine keys from a repository + # In addition, if there are any redmine keys, delete the GIT_DAEMON_KEY as well, + # since we assume this under control of redmine server. + def delete_redmine_keys repo_name return unless @repositories[repo_name] && is_redmine_repo?(repo_name) - - repository(repo_name).rights.each do |perm, users| - users.delete_if {|key| ((is_redmine_key? key) || (is_daemon_key? key))} - end - end - + + repository(repo_name).rights.each do |perm, users| + users.delete_if {|key| ((is_redmine_key? key) || (is_daemon_key? key))} + end + end + def repo_has_no_keys? repo_name - !repository(repo_name).rights.detect {|perm, users| users.length > 0} - end + !repository(repo_name).rights.detect {|perm, users| users.length > 0} + end - def is_redmine_key? keyname - (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY || keyname == ARCHIVED_REDMINE_KEY) ? true : false - end + def is_redmine_key? keyname + (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY || keyname == ARCHIVED_REDMINE_KEY) ? true : false + end - def is_daemon_key? keyname - (keyname == GIT_DAEMON_KEY) - end + def is_daemon_key? keyname + (keyname == GIT_DAEMON_KEY) + end def changed? @original_content != content @@ -108,50 +126,57 @@ def all_repos return repos end - # For redmine repos, return map of basename (unique for redmine repos) => repo path - def redmine_repo_map - redmine_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list - @repositories.each do |repo, rights| - if is_redmine_repo? repo - # Represents bug in conf file, but must allow more than one - mybase = File.basename(repo) - redmine_repos[mybase] << repo - end - end - return redmine_repos - end - - def self.gitolite_repository_map - gitolite_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list - myfiles = %x[#{GitHosting.git_user_runner} 'find #{GitHosting.repository_base} -type d -name "*.git" -prune -print'].chomp.split("\n") - filesplit = /(\.\/)*#{GitHosting.repository_base}(.*?)([^\/]+)\.git/ - myfiles.each do |nextfile| - if filesplit =~ nextfile - gitolite_repos[$3] << "#{$2}#{$3}" - end - end - gitolite_repos - end + # For redmine repos, return map of basename (unique for redmine repos) => repo path + def redmine_repo_map + redmine_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list + @repositories.each do |repo, rights| + if is_redmine_repo? repo + # Represents bug in conf file, but must allow more than one + mybase = File.basename(repo) + redmine_repos[mybase] << repo + end + end + return redmine_repos + end + + def self.gitolite_repository_map + gitolite_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list + myfiles = %x[#{GitHosting.git_user_runner} 'find #{GitHosting.repository_base} -type d -name "*.git" -prune -print'].chomp.split("\n") + filesplit = /(\.\/)*#{GitHosting.repository_base}(.*?)([^\/]+)\.git/ + myfiles.each do |nextfile| + if filesplit =~ nextfile + gitolite_repos[$3] << "#{$2}#{$3}" + end + end + gitolite_repos + end private def load @original_content = [] @repositories = ActiveSupport::OrderedHash.new - cur_repo_name = nil - File.open(@path).each_line do |line| - @original_content << line - tokens = line.strip.split - if tokens.first == 'repo' - cur_repo_name = tokens.last - @repositories[cur_repo_name] = GitoliteAccessRights.new - next - end - cur_repo_right = @repositories[cur_repo_name] - if cur_repo_right and tokens[1] == '=' - cur_repo_right.add tokens.first, tokens[2..-1] + begin + cur_repo_name = nil + File.open(@path).each_line do |line| + @original_content << line + tokens = line.strip.split + if tokens.first == 'repo' + cur_repo_name = tokens.last + @repositories[cur_repo_name] = GitoliteAccessRights.new + next + end + cur_repo_right = @repositories[cur_repo_name] + if cur_repo_right and tokens[1] == '=' + cur_repo_right.add tokens.first, tokens[2..-1] + end end + # If no admin key in repo, delete any residual + @repositories.delete(ADMIN_REPO) unless self.class.has_admin_key? + + @original_content = @original_content.join + rescue => e + GitHosting.logger.error "Error trying to read config file: #{e.to_s}" end - @original_content = @original_content.join end def repository repo_name @@ -161,21 +186,23 @@ def repository repo_name def content content = [] - # Make sure that admin repo is first - content << "repo\t#{ADMIN_REPO}" - admin_key = (@repositories[ADMIN_REPO].get "RW+").first - unless admin_key.nil? - # Put in a single admi nkey - content << "\tRW+\t=\t#{admin_key}" - else - # If no repo, put in a default -- will try to fix later if problem. - content << "\tRW+\t=\t#{DEFAULT_ADMIN_KEY_NAME}" - end - content << "" + if self.class.has_admin_key? + # Make sure that admin repo is first + content << "repo\t#{ADMIN_REPO}" + admin_key = @repositories[ADMIN_REPO] && (@repositories[ADMIN_REPO].get "RW+").first + unless admin_key.nil? + # Put in a single admin key + content << "\tRW+\t=\t#{admin_key}" + else + # If no repo, put in a default -- will try to fix later if problem. + content << "\tRW+\t=\t#{DEFAULT_ADMIN_KEY_NAME}" + end + content << "" + end @repositories.each do |repo, rights| - unless repo == ADMIN_REPO - content << "repo\t#{repo}" + unless repo == ADMIN_REPO + content << "repo\t#{repo}" has_users=false rights.each do |perm, users| if users.length > 0 @@ -185,10 +212,10 @@ def content end if !has_users # If no users, use dummy key to make sure repo created - content << "\tR\t=\t#{DUMMY_REDMINE_KEY}" + content << "\tR\t=\t#{DUMMY_REDMINE_KEY}" end content << "" - end + end end return content.join("\n") end @@ -215,13 +242,12 @@ def set perm, users add perm, users end - def get perm - @rights[perm.to_sym] || [] - end + def get perm + @rights[perm.to_sym] || [] + end def each @rights.each {|k,v| yield k, v} end end end - From 946aafced67b6d475daec985e887ca260d9d7aa1 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Fri, 27 Jul 2012 16:26:01 -0700 Subject: [PATCH 057/107] Remove dispatcher from init file, since this nolonger exists in Rails 3.0. Reorganized patches to put all of the patch-specific includes and patching operations into the individual patch files. This leaves only a need for explicitly loading each of the patch files, which is now done with a require_dependency inside a loop... --- init.rb | 65 +-------- lib/git_hosting/patches/git_adapter_patch.rb | 31 ++-- .../patches/git_repository_patch.rb | 25 ++-- .../patches/groups_controller_patch.rb | 8 ++ .../patches/members_controller_patch.rb | 62 ++++---- .../patches/my_controller_patch.rb | 49 ++++--- lib/git_hosting/patches/project_patch.rb | 20 ++- .../patches/projects_controller_patch.rb | 120 ++++++++-------- .../patches/repositories_controller_patch.rb | 22 ++- .../patches/repository_cia_filters.rb | 1 + lib/git_hosting/patches/repository_patch.rb | 72 +++++----- .../patches/roles_controller_patch.rb | 46 +++--- .../patches/sys_controller_patch.rb | 30 ++-- lib/git_hosting/patches/user_patch.rb | 21 +++ .../patches/users_controller_patch.rb | 136 +++++++++--------- 15 files changed, 385 insertions(+), 323 deletions(-) create mode 100644 lib/git_hosting/patches/user_patch.rb diff --git a/init.rb b/init.rb index 4084f9e52..88990ef31 100755 --- a/init.rb +++ b/init.rb @@ -17,7 +17,7 @@ 'gitServer' => 'localhost', 'gitUser' => 'git', 'gitConfigPath' => 'gitolite.conf', # Redmine gitolite config file - 'gitConfigHasAdminKey' => 'true', # Conf file should have admin key + 'gitConfigHasAdminKey' => 'true', # Conf file should have admin key 'gitRepositoryBasePath' => 'repositories/', 'gitRedmineSubdir' => '', 'gitRepositoryHierarchy' => 'true', @@ -51,67 +51,10 @@ permission :edit_repository_post_receive_urls, :repository_post_receive_urls => :edit end end -require "dispatcher" -Dispatcher.to_prepare :redmine_git_patches do - require_dependency 'principal' - require_dependency 'user' - require_dependency 'git_hosting' - - require_dependency 'projects_controller' - require 'git_hosting/patches/projects_controller_patch' - ProjectsController.send(:include, GitHosting::Patches::ProjectsControllerPatch) - - require_dependency 'project' - require 'git_hosting/patches/project_patch' - Project.send(:include, GitHosting::Patches::ProjectPatch) - - require_dependency 'repositories_controller' - require 'git_hosting/patches/repositories_controller_patch' - RepositoriesController.send(:include, GitHosting::Patches::RepositoriesControllerPatch) - - require_dependency 'repository' - require 'git_hosting/patches/repository_patch' - Repository.send(:include, GitHosting::Patches::RepositoryPatch) - - require 'stringio' - require_dependency 'redmine/scm/adapters/git_adapter' - require 'git_hosting/patches/git_adapter_patch' - Redmine::Scm::Adapters::GitAdapter.send(:include, GitHosting::Patches::GitAdapterPatch) - - require_dependency 'groups_controller' - require 'git_hosting/patches/groups_controller_patch' - GroupsController.send(:include, GitHosting::Patches::GroupsControllerPatch) - - require_dependency 'repository' - require_dependency 'repository/git' - require 'git_hosting/patches/git_repository_patch' - Repository::Git.send(:include, GitHosting::Patches::GitRepositoryPatch) - - require_dependency 'sys_controller' - require 'git_hosting/patches/sys_controller_patch' - SysController.send(:include, GitHosting::Patches::SysControllerPatch) - - require_dependency 'members_controller' - require 'git_hosting/patches/members_controller_patch' - MembersController.send(:include, GitHosting::Patches::MembersControllerPatch) - - # initialize association from user -> public keys - User.send(:has_many, :gitolite_public_keys, :dependent => :destroy) - - require_dependency 'users_controller' - require 'git_hosting/patches/users_controller_patch' - UsersController.send(:include, GitHosting::Patches::UsersControllerPatch) - - require_dependency 'roles_controller' - require 'git_hosting/patches/roles_controller_patch' - RolesController.send(:include, GitHosting::Patches::RolesControllerPatch) - - require_dependency 'my_controller' - require 'git_hosting/patches/my_controller_patch' - MyController.send(:include, GitHosting::Patches::MyControllerPatch) - - require_dependency 'git_hosting/patches/repository_cia_filters' +# Set up autoload of patches +Dir[File.dirname(__FILE__)+"/lib/git_hosting/patches/*.rb"].each do |patch| + require_dependency 'git_hosting/patches/'+File.basename(patch,".rb") end # initialize hooks diff --git a/lib/git_hosting/patches/git_adapter_patch.rb b/lib/git_hosting/patches/git_adapter_patch.rb index 90298b685..f09d181b6 100644 --- a/lib/git_hosting/patches/git_adapter_patch.rb +++ b/lib/git_hosting/patches/git_adapter_patch.rb @@ -1,3 +1,9 @@ +require 'stringio' +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'redmine/scm/adapters/git_adapter' + module GitHosting module Patches module GitAdapterPatch @@ -15,17 +21,17 @@ def self.included(base) base.extend(ClassMethods) base.class_eval do class << self - begin - alias_method_chain :sq_bin, :sudo - begin + begin + alias_method_chain :sq_bin, :sudo + begin alias_method_chain :client_command, :sudo rescue Exception =>e end - rescue Exception => e - # Hm.... Might be Redmine version < 1.2 (i.e. 1.1). Try redefining GIT_BIN. - GitHosting.logger.warn "Seems to be early version of Redmine(1.1?), try redefining GIT_BIN." - Redmine::Scm::Adapters::GitAdapter::GIT_BIN = GitHosting::git_exec() - end + rescue Exception => e + # Hm.... Might be Redmine version < 1.2 (i.e. 1.1). Try redefining GIT_BIN. + GitHosting.logger.warn "Seems to be early version of Redmine(1.1?), try redefining GIT_BIN." + Redmine::Scm::Adapters::GitAdapter::GIT_BIN = GitHosting::git_exec() + end end end end @@ -43,9 +49,9 @@ def client_command_with_sudo def scm_cmd_with_sudo(*args, &block) - max_cache_time = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i # in seconds, default = 60 - max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i # default = 100 - max_cache_size = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024 # In MB, default = 16MB, converted to bytes + max_cache_time = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i # in seconds, default = 60 + max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i # default = 100 + max_cache_size = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024 # In MB, default = 16MB, converted to bytes repo_path = root_url || url full_args = [GitHosting::git_exec(), '--git-dir', repo_path] @@ -107,3 +113,6 @@ def scm_cmd_with_sudo(*args, &block) end end end + +# Patch in changes +Redmine::Scm::Adapters::GitAdapter.send(:include, GitHosting::Patches::GitAdapterPatch) diff --git a/lib/git_hosting/patches/git_repository_patch.rb b/lib/git_hosting/patches/git_repository_patch.rb index af081f1ff..60027d79d 100644 --- a/lib/git_hosting/patches/git_repository_patch.rb +++ b/lib/git_hosting/patches/git_repository_patch.rb @@ -1,3 +1,9 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'repository' +require_dependency 'repository/git' + module GitHosting module Patches module GitRepositoryPatch @@ -9,16 +15,16 @@ def extra_report_last_commit_with_always_true true end - def fetch_changesets_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - fetch_changesets_without_disable_update + # Do actual update + fetch_changesets_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end + end def self.included(base) base.class_eval do @@ -27,7 +33,7 @@ def self.included(base) begin base.send(:alias_method_chain, :report_last_commit, :always_true) base.send(:alias_method_chain, :extra_report_last_commit, :always_true) - base.send(:alias_method_chain, :fetch_changesets, :disable_update) + base.send(:alias_method_chain, :fetch_changesets, :disable_update) rescue end @@ -35,3 +41,6 @@ def self.included(base) end end end + +# Patch in changes +Repository::Git.send(:include, GitHosting::Patches::GitRepositoryPatch) diff --git a/lib/git_hosting/patches/groups_controller_patch.rb b/lib/git_hosting/patches/groups_controller_patch.rb index 66e4656ab..5f8df39a8 100644 --- a/lib/git_hosting/patches/groups_controller_patch.rb +++ b/lib/git_hosting/patches/groups_controller_patch.rb @@ -1,3 +1,8 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'groups_controller' + module GitHosting module Patches module GroupsControllerPatch @@ -23,3 +28,6 @@ def self.included(base) end end end + +# Patch in changes +GroupsController.send(:include, GitHosting::Patches::GroupsControllerPatch) diff --git a/lib/git_hosting/patches/members_controller_patch.rb b/lib/git_hosting/patches/members_controller_patch.rb index faef8a623..57ab30493 100644 --- a/lib/git_hosting/patches/members_controller_patch.rb +++ b/lib/git_hosting/patches/members_controller_patch.rb @@ -1,48 +1,56 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'members_controller' + module GitHosting module Patches module MembersControllerPatch - def new_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def new_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - new_without_disable_update + # Do actual update + new_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - def edit_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + end + def edit_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - edit_without_disable_update + # Do actual update + edit_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - destroy_without_disable_update + # Do actual update + destroy_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(:delete => true); - end + end def self.included(base) base.class_eval do unloadable end - begin - base.send(:alias_method_chain, :new, :disable_update) - base.send(:alias_method_chain, :edit, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - rescue - end + begin + base.send(:alias_method_chain, :new, :disable_update) + base.send(:alias_method_chain, :edit, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end end end end end + +# Patch in changes +MembersController.send(:include, GitHosting::Patches::MembersControllerPatch) diff --git a/lib/git_hosting/patches/my_controller_patch.rb b/lib/git_hosting/patches/my_controller_patch.rb index 359a84f8c..9dfd582cb 100644 --- a/lib/git_hosting/patches/my_controller_patch.rb +++ b/lib/git_hosting/patches/my_controller_patch.rb @@ -1,34 +1,41 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'my_controller' + module GitHosting module Patches module MyControllerPatch - # Add in values for viewing public keys: - def account_with_public_keys - # Previous routine - account_without_public_keys + # Add in values for viewing public keys: + def account_with_public_keys + # Previous routine + account_without_public_keys - @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") - @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} - if @gitolite_public_key.nil? - if params[:public_key_id] - # public_key specified that doesn't belong to @user. Kill off public_key_id and try again - redirect_to :public_key_id => nil, :tab => nil - return - else - @gitolite_public_key = GitolitePublicKey.new - end - end - end + @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") + @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} + if @gitolite_public_key.nil? + if params[:public_key_id] + # public_key specified that doesn't belong to @user. Kill off public_key_id and try again + redirect_to :public_key_id => nil, :tab => nil + return + else + @gitolite_public_key = GitolitePublicKey.new + end + end + end def self.included(base) base.class_eval do unloadable end - begin - base.send(:alias_method_chain, :account, :public_keys) - rescue - end + begin + base.send(:alias_method_chain, :account, :public_keys) + rescue + end end end end end - + +# Patch in changes +MyController.send(:include, GitHosting::Patches::MyControllerPatch) diff --git a/lib/git_hosting/patches/project_patch.rb b/lib/git_hosting/patches/project_patch.rb index b040364d9..231054aef 100644 --- a/lib/git_hosting/patches/project_patch.rb +++ b/lib/git_hosting/patches/project_patch.rb @@ -1,3 +1,8 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'project' + module GitHosting module Patches module ProjectPatch @@ -5,17 +10,20 @@ def self.included(base) base.class_eval do unloadable - named_scope :archived, { :conditions => {:status => "#{Project::STATUS_ARCHIVED}"}} - named_scope :active_or_archived, { :conditions => "status=#{Project::STATUS_ACTIVE} OR status=#{Project::STATUS_ARCHIVED}" } + named_scope :archived, { :conditions => {:status => "#{Project::STATUS_ARCHIVED}"}} + named_scope :active_or_archived, { :conditions => "status=#{Project::STATUS_ACTIVE} OR status=#{Project::STATUS_ARCHIVED}" } - # initialize association from project -> repository mirrors + # initialize association from project -> repository mirrors has_many :repository_mirrors, :dependent => :destroy - # initialize association from project -> repository post receive urls - has_many :repository_post_receive_urls, :dependent => :destroy + # initialize association from project -> repository post receive urls + has_many :repository_post_receive_urls, :dependent => :destroy - end + end end end end end + +# Patch in changes +Project.send(:include, GitHosting::Patches::ProjectPatch) diff --git a/lib/git_hosting/patches/projects_controller_patch.rb b/lib/git_hosting/patches/projects_controller_patch.rb index 6a7583747..3862cb6fa 100644 --- a/lib/git_hosting/patches/projects_controller_patch.rb +++ b/lib/git_hosting/patches/projects_controller_patch.rb @@ -1,3 +1,8 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'projects_controller' + module GitHosting module Patches module ProjectsControllerPatch @@ -12,10 +17,10 @@ def git_repo_init membership.save end if @project.module_enabled?('repository') && Setting.plugin_redmine_git_hosting['allProjectsUseGit'] == "true" - # Create new repository + # Create new repository repo = Repository.factory("Git") - # Set urls - repo.url = repo.root_url = GitHosting.repository_path(@project) + # Set urls + repo.url = repo.root_url = GitHosting.repository_path(@project) @project.repository = repo repo.save end @@ -27,92 +32,95 @@ def disable_git_daemon_if_not_public if @project.repository.extra.git_daemon == 1 && (not @project.is_public ) @project.repository.extra.git_daemon = 0; @project.repository.extra.save - @project.repository.save # Trigger update_repositories + @project.repository.save # Trigger update_repositories end end end end - def create_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - create_without_disable_update + # Do actual update + create_without_disable_update - # Fix up repository - git_repo_init + # Fix up repository + git_repo_init - # Adjust daemon status - disable_git_daemon_if_not_public + # Adjust daemon status + disable_git_daemon_if_not_public - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end + end - def update_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - update_without_disable_update + # Do actual update + update_without_disable_update - # Adjust daemon status - disable_git_daemon_if_not_public + # Adjust daemon status + disable_git_daemon_if_not_public - myrepo = @project.repository - if myrepo.is_a?(Repository::Git) && (myrepo.url != GitHosting::repository_path(@project) || myrepo.url != myrepo.root_url) - # Hm... something about parent hierarchy changed. Update us and our children - GitHostingObserver.set_update_active(@project, :descendants) - else - # Reenable updates to perform a single update + myrepo = @project.repository + if myrepo.is_a?(Repository::Git) && (myrepo.url != GitHosting::repository_path(@project) || myrepo.url != myrepo.root_url) + # Hm... something about parent hierarchy changed. Update us and our children + GitHostingObserver.set_update_active(@project, :descendants) + else + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - end + end + end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - destroy_without_disable_update + # Do actual update + destroy_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end + end - def archive_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def archive_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - archive_without_disable_update + # Do actual update + archive_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(@project, :archive); - end + end - def unarchive_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def unarchive_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - unarchive_without_disable_update + # Do actual update + unarchive_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(@project); - end + end def self.included(base) base.class_eval do unloadable end - base.send(:alias_method_chain, :create, :disable_update) - base.send(:alias_method_chain, :update, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - base.send(:alias_method_chain, :archive, :disable_update) - base.send(:alias_method_chain, :unarchive, :disable_update) + base.send(:alias_method_chain, :create, :disable_update) + base.send(:alias_method_chain, :update, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + base.send(:alias_method_chain, :archive, :disable_update) + base.send(:alias_method_chain, :unarchive, :disable_update) end end end end + +# Patch in changes +ProjectsController.send(:include, GitHosting::Patches::ProjectsControllerPatch) diff --git a/lib/git_hosting/patches/repositories_controller_patch.rb b/lib/git_hosting/patches/repositories_controller_patch.rb index 77b7c4e0a..64dadb462 100644 --- a/lib/git_hosting/patches/repositories_controller_patch.rb +++ b/lib/git_hosting/patches/repositories_controller_patch.rb @@ -1,3 +1,8 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'repositories_controller' + module GitHosting module Patches module RepositoriesControllerPatch @@ -12,8 +17,8 @@ def show_with_git_instructions def edit_with_scm_settings GitHosting.logger.debug "On edit_with_scm_settings" - # Turn off updates during repository update - GitHostingObserver.set_update_active(false); + # Turn off updates during repository update + GitHostingObserver.set_update_active(false); params[:repository] ||= {} if params[:repository_scm] == "Git" @@ -44,15 +49,15 @@ def edit_with_scm_settings page.replace_html "main-menu", render_main_menu(@project) end end - - if !@project.repository.nil? - GitHostingObserver.bracketed_update_repositories(@project) - end + + if !@project.repository.nil? + GitHostingObserver.bracketed_update_repositories(@project) + end else edit_without_scm_settings end - GitHostingObserver.set_update_active(true); + GitHostingObserver.set_update_active(true); end def self.included(base) @@ -65,3 +70,6 @@ def self.included(base) end end end + +# Patch in changes +RepositoriesController.send(:include, GitHosting::Patches::RepositoriesControllerPatch) diff --git a/lib/git_hosting/patches/repository_cia_filters.rb b/lib/git_hosting/patches/repository_cia_filters.rb index be72b52fb..839a05040 100644 --- a/lib/git_hosting/patches/repository_cia_filters.rb +++ b/lib/git_hosting/patches/repository_cia_filters.rb @@ -29,3 +29,4 @@ def self.included(base) end end + diff --git a/lib/git_hosting/patches/repository_patch.rb b/lib/git_hosting/patches/repository_patch.rb index 8b68284e4..c422119da 100644 --- a/lib/git_hosting/patches/repository_patch.rb +++ b/lib/git_hosting/patches/repository_patch.rb @@ -1,3 +1,8 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'repository' + module GitHosting module Patches module RepositoryPatch @@ -20,65 +25,68 @@ def factory_with_git_extra_init(klass_name, *args) if new_repo.is_a?(Repository::Git) if new_repo.extra.nil? # Note that this autoinitializes default values and hook key - GitHosting.logger.error "Automatic initialization of git_repository_extra failed for #{self.project.to_s}" + GitHosting.logger.error "Automatic initialization of git_repository_extra failed for #{self.project.to_s}" end end return new_repo end - def fetch_changesets_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - fetch_changesets_without_disable_update + # Do actual update + fetch_changesets_without_disable_update - # Reenable updates to perform a sync of all projects + # Reenable updates to perform a sync of all projects GitHostingObserver.set_update_active(:resync_all); - end + end end - module InstanceMethods - # New version of extra() -- construct extra association if missing - def extra + module InstanceMethods + # New version of extra() -- construct extra association if missing + def extra retval = self.git_extra - if retval.nil? - # Construct new extra structure, followed by updating hooks (if necessary) - GitHostingObserver.set_update_active(false); + if retval.nil? + # Construct new extra structure, followed by updating hooks (if necessary) + GitHostingObserver.set_update_active(false); - retval = GitRepositoryExtra.new() - self.git_extra = retval # Should save object... + retval = GitRepositoryExtra.new() + self.git_extra = retval # Should save object... - # If self.project != nil, trigger repair of hooks - GitHostingObserver.set_update_active(true, self.project, :resync_hooks => true) - end - retval - end + # If self.project != nil, trigger repair of hooks + GitHostingObserver.set_update_active(true, self.project, :resync_hooks => true) + end + retval + end - def extra=(new_extra_struct) - self.git_extra=(new_extra_struct) - end - end + def extra=(new_extra_struct) + self.git_extra=(new_extra_struct) + end + end def self.included(base) base.class_eval do unloadable - extend(ClassMethods) + extend(ClassMethods) class << self alias_method_chain :factory, :git_extra_init - alias_method_chain :fetch_changesets, :disable_update + alias_method_chain :fetch_changesets, :disable_update end - # initialize association from git repository -> git_extra - has_one :git_extra, :foreign_key =>'repository_id', :class_name => 'GitRepositoryExtra', :dependent => :destroy + # initialize association from git repository -> git_extra + has_one :git_extra, :foreign_key =>'repository_id', :class_name => 'GitRepositoryExtra', :dependent => :destroy - # initialize association from git repository -> cia_notifications - has_many :cia_notifications, :foreign_key =>'repository_id', :class_name => 'GitCiaNotification', :dependent => :destroy, :extend => GitHosting::Patches::RepositoryCiaFilters::FilterMethods + # initialize association from git repository -> cia_notifications + has_many :cia_notifications, :foreign_key =>'repository_id', :class_name => 'GitCiaNotification', :dependent => :destroy, :extend => GitHosting::Patches::RepositoryCiaFilters::FilterMethods - include(InstanceMethods) + include(InstanceMethods) end end end end end + +# Patch in changes +Repository.send(:include, GitHosting::Patches::RepositoryPatch) diff --git a/lib/git_hosting/patches/roles_controller_patch.rb b/lib/git_hosting/patches/roles_controller_patch.rb index 33782df66..73b0be3a5 100644 --- a/lib/git_hosting/patches/roles_controller_patch.rb +++ b/lib/git_hosting/patches/roles_controller_patch.rb @@ -1,36 +1,44 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'roles_controller' + module GitHosting module Patches module RolesControllerPatch - def edit_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def edit_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - edit_without_disable_update + # Do actual update + edit_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - destroy_without_disable_update + # Do actual update + destroy_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end + end def self.included(base) base.class_eval do unloadable end - begin - base.send(:alias_method_chain, :edit, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - rescue - end + begin + base.send(:alias_method_chain, :edit, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end end end end end + +# Patch in changes +RolesController.send(:include, GitHosting::Patches::RolesControllerPatch) diff --git a/lib/git_hosting/patches/sys_controller_patch.rb b/lib/git_hosting/patches/sys_controller_patch.rb index cf473c949..0eddce9ed 100644 --- a/lib/git_hosting/patches/sys_controller_patch.rb +++ b/lib/git_hosting/patches/sys_controller_patch.rb @@ -1,26 +1,34 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'sys_controller' + module GitHosting module Patches module SysControllerPatch - def fetch_changesets_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - fetch_changesets_without_disable_update + # Do actual update + fetch_changesets_without_disable_update - # Perform the updating process on all projects + # Perform the updating process on all projects GitHostingObserver.set_update_active(:resync_all); - end + end def self.included(base) base.class_eval do unloadable end - begin - base.send(:alias_method_chain, :fetch_changesets, :disable_update) - rescue - end + begin + base.send(:alias_method_chain, :fetch_changesets, :disable_update) + rescue + end end end end end + +# Patch in changes +SysController.send(:include, GitHosting::Patches::SysControllerPatch) diff --git a/lib/git_hosting/patches/user_patch.rb b/lib/git_hosting/patches/user_patch.rb new file mode 100644 index 000000000..e749e6cfb --- /dev/null +++ b/lib/git_hosting/patches/user_patch.rb @@ -0,0 +1,21 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' + +module GitHosting + module Patches + module UserPatch + def self.included(base) + base.class_eval do + unloadable + + # initialize association from user -> gitolite_public_keys + has_many :gitolite_public_keys, :dependent => :destroy + end + end + end + end +end + +# Patch in changes +User.send(:include, GitHosting::Patches::UserPatch) diff --git a/lib/git_hosting/patches/users_controller_patch.rb b/lib/git_hosting/patches/users_controller_patch.rb index 9d0086dba..0b283447e 100644 --- a/lib/git_hosting/patches/users_controller_patch.rb +++ b/lib/git_hosting/patches/users_controller_patch.rb @@ -1,90 +1,98 @@ +require_dependency 'principal' +require_dependency 'user' +require_dependency 'git_hosting' +require_dependency 'users_controller' + module GitHosting module Patches module UsersControllerPatch - def create_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - create_without_disable_update + # Do actual update + create_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - def update_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + end + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Set public key values for view - set_public_key_values + # Set public key values for view + set_public_key_values - # Do actual update - update_without_disable_update + # Do actual update + update_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - destroy_without_disable_update + # Do actual update + destroy_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(:delete); - end - def edit_membership_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + end + def edit_membership_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - edit_membership_without_disable_update + # Do actual update + edit_membership_without_disable_update - # Reenable updates to perform a single update + # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); - end - - # Add in values for viewing public keys: - def edit_with_public_keys - # Set public key values for view - set_public_key_values - - # Previous routine - edit_without_public_keys - end - - # Add in values for viewing public keys: - def set_public_key_values - @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") - @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} - if @gitolite_public_key.nil? - if params[:public_key_id] - # public_key specified that doesn't belong to @user. Kill off public_key_id and try again - redirect_to :public_key_id => nil, :tab => params[:tab] - return - else - @gitolite_public_key = GitolitePublicKey.new - end - end - end + end + + # Add in values for viewing public keys: + def edit_with_public_keys + # Set public key values for view + set_public_key_values + + # Previous routine + edit_without_public_keys + end + + # Add in values for viewing public keys: + def set_public_key_values + @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") + @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} + if @gitolite_public_key.nil? + if params[:public_key_id] + # public_key specified that doesn't belong to @user. Kill off public_key_id and try again + redirect_to :public_key_id => nil, :tab => params[:tab] + return + else + @gitolite_public_key = GitolitePublicKey.new + end + end + end def self.included(base) base.class_eval do unloadable end - # Edit adds new functionality, so don't silently fail! - base.send(:alias_method_chain, :edit, :public_keys) - begin - base.send(:alias_method_chain, :create, :disable_update) - base.send(:alias_method_chain, :update, :disable_update) - base.send(:alias_method_chain, :edit_membership, :disable_update) - # Put this last, since Redmine 1.1 doesn't have it.... - base.send(:alias_method_chain, :destroy, :disable_update) - rescue - end + # Edit adds new functionality, so don't silently fail! + base.send(:alias_method_chain, :edit, :public_keys) + begin + base.send(:alias_method_chain, :create, :disable_update) + base.send(:alias_method_chain, :update, :disable_update) + base.send(:alias_method_chain, :edit_membership, :disable_update) + # Put this last, since Redmine 1.1 doesn't have it.... + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end end end end end + +# Patch in changes +UsersController.send(:include, GitHosting::Patches::UsersControllerPatch) From c16bbb308081d240947d5244772de2799fe362a2 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sat, 28 Jul 2012 13:24:26 -0700 Subject: [PATCH 058/107] Fix slight errors on 'my_account' page --- app/views/my/account.html.erb | 2 +- config/routes.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 67623e766..8f8e46b49 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -4,7 +4,7 @@

<%=l(:label_my_account)%>

<%= error_messages_for 'user' %> -<%= error_messages_for :gitolite_public_key %> +<%= error_messages_for 'gitolite_public_key' %>
<% labelled_form_for :user, @user, :url => { :action => "account" }, :html => { :id => 'my_account_form' } do |f| %> diff --git a/config/routes.rb b/config/routes.rb index 81d3fbe60..38b654058 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,15 +1,15 @@ def install_redmine_git_hosting_routes(map) # URL for items of type httpServer/XXX.git. Some versions of rails has problems with multiple regex expressions, so avoid... - # Note that 'http_server_subdir' is either empty (default case) or ends in '/'. - map.connect ":project_path/*path", - :prefix => Setting.plugin_redmine_git_hosting['httpServerSubdir'], :project_path => /([^\/]+\/)*?[^\/]+\.git/, :controller => 'git_http' + # Note that 'http_server_subdir' is either empty (default case) or ends in '/'. + map.connect ":project_path/*path", + :prefix => Setting.plugin_redmine_git_hosting['httpServerSubdir'], :project_path => /([^\/]+\/)*?[^\/]+\.git/, :controller => 'git_http' # Handle the public keys plugin to my/account. map.resources :public_keys, :controller => 'gitolite_public_keys', :path_prefix => 'my' - map.connect 'my/account/public_key/:public_key_id', :controller => 'my', :action => 'account', :conditions => {:method => [:get]} + map.connect 'my/account/public_key/:public_key_id', :controller => 'my', :action => 'account' map.connect 'users/:id/edit/public_key/:public_key_id', :controller => 'users', :action => 'edit', :conditions => {:method => [:get]} - # Handle hooks and mirrors + # Handle hooks and mirrors map.connect 'githooks', :controller => 'gitolite_hooks', :action => 'stub' map.connect 'githooks/post-receive', :controller => 'gitolite_hooks', :action => 'post_receive' map.connect 'githooks/test', :controller => 'gitolite_hooks', :action => 'test' @@ -35,5 +35,5 @@ def install_redmine_git_hosting_routes(map) else ActionController::Routing::Routes.draw do |map| install_redmine_git_hosting_routes(map) - end + end end From 71a5b20d61323a59f954a9bb2accf4b6bef3478c Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Tue, 31 Jul 2012 20:23:44 -0700 Subject: [PATCH 059/107] Removed validation of repository mirror url, but enclose url in quotes for actual command (to prevent shell expansion of bad characters). --- app/models/repository_mirror.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/models/repository_mirror.rb b/app/models/repository_mirror.rb index 31c1e05b2..ad9359082 100644 --- a/app/models/repository_mirror.rb +++ b/app/models/repository_mirror.rb @@ -12,7 +12,6 @@ class RepositoryMirror < ActiveRecord::Base validates_uniqueness_of :url, :scope => [:project_id] validates_presence_of :project_id, :url - validates_format_of :url, :with => URI::regexp(%w(http https ssh ftp ftps rsync file)) validates_associated :project validate :check_refspec @@ -38,8 +37,8 @@ def push push_args << "--all " if include_all_branches push_args << "--tags " if include_all_tags end - push_args << "#{url} " - push_args << "\"#{explicit_refspec}\" " unless explicit_refspec.blank? + push_args << "\"#{dequote(url)}\" " + push_args << "\"#{dequote(explicit_refspec)}\" " unless explicit_refspec.blank? # mycom = %[ echo 'cd "#{repo_path}" ; env GIT_SSH=~/.ssh/run_gitolite_admin_ssh git push #{push_args}2>&1' | #{GitHosting.git_user_runner} "bash" ] # GitHosting.logger.error "Pushing: #{mycom}" @@ -78,6 +77,11 @@ def to_s protected + # Put backquote in front of crucial characters + def dequote(in_string) + in_string.gsub(/[$,"\\\n]/) {|x| "\\"+x} + end + def check_refspec self.explicit_refspec = explicit_refspec.strip From 7a71a7ceefa607ca45fedf636d0157baa23c73ac Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Fri, 17 Aug 2012 13:19:18 -0700 Subject: [PATCH 060/107] Fixed indentation => 4 spaces (far more readable) --- app/controllers/git_http_controller.rb | 520 ++--- app/controllers/gitolite_hooks_controller.rb | 406 ++-- .../gitolite_public_keys_controller.rb | 126 +- .../repository_mirrors_controller.rb | 260 +-- ...repository_post_receive_urls_controller.rb | 238 +- app/helpers/application_helper.rb | 132 +- app/helpers/git_hosting_helper.rb | 172 +- app/helpers/gitolite_public_keys_helper.rb | 14 +- app/helpers/users_helper.rb | 28 +- app/models/cia_notification_mailer.rb | 118 +- app/models/git_cache.rb | 2 +- app/models/git_cia_notification.rb | 8 +- app/models/git_hosting_observer.rb | 172 +- app/models/git_hosting_settings_observer.rb | 404 ++-- app/models/git_repository_extra.rb | 68 +- app/models/gitolite_public_key.rb | 98 +- app/models/repository_mirror.rb | 4 +- app/models/repository_post_receive_url.rb | 40 +- config/routes.rb | 60 +- .../hooks/post-receive.redmine_gitolite.rb | 136 +- ...91119162426_set_mirror_role_permissions.rb | 54 +- ...91119162427_create_gitolite_public_keys.rb | 26 +- .../20091119162428_create_git_caches.rb | 22 +- ...72600000_extend_changesets_notified_cia.rb | 12 +- ...2011080700000_create_repository_mirrors.rb | 22 +- ...1081300000_create_git_repository_extras.rb | 134 +- ...e_notified_cia_to_git_cia_notifications.rb | 78 +- ...0948_add_indexes_to_gitolite_public_key.rb | 16 +- .../20111123214911_add_settings_to_plugin.rb | 58 +- ...20111220055819_add_settings_to_plugin_2.rb | 80 +- ...20120226013750_add_settings_to_plugin_3.rb | 74 +- ...000_create_repository_post_receive_urls.rb | 22 +- ...1_set_post_receive_url_role_permissions.rb | 54 +- ...012052200000_add_post_receive_url_modes.rb | 14 +- ...20120708070841_add_settings_to_plugin_4.rb | 50 +- ...0710204007_add_repository_mirror_fields.rb | 24 +- ...20120724211806_add_settings_to_plugin_5.rb | 44 +- init.rb | 112 +- lib/git_adapter_hooks.rb | 400 ++-- lib/git_hosting.rb | 2049 ++++++++--------- lib/git_hosting/patches/git_adapter_patch.rb | 194 +- .../patches/git_repository_patch.rb | 66 +- .../patches/groups_controller_patch.rb | 32 +- .../patches/members_controller_patch.rb | 88 +- .../patches/my_controller_patch.rb | 54 +- lib/git_hosting/patches/project_patch.rb | 26 +- .../patches/projects_controller_patch.rb | 188 +- .../patches/repositories_controller_patch.rb | 108 +- .../patches/repository_cia_filters.rb | 50 +- lib/git_hosting/patches/repository_patch.rb | 134 +- .../patches/roles_controller_patch.rb | 56 +- .../patches/sys_controller_patch.rb | 38 +- lib/git_hosting/patches/user_patch.rb | 18 +- .../patches/users_controller_patch.rb | 172 +- lib/gitolite_conf.rb | 424 ++-- lib/gitolite_recycle.rb | 198 +- test/unit/gitolite_public_key_test.rb | 10 +- 57 files changed, 4103 insertions(+), 4104 deletions(-) diff --git a/app/controllers/git_http_controller.rb b/app/controllers/git_http_controller.rb index 83872f5fe..6225fa1c8 100644 --- a/app/controllers/git_http_controller.rb +++ b/app/controllers/git_http_controller.rb @@ -5,315 +5,315 @@ class GitHttpController < ApplicationController - # prevents login action to be filtered by check_if_login_required application scope filter - skip_before_filter :check_if_login_required - - before_filter :authenticate - - def index - if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) - proj_id = proj_path_split[2] - project = Project.find_by_identifier(proj_id) - @git_http_repo_path = project_path = proj_path_split[1] - - if GitHosting.http_access_url(project) == project_path - p1 = params[:path][0] || "" - p2 = params[:path][1] || "" - p3 = params[:path][2] || "" - - # Full requested path from .git repo - reqfile = params[:path].join('/') - - # git http protocol - if p1 == "git-upload-pack" - service_rpc("upload-pack") - elsif p1 == "git-receive-pack" - service_rpc("receive-pack") - elsif p1 == "info" && p2 == "refs" - get_info_refs(reqfile) - elsif p1 == "HEAD" - get_text_file(reqfile) - elsif p1 == "objects" && p2 == "info" - if p3 != "packs" - get_text_file(reqfile) - else - get_info_packs(reqfile) - end - elsif p1 == "objects" && p2 != "pack" - get_loose_object(reqfile) - elsif p1 == "objects" && p2 == "pack" && p3.match(/\.pack$/) - get_pack_file(reqfile) - elsif p1 == "objects" && p2 == "pack" && p3.match(/\.idx$/) - get_idx_file(reqfile) - else - render_not_found - end - else - # repository URL doesn't match - render_not_found - end - return - end - # Wrong prefix or :base doesn't end in .git or doesn't at least have one char in base name - render_not_found + # prevents login action to be filtered by check_if_login_required application scope filter + skip_before_filter :check_if_login_required + + before_filter :authenticate + + def index + if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) + proj_id = proj_path_split[2] + project = Project.find_by_identifier(proj_id) + @git_http_repo_path = project_path = proj_path_split[1] + + if GitHosting.http_access_url(project) == project_path + p1 = params[:path][0] || "" + p2 = params[:path][1] || "" + p3 = params[:path][2] || "" + + # Full requested path from .git repo + reqfile = params[:path].join('/') + + # git http protocol + if p1 == "git-upload-pack" + service_rpc("upload-pack") + elsif p1 == "git-receive-pack" + service_rpc("receive-pack") + elsif p1 == "info" && p2 == "refs" + get_info_refs(reqfile) + elsif p1 == "HEAD" + get_text_file(reqfile) + elsif p1 == "objects" && p2 == "info" + if p3 != "packs" + get_text_file(reqfile) + else + get_info_packs(reqfile) + end + elsif p1 == "objects" && p2 != "pack" + get_loose_object(reqfile) + elsif p1 == "objects" && p2 == "pack" && p3.match(/\.pack$/) + get_pack_file(reqfile) + elsif p1 == "objects" && p2 == "pack" && p3.match(/\.idx$/) + get_idx_file(reqfile) + else + render_not_found + end + else + # repository URL doesn't match + render_not_found + end + return end - - private - - def authenticate - is_push = (params[:path][0] == "git-receive-pack") - query_valid = false - authentication_valid = true - - if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) - project = Project.find_by_identifier(proj_path_split[2]) - repository = project != nil ? project.repository : nil - if(project != nil && repository !=nil) - if repository.extra[:git_http] == 2 || (repository.extra[:git_http] == 1 && is_ssl?) - query_valid = true - allow_anonymous_read = project.is_public - if is_push || (!allow_anonymous_read) - authentication_valid = false - authenticate_or_request_with_http_basic do |login, password| - user = User.find_by_login(login); - if user.is_a?(User) - if user.allowed_to?( :commit_access, project ) || ((!is_push) && user.allowed_to?( :view_changesets, project )) - authentication_valid = user.check_password?(password) - end - end - authentication_valid - end - end + # Wrong prefix or :base doesn't end in .git or doesn't at least have one char in base name + render_not_found + end + + private + + def authenticate + is_push = (params[:path][0] == "git-receive-pack") + query_valid = false + authentication_valid = true + + if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) + project = Project.find_by_identifier(proj_path_split[2]) + repository = project != nil ? project.repository : nil + if(project != nil && repository !=nil) + if repository.extra[:git_http] == 2 || (repository.extra[:git_http] == 1 && is_ssl?) + query_valid = true + allow_anonymous_read = project.is_public + if is_push || (!allow_anonymous_read) + authentication_valid = false + authenticate_or_request_with_http_basic do |login, password| + user = User.find_by_login(login); + if user.is_a?(User) + if user.allowed_to?( :commit_access, project ) || ((!is_push) && user.allowed_to?( :view_changesets, project )) + authentication_valid = user.check_password?(password) end + end + authentication_valid end - end - - #if authentication failed, error already rendered - #so, just render case where user queried a project - #that's nonexistant or for which smart http isn't active - if !query_valid - render_not_found + end end - - return query_valid && authentication_valid + end end - def service_rpc(rpc) - input = read_body - - response.headers["Content-Type"] = "application/x-git-%s-result" % rpc - command = git_command("#{rpc} --stateless-rpc .") - @git_http_control_pipe = IO.popen(command, File::RDWR) - @git_http_control_pipe.write(input) - - render :text => proc { |response, output| - buf_length=131072 - buf = @git_http_control_pipe.read(buf_length) - while(!(buf.nil?) && buf.length == buf_length) - output.write( buf ) - buf = @git_http_control_pipe.read(buf_length) - end - if(!(buf.nil?) && buf.length > 0) - output.write( buf ) - end - @git_http_control_pipe.close - @git_http_control_pipe = nil - } + #if authentication failed, error already rendered + #so, just render case where user queried a project + #that's nonexistant or for which smart http isn't active + if !query_valid + render_not_found end - def get_info_refs(reqfile) - service_name = get_service_type - if service_name - cmd = git_command("#{service_name} --stateless-rpc --advertise-refs .") - refs = %x[#{cmd}] - - response.headers["Content-Type"] = "application/x-git-%s-advertisement" % service_name - hdr_nocache - - response_data = pkt_write("# service=git-#{service_name}\n") + pkt_flush + refs - render :text=>response_data - else - dumb_info_refs(reqfile) - end + return query_valid && authentication_valid + end + + def service_rpc(rpc) + input = read_body + + response.headers["Content-Type"] = "application/x-git-%s-result" % rpc + command = git_command("#{rpc} --stateless-rpc .") + @git_http_control_pipe = IO.popen(command, File::RDWR) + @git_http_control_pipe.write(input) + + render :text => proc { |response, output| + buf_length=131072 + buf = @git_http_control_pipe.read(buf_length) + while(!(buf.nil?) && buf.length == buf_length) + output.write( buf ) + buf = @git_http_control_pipe.read(buf_length) + end + if(!(buf.nil?) && buf.length > 0) + output.write( buf ) + end + @git_http_control_pipe.close + @git_http_control_pipe = nil + } + end + + def get_info_refs(reqfile) + service_name = get_service_type + if service_name + cmd = git_command("#{service_name} --stateless-rpc --advertise-refs .") + refs = %x[#{cmd}] + + response.headers["Content-Type"] = "application/x-git-%s-advertisement" % service_name + hdr_nocache + + response_data = pkt_write("# service=git-#{service_name}\n") + pkt_flush + refs + render :text=>response_data + else + dumb_info_refs(reqfile) end + end - def dumb_info_refs(reqfile) - update_server_info - internal_send_file(reqfile, "text/plain; charset=utf-8") do - hdr_nocache - end + def dumb_info_refs(reqfile) + update_server_info + internal_send_file(reqfile, "text/plain; charset=utf-8") do + hdr_nocache end + end - def get_info_packs(reqfile) - # objects/info/packs - internal_send_file(reqfile, "text/plain; charset=utf-8") do - hdr_nocache - end + def get_info_packs(reqfile) + # objects/info/packs + internal_send_file(reqfile, "text/plain; charset=utf-8") do + hdr_nocache end + end - def get_loose_object(reqfile) - internal_send_file(reqfile, "application/x-git-loose-object") do - hdr_cache_forever - end + def get_loose_object(reqfile) + internal_send_file(reqfile, "application/x-git-loose-object") do + hdr_cache_forever end + end - def get_pack_file(reqfile) + def get_pack_file(reqfile) - internal_send_file(reqfile, "application/x-git-packed-objects") do - hdr_cache_forever - end + internal_send_file(reqfile, "application/x-git-packed-objects") do + hdr_cache_forever end + end - def get_idx_file(reqfile) - internal_send_file(reqfile, "application/x-git-packed-objects-toc") do - hdr_cache_forever - end + def get_idx_file(reqfile) + internal_send_file(reqfile, "application/x-git-packed-objects-toc") do + hdr_cache_forever end + end - def get_text_file(reqfile) - internal_send_file(reqfile, "text/plain") do - hdr_nocache - end + def get_text_file(reqfile) + internal_send_file(reqfile, "text/plain") do + hdr_nocache end - - # ------------------------ - # logic helping functions - # ------------------------ - - # some of this borrowed from the Rack::File implementation - def internal_send_file(reqfile, content_type) - - response.headers["Content-Type"] = content_type - if !file_exists(reqfile) - return render_not_found - else - command = "#{run_git_prefix()} dd if=#{reqfile} '" - @git_http_control_pipe = IO.popen(command, File::RDWR) - render :text => proc { |response, output| - buf_length=131072 - buf = @git_http_control_pipe.read(buf_length) - while(!(buf.nil?) && buf.length == buf_length) - output.write( buf ) - buf = @git_http_control_pipe.read(buf_length) - end - if(!(buf.nil?) && buf.length > 0) - output.write( buf ) - end - @git_http_control_pipe.close - @git_http_control_pipe = nil - } + end + + # ------------------------ + # logic helping functions + # ------------------------ + + # some of this borrowed from the Rack::File implementation + def internal_send_file(reqfile, content_type) + + response.headers["Content-Type"] = content_type + if !file_exists(reqfile) + return render_not_found + else + command = "#{run_git_prefix()} dd if=#{reqfile} '" + @git_http_control_pipe = IO.popen(command, File::RDWR) + render :text => proc { |response, output| + buf_length=131072 + buf = @git_http_control_pipe.read(buf_length) + while(!(buf.nil?) && buf.length == buf_length) + output.write( buf ) + buf = @git_http_control_pipe.read(buf_length) + end + if(!(buf.nil?) && buf.length > 0) + output.write( buf ) end + @git_http_control_pipe.close + @git_http_control_pipe = nil + } end + end - def file_exists(reqfile) + def file_exists(reqfile) - cmd="#{run_git_prefix()} if [ -e \"#{reqfile}\" ] ; then echo found ; else echo bad ; fi ' " - is_found=%x[#{cmd}] - is_found.chomp! - return is_found == "found" - end + cmd="#{run_git_prefix()} if [ -e \"#{reqfile}\" ] ; then echo found ; else echo bad ; fi ' " + is_found=%x[#{cmd}] + is_found.chomp! + return is_found == "found" + end - def get_service_type - service_type = params[:service] - return false if !service_type - return false if service_type[0, 4] != 'git-' - service_type.gsub('git-', '') - end + def get_service_type + service_type = params[:service] + return false if !service_type + return false if service_type[0, 4] != 'git-' + service_type.gsub('git-', '') + end - def get_config_setting(service_name) - service_name = service_name.gsub('-', '') - setting = get_git_config("http.#{service_name}") - if service_name == 'uploadpack' - return setting != 'false' - else - return setting == 'true' - end + def get_config_setting(service_name) + service_name = service_name.gsub('-', '') + setting = get_git_config("http.#{service_name}") + if service_name == 'uploadpack' + return setting != 'false' + else + return setting == 'true' end - - def get_git_config(config_name) - cmd = git_command("config #{config_name}") - %x[#{cmd}].chomp + end + + def get_git_config(config_name) + cmd = git_command("config #{config_name}") + %x[#{cmd}].chomp + end + + def read_body + enc_header = (request.headers['HTTP_CONTENT_ENCODING']).to_s + if enc_header =~ /gzip/ + input = Zlib::GzipReader.new(request.body).read + else + input = request.body.read end + end - def read_body - enc_header = (request.headers['HTTP_CONTENT_ENCODING']).to_s - if enc_header =~ /gzip/ - input = Zlib::GzipReader.new(request.body).read - else - input = request.body.read - end - end + def update_server_info + cmd = git_command("update-server-info") + %x[#{cmd}] + end - def update_server_info - cmd = git_command("update-server-info") - %x[#{cmd}] - end + def git_command(command) + return "#{run_git_prefix()} env GL_BYPASS_UPDATE_HOOK=true git #{command} '" + end - def git_command(command) - return "#{run_git_prefix()} env GL_BYPASS_UPDATE_HOOK=true git #{command} '" - end + #note command needs to be terminated with a quote! + def run_git_prefix + return "#{GitHosting::git_user_runner()} 'cd #{Setting.plugin_redmine_git_hosting['gitRepositoryBasePath']}/#{@git_http_repo_path}.git ; " + end - #note command needs to be terminated with a quote! - def run_git_prefix - return "#{GitHosting::git_user_runner()} 'cd #{Setting.plugin_redmine_git_hosting['gitRepositoryBasePath']}/#{@git_http_repo_path}.git ; " - end - - def is_ssl? - return request.ssl? || (request.env['HTTPS']).to_s == 'on' || (request.env['HTTP_X_FORWARDED_PROTO']).to_s == 'https' || (request.env['HTTP_X_FORWARDED_SSL']).to_s == 'on' - end + def is_ssl? + return request.ssl? || (request.env['HTTPS']).to_s == 'on' || (request.env['HTTP_X_FORWARDED_PROTO']).to_s == 'https' || (request.env['HTTP_X_FORWARDED_SSL']).to_s == 'on' + end - # -------------------------------------- - # HTTP error response handling functions - # -------------------------------------- + # -------------------------------------- + # HTTP error response handling functions + # -------------------------------------- - def render_method_not_allowed - if request.env['SERVER_PROTOCOL'] == "HTTP/1.1" - head :method_not_allowed - else - head :bad_request - end + def render_method_not_allowed + if request.env['SERVER_PROTOCOL'] == "HTTP/1.1" + head :method_not_allowed + else + head :bad_request end + end - def render_not_found - head :not_found - end + def render_not_found + head :not_found + end - def render_no_access - head :forbidden - end + def render_no_access + head :forbidden + end - # ------------------------------ - # packet-line handling functions - # ------------------------------ + # ------------------------------ + # packet-line handling functions + # ------------------------------ - def pkt_flush - '0000' - end + def pkt_flush + '0000' + end - def pkt_write(str) - (str.size + 4).to_s(base=16).rjust(4, '0') + str - end + def pkt_write(str) + (str.size + 4).to_s(base=16).rjust(4, '0') + str + end - # ------------------------ - # header writing functions - # ------------------------ + # ------------------------ + # header writing functions + # ------------------------ - def hdr_nocache - response.headers["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT" - response.headers["Pragma"] = "no-cache" - response.headers["Cache-Control"] = "no-cache, max-age=0, must-revalidate" - end + def hdr_nocache + response.headers["Expires"] = "Fri, 01 Jan 1980 00:00:00 GMT" + response.headers["Pragma"] = "no-cache" + response.headers["Cache-Control"] = "no-cache, max-age=0, must-revalidate" + end - def hdr_cache_forever - now = Time.now().to_i - response.headers["Date"] = now.to_s - response.headers["Expires"] = (now + 31536000).to_s; - response.headers["Cache-Control"] = "public, max-age=31536000"; - end + def hdr_cache_forever + now = Time.now().to_i + response.headers["Date"] = now.to_s + response.headers["Expires"] = (now + 31536000).to_s; + response.headers["Cache-Control"] = "public, max-age=31536000"; + end end diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index 3c3e9e244..fb48cc4c1 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -1,218 +1,218 @@ class GitoliteHooksController < ApplicationController - skip_before_filter :verify_authenticity_token, :check_if_login_required, :except => :test - before_filter :find_project - - helper :git_hosting - include GitHostingHelper + skip_before_filter :verify_authenticity_token, :check_if_login_required, :except => :test + before_filter :find_project + + helper :git_hosting + include GitHostingHelper + + + def stub + # Stub method simply to generate correct urls, just return a 404 to any user requesting this + render(:code => 404) + end + + # Returns an array of GitHub post-receive hook style hashes + # http://help.github.com/post-receive-hooks/ + def post_receive_payloads(refs, project=nil) + project ||= @project + payloads = [] + refs.each do |ref| + oldhead, newhead, refname = ref.split(',') + + # Only pay attention to branch updates + next if not refname.match(/refs\/heads\//) + branch = refname.gsub('refs/heads/', '') + + if newhead.match(/^0{40}$/) + # Deleting a branch + GitHosting.logger.debug "Deleting branch \"#{branch}\"" + next + elsif oldhead.match(/^0{40}$/) + # Creating a branch + GitHosting.logger.debug "Creating branch \"#{branch}\"" + range = newhead + else + range = "#{oldhead}..#{newhead}" + end + + revisions_in_range = %x[#{GitHosting.git_exec} --git-dir='#{GitHosting.repository_path(project)}' rev-list --reverse #{range}] + #GitHosting.logger.debug "Revisions in Range: #{revisions.split().join(' ')}" + + commits = [] + revisions_in_range.split().each do |rev| + revision = project.repository.find_changeset_by_name(rev.strip) + commit = { + :id => revision.revision, + :url => url_for(:controller => "repositories", :action => "revision", + :id => project, :rev => rev, :only_path => false, + :host => Setting['host_name'], :protocol => Setting['protocol'] + ), + :author => { + :name => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), + :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') + }, + :message => revision.comments, + :timestamp => revision.committed_on, + :added => [], + :modified => [], + :removed => [] + } + revision.changes.each do |change| + if change.action == "M" + commit[:modified] << change.path + elsif change.action == "A" + commit[:added] << change.path + elsif change.action == "D" + commit[:removed] << change.path + end + end + commits << commit + end + + payloads << { + :before => oldhead, + :after => newhead, + :ref => refname, + :commits => commits, + :repository => { + :description => project.description, + :fork => false, + :forks => 0, + :homepage => project.homepage, + :name => project.identifier, + :open_issues => project.issues.open.length, + :owner => { + :name => Setting["app_title"], + :email => Setting["mail_from"] + }, + :private => !project.is_public, + :url => url_for(:controller => "repositories", :action => "show", + :id => project, :only_path => false, + :host => Setting["host_name"], :protocol => Setting["protocol"] + ), + :watchers => 0 + } + } + end + payloads + end + def post_receive - def stub - # Stub method simply to generate correct urls, just return a 404 to any user requesting this - render(:code => 404) + if not @project.repository.extra.validate_encoded_time(params[:clear_time], params[:encoded_time]) + render(:text => "The hook key provided is not valid. Please let your server admin know about it") + return end - # Returns an array of GitHub post-receive hook style hashes - # http://help.github.com/post-receive-hooks/ - def post_receive_payloads(refs, project=nil) - project ||= @project - payloads = [] - refs.each do |ref| - oldhead, newhead, refname = ref.split(',') - - # Only pay attention to branch updates - next if not refname.match(/refs\/heads\//) - branch = refname.gsub('refs/heads/', '') - - if newhead.match(/^0{40}$/) - # Deleting a branch - GitHosting.logger.debug "Deleting branch \"#{branch}\"" - next - elsif oldhead.match(/^0{40}$/) - # Creating a branch - GitHosting.logger.debug "Creating branch \"#{branch}\"" - range = newhead - else - range = "#{oldhead}..#{newhead}" - end - - revisions_in_range = %x[#{GitHosting.git_exec} --git-dir='#{GitHosting.repository_path(project)}' rev-list --reverse #{range}] - #GitHosting.logger.debug "Revisions in Range: #{revisions.split().join(' ')}" - - commits = [] - revisions_in_range.split().each do |rev| - revision = project.repository.find_changeset_by_name(rev.strip) - commit = { - :id => revision.revision, - :url => url_for(:controller => "repositories", :action => "revision", - :id => project, :rev => rev, :only_path => false, - :host => Setting['host_name'], :protocol => Setting['protocol'] - ), - :author => { - :name => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), - :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') - }, - :message => revision.comments, - :timestamp => revision.committed_on, - :added => [], - :modified => [], - :removed => [] - } - revision.changes.each do |change| - if change.action == "M" - commit[:modified] << change.path - elsif change.action == "A" - commit[:added] << change.path - elsif change.action == "D" - commit[:removed] << change.path - end - end - commits << commit - end - - payloads << { - :before => oldhead, - :after => newhead, - :ref => refname, - :commits => commits, - :repository => { - :description => project.description, - :fork => false, - :forks => 0, - :homepage => project.homepage, - :name => project.identifier, - :open_issues => project.issues.open.length, - :owner => { - :name => Setting["app_title"], - :email => Setting["mail_from"] - }, - :private => !project.is_public, - :url => url_for(:controller => "repositories", :action => "show", - :id => project, :only_path => false, - :host => Setting["host_name"], :protocol => Setting["protocol"] - ), - :watchers => 0 - } - } - end - payloads - end + # Clear existing cache + GitHosting::clear_cache_for_project(@project) - def post_receive + repo_path = GitHosting.repository_path(@project) - if not @project.repository.extra.validate_encoded_time(params[:clear_time], params[:encoded_time]) - render(:text => "The hook key provided is not valid. Please let your server admin know about it") - return - end + render :text => Proc.new { |response, output| + response.headers["Content-Type"] = "text/plain;" - # Clear existing cache - GitHosting::clear_cache_for_project(@project) - - repo_path = GitHosting.repository_path(@project) - - render :text => Proc.new { |response, output| - response.headers["Content-Type"] = "text/plain;" - - # Fetch commits from the repository - GitHosting.logger.debug "Fetching changesets for #{@project.name}'s repository" - output.write("Fetching changesets for #{@project.name}'s repository ... ") - output.flush - Repository.fetch_changesets_for_project(@project.identifier) - output.write("Done\n") - output.flush - - payloads = [] - if @project.repository_mirrors.has_explicit_refspec.any? or @project.repository.extra.notify_cia == 1 or @project.repository_post_receive_urls.any? - payloads = post_receive_payloads(params[:refs]) - end - - @project.repository_mirrors.all(:order => 'active DESC, created_at ASC', :conditions => "active=1").each {|mirror| - if mirror.needs_push payloads - GitHosting.logger.debug "Pushing changes to #{mirror.url} ... " - output.write("Pushing changes to mirror #{mirror.url} ... ") - output.flush - - (mirror_err,mirror_message) = mirror.push - - result = mirror_err ? "Failed!\n" + mirror_message : "Done\n" - output.write(result) - output.flush - end - } if @project.repository_mirrors.any? - - # Post to each post-receive URL - @project.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each {|prurl| - msg = "Posting #{payloads.length} post-receive payloads to #{prurl.url} ... " - GitHosting.logger.debug msg - output.write msg - output.flush - uri = URI(prurl.url) - payloads.each {|payload| - if prurl.mode == :github - res = Net::HTTP.post_form(uri, {"payload" => payload.to_json}) - else - res = Net::HTTP.get_response(uri) - end - output.write res.is_a?(Net::HTTPSuccess) ? "[success] " : "[failure] " - output.flush - } - output.write "done\n" - output.flush - } if @project.repository_post_receive_urls.any? - - # Notify CIA - #Thread.abort_on_exception = true - Thread.new(@project, payloads) {|project, payloads| - GitHosting.logger.debug "Notifying CIA" - output.write("Notifying CIA\n") - output.flush - - payloads.each do |payload| - branch = payload[:ref].gsub("refs/heads/", "") - payload[:commits].each do |commit| - revision = project.repository.find_changeset_by_name(commit["id"]) - next if project.repository.cia_notifications.notified?(revision) # Already notified about this commit - GitHosting.logger.info "Notifying CIA: Branch => #{branch} REVISION => #{revision.revision}" - CiaNotificationMailer.deliver_notification(revision, branch) - project.repository.cia_notifications.notified(revision) - end - end - - } if !params[:refs].nil? && @project.repository.extra.notify_cia==1 - }, :layout => false - end + # Fetch commits from the repository + GitHosting.logger.debug "Fetching changesets for #{@project.name}'s repository" + output.write("Fetching changesets for #{@project.name}'s repository ... ") + output.flush + Repository.fetch_changesets_for_project(@project.identifier) + output.write("Done\n") + output.flush - def test - # Deny access if the curreent user is not allowed to manage the project's repositoy - not_enough_perms = true - User.current.roles_for_project(@project).each{|role| - if role.allowed_to? :manage_repository - not_enough_perms = false - break - end - } - return render(:text => l(:cia_not_enough_permissions), :status => 403) if not_enough_perms - - # Grab the repository path - repo_path = GitHosting.repository_path(@project) - # Get the last revision we have on the database for this project - revision = @project.repository.changesets.find(:first) - # Find out to which branch this commit belongs to - branch = %x[#{GitHosting.git_exec} --git-dir='#{repo_path}' branch --contains #{revision.scmid}].split('\n')[0].strip.gsub(/\* /, '') - GitHosting.logger.debug "Revision #{revision.scmid} found on branch #{branch}" - - # Send the test notification - GitHosting.logger.info "Sending Test Notification to CIA: Branch => #{branch} RANGE => #{revision.revision}" - CiaNotificationMailer.deliver_notification(revision, branch) - render(:text => l(:cia_notification_ok)) - end + payloads = [] + if @project.repository_mirrors.has_explicit_refspec.any? or @project.repository.extra.notify_cia == 1 or @project.repository_post_receive_urls.any? + payloads = post_receive_payloads(params[:refs]) + end + + @project.repository_mirrors.all(:order => 'active DESC, created_at ASC', :conditions => "active=1").each {|mirror| + if mirror.needs_push payloads + GitHosting.logger.debug "Pushing changes to #{mirror.url} ... " + output.write("Pushing changes to mirror #{mirror.url} ... ") + output.flush - def find_project - @project = Project.find_by_identifier(params[:projectid]) - if @project.nil? - render(:text => l(:project_not_found, :identifier => params[:projectid])) if @project.nil? - return + (mirror_err,mirror_message) = mirror.push + + result = mirror_err ? "Failed!\n" + mirror_message : "Done\n" + output.write(result) + output.flush + end + } if @project.repository_mirrors.any? + + # Post to each post-receive URL + @project.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each {|prurl| + msg = "Posting #{payloads.length} post-receive payloads to #{prurl.url} ... " + GitHosting.logger.debug msg + output.write msg + output.flush + uri = URI(prurl.url) + payloads.each {|payload| + if prurl.mode == :github + res = Net::HTTP.post_form(uri, {"payload" => payload.to_json}) + else + res = Net::HTTP.get_response(uri) + end + output.write res.is_a?(Net::HTTPSuccess) ? "[success] " : "[failure] " + output.flush + } + output.write "done\n" + output.flush + } if @project.repository_post_receive_urls.any? + + # Notify CIA + #Thread.abort_on_exception = true + Thread.new(@project, payloads) {|project, payloads| + GitHosting.logger.debug "Notifying CIA" + output.write("Notifying CIA\n") + output.flush + + payloads.each do |payload| + branch = payload[:ref].gsub("refs/heads/", "") + payload[:commits].each do |commit| + revision = project.repository.find_changeset_by_name(commit["id"]) + next if project.repository.cia_notifications.notified?(revision) # Already notified about this commit + GitHosting.logger.info "Notifying CIA: Branch => #{branch} REVISION => #{revision.revision}" + CiaNotificationMailer.deliver_notification(revision, branch) + project.repository.cia_notifications.notified(revision) + end end + + } if !params[:refs].nil? && @project.repository.extra.notify_cia==1 + }, :layout => false + end + + def test + # Deny access if the curreent user is not allowed to manage the project's repositoy + not_enough_perms = true + User.current.roles_for_project(@project).each{|role| + if role.allowed_to? :manage_repository + not_enough_perms = false + break + end + } + return render(:text => l(:cia_not_enough_permissions), :status => 403) if not_enough_perms + + # Grab the repository path + repo_path = GitHosting.repository_path(@project) + # Get the last revision we have on the database for this project + revision = @project.repository.changesets.find(:first) + # Find out to which branch this commit belongs to + branch = %x[#{GitHosting.git_exec} --git-dir='#{repo_path}' branch --contains #{revision.scmid}].split('\n')[0].strip.gsub(/\* /, '') + GitHosting.logger.debug "Revision #{revision.scmid} found on branch #{branch}" + + # Send the test notification + GitHosting.logger.info "Sending Test Notification to CIA: Branch => #{branch} RANGE => #{revision.revision}" + CiaNotificationMailer.deliver_notification(revision, branch) + render(:text => l(:cia_notification_ok)) + end + + def find_project + @project = Project.find_by_identifier(params[:projectid]) + if @project.nil? + render(:text => l(:project_not_found, :identifier => params[:projectid])) if @project.nil? + return end + end end diff --git a/app/controllers/gitolite_public_keys_controller.rb b/app/controllers/gitolite_public_keys_controller.rb index e0e68d7e9..6b4887669 100644 --- a/app/controllers/gitolite_public_keys_controller.rb +++ b/app/controllers/gitolite_public_keys_controller.rb @@ -1,80 +1,80 @@ class GitolitePublicKeysController < ApplicationController - unloadable + unloadable - before_filter :require_login - before_filter :set_user_variable - before_filter :find_gitolite_public_key, :except => [:index, :new, :create] + before_filter :require_login + before_filter :set_user_variable + before_filter :find_gitolite_public_key, :except => [:index, :new, :create] - def edit - redirect_to url_for(:controller=>'my', :action=>'account', :public_key_id => @gitolite_public_key[:id]) - end + def edit + redirect_to url_for(:controller=>'my', :action=>'account', :public_key_id => @gitolite_public_key[:id]) + end - def destroy - GitHostingObserver.set_update_active(false) - if !request.get? - destroy_key - end - redirect_to @redirect_url - GitHostingObserver.set_update_active(true) + def destroy + GitHostingObserver.set_update_active(false) + if !request.get? + destroy_key end + redirect_to @redirect_url + GitHostingObserver.set_update_active(true) + end - def update - GitHostingObserver.set_update_active(false) - if !request.get? - if params[:save_button] && @gitolite_public_key.update_attributes(params[:public_key]) - @gitolite_public_key.save - flash[:notice] = l(:notice_public_key_updated, :title=>@gitolite_public_key[:title]) - elsif params[:delete_button] - destroy_key - end - end - redirect_to @redirect_url - GitHostingObserver.set_update_active(true) + def update + GitHostingObserver.set_update_active(false) + if !request.get? + if params[:save_button] && @gitolite_public_key.update_attributes(params[:public_key]) + @gitolite_public_key.save + flash[:notice] = l(:notice_public_key_updated, :title=>@gitolite_public_key[:title]) + elsif params[:delete_button] + destroy_key + end end + redirect_to @redirect_url + GitHostingObserver.set_update_active(true) + end - def create - GitHostingObserver.set_update_active(false) - @gitolite_public_key = GitolitePublicKey.new(params[:public_key].merge(:user => @user)) - if @gitolite_public_key.save - flash[:notice] = l(:notice_public_key_added, :title=>@gitolite_public_key[:title]) - else - @gitolite_public_key = GitolitePublicKey.new(:user => @user) - end - redirect_to @redirect_url - GitHostingObserver.set_update_active(true) + def create + GitHostingObserver.set_update_active(false) + @gitolite_public_key = GitolitePublicKey.new(params[:public_key].merge(:user => @user)) + if @gitolite_public_key.save + flash[:notice] = l(:notice_public_key_added, :title=>@gitolite_public_key[:title]) + else + @gitolite_public_key = GitolitePublicKey.new(:user => @user) end + redirect_to @redirect_url + GitHostingObserver.set_update_active(true) + end - protected + protected - def set_user_variable - if params[:user_id] - @user = (params[:user_id]=='current') ? User.current : User.find_by_id(params[:user_id]) - if @user - @redirect_url = url_for(:controller => 'users', :action => 'edit', :id => params[:user_id], :tab => 'keys') - else - render_404 - end - else - @user = User.current - @redirect_url = url_for(:controller => 'my', :action => 'account') - end + def set_user_variable + if params[:user_id] + @user = (params[:user_id]=='current') ? User.current : User.find_by_id(params[:user_id]) + if @user + @redirect_url = url_for(:controller => 'users', :action => 'edit', :id => params[:user_id], :tab => 'keys') + else + render_404 + end + else + @user = User.current + @redirect_url = url_for(:controller => 'my', :action => 'account') end + end - def find_gitolite_public_key - key = GitolitePublicKey.find_by_id(params[:id]) - if key and (@user == key.user || @user.admin?) - @gitolite_public_key = key - elsif key - render_403 - else - render_404 - end + def find_gitolite_public_key + key = GitolitePublicKey.find_by_id(params[:id]) + if key and (@user == key.user || @user.admin?) + @gitolite_public_key = key + elsif key + render_403 + else + render_404 end + end - def destroy_key - @gitolite_public_key[:active] = 0 - @gitolite_public_key.save - flash[:notice] = l(:notice_public_key_deleted, :title=>@gitolite_public_key[:title]) - end + def destroy_key + @gitolite_public_key[:active] = 0 + @gitolite_public_key.save + flash[:notice] = l(:notice_public_key_deleted, :title=>@gitolite_public_key[:title]) + end end diff --git a/app/controllers/repository_mirrors_controller.rb b/app/controllers/repository_mirrors_controller.rb index f43209d50..efbaf5500 100644 --- a/app/controllers/repository_mirrors_controller.rb +++ b/app/controllers/repository_mirrors_controller.rb @@ -7,161 +7,161 @@ class RepositoryMirrorsController < ApplicationController before_filter :check_required_permissions before_filter :check_xhr_request before_filter :find_repository_mirror, :except => [:index, :create] - + menu_item :settings, :only => :settings - + layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'base' } - + def index - render_404 + render_404 end - + def create - @mirror = RepositoryMirror.new(params[:repository_mirrors]) - if request.get? - # display create view - else - @mirror.update_attributes(params[:repository_mirrors]) - @mirror.project = @project - - if @mirror.save - flash[:notice] = l(:mirror_notice_created) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') - - respond_to do |format| - format.html { - redirect_to redirect_url - } - format.js { - render :update do |page| - page.redirect_to redirect_url - end - } - end - else - respond_to do |format| - format.html { - flash[:error] = l(:mirror_notice_create_failed) - render :action => "create" - } - format.js { - render :action => "form_error" - } - end - end - end + @mirror = RepositoryMirror.new(params[:repository_mirrors]) + if request.get? + # display create view + else + @mirror.update_attributes(params[:repository_mirrors]) + @mirror.project = @project + + if @mirror.save + flash[:notice] = l(:mirror_notice_created) + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @mirror.project.identifier, + :tab => 'repository') + + respond_to do |format| + format.html { + redirect_to redirect_url + } + format.js { + render :update do |page| + page.redirect_to redirect_url + end + } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:mirror_notice_create_failed) + render :action => "create" + } + format.js { + render :action => "form_error" + } + end + end + end end - + def edit end - + def update - if @mirror.update_attributes(params[:repository_mirrors]) - flash[:notice] = l(:mirror_notice_updated) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') - - respond_to do |format| - format.html { - redirect_to redirect_url - } - format.js { - render :update do |page| - page.redirect_to redirect_url - end - } - end - else - respond_to do |format| - format.html { - flash[:error] = l(:mirror_notice_update_failed) - render :action => "edit" - } - format.js { - render :action => "form_error" - } - end - - end + if @mirror.update_attributes(params[:repository_mirrors]) + flash[:notice] = l(:mirror_notice_updated) + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @mirror.project.identifier, + :tab => 'repository') + + respond_to do |format| + format.html { + redirect_to redirect_url + } + format.js { + render :update do |page| + page.redirect_to redirect_url + end + } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:mirror_notice_update_failed) + render :action => "edit" + } + format.js { + render :action => "form_error" + } + end + + end end - + def destroy - if request.get? - # display confirmation view - else - if params[:confirm] - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') - @mirror.destroy - respond_to do |format| - format.html {redirect_to(redirect_url, :notice => l(:mirror_notice_deleted))} - end - end - end + if request.get? + # display confirmation view + else + if params[:confirm] + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @mirror.project.identifier, + :tab => 'repository') + @mirror.destroy + respond_to do |format| + format.html {redirect_to(redirect_url, :notice => l(:mirror_notice_deleted))} + end + end + end end - + def settings end - + def push - respond_to do |format| - format.html { - (@push_failed,@shellout) = @mirror.push - } - end + respond_to do |format| + format.html { + (@push_failed,@shellout) = @mirror.push + } + end end - + protected - - + + def set_user_variable - @user = User.current + @user = User.current end - + def set_project_variable - @project = Project.find(:first, :conditions => ["identifier = ?", params[:project_id]]) + @project = Project.find(:first, :conditions => ["identifier = ?", params[:project_id]]) end - + def find_repository_mirror - mirror = RepositoryMirror.find_by_id(params[:id]) - - @mirrors = @project.repository_mirrors - - if mirror and mirror.project == @project - @mirror = mirror - elsif mirror - render_403 - else - render_404 - end + mirror = RepositoryMirror.find_by_id(params[:id]) + + @mirrors = @project.repository_mirrors + + if mirror and mirror.project == @project + @mirror = mirror + elsif mirror + render_403 + else + render_404 + end end - + def check_required_permissions - # Deny access if the curreent user is not allowed to manage the project's repositoy - if not @project.module_enabled?(:repository) - render_403 - end - not_enough_perms = true - @user.roles_for_project(@project).each{|role| - if role.allowed_to? :manage_repository - not_enough_perms = false - break - end - } - if not_enough_perms - render_403 - end + # Deny access if the curreent user is not allowed to manage the project's repositoy + if not @project.module_enabled?(:repository) + render_403 + end + not_enough_perms = true + @user.roles_for_project(@project).each{|role| + if role.allowed_to? :manage_repository + not_enough_perms = false + break + end + } + if not_enough_perms + render_403 + end end - + def check_xhr_request - @is_xhr ||= request.xhr? + @is_xhr ||= request.xhr? end - + end diff --git a/app/controllers/repository_post_receive_urls_controller.rb b/app/controllers/repository_post_receive_urls_controller.rb index 340ee3a71..bdb2fc25b 100644 --- a/app/controllers/repository_post_receive_urls_controller.rb +++ b/app/controllers/repository_post_receive_urls_controller.rb @@ -7,153 +7,153 @@ class RepositoryPostReceiveUrlsController < ApplicationController before_filter :check_required_permissions before_filter :check_xhr_request before_filter :find_repository_post_receive_url, :except => [:index, :create] - + menu_item :settings, :only => :settings - + layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'base' } - + def index - render_404 + render_404 end - + def create - @prurl = RepositoryPostReceiveUrl.new(params[:repository_post_receive_urls]) - if request.get? - # display create view - else - @prurl.update_attributes(params[:repository_post_receive_urls]) - @prurl.project = @project - - if @prurl.save - flash[:notice] = l(:post_receive_url_notice_created) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') - - respond_to do |format| - format.html { - redirect_to redirect_url - } - format.js { - render :update do |page| - page.redirect_to redirect_url - end - } - end - else - respond_to do |format| - format.html { - flash[:error] = l(:post_receive_url_notice_create_failed) - render :action => "create" - } - format.js { - render :action => "form_error" - } - end - end - end + @prurl = RepositoryPostReceiveUrl.new(params[:repository_post_receive_urls]) + if request.get? + # display create view + else + @prurl.update_attributes(params[:repository_post_receive_urls]) + @prurl.project = @project + + if @prurl.save + flash[:notice] = l(:post_receive_url_notice_created) + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @prurl.project.identifier, + :tab => 'repository') + + respond_to do |format| + format.html { + redirect_to redirect_url + } + format.js { + render :update do |page| + page.redirect_to redirect_url + end + } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:post_receive_url_notice_create_failed) + render :action => "create" + } + format.js { + render :action => "form_error" + } + end + end + end end - + def edit end def update - if @prurl.update_attributes(params[:repository_post_receive_urls]) - flash[:notice] = l(:post_receive_url_notice_updated) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') - - respond_to do |format| - format.html { - redirect_to redirect_url - } - format.js { - render :update do |page| - page.redirect_to redirect_url - end - } - end - else - respond_to do |format| - format.html { - flash[:error] = l(:post_receive_url_notice_update_failed) - render :action => "edit" - } - format.js { - render :action => "form_error" - } - - end - end + if @prurl.update_attributes(params[:repository_post_receive_urls]) + flash[:notice] = l(:post_receive_url_notice_updated) + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @prurl.project.identifier, + :tab => 'repository') + + respond_to do |format| + format.html { + redirect_to redirect_url + } + format.js { + render :update do |page| + page.redirect_to redirect_url + end + } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:post_receive_url_notice_update_failed) + render :action => "edit" + } + format.js { + render :action => "form_error" + } + + end + end end - + def destroy - if request.get? - # display confirmation view - else - if params[:confirm] - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') - @prurl.destroy - respond_to do |format| - format.html {redirect_to(redirect_url, :notice => l(:post_receive_url_notice_deleted))} - end - end - end + if request.get? + # display confirmation view + else + if params[:confirm] + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @prurl.project.identifier, + :tab => 'repository') + @prurl.destroy + respond_to do |format| + format.html {redirect_to(redirect_url, :notice => l(:post_receive_url_notice_deleted))} + end + end + end end - + def settings end - + protected def set_user_variable - @user = User.current + @user = User.current end - + def set_project_variable - @project = Project.find(:first, :conditions => ["identifier = ?", params[:project_id]]) + @project = Project.find(:first, :conditions => ["identifier = ?", params[:project_id]]) end def find_repository_post_receive_url - prurl = RepositoryPostReceiveUrl.find_by_id(params[:id]) - - @prurls = @project.repository_post_receive_urls - - if prurl and prurl.project == @project - @prurl = prurl - elsif prurl - render_403 - else - render_404 - end + prurl = RepositoryPostReceiveUrl.find_by_id(params[:id]) + + @prurls = @project.repository_post_receive_urls + + if prurl and prurl.project == @project + @prurl = prurl + elsif prurl + render_403 + else + render_404 + end end - + def check_required_permissions - # Deny access if the curreent user is not allowed to manage the project's repositoy - if not @project.module_enabled?(:repository) - render_403 - end - not_enough_perms = true - @user.roles_for_project(@project).each{|role| - if role.allowed_to? :manage_repository - not_enough_perms = false - break - end - } - if not_enough_perms - render_403 - end + # Deny access if the curreent user is not allowed to manage the project's repositoy + if not @project.module_enabled?(:repository) + render_403 + end + not_enough_perms = true + @user.roles_for_project(@project).each{|role| + if role.allowed_to? :manage_repository + not_enough_perms = false + break + end + } + if not_enough_perms + render_403 + end end - + def check_xhr_request - @is_xhr ||= request.xhr? + @is_xhr ||= request.xhr? end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 11ee5ec63..99d583e3b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,73 +1,73 @@ module ApplicationHelper - # - # These functions are for backward compatability with versions of redmine < 1.3 - # Only define these if not already defined (although load-order seems to load plugin - # helpers before main ones, so check not necessary). - # - # 1/05/12 John Kubiatowicz - # 4/01/12 Only define backward-compatible functions if TabularFormBuilder exists - # (seems some versions of rails will go ahead and define these functions - # and override properly defined versions). Note that Redmine 1.4+ removes - # lib/tabular_form_builder.rb but defines these functions using new - # builder functions... - if !defined?(labelled_form_for) && File.exists?(Rails.root.join("lib/tabular_form_builder.rb")) - def labelled_form_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => TabularFormBuilder,:lang => current_language}) - form_for(*args, &proc) - end - end + # + # These functions are for backward compatability with versions of redmine < 1.3 + # Only define these if not already defined (although load-order seems to load plugin + # helpers before main ones, so check not necessary). + # + # 1/05/12 John Kubiatowicz + # 4/01/12 Only define backward-compatible functions if TabularFormBuilder exists + # (seems some versions of rails will go ahead and define these functions + # and override properly defined versions). Note that Redmine 1.4+ removes + # lib/tabular_form_builder.rb but defines these functions using new + # builder functions... + if !defined?(labelled_form_for) && File.exists?(Rails.root.join("lib/tabular_form_builder.rb")) + def labelled_form_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => TabularFormBuilder,:lang => current_language}) + form_for(*args, &proc) + end + end - if !defined?(labelled_remote_form_for) && File.exists?(Rails.root.join("lib/tabular_form_builder.rb")) - def labelled_remote_form_for(*args, &proc) - args << {} unless args.last.is_a?(Hash) - options = args.last - options.merge!({:builder => TabularFormBuilder,:lang => current_language}) - remote_form_for(*args, &proc) - end - end + if !defined?(labelled_remote_form_for) && File.exists?(Rails.root.join("lib/tabular_form_builder.rb")) + def labelled_remote_form_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => TabularFormBuilder,:lang => current_language}) + remote_form_for(*args, &proc) + end + end - # Generic helper functions - def reldir_add_dotslash(path) - # Is this a relative path? - stripped = (path || "").lstrip.rstrip - norm = File.expand_path(stripped,"/") - ((stripped[0,1] != "/")?".":"") + norm + ((norm[-1,1] != "/")?"/":"") - end - - # Port-receive Mode - def post_receive_mode(prurl) - if prurl.active==0 - l(:label_inactive) - elsif prurl.mode == :github - l(:label_github_post) - else - l(:label_empty_get) - end - end + # Generic helper functions + def reldir_add_dotslash(path) + # Is this a relative path? + stripped = (path || "").lstrip.rstrip + norm = File.expand_path(stripped,"/") + ((stripped[0,1] != "/")?".":"") + norm + ((norm[-1,1] != "/")?"/":"") + end - # Refspec for mirrors - def refspec(mirror, max_refspec=0) - if mirror.push_mode==RepositoryMirror::PUSHMODE_MIRROR - l(:all_references) - else - result=[] - result << l(:all_branches) if mirror.include_all_branches - result << l(:all_tags) if mirror.include_all_tags - result << mirror.explicit_refspec if (max_refspec == 0) || ((1..max_refspec) === mirror.explicit_refspec.length) - result << l(:explicit) if (max_refspec > 0) && (mirror.explicit_refspec.length > max_refspec) - result.join(",
") - end - end + # Port-receive Mode + def post_receive_mode(prurl) + if prurl.active==0 + l(:label_inactive) + elsif prurl.mode == :github + l(:label_github_post) + else + l(:label_empty_get) + end + end - # Mirror Mode - def mirror_mode(mirror) - if mirror.active==0 - l(:label_inactive) - else - [l(:label_mirror),l(:label_forced),l(:label_unforced)][mirror.push_mode] - end - end + # Refspec for mirrors + def refspec(mirror, max_refspec=0) + if mirror.push_mode==RepositoryMirror::PUSHMODE_MIRROR + l(:all_references) + else + result=[] + result << l(:all_branches) if mirror.include_all_branches + result << l(:all_tags) if mirror.include_all_tags + result << mirror.explicit_refspec if (max_refspec == 0) || ((1..max_refspec) === mirror.explicit_refspec.length) + result << l(:explicit) if (max_refspec > 0) && (mirror.explicit_refspec.length > max_refspec) + result.join(",
") + end + end + + # Mirror Mode + def mirror_mode(mirror) + if mirror.active==0 + l(:label_inactive) + else + [l(:label_mirror),l(:label_forced),l(:label_unforced)][mirror.push_mode] + end + end end diff --git a/app/helpers/git_hosting_helper.rb b/app/helpers/git_hosting_helper.rb index e0e81294c..df822121a 100644 --- a/app/helpers/git_hosting_helper.rb +++ b/app/helpers/git_hosting_helper.rb @@ -3,106 +3,106 @@ module GitHostingHelper - def self.git_daemon_enabled(repository, value) - gd = 1 - if repository && !repository.extra.nil? - gd = repository.extra[:git_daemon] ? repository.extra[:git_daemon] : gd - end - gd = repository.project.is_public ? gd : 0 - return return_selected_string(gd, value) + def self.git_daemon_enabled(repository, value) + gd = 1 + if repository && !repository.extra.nil? + gd = repository.extra[:git_daemon] ? repository.extra[:git_daemon] : gd end + gd = repository.project.is_public ? gd : 0 + return return_selected_string(gd, value) + end - def self.git_http_enabled(repository, value) - gh = 1 - if repository && !repository.extra.nil? - gh = repository.extra[:git_http] ? repository.extra[:git_http] : gh - end - return return_selected_string(gh, value) + def self.git_http_enabled(repository, value) + gh = 1 + if repository && !repository.extra.nil? + gh = repository.extra[:git_http] ? repository.extra[:git_http] : gh end + return return_selected_string(gh, value) + end - def self.git_notify_cia(repository, value) - nc = 0 - if repository && !repository.extra.nil? - nc = repository.extra[:notify_cia] ? repository.extra[:notify_cia] : nc - end - return return_selected_string(nc, value) + def self.git_notify_cia(repository, value) + nc = 0 + if repository && !repository.extra.nil? + nc = repository.extra[:notify_cia] ? repository.extra[:notify_cia] : nc end + return return_selected_string(nc, value) + end - def self.return_selected_string(found_value, to_check_value) - return "selected='selected'" if (found_value == to_check_value) - return "" - end + def self.return_selected_string(found_value, to_check_value) + return "selected='selected'" if (found_value == to_check_value) + return "" + end - def self.can_create_mirrors(project) - return User.current.allowed_to?(:create_repository_mirrors, project) - end - def self.can_view_mirrors(project) - return User.current.allowed_to?(:view_repository_mirrors, project) - end - def self.can_edit_mirrors(project) - return User.current.allowed_to?(:edit_repository_mirrors, project) - end + def self.can_create_mirrors(project) + return User.current.allowed_to?(:create_repository_mirrors, project) + end + def self.can_view_mirrors(project) + return User.current.allowed_to?(:view_repository_mirrors, project) + end + def self.can_edit_mirrors(project) + return User.current.allowed_to?(:edit_repository_mirrors, project) + end - def self.can_create_post_receive_urls(project) - return User.current.allowed_to?(:create_repository_post_receive_urls, project) - end - def self.can_view_post_receive_urls(project) - return User.current.allowed_to?(:view_repository_post_receive_urls, project) - end - def self.can_edit_post_receive_urls(project) - return User.current.allowed_to?(:edit_repository_post_receive_urls, project) - end + def self.can_create_post_receive_urls(project) + return User.current.allowed_to?(:create_repository_post_receive_urls, project) + end + def self.can_view_post_receive_urls(project) + return User.current.allowed_to?(:view_repository_post_receive_urls, project) + end + def self.can_edit_post_receive_urls(project) + return User.current.allowed_to?(:edit_repository_post_receive_urls, project) + end - @@file_actions = { - "a" => "add", - "m" => "modify", - "r" => "remove", - "d" => "remove" - } + @@file_actions = { + "a" => "add", + "m" => "modify", + "r" => "remove", + "d" => "remove" + } - @http_server = nil + @http_server = nil - def url_for_revision(revision) - rev = revision.respond_to?(:identifier) ? revision.identifier : revision - shorten_url( - url_for(:controller => 'repositories', :action => 'revision', :id => revision.project, - :rev => rev, :only_path => false, :host => Setting['host_name'], :protocol => Setting['protocol'] - ) - ) - end + def url_for_revision(revision) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision + shorten_url( + url_for(:controller => 'repositories', :action => 'revision', :id => revision.project, + :rev => rev, :only_path => false, :host => Setting['host_name'], :protocol => Setting['protocol'] + ) + ) + end - def url_for_revision_path(revision, path) - rev = revision.respond_to?(:identifier) ? revision.identifier : revision - shorten_url( - url_for(:controller => 'repositories', :action => 'entry', :id => revision.project, - :rev => rev, :path => path, :only_path => false, :host => Setting['host_name'], - :protocol => Setting['protocol'] - ) - ) - end + def url_for_revision_path(revision, path) + rev = revision.respond_to?(:identifier) ? revision.identifier : revision + shorten_url( + url_for(:controller => 'repositories', :action => 'entry', :id => revision.project, + :rev => rev, :path => path, :only_path => false, :host => Setting['host_name'], + :protocol => Setting['protocol'] + ) + ) + end - def map_file_action(action) - @@file_actions.fetch(action.downcase, action) - end + def map_file_action(action) + @@file_actions.fetch(action.downcase, action) + end - def shorten_url(url) - if @http_server.nil? - @uri = URI.parse("http://tinyurl.com/api-create.php") - @http_server = Net::HTTP.new(@uri.host, @uri.port) - @http_server.open_timeout = 1 # in seconds - @http_server.read_timeout = 1 # in seconds - end - uri = @uri - uri.query = "url=#{url}" - request = Net::HTTP::Get.new(uri.request_uri) - begin - response = @http_server.request(request) - GitHosting.logger.debug "Shortened URL is: #{response.body}" - return response.body - rescue Exception => e - GitHosting.logger.warn "Failed to shorten url: #{e}" - return url - end + def shorten_url(url) + if @http_server.nil? + @uri = URI.parse("http://tinyurl.com/api-create.php") + @http_server = Net::HTTP.new(@uri.host, @uri.port) + @http_server.open_timeout = 1 # in seconds + @http_server.read_timeout = 1 # in seconds + end + uri = @uri + uri.query = "url=#{url}" + request = Net::HTTP::Get.new(uri.request_uri) + begin + response = @http_server.request(request) + GitHosting.logger.debug "Shortened URL is: #{response.body}" + return response.body + rescue Exception => e + GitHosting.logger.warn "Failed to shorten url: #{e}" + return url end + end end diff --git a/app/helpers/gitolite_public_keys_helper.rb b/app/helpers/gitolite_public_keys_helper.rb index 941ec93da..a31fcf3e9 100644 --- a/app/helpers/gitolite_public_keys_helper.rb +++ b/app/helpers/gitolite_public_keys_helper.rb @@ -1,9 +1,9 @@ module GitolitePublicKeysHelper - def gitolite_public_keys_status_options_for_select(user, selected) - key_count_by_active = user.gitolite_public_keys.count(:group => 'active').to_hash - options_for_select([[l(:label_all), nil], - ["#{l(:status_active)} (#{key_count_by_active[true].to_i})", GitolitePublicKey::STATUS_ACTIVE], - ["#{l(:status_locked)} (#{key_count_by_active[false].to_i})", GitolitePublicKey::STATUS_LOCKED]], selected) - end + def gitolite_public_keys_status_options_for_select(user, selected) + key_count_by_active = user.gitolite_public_keys.count(:group => 'active').to_hash + options_for_select([[l(:label_all), nil], + ["#{l(:status_active)} (#{key_count_by_active[true].to_i})", GitolitePublicKey::STATUS_ACTIVE], + ["#{l(:status_locked)} (#{key_count_by_active[false].to_i})", GitolitePublicKey::STATUS_LOCKED]], selected) + end -end \ No newline at end of file +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 17f17d031..aa8ea5c1d 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -2,21 +2,21 @@ # Add a tab for editing public keys to the user's page.... module GitHostingUsersHelperPatch - def self.included(base) - base.send(:include, InstanceMethods) - base.class_eval do - alias_method_chain :user_settings_tabs, :public_keys - end - end + def self.included(base) + base.send(:include, InstanceMethods) + base.class_eval do + alias_method_chain :user_settings_tabs, :public_keys + end + end - module InstanceMethods - # Add a public_keys tab to the user administration page - def user_settings_tabs_with_public_keys - tabs = user_settings_tabs_without_public_keys - tabs << { :name => 'keys', :partial => 'gitolite_public_keys/view', :label => :label_public_keys } - return tabs - end - end + module InstanceMethods + # Add a public_keys tab to the user administration page + def user_settings_tabs_with_public_keys + tabs = user_settings_tabs_without_public_keys + tabs << { :name => 'keys', :partial => 'gitolite_public_keys/view', :label => :label_public_keys } + return tabs + end + end end diff --git a/app/models/cia_notification_mailer.rb b/app/models/cia_notification_mailer.rb index eb763b85c..6430b8a0a 100644 --- a/app/models/cia_notification_mailer.rb +++ b/app/models/cia_notification_mailer.rb @@ -2,72 +2,72 @@ class CiaNotificationMailer < ActionMailer::Base - helper :git_hosting - include GitHostingHelper + helper :git_hosting + include GitHostingHelper - def notification(revision, branch) - @subject = "DeliverXML" - @recipients = ["cia@cia.vc"] - #@recipients = ["ufs@ufsoft.org"] - @content_type = "text/xml" - from "CIABOT-NOREPLY@#{ Setting['host_name'].match('localhost')? Setting['mail_from'].split('@')[-1] : Setting['host_name'] }" - @sent_on = Time.now - @body = render_message( - "cia_notification.erb", :revision => revision, :branch => branch, - :plugin => Redmine::Plugin.find('redmine_git_hosting') - ) - GitHosting.logger.debug "---8<----8<--- CIA Notification Body ---8<----8<---\n#{body}---8<----8<--- CIA Notification Body ---8<----8<---" - @headers = { - "Message-ID" => "<#{revision.revision}.#{revision.author}@#{revision.project.name}>" - } - end + def notification(revision, branch) + @subject = "DeliverXML" + @recipients = ["cia@cia.vc"] + #@recipients = ["ufs@ufsoft.org"] + @content_type = "text/xml" + from "CIABOT-NOREPLY@#{ Setting['host_name'].match('localhost')? Setting['mail_from'].split('@')[-1] : Setting['host_name'] }" + @sent_on = Time.now + @body = render_message( + "cia_notification.erb", :revision => revision, :branch => branch, + :plugin => Redmine::Plugin.find('redmine_git_hosting') + ) + GitHosting.logger.debug "---8<----8<--- CIA Notification Body ---8<----8<---\n#{body}---8<----8<--- CIA Notification Body ---8<----8<---" + @headers = { + "Message-ID" => "<#{revision.revision}.#{revision.author}@#{revision.project.name}>" + } + end - # Overrides default deliver! method to first try to deliver the CIA notification - # through RPC(3 seconds timeout). If failed, send it by email. - def deliver!(mail = @mail) + # Overrides default deliver! method to first try to deliver the CIA notification + # through RPC(3 seconds timeout). If failed, send it by email. + def deliver!(mail = @mail) - rpc_server = XMLRPC::Client.new( - host="www.cia.vc", path="/RPC2", port=nil, proxy_host=nil, - proxy_port=nil, user=nil, password=nil, use_ssl=false, timeout=3 - ) + rpc_server = XMLRPC::Client.new( + host="www.cia.vc", path="/RPC2", port=nil, proxy_host=nil, + proxy_port=nil, user=nil, password=nil, use_ssl=false, timeout=3 + ) - begin - ok, result = rpc_server.call2("hub.deliver", @body) - if ok: - GitHosting.logger.info "RPC Called. OK => #{ok} Result => #{result}" - return false - end - GitHosting.logger.info "Failed to post the RPC call: #{result}" - rescue XMLRPC::FaultException => e - GitHosting.logger.info "RPC Failed. Error => #{e}" - rescue Errno::ECONNREFUSED => e - GitHosting.logger.info "RPC Connection Refused. Error => #{e}" - rescue SocketError => e - GitHosting.logger.info "RPC Socket Error. Error => #{e}" - rescue Exception => e - GitHosting.logger.info "RPC Error. Error => #{e}" - end + begin + ok, result = rpc_server.call2("hub.deliver", @body) + if ok: + GitHosting.logger.info "RPC Called. OK => #{ok} Result => #{result}" + return false + end + GitHosting.logger.info "Failed to post the RPC call: #{result}" + rescue XMLRPC::FaultException => e + GitHosting.logger.info "RPC Failed. Error => #{e}" + rescue Errno::ECONNREFUSED => e + GitHosting.logger.info "RPC Connection Refused. Error => #{e}" + rescue SocketError => e + GitHosting.logger.info "RPC Socket Error. Error => #{e}" + rescue Exception => e + GitHosting.logger.info "RPC Error. Error => #{e}" + end - GitHosting.logger.info "Delivering By Email" + GitHosting.logger.info "Delivering By Email" - return false if (recipients.nil? || recipients.empty?) && - (cc.nil? || cc.empty?) && - (bcc.nil? || bcc.empty?) + return false if (recipients.nil? || recipients.empty?) && + (cc.nil? || cc.empty?) && + (bcc.nil? || bcc.empty?) - # Log errors when raise_delivery_errors is set to false, Rails does not - raise_errors = self.class.raise_delivery_errors - self.class.raise_delivery_errors = true - begin - return super(mail) - rescue Exception => e - if raise_errors - raise e - elsif mylogger - GitHosting.logger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." - end - ensure - self.class.raise_delivery_errors = raise_errors - end + # Log errors when raise_delivery_errors is set to false, Rails does not + raise_errors = self.class.raise_delivery_errors + self.class.raise_delivery_errors = true + begin + return super(mail) + rescue Exception => e + if raise_errors + raise e + elsif mylogger + GitHosting.logger.error "The following error occured while sending email notification: \"#{e.message}\". Check your configuration in config/configuration.yml." + end + ensure + self.class.raise_delivery_errors = raise_errors end + end end diff --git a/app/models/git_cache.rb b/app/models/git_cache.rb index b2909acb1..8b9d6844e 100755 --- a/app/models/git_cache.rb +++ b/app/models/git_cache.rb @@ -1,3 +1,3 @@ class GitCache < ActiveRecord::Base - attr_accessible :command, :command_output, :proj_identifier + attr_accessible :command, :command_output, :proj_identifier end diff --git a/app/models/git_cia_notification.rb b/app/models/git_cia_notification.rb index c8e2a80dd..ac03a7336 100644 --- a/app/models/git_cia_notification.rb +++ b/app/models/git_cia_notification.rb @@ -1,10 +1,10 @@ class GitCiaNotification < ActiveRecord::Base - belongs_to :repository + belongs_to :repository - validates_uniqueness_of :scmid, :scope => [:repository_id] - validates_presence_of :repository_id, :scmid + validates_uniqueness_of :scmid, :scope => [:repository_id] + validates_presence_of :repository_id, :scmid - validates_associated :repository + validates_associated :repository end diff --git a/app/models/git_hosting_observer.rb b/app/models/git_hosting_observer.rb index d254e8e1f..e82fee5c4 100644 --- a/app/models/git_hosting_observer.rb +++ b/app/models/git_hosting_observer.rb @@ -1,106 +1,106 @@ class GitHostingObserver < ActiveRecord::Observer - observe :project, :user, :gitolite_public_key, :member, :role, :repository + observe :project, :user, :gitolite_public_key, :member, :role, :repository - @@updating_active = true - @@updating_active_stack = 0 - @@updating_active_flags = {} - @@cached_project_updates = [] + @@updating_active = true + @@updating_active_stack = 0 + @@updating_active_flags = {} + @@cached_project_updates = [] - def reload_this_observer - observed_classes.each do |klass| - klass.name.constantize.add_observer(self) - end + def reload_this_observer + observed_classes.each do |klass| + klass.name.constantize.add_observer(self) end - - - def self.set_update_active(*is_active) - if !is_active || !is_active.first - @@updating_active_stack += 1 - else - is_active.each do |item| - case item - when Symbol then @@updating_active_flags[item] = true - when Hash then @@updating_active_flags.merge!(item) - when Project then @@cached_project_updates |= [item] - end - end - - # If about to transition to zero and have something to run, do it - if @@updating_active_stack == 1 && (@@cached_project_updates.length > 0 || !@@updating_active_flags.empty?) - @@cached_project_updates = @@cached_project_updates.flatten.uniq.compact - GitHosting::update_repositories(@@cached_project_updates, @@updating_active_flags) - @@cached_project_updates = [] - @@updating_active_flags = {} - end - - # Wait until after running update_repositories before releasing - @@updating_active_stack -= 1 - if @@updating_active_stack < 0 - @@updating_active_stack = 0 - end + end + + + def self.set_update_active(*is_active) + if !is_active || !is_active.first + @@updating_active_stack += 1 + else + is_active.each do |item| + case item + when Symbol then @@updating_active_flags[item] = true + when Hash then @@updating_active_flags.merge!(item) + when Project then @@cached_project_updates |= [item] end - @@updating_active = (@@updating_active_stack == 0) + end + + # If about to transition to zero and have something to run, do it + if @@updating_active_stack == 1 && (@@cached_project_updates.length > 0 || !@@updating_active_flags.empty?) + @@cached_project_updates = @@cached_project_updates.flatten.uniq.compact + GitHosting::update_repositories(@@cached_project_updates, @@updating_active_flags) + @@cached_project_updates = [] + @@updating_active_flags = {} + end + + # Wait until after running update_repositories before releasing + @@updating_active_stack -= 1 + if @@updating_active_stack < 0 + @@updating_active_stack = 0 + end end - - # Register args for updating and then do it without allowing recursive calls - def self.bracketed_update_repositories(*args) - set_update_active(false) - set_update_active(*args) - end - - def after_create(object) - if not object.is_a?(Project) - update_repositories(object) - end + @@updating_active = (@@updating_active_stack == 0) + end + + # Register args for updating and then do it without allowing recursive calls + def self.bracketed_update_repositories(*args) + set_update_active(false) + set_update_active(*args) + end + + def after_create(object) + if not object.is_a?(Project) + update_repositories(object) end + end - def before_save(object) - if object.is_a?(Repository::Git) - GitHosting.logger.debug "On GitHostingObserver.before_save for Repository::Git" - end + def before_save(object) + if object.is_a?(Repository::Git) + GitHosting.logger.debug "On GitHostingObserver.before_save for Repository::Git" end + end - def after_save(object) - update_repositories(object) - end - - - def after_destroy(object) - if object.is_a?(Repository::Git) - update_repositories(object,:delete=>true) - GitHosting::clear_cache_for_project(object.project) - else - update_repositories(object) - end + def after_save(object) + update_repositories(object) + end + + + def after_destroy(object) + if object.is_a?(Repository::Git) + update_repositories(object,:delete=>true) + GitHosting::clear_cache_for_project(object.project) + else + update_repositories(object) end + end - protected + protected - def update_repositories(object,*flags) - projects = [] - case object - when Repository::Git then projects.push(object.project) - when User then projects = object.projects unless is_login_save?(object) - when GitolitePublicKey then projects = object.user.projects - when Member then projects.push(object.project) - when Role then projects = object.members.map(&:project).flatten.uniq.compact - end - if (projects.length > 0) - if (@@updating_active) - GitHosting::update_repositories(projects,*flags) - else - @@cached_project_updates.concat(projects) - @@updating_active_flags.merge!(*flags) unless flags.empty? - end - end + def update_repositories(object,*flags) + projects = [] + case object + when Repository::Git then projects.push(object.project) + when User then projects = object.projects unless is_login_save?(object) + when GitolitePublicKey then projects = object.user.projects + when Member then projects.push(object.project) + when Role then projects = object.members.map(&:project).flatten.uniq.compact + end + if (projects.length > 0) + if (@@updating_active) + GitHosting::update_repositories(projects,*flags) + else + @@cached_project_updates.concat(projects) + @@updating_active_flags.merge!(*flags) unless flags.empty? + end end + end - # Test for the fingerprint of changes to the user model when the User actually logs in. - def is_login_save?(user) - user.changed? && user.changed.length == 2 && user.changed.include?("updated_on") && user.changed.include?("last_login_on") - end + # Test for the fingerprint of changes to the user model when the User actually logs in. + def is_login_save?(user) + user.changed? && user.changed.length == 2 && user.changed.include?("updated_on") && user.changed.include?("last_login_on") + end end diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb index fbab2c2e5..1e64873eb 100644 --- a/app/models/git_hosting_settings_observer.rb +++ b/app/models/git_hosting_settings_observer.rb @@ -1,211 +1,211 @@ class GitHostingSettingsObserver < ActiveRecord::Observer - observe :setting + observe :setting - @@old_valuehash = (Setting.plugin_redmine_git_hosting).clone + @@old_valuehash = (Setting.plugin_redmine_git_hosting).clone - def reload_this_observer - observed_classes.each do |klass| - klass.name.constantize.add_observer(self) - end + def reload_this_observer + observed_classes.each do |klass| + klass.name.constantize.add_observer(self) end - - # There is a long-running bug in ActiveRecord::Observer that prevents us from - # returning from before_save() with false to signal verification failure. - # - # Thus, we can only silently refuse to perform bad changes and/or perform - # slight corrections to badly formatted values. - def before_save(object) - # Only validate settings for our plugin - if object.name == "plugin_redmine_git_hosting" - valuehash = object.value - if !GitHosting.bin_dir_writeable? - # If bin directory not alterable, don't allow changes to - # Script directory, Git Username, or Gitolite public or private keys - valuehash['gitScriptDir'] = @@old_valuehash['gitScriptDir'] - valuehash['gitUser'] = @@old_valuehash['gitUser'] - valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile'] - valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile'] - elsif valuehash['gitScriptDir'] && (valuehash['gitScriptDir'] != @@old_valuehash['gitScriptDir']) - # Remove old bin directory and scripts, since about to change directory - %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] - - # Script directory either absolute or relative to redmine root - stripped = valuehash['gitScriptDir'].lstrip.rstrip - normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components - if (normalizedFile == "/") - # Assume that we are relative bin directory ("/" and "" => "") - valuehash['gitScriptDir'] = "" - elsif (stripped[0,1] != "/") - valuehash['gitScriptDir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitScriptDir'] = normalizedFile + "/" # Add trailing '/' - end - elsif valuehash['gitUser'] != @@old_valuehash['gitUser'] || - valuehash['gitoliteIdentityFile'] != @@old_valuehash['gitoliteIdentityFile'] || - valuehash['gitoliteIdentityPublicKeyFile'] != @@old_valuehash['gitoliteIdentityPublicKeyFile'] - # Remove old scripts, since about to change content (leave directory alone) - %x[ rm -f '#{ GitHosting.get_bin_dir }*' ] - end - - # Temp directory must be absolute and not-empty - if valuehash['gitTempDataDir'] && (valuehash['gitTempDataDir'] != @@old_valuehash['gitTempDataDir']) - # Remove old tmp directory, since about to change - %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] - - stripped = valuehash['gitTempDataDir'].lstrip.rstrip - normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components - if (normalizedFile == "/" || stripped[0,1] != "/") - # Don't allow either root-level (absolute) or relative - valuehash['gitTempDataDir'] = "/tmp/redmine_git_hosting/" - else - valuehash['gitTempDataDir'] = normalizedFile + "/" # Add trailing '/' - end - end - - # Server should not include any path components. Also, ports should be numeric. - if valuehash['gitServer'] - normalizedServer = valuehash['gitServer'].lstrip.rstrip.split('/').first - if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) - valuehash['gitServer'] = @@old_valuehash['gitServer'] - else - valuehash['gitServer'] = normalizedServer - end - end - - # Server should not include any path components. Also, ports should be numeric. - if valuehash['httpServer'] - normalizedServer = valuehash['httpServer'].lstrip.rstrip.split('/').first - if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) - valuehash['httpServer'] = @@old_valuehash['httpServer'] - else - valuehash['httpServer'] = normalizedServer - end - end - - # Normalize http repository subdirectory path, should be either empty or relative and end in '/' - if valuehash['httpServerSubdir'] - normalizedFile = File.expand_path(valuehash['httpServerSubdir'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['httpServerSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['httpServerSubdir'] = '' - end - end - - # Normalize Config File - if valuehash['gitConfigFile'] - # Must be relative! - normalizedFile = File.expand_path(valuehash['gitConfigFile'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitConfigFile'] = normalizedFile[1..-1] # Clobber leading '/' - else - valuehash['gitConfigFile'] = GitHosting::GitoliteConfig::PRIMARY_CONF_FILE - end - - # Repair key must be true if default path - if valuehash['gitConfigFile'] == GitHosting::GitoliteConfig::PRIMARY_CONF_FILE - valuehash['gitConfigHasAdminKey'] = 'true' - end - end - - # Normalize Repository path, should be relative and end in '/' - if valuehash['gitRepositoryBasePath'] - normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitRepositoryBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitRepositoryBasePath'] = @@old_valuehash['gitRepositoryBasePath'] - end - end - - # Normalize Redmine Subdirectory path, should be either empty or relative and end in '/' - if valuehash['gitRedmineSubdir'] - normalizedFile = File.expand_path(valuehash['gitRedmineSubdir'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitRedmineSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitRedmineSubdir'] = '' - end - end - - # Normalize Recycle bin path, should be relative and end in '/' - if valuehash['gitRecycleBasePath'] - normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/") - if (normalizedFile != "/") - valuehash['gitRecycleBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' - else - valuehash['gitRecycleBasePath'] = @@old_valuehash['gitRecycleBasePath'] - end - end - - # Exclude bad expire times (and exclude non-numbers) - if valuehash['gitRecycleExpireTime'] - if valuehash['gitRecycleExpireTime'].to_f > 0 - valuehash['gitRecycleExpireTime'] = "#{(valuehash['gitRecycleExpireTime'].to_f * 10).to_i / 10.0}" - else - valuehash['gitRecycleExpireTime'] = @@old_valuehash['gitRecycleExpireTime'] - end - end - - # Validate wait time > 0 (and exclude non-numbers) - if valuehash['gitLockWaitTime'] - if valuehash['gitLockWaitTime'].to_i > 0 - valuehash['gitLockWaitTime'] = "#{valuehash['gitLockWaitTime'].to_i}" - else - valuehash['gitLockWaitTime'] = @@old_valuehash['gitLockWaitTime'] - end - end - # Save back results - object.value = valuehash + end + + # There is a long-running bug in ActiveRecord::Observer that prevents us from + # returning from before_save() with false to signal verification failure. + # + # Thus, we can only silently refuse to perform bad changes and/or perform + # slight corrections to badly formatted values. + def before_save(object) + # Only validate settings for our plugin + if object.name == "plugin_redmine_git_hosting" + valuehash = object.value + if !GitHosting.bin_dir_writeable? + # If bin directory not alterable, don't allow changes to + # Script directory, Git Username, or Gitolite public or private keys + valuehash['gitScriptDir'] = @@old_valuehash['gitScriptDir'] + valuehash['gitUser'] = @@old_valuehash['gitUser'] + valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile'] + valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile'] + elsif valuehash['gitScriptDir'] && (valuehash['gitScriptDir'] != @@old_valuehash['gitScriptDir']) + # Remove old bin directory and scripts, since about to change directory + %x[ rm -rf '#{ GitHosting.get_bin_dir }' ] + + # Script directory either absolute or relative to redmine root + stripped = valuehash['gitScriptDir'].lstrip.rstrip + normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components + if (normalizedFile == "/") + # Assume that we are relative bin directory ("/" and "" => "") + valuehash['gitScriptDir'] = "" + elsif (stripped[0,1] != "/") + valuehash['gitScriptDir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitScriptDir'] = normalizedFile + "/" # Add trailing '/' + end + elsif valuehash['gitUser'] != @@old_valuehash['gitUser'] || + valuehash['gitoliteIdentityFile'] != @@old_valuehash['gitoliteIdentityFile'] || + valuehash['gitoliteIdentityPublicKeyFile'] != @@old_valuehash['gitoliteIdentityPublicKeyFile'] + # Remove old scripts, since about to change content (leave directory alone) + %x[ rm -f '#{ GitHosting.get_bin_dir }*' ] + end + + # Temp directory must be absolute and not-empty + if valuehash['gitTempDataDir'] && (valuehash['gitTempDataDir'] != @@old_valuehash['gitTempDataDir']) + # Remove old tmp directory, since about to change + %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + + stripped = valuehash['gitTempDataDir'].lstrip.rstrip + normalizedFile = File.expand_path(stripped,"/") # Get rid of extra path components + if (normalizedFile == "/" || stripped[0,1] != "/") + # Don't allow either root-level (absolute) or relative + valuehash['gitTempDataDir'] = "/tmp/redmine_git_hosting/" + else + valuehash['gitTempDataDir'] = normalizedFile + "/" # Add trailing '/' + end + end + + # Server should not include any path components. Also, ports should be numeric. + if valuehash['gitServer'] + normalizedServer = valuehash['gitServer'].lstrip.rstrip.split('/').first + if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) + valuehash['gitServer'] = @@old_valuehash['gitServer'] + else + valuehash['gitServer'] = normalizedServer + end + end + + # Server should not include any path components. Also, ports should be numeric. + if valuehash['httpServer'] + normalizedServer = valuehash['httpServer'].lstrip.rstrip.split('/').first + if (!normalizedServer.match(/^[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*(:\d+)?$/)) + valuehash['httpServer'] = @@old_valuehash['httpServer'] + else + valuehash['httpServer'] = normalizedServer + end + end + + # Normalize http repository subdirectory path, should be either empty or relative and end in '/' + if valuehash['httpServerSubdir'] + normalizedFile = File.expand_path(valuehash['httpServerSubdir'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['httpServerSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['httpServerSubdir'] = '' + end + end + + # Normalize Config File + if valuehash['gitConfigFile'] + # Must be relative! + normalizedFile = File.expand_path(valuehash['gitConfigFile'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitConfigFile'] = normalizedFile[1..-1] # Clobber leading '/' + else + valuehash['gitConfigFile'] = GitHosting::GitoliteConfig::PRIMARY_CONF_FILE end - end - def after_save(object) - # Only perform after-actions on settings for our plugin - if object.name == "plugin_redmine_git_hosting" - valuehash = object.value - - # Settings cache doesn't seem to invalidate symbolic versions of Settings immediately, - # so, any use of Setting.plugin_redmine_git_hosting[] by things called during this - # callback will be outdated.... True for at least some versions of redmine plugin... - # - # John Kubiatowicz 12/21/2011 - if Setting.respond_to?(:check_cache) - # Clear out all cached settings. - Setting.check_cache - end - - if @@old_valuehash['gitScriptDir'] != valuehash['gitScriptDir'] || - @@old_valuehash['gitUser'] != valuehash['gitUser'] || - @@old_valuehash['gitoliteIdentityFile'] != valuehash['gitoliteIdentityFile'] || - @@old_valuehash['gitoliteIdentityPublicKeyFile'] != valuehash['gitoliteIdentityPublicKeyFile'] - # Need to update scripts - GitHosting.update_git_exec - end - - if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] || - @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || - @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || - @@old_valuehash['gitUser'] != valuehash['gitUser'] || - @@old_valuehash['gitConfigFile'] != valuehash['gitConfigFile'] || - @@old_valuehash['gitConfigHasAdminKey'] != valuehash['gitConfigHasAdminKey'] - # Need to update everyone! - GitHostingObserver.bracketed_update_repositories(:resync_all) - end - - if @@old_valuehash['gitUser'] != valuehash['gitUser'] - - GitHosting.setup_hooks - - elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] || - @@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] || - @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || - @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || - @@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous'] - - GitHosting.update_global_hook_params - end - @@old_valuehash = valuehash.clone + # Repair key must be true if default path + if valuehash['gitConfigFile'] == GitHosting::GitoliteConfig::PRIMARY_CONF_FILE + valuehash['gitConfigHasAdminKey'] = 'true' + end + end + + # Normalize Repository path, should be relative and end in '/' + if valuehash['gitRepositoryBasePath'] + normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRepositoryBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRepositoryBasePath'] = @@old_valuehash['gitRepositoryBasePath'] + end + end + + # Normalize Redmine Subdirectory path, should be either empty or relative and end in '/' + if valuehash['gitRedmineSubdir'] + normalizedFile = File.expand_path(valuehash['gitRedmineSubdir'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRedmineSubdir'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRedmineSubdir'] = '' + end + end + + # Normalize Recycle bin path, should be relative and end in '/' + if valuehash['gitRecycleBasePath'] + normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/") + if (normalizedFile != "/") + valuehash['gitRecycleBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/' + else + valuehash['gitRecycleBasePath'] = @@old_valuehash['gitRecycleBasePath'] end + end + + # Exclude bad expire times (and exclude non-numbers) + if valuehash['gitRecycleExpireTime'] + if valuehash['gitRecycleExpireTime'].to_f > 0 + valuehash['gitRecycleExpireTime'] = "#{(valuehash['gitRecycleExpireTime'].to_f * 10).to_i / 10.0}" + else + valuehash['gitRecycleExpireTime'] = @@old_valuehash['gitRecycleExpireTime'] + end + end + + # Validate wait time > 0 (and exclude non-numbers) + if valuehash['gitLockWaitTime'] + if valuehash['gitLockWaitTime'].to_i > 0 + valuehash['gitLockWaitTime'] = "#{valuehash['gitLockWaitTime'].to_i}" + else + valuehash['gitLockWaitTime'] = @@old_valuehash['gitLockWaitTime'] + end + end + # Save back results + object.value = valuehash + end + end + + def after_save(object) + # Only perform after-actions on settings for our plugin + if object.name == "plugin_redmine_git_hosting" + valuehash = object.value + + # Settings cache doesn't seem to invalidate symbolic versions of Settings immediately, + # so, any use of Setting.plugin_redmine_git_hosting[] by things called during this + # callback will be outdated.... True for at least some versions of redmine plugin... + # + # John Kubiatowicz 12/21/2011 + if Setting.respond_to?(:check_cache) + # Clear out all cached settings. + Setting.check_cache + end + + if @@old_valuehash['gitScriptDir'] != valuehash['gitScriptDir'] || + @@old_valuehash['gitUser'] != valuehash['gitUser'] || + @@old_valuehash['gitoliteIdentityFile'] != valuehash['gitoliteIdentityFile'] || + @@old_valuehash['gitoliteIdentityPublicKeyFile'] != valuehash['gitoliteIdentityPublicKeyFile'] + # Need to update scripts + GitHosting.update_git_exec + end + + if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] || + @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || + @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || + @@old_valuehash['gitUser'] != valuehash['gitUser'] || + @@old_valuehash['gitConfigFile'] != valuehash['gitConfigFile'] || + @@old_valuehash['gitConfigHasAdminKey'] != valuehash['gitConfigHasAdminKey'] + # Need to update everyone! + GitHostingObserver.bracketed_update_repositories(:resync_all) + end + + if @@old_valuehash['gitUser'] != valuehash['gitUser'] + + GitHosting.setup_hooks + + elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] || + @@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] || + @@old_valuehash['gitRedmineSubdir'] != valuehash['gitRedmineSubdir'] || + @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || + @@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous'] + + GitHosting.update_global_hook_params + end + @@old_valuehash = valuehash.clone end + end end diff --git a/app/models/git_repository_extra.rb b/app/models/git_repository_extra.rb index 3bb0ca570..f9ebe12d5 100755 --- a/app/models/git_repository_extra.rb +++ b/app/models/git_repository_extra.rb @@ -2,46 +2,46 @@ class GitRepositoryExtra < ActiveRecord::Base - belongs_to :repository, :class_name => 'Repository', :foreign_key => 'repository_id' - validates_associated :repository - attr_accessible :id, :repository_id, :key, :git_http, :git_daemon, :notify_cia + belongs_to :repository, :class_name => 'Repository', :foreign_key => 'repository_id' + validates_associated :repository + attr_accessible :id, :repository_id, :key, :git_http, :git_daemon, :notify_cia - def after_initialize - if self.repository.nil? - generate - setup_defaults - end + def after_initialize + if self.repository.nil? + generate + setup_defaults end + end - def validate_encoded_time(clear_time, encoded_time) - valid = false - begin - cur_time_seconds = Time.new.utc.to_i - test_time_seconds = clear_time.to_i - if cur_time_seconds - test_time_seconds < 5*60 - key = read_attribute(:key) - test_encoded = Digest::SHA1.hexdigest(clear_time.to_s + key.to_s) - if test_encoded.to_s == encoded_time.to_s - valid = true - end - end - rescue Exception=>e + def validate_encoded_time(clear_time, encoded_time) + valid = false + begin + cur_time_seconds = Time.new.utc.to_i + test_time_seconds = clear_time.to_i + if cur_time_seconds - test_time_seconds < 5*60 + key = read_attribute(:key) + test_encoded = Digest::SHA1.hexdigest(clear_time.to_s + key.to_s) + if test_encoded.to_s == encoded_time.to_s + valid = true end - valid + end + rescue Exception=>e end + valid + end - def generate - if self.key.nil? - write_attribute(:key, (0...64+rand(64) ).map{65.+(rand(25)).chr}.join ) - self.save - end + def generate + if self.key.nil? + write_attribute(:key, (0...64+rand(64) ).map{65.+(rand(25)).chr}.join ) + self.save end + end + + def setup_defaults + write_attribute(:git_http,Setting.plugin_redmine_git_hosting['gitHttpDefault']) if Setting.plugin_redmine_git_hosting['gitHttpDefault'] + write_attribute(:git_daemon,Setting.plugin_redmine_git_hosting['gitDaemonDefault']) if Setting.plugin_redmine_git_hosting['gitDaemonDefault'] + write_attribute(:notify_cia,Setting.plugin_redmine_git_hosting['gitNotifyCIADefault']) if Setting.plugin_redmine_git_hosting['gitNotifyCIADefault'] + self.save + end - def setup_defaults - write_attribute(:git_http,Setting.plugin_redmine_git_hosting['gitHttpDefault']) if Setting.plugin_redmine_git_hosting['gitHttpDefault'] - write_attribute(:git_daemon,Setting.plugin_redmine_git_hosting['gitDaemonDefault']) if Setting.plugin_redmine_git_hosting['gitDaemonDefault'] - write_attribute(:notify_cia,Setting.plugin_redmine_git_hosting['gitNotifyCIADefault']) if Setting.plugin_redmine_git_hosting['gitNotifyCIADefault'] - self.save - end - end diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 6f250e455..74dc5d31b 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -1,65 +1,65 @@ class GitolitePublicKey < ActiveRecord::Base - STATUS_ACTIVE = 1 - STATUS_LOCKED = 0 + STATUS_ACTIVE = 1 + STATUS_LOCKED = 0 - belongs_to :user - validates_uniqueness_of :title, :scope => :user_id - validates_uniqueness_of :identifier, :score => :user_id - validates_presence_of :title, :key, :identifier + belongs_to :user + validates_uniqueness_of :title, :scope => :user_id + validates_uniqueness_of :identifier, :score => :user_id + validates_presence_of :title, :key, :identifier - named_scope :active, {:conditions => {:active => GitolitePublicKey::STATUS_ACTIVE}} - named_scope :inactive, {:conditions => {:active => GitolitePublicKey::STATUS_LOCKED}} + named_scope :active, {:conditions => {:active => GitolitePublicKey::STATUS_ACTIVE}} + named_scope :inactive, {:conditions => {:active => GitolitePublicKey::STATUS_LOCKED}} - validate :has_not_been_changed + validate :has_not_been_changed - before_validation :set_identifier - before_validation :remove_control_characters + before_validation :set_identifier + before_validation :remove_control_characters - def has_not_been_changed - unless new_record? - %w(identifier key user_id).each do |attribute| - errors.add(attribute, 'may not be changed') unless changes[attribute].blank? - end - end + def has_not_been_changed + unless new_record? + %w(identifier key user_id).each do |attribute| + errors.add(attribute, 'may not be changed') unless changes[attribute].blank? + end end + end - def set_identifier - # add "redmine_" as a prefix to the username, and then the current date - # this helps ensure uniqueness of each key identifier - # - # also, it ensures that it is very, very unlikely to conflict with any - # existing key name if gitolite config is also being edited manually - self.identifier ||= "redmine_#{self.user.login.underscore}_#{Time.now.to_i.to_s}_#{Time.now.usec.to_s}".gsub(/[^0-9a-zA-Z\-]/,'_') - end + def set_identifier + # add "redmine_" as a prefix to the username, and then the current date + # this helps ensure uniqueness of each key identifier + # + # also, it ensures that it is very, very unlikely to conflict with any + # existing key name if gitolite config is also being edited manually + self.identifier ||= "redmine_#{self.user.login.underscore}_#{Time.now.to_i.to_s}_#{Time.now.usec.to_s}".gsub(/[^0-9a-zA-Z\-]/,'_') + end - # Make sure that current identifier is consistent with current user login. - # This method explicitly overrides the static nature of the identifier - def reset_identifier - # Fix identifier - self.identifier = nil - set_identifier + # Make sure that current identifier is consistent with current user login. + # This method explicitly overrides the static nature of the identifier + def reset_identifier + # Fix identifier + self.identifier = nil + set_identifier - # Need to override the "never change identifier" constraint - # Note that Rails 3 has a different calling convention... - self.save((Rails::VERSION::STRING.split('.')[0].to_i > 2) ? { :validate => false } : false) + # Need to override the "never change identifier" constraint + # Note that Rails 3 has a different calling convention... + self.save((Rails::VERSION::STRING.split('.')[0].to_i > 2) ? { :validate => false } : false) - self.identifier - end + self.identifier + end - # Remove control characters from key - def remove_control_characters - self.key=key.gsub(/[\a\r\n\t]/,'') - end + # Remove control characters from key + def remove_control_characters + self.key=key.gsub(/[\a\r\n\t]/,'') + end - def to_s ; title ; end + def to_s ; title ; end - @@myregular = /^redmine_(.*)_\d*_\d*(.pub)?$/ - def self.ident_to_user_token(identifier) - result = @@myregular.match(identifier) - (result!=nil) ? result[1] : nil - end + @@myregular = /^redmine_(.*)_\d*_\d*(.pub)?$/ + def self.ident_to_user_token(identifier) + result = @@myregular.match(identifier) + (result!=nil) ? result[1] : nil + end - def self.user_to_user_token(user) - user.login.underscore.gsub(/[^0-9a-zA-Z\-]/,'_') - end + def self.user_to_user_token(user) + user.login.underscore.gsub(/[^0-9a-zA-Z\-]/,'_') + end end diff --git a/app/models/repository_mirror.rb b/app/models/repository_mirror.rb index ad9359082..4bd6e4e94 100644 --- a/app/models/repository_mirror.rb +++ b/app/models/repository_mirror.rb @@ -40,8 +40,8 @@ def push push_args << "\"#{dequote(url)}\" " push_args << "\"#{dequote(explicit_refspec)}\" " unless explicit_refspec.blank? -# mycom = %[ echo 'cd "#{repo_path}" ; env GIT_SSH=~/.ssh/run_gitolite_admin_ssh git push #{push_args}2>&1' | #{GitHosting.git_user_runner} "bash" ] -# GitHosting.logger.error "Pushing: #{mycom}" + # mycom = %[ echo 'cd "#{repo_path}" ; env GIT_SSH=~/.ssh/run_gitolite_admin_ssh git push #{push_args}2>&1' | #{GitHosting.git_user_runner} "bash" ] + # GitHosting.logger.error "Pushing: #{mycom}" shellout = %x[ echo 'cd "#{repo_path}" ; env GIT_SSH=~/.ssh/run_gitolite_admin_ssh git push #{push_args}2>&1' | #{GitHosting.git_user_runner} "bash" ].chomp push_failed = ($?.to_i!=0) ? true : false if (push_failed) diff --git a/app/models/repository_post_receive_url.rb b/app/models/repository_post_receive_url.rb index 767a52ffa..620d1ec86 100644 --- a/app/models/repository_post_receive_url.rb +++ b/app/models/repository_post_receive_url.rb @@ -1,28 +1,28 @@ class RepositoryPostReceiveUrl < ActiveRecord::Base - STATUS_ACTIVE = 1 - STATUS_INACTIVE = 0 + STATUS_ACTIVE = 1 + STATUS_INACTIVE = 0 - belongs_to :project + belongs_to :project - attr_accessible :url, :mode, :active + attr_accessible :url, :mode, :active - validates_uniqueness_of :url, :scope => [:project_id] - validates_presence_of :project_id, :url - validates_format_of :url, :with => URI::regexp(%w(http https)) - validates_associated :project + validates_uniqueness_of :url, :scope => [:project_id] + validates_presence_of :project_id, :url + validates_format_of :url, :with => URI::regexp(%w(http https)) + validates_associated :project - named_scope :active, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_ACTIVE}} - named_scope :inactive, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_INACTIVE}} + named_scope :active, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_ACTIVE}} + named_scope :inactive, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_INACTIVE}} - validates_inclusion_of :mode, :in => [:github, :get] - def mode - read_attribute(:mode).to_sym rescue nil - end - def mode= (value) - write_attribute(:mode, (value.to_sym && value.to_sym.to_s rescue nil)) - end + validates_inclusion_of :mode, :in => [:github, :get] + def mode + read_attribute(:mode).to_sym rescue nil + end + def mode= (value) + write_attribute(:mode, (value.to_sym && value.to_sym.to_s rescue nil)) + end - def to_s - return File.join("#{project.identifier}-#{url}") - end + def to_s + return File.join("#{project.identifier}-#{url}") + end end diff --git a/config/routes.rb b/config/routes.rb index 38b654058..0fd926585 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,39 +1,39 @@ def install_redmine_git_hosting_routes(map) - # URL for items of type httpServer/XXX.git. Some versions of rails has problems with multiple regex expressions, so avoid... - # Note that 'http_server_subdir' is either empty (default case) or ends in '/'. - map.connect ":project_path/*path", - :prefix => Setting.plugin_redmine_git_hosting['httpServerSubdir'], :project_path => /([^\/]+\/)*?[^\/]+\.git/, :controller => 'git_http' + # URL for items of type httpServer/XXX.git. Some versions of rails has problems with multiple regex expressions, so avoid... + # Note that 'http_server_subdir' is either empty (default case) or ends in '/'. + map.connect ":project_path/*path", + :prefix => Setting.plugin_redmine_git_hosting['httpServerSubdir'], :project_path => /([^\/]+\/)*?[^\/]+\.git/, :controller => 'git_http' - # Handle the public keys plugin to my/account. - map.resources :public_keys, :controller => 'gitolite_public_keys', :path_prefix => 'my' - map.connect 'my/account/public_key/:public_key_id', :controller => 'my', :action => 'account' - map.connect 'users/:id/edit/public_key/:public_key_id', :controller => 'users', :action => 'edit', :conditions => {:method => [:get]} + # Handle the public keys plugin to my/account. + map.resources :public_keys, :controller => 'gitolite_public_keys', :path_prefix => 'my' + map.connect 'my/account/public_key/:public_key_id', :controller => 'my', :action => 'account' + map.connect 'users/:id/edit/public_key/:public_key_id', :controller => 'users', :action => 'edit', :conditions => {:method => [:get]} - # Handle hooks and mirrors - map.connect 'githooks', :controller => 'gitolite_hooks', :action => 'stub' - map.connect 'githooks/post-receive', :controller => 'gitolite_hooks', :action => 'post_receive' - map.connect 'githooks/test', :controller => 'gitolite_hooks', :action => 'test' - map.with_options :controller => 'projects' do |project_mapper| - project_mapper.with_options :controller => 'repository_mirrors' do |project_views| - project_views.connect 'projects/:project_id/settings/repository/mirrors/new', :action => 'create', :conditions => {:method => [:get, :post]} - project_views.connect 'projects/:project_id/settings/repository/mirrors/edit/:id', :action => 'edit' - project_views.connect 'projects/:project_id/settings/repository/mirrors/push/:id', :action => 'push' - project_views.connect 'projects/:project_id/settings/repository/mirrors/update/:id', :action => 'update', :conditions => {:method => :post} - project_views.connect 'projects/:project_id/settings/repository/mirrors/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} - end - project_mapper.with_options :controller => 'repository_post_receive_urls' do |project_views| - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/new', :action => 'create', :conditions => {:method => [:get, :post]} - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/edit/:id', :action => 'edit' - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/update/:id', :action => 'update', :conditions => {:method => :post} - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} - end + # Handle hooks and mirrors + map.connect 'githooks', :controller => 'gitolite_hooks', :action => 'stub' + map.connect 'githooks/post-receive', :controller => 'gitolite_hooks', :action => 'post_receive' + map.connect 'githooks/test', :controller => 'gitolite_hooks', :action => 'test' + map.with_options :controller => 'projects' do |project_mapper| + project_mapper.with_options :controller => 'repository_mirrors' do |project_views| + project_views.connect 'projects/:project_id/settings/repository/mirrors/new', :action => 'create', :conditions => {:method => [:get, :post]} + project_views.connect 'projects/:project_id/settings/repository/mirrors/edit/:id', :action => 'edit' + project_views.connect 'projects/:project_id/settings/repository/mirrors/push/:id', :action => 'push' + project_views.connect 'projects/:project_id/settings/repository/mirrors/update/:id', :action => 'update', :conditions => {:method => :post} + project_views.connect 'projects/:project_id/settings/repository/mirrors/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} end + project_mapper.with_options :controller => 'repository_post_receive_urls' do |project_views| + project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/new', :action => 'create', :conditions => {:method => [:get, :post]} + project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/edit/:id', :action => 'edit' + project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/update/:id', :action => 'update', :conditions => {:method => :post} + project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} + end + end end if defined? map - install_redmine_git_hosting_routes(map) + install_redmine_git_hosting_routes(map) else - ActionController::Routing::Routes.draw do |map| - install_redmine_git_hosting_routes(map) - end + ActionController::Routing::Routes.draw do |map| + install_redmine_git_hosting_routes(map) + end end diff --git a/contrib/hooks/post-receive.redmine_gitolite.rb b/contrib/hooks/post-receive.redmine_gitolite.rb index d0f51b779..2b1b39062 100755 --- a/contrib/hooks/post-receive.redmine_gitolite.rb +++ b/contrib/hooks/post-receive.redmine_gitolite.rb @@ -10,68 +10,68 @@ def log(msg, debug_only=false, with_newline=true) - if $debug || (!debug_only) - print msg + (with_newline ? "\n" : "") - end + if $debug || (!debug_only) + print msg + (with_newline ? "\n" : "") + end end def get_git_repository_config(varname) - (%x[git config #{varname} ]).chomp.strip + (%x[git config #{varname} ]).chomp.strip end def get_http_params(rgh_vars) - clear_time = Time.new.utc.to_i.to_s - params = { "clear_time" => clear_time, "encoded_time" => Digest::SHA1.hexdigest(clear_time.to_s + rgh_vars["key"]) } - rgh_vars.each_key do |v| - if v != "key" - params[v] = rgh_vars[v] - end + clear_time = Time.new.utc.to_i.to_s + params = { "clear_time" => clear_time, "encoded_time" => Digest::SHA1.hexdigest(clear_time.to_s + rgh_vars["key"]) } + rgh_vars.each_key do |v| + if v != "key" + params[v] = rgh_vars[v] end - params + end + params end # Need to do this ourselves, because 1.8.7 ruby is broken def set_form_data(request, params, sep = '&') - request.body = params.map {|k,v| - if v.instance_of?(Array) - v.map {|e| "#{urlencode(k.to_s)}=#{urlencode(e.to_s)}"}.join(sep) - else - "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" - end - }.join(sep) - - request.content_type = 'application/x-www-form-urlencoded' + request.body = params.map {|k,v| + if v.instance_of?(Array) + v.map {|e| "#{urlencode(k.to_s)}=#{urlencode(e.to_s)}"}.join(sep) + else + "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" + end + }.join(sep) + + request.content_type = 'application/x-www-form-urlencoded' end def urlencode(str) - str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) } + str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) } end def run_query(url_str, params, with_https) - url_str = (with_https ? "https://" : "http://" ) + url_str.gsub(/^http[s]*:\/\//, "") - success = false - begin - url = URI.parse(url_str) - http = Net::HTTP.new( url.host, url.port ) - http.open_timeout = 20 - http.read_timeout = 180 - if with_https - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - req = Net::HTTP::Post.new(url.request_uri) - set_form_data(req,params) - response = http.request(req) do |response| - response.read_body do |body_frag| - success = response.code.to_i == 200 ? true : false - log(body_frag, false, false) - end - end - #response = http.request(req) - #puts response.header - rescue Exception =>e - #log("HTTP_ERROR:" + e.to_s, true, true) - success = false + url_str = (with_https ? "https://" : "http://" ) + url_str.gsub(/^http[s]*:\/\//, "") + success = false + begin + url = URI.parse(url_str) + http = Net::HTTP.new( url.host, url.port ) + http.open_timeout = 20 + http.read_timeout = 180 + if with_https + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE end - success + req = Net::HTTP::Post.new(url.request_uri) + set_form_data(req,params) + response = http.request(req) do |response| + response.read_body do |body_frag| + success = response.code.to_i == 200 ? true : false + log(body_frag, false, false) + end + end + #response = http.request(req) + #puts response.header + rescue Exception =>e + #log("HTTP_ERROR:" + e.to_s, true, true) + success = false + end + success end @@ -80,14 +80,14 @@ def run_query(url_str, params, with_https) rgh_vars = {} rgh_var_names = [ "hooks.redmine_gitolite.key", "hooks.redmine_gitolite.url", "hooks.redmine_gitolite.projectid", "hooks.redmine_gitolite.debug", "hooks.redmine_gitolite.asynch"] rgh_var_names.each do |var_name| - var_val = get_git_repository_config(var_name) - if var_val.to_s == "" - log("\n\nRepository does not have \"#{var_name}\" set. Skipping hook.\n\n", false, true) - exit - else - var_name = var_name.gsub(/^.*\./, "") - rgh_vars[ var_name ] = var_val - end + var_val = get_git_repository_config(var_name) + if var_val.to_s == "" + log("\n\nRepository does not have \"#{var_name}\" set. Skipping hook.\n\n", false, true) + exit + else + var_name = var_name.gsub(/^.*\./, "") + rgh_vars[ var_name ] = var_val + end end $debug = rgh_vars["debug"] == "true" @@ -96,22 +96,22 @@ def run_query(url_str, params, with_https) # Let's read the refs passed to us refs = [] $<.each do |line| - r = line.chomp.strip.split - refs.push( [ r[0].to_s, r[1].to_s, r[2].to_s ].join(",") ) + r = line.chomp.strip.split + refs.push( [ r[0].to_s, r[1].to_s, r[2].to_s ].join(",") ) end rgh_vars["refs[]"] = refs if rgh_vars["asynch"] == "true" - pid = fork - exit unless pid.nil? - pid = fork - exit unless pid.nil? + pid = fork + exit unless pid.nil? + pid = fork + exit unless pid.nil? - File.umask 0000 + File.umask 0000 - STDIN.reopen '/dev/null' - STDOUT.reopen '/dev/null', 'a' - STDERR.reopen STDOUT + STDIN.reopen '/dev/null' + STDOUT.reopen '/dev/null', 'a' + STDERR.reopen STDOUT end @@ -119,13 +119,13 @@ def run_query(url_str, params, with_https) log("Notifying ChiliProject/Redmine project #{rgh_vars['projectid']} about changes to this repo...", true, true) success = run_query(rgh_vars["url"], get_http_params(rgh_vars), true) if !success - success = run_query(rgh_vars["url"], get_http_params(rgh_vars), false) + success = run_query(rgh_vars["url"], get_http_params(rgh_vars), false) end if(!success) - log("Error contacting ChiliProject/Redmine about changes to this repo.", false, true) + log("Error contacting ChiliProject/Redmine about changes to this repo.", false, true) else - log("Success", true, true) - log("", true, true) + log("Success", true, true) + log("", true, true) end log("\n\n", false, true) diff --git a/db/migrate/20091119162426_set_mirror_role_permissions.rb b/db/migrate/20091119162426_set_mirror_role_permissions.rb index 904b816ab..1a4746507 100644 --- a/db/migrate/20091119162426_set_mirror_role_permissions.rb +++ b/db/migrate/20091119162426_set_mirror_role_permissions.rb @@ -1,38 +1,38 @@ class SetMirrorRolePermissions < ActiveRecord::Migration - def self.up + def self.up - begin - GitHostingObserver.set_update_active(false) + begin + GitHostingObserver.set_update_active(false) - manager_role = Role.find_by_name(I18n.t(:default_role_manager)) - manager_role.add_permission! :view_repository_mirrors - manager_role.add_permission! :edit_repository_mirrors - manager_role.add_permission! :create_repository_mirrors - manager_role.save - - developer_role = Role.find_by_name(I18n.t(:default_role_developer)) - developer_role.add_permission! :view_repository_mirrors - developer_role.save - rescue - end + manager_role = Role.find_by_name(I18n.t(:default_role_manager)) + manager_role.add_permission! :view_repository_mirrors + manager_role.add_permission! :edit_repository_mirrors + manager_role.add_permission! :create_repository_mirrors + manager_role.save + developer_role = Role.find_by_name(I18n.t(:default_role_developer)) + developer_role.add_permission! :view_repository_mirrors + developer_role.save + rescue end - def self.down + end + + def self.down - begin - GitHostingObserver.set_update_active(false) + begin + GitHostingObserver.set_update_active(false) - manager_role = Role.find_by_name(I18n.t(:default_role_manager)) - manager_role.remove_permission! :view_repository_mirrors - manager_role.remove_permission! :edit_repository_mirrors - manager_role.remove_permission! :create_repository_mirrors - manager_role.save + manager_role = Role.find_by_name(I18n.t(:default_role_manager)) + manager_role.remove_permission! :view_repository_mirrors + manager_role.remove_permission! :edit_repository_mirrors + manager_role.remove_permission! :create_repository_mirrors + manager_role.save - developer_role = Role.find_by_name(I18n.t(:default_role_developer)) - developer_role.remove_permission! :view_repository_mirrors - developer_role.save - rescue - end + developer_role = Role.find_by_name(I18n.t(:default_role_developer)) + developer_role.remove_permission! :view_repository_mirrors + developer_role.save + rescue end + end end diff --git a/db/migrate/20091119162427_create_gitolite_public_keys.rb b/db/migrate/20091119162427_create_gitolite_public_keys.rb index 6b3a6e57a..e93401c94 100644 --- a/db/migrate/20091119162427_create_gitolite_public_keys.rb +++ b/db/migrate/20091119162427_create_gitolite_public_keys.rb @@ -1,17 +1,17 @@ class CreateGitolitePublicKeys < ActiveRecord::Migration - def self.up - create_table :gitolite_public_keys do |t| - t.column :title, :string - t.column :identifier, :string - t.column :key, :text - t.column :active, :integer, :default => 1 - t.references :user - t.timestamps - end - + def self.up + create_table :gitolite_public_keys do |t| + t.column :title, :string + t.column :identifier, :string + t.column :key, :text + t.column :active, :integer, :default => 1 + t.references :user + t.timestamps end - def self.down - drop_table :gitolite_public_keys - end + end + + def self.down + drop_table :gitolite_public_keys + end end diff --git a/db/migrate/20091119162428_create_git_caches.rb b/db/migrate/20091119162428_create_git_caches.rb index 78491bf58..7cfe8d8f3 100644 --- a/db/migrate/20091119162428_create_git_caches.rb +++ b/db/migrate/20091119162428_create_git_caches.rb @@ -1,15 +1,15 @@ class CreateGitCaches < ActiveRecord::Migration - def self.up - create_table :git_caches do |t| - t.column :command, :text - t.column :command_output, :binary - t.column :proj_identifier, :string - t.timestamps - t.index :command - end + def self.up + create_table :git_caches do |t| + t.column :command, :text + t.column :command_output, :binary + t.column :proj_identifier, :string + t.timestamps + t.index :command end + end - def self.down - drop_table :git_caches - end + def self.down + drop_table :git_caches + end end diff --git a/db/migrate/2011072600000_extend_changesets_notified_cia.rb b/db/migrate/2011072600000_extend_changesets_notified_cia.rb index 7fa4cf480..7da3e9705 100644 --- a/db/migrate/2011072600000_extend_changesets_notified_cia.rb +++ b/db/migrate/2011072600000_extend_changesets_notified_cia.rb @@ -1,9 +1,9 @@ class ExtendChangesetsNotifiedCia < ActiveRecord::Migration - def self.up - add_column :changesets, :notified_cia, :integer, :default=>0 - end + def self.up + add_column :changesets, :notified_cia, :integer, :default=>0 + end - def self.down - remove_column :changesets, :notified_cia - end + def self.down + remove_column :changesets, :notified_cia + end end diff --git a/db/migrate/2011080700000_create_repository_mirrors.rb b/db/migrate/2011080700000_create_repository_mirrors.rb index 58ca8c104..5c95de78d 100644 --- a/db/migrate/2011080700000_create_repository_mirrors.rb +++ b/db/migrate/2011080700000_create_repository_mirrors.rb @@ -1,15 +1,15 @@ class CreateRepositoryMirrors < ActiveRecord::Migration - def self.up - create_table :repository_mirrors do |t| - t.column :project_id, :integer - t.column :active, :integer, :default => 1 - t.column :url, :string - t.references :project - t.timestamps - end + def self.up + create_table :repository_mirrors do |t| + t.column :project_id, :integer + t.column :active, :integer, :default => 1 + t.column :url, :string + t.references :project + t.timestamps end + end - def self.down - drop_table :repository_mirrors - end + def self.down + drop_table :repository_mirrors + end end diff --git a/db/migrate/2011081300000_create_git_repository_extras.rb b/db/migrate/2011081300000_create_git_repository_extras.rb index 7db885fde..8dd8ab288 100644 --- a/db/migrate/2011081300000_create_git_repository_extras.rb +++ b/db/migrate/2011081300000_create_git_repository_extras.rb @@ -1,86 +1,86 @@ class CreateGitRepositoryExtras < ActiveRecord::Migration - def self.up + def self.up - drop_table :git_repository_extras if self.table_exists?("git_repository_extras") + drop_table :git_repository_extras if self.table_exists?("git_repository_extras") - create_table :git_repository_extras do |t| - t.column :repository_id, :integer - # from repository extra columns - t.column :git_daemon, :integer, :default =>1 - t.column :git_http, :integer, :default=>1 - t.column :notify_cia, :integer, :default=>0 - # from Hooks Keys table - t.column :key, :string - end - - - GitHostingObserver.set_update_active(false) - Project.find(:all).each do |project| - if project.repository.is_a?(Repository::Git) - - #create extra object - e = GitRepositoryExtra.new() - begin - e.git_daemon = project.repository.git_daemon || 1 - e.git_http = project.repository.git_http || 1 - e.key = project.repository.hook_key.key - rescue - e.git_daemon = 1 - e.git_http = 1 - end - e.repository_id = project.repository.id - e.save - - #update repo url to match location of gitolite repos - r = project.repository - repo_name= project.parent ? File.join(GitHosting::get_full_parent_path(project, true),project.identifier) : project.identifier - r.url = File.join(Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'], "#{repo_name}.git") - r.root_url = r.url - r.extra = e - r.save + create_table :git_repository_extras do |t| + t.column :repository_id, :integer + # from repository extra columns + t.column :git_daemon, :integer, :default =>1 + t.column :git_http, :integer, :default=>1 + t.column :notify_cia, :integer, :default=>0 + # from Hooks Keys table + t.column :key, :string + end - end - end - # this next part requires running commands as git user - # use a begin/rescue block because this could easily bomb out - # if settings aren't correct to begin with - begin - %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] - GitHosting.setup_hooks - GitHostingObserver.set_update_active(false) - rescue - end + GitHostingObserver.set_update_active(false) + Project.find(:all).each do |project| + if project.repository.is_a?(Repository::Git) - # even if git commands above didn't work properly, attempt to - # eliminate tmp dir in case they partially worked, and we have - # residual crap belonging to wrong user + #create extra object + e = GitRepositoryExtra.new() begin - %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + e.git_daemon = project.repository.git_daemon || 1 + e.git_http = project.repository.git_http || 1 + e.key = project.repository.hook_key.key rescue + e.git_daemon = 1 + e.git_http = 1 end + e.repository_id = project.repository.id + e.save + #update repo url to match location of gitolite repos + r = project.repository + repo_name= project.parent ? File.join(GitHosting::get_full_parent_path(project, true),project.identifier) : project.identifier + r.url = File.join(Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'], "#{repo_name}.git") + r.root_url = r.url + r.extra = e + r.save - if self.table_exists?("git_hook_keys") - drop_table :git_hook_keys - end - if self.column_exists?(:repositories, :git_daemon) - remove_column :repositories, :git_daemon - end - if self.column_exists?(:repositories, :git_http) - remove_column :repositories, :git_http - end + end + end + # this next part requires running commands as git user + # use a begin/rescue block because this could easily bomb out + # if settings aren't correct to begin with + begin + %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + GitHosting.setup_hooks + GitHostingObserver.set_update_active(false) + rescue end - def self.down - drop_table :git_repository_extras + # even if git commands above didn't work properly, attempt to + # eliminate tmp dir in case they partially worked, and we have + # residual crap belonging to wrong user + begin + %x[ rm -rf '#{ GitHosting.get_tmp_dir }' ] + rescue end - def self.table_exists?(name) - ActiveRecord::Base.connection.tables.include?(name) + + if self.table_exists?("git_hook_keys") + drop_table :git_hook_keys end - def self.column_exists?(table_name, column_name) - columns(table_name).any?{ |c| c.name == column_name.to_s } + if self.column_exists?(:repositories, :git_daemon) + remove_column :repositories, :git_daemon end + if self.column_exists?(:repositories, :git_http) + remove_column :repositories, :git_http + end + + end + + def self.down + drop_table :git_repository_extras + end + + def self.table_exists?(name) + ActiveRecord::Base.connection.tables.include?(name) + end + def self.column_exists?(table_name, column_name) + columns(table_name).any?{ |c| c.name == column_name.to_s } + end end diff --git a/db/migrate/2011081700000_move_notified_cia_to_git_cia_notifications.rb b/db/migrate/2011081700000_move_notified_cia_to_git_cia_notifications.rb index 1d161ec6d..65c81d5c0 100644 --- a/db/migrate/2011081700000_move_notified_cia_to_git_cia_notifications.rb +++ b/db/migrate/2011081700000_move_notified_cia_to_git_cia_notifications.rb @@ -1,45 +1,45 @@ class MoveNotifiedCiaToGitCiaNotifications < ActiveRecord::Migration - def self.up - - drop_table :git_cia_notifications if self.table_exists?("git_cia_notifications") - - create_table :git_cia_notifications do |t| - t.column :repository_id, :integer - t.column :scmid, :string - end - - - # Speed up searches - add_index(:git_cia_notifications, :scmid) - # Make sure uniqueness of the two columns, :scmid, :repository_id - add_index(:git_cia_notifications, [:scmid, :repository_id], :unique => true) - - Project.find(:all).each {|project| - if project.repository.is_a?(Repository::Git) - project.repository.changesets.each { |changeset| - if not changeset.notified_cia.nil? and changeset.notified_cia == 1 - #project.repository.set_notified(changeset) - project.repository.cia_notifications.push GitCiaNotification.new(:scmid => changeset.scmid) - #cia_notification = GitCiaNotification.new - #cia_notification.scmid = changeset.scmid - #cia_notification.repository = project.repository - #cia_notification.save - end - } - end - } - remove_column :changesets, :notified_cia if self.column_exists?(:changesets, :notified_cia) - end + def self.up - def self.down - drop_table :git_cia_notifications - end + drop_table :git_cia_notifications if self.table_exists?("git_cia_notifications") - def self.table_exists?(name) - ActiveRecord::Base.connection.tables.include?(name) + create_table :git_cia_notifications do |t| + t.column :repository_id, :integer + t.column :scmid, :string end - def self.column_exists?(table_name, column_name) - columns(table_name).any?{ |c| c.name == column_name.to_s } - end + + # Speed up searches + add_index(:git_cia_notifications, :scmid) + # Make sure uniqueness of the two columns, :scmid, :repository_id + add_index(:git_cia_notifications, [:scmid, :repository_id], :unique => true) + + Project.find(:all).each {|project| + if project.repository.is_a?(Repository::Git) + project.repository.changesets.each { |changeset| + if not changeset.notified_cia.nil? and changeset.notified_cia == 1 + #project.repository.set_notified(changeset) + project.repository.cia_notifications.push GitCiaNotification.new(:scmid => changeset.scmid) + #cia_notification = GitCiaNotification.new + #cia_notification.scmid = changeset.scmid + #cia_notification.repository = project.repository + #cia_notification.save + end + } + end + } + remove_column :changesets, :notified_cia if self.column_exists?(:changesets, :notified_cia) + end + + def self.down + drop_table :git_cia_notifications + end + + def self.table_exists?(name) + ActiveRecord::Base.connection.tables.include?(name) + end + + def self.column_exists?(table_name, column_name) + columns(table_name).any?{ |c| c.name == column_name.to_s } + end end diff --git a/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb b/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb index d5596275e..535ed647f 100644 --- a/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb +++ b/db/migrate/20111119170948_add_indexes_to_gitolite_public_key.rb @@ -1,11 +1,11 @@ class AddIndexesToGitolitePublicKey < ActiveRecord::Migration - def self.up - add_index :gitolite_public_keys, :user_id - add_index :gitolite_public_keys, :identifier - end + def self.up + add_index :gitolite_public_keys, :user_id + add_index :gitolite_public_keys, :identifier + end - def self.down - remove_index :gitolite_public_keys, :user_id - remove_index :gitolite_public_keys, :identifier - end + def self.down + remove_index :gitolite_public_keys, :user_id + remove_index :gitolite_public_keys, :identifier + end end diff --git a/db/migrate/20111123214911_add_settings_to_plugin.rb b/db/migrate/20111123214911_add_settings_to_plugin.rb index dc801779c..f496d568a 100644 --- a/db/migrate/20111123214911_add_settings_to_plugin.rb +++ b/db/migrate/20111123214911_add_settings_to_plugin.rb @@ -1,34 +1,34 @@ class AddSettingsToPlugin < ActiveRecord::Migration - def self.up + def self.up begin - # Add some new settings to settings page, if they don't exist - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash['gitRecycleBasePath'] ||= 'recycle_bin/' - valuehash['gitRecycleExpireTime'] ||= '24.0' - valuehash['gitLockWaitTime'] ||= '10' - if (Setting.plugin_redmine_git_hosting != valuehash) - Setting.plugin_redmine_git_hosting = valuehash - say "Added redmine_git_hosting settings: 'gitRecycleBasePath', 'getRecycleExpireTime', 'getLockWaitTime'" - end - rescue - # ignore problems if plugin settings don't exist yet - end - end + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitRecycleBasePath'] ||= 'recycle_bin/' + valuehash['gitRecycleExpireTime'] ||= '24.0' + valuehash['gitLockWaitTime'] ||= '10' + if (Setting.plugin_redmine_git_hosting != valuehash) + Setting.plugin_redmine_git_hosting = valuehash + say "Added redmine_git_hosting settings: 'gitRecycleBasePath', 'getRecycleExpireTime', 'getLockWaitTime'" + end + rescue + # ignore problems if plugin settings don't exist yet + end + end - def self.down - begin - # Remove above settings from plugin page - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash.delete('gitRecycleBasePath') - valuehash.delete('gitRecycleExpireTime') - valuehash.delete('gitLockWaitTime') - if (Setting.plugin_redmine_git_hosting != valuehash) - Setting.plugin_redmine_git_hosting = valuehash - say "Removed redmine_git_hosting settings: 'gitRecycleBasePath', 'getRecycleExpireTime', 'getLockWaitTime'" - end + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitRecycleBasePath') + valuehash.delete('gitRecycleExpireTime') + valuehash.delete('gitLockWaitTime') + if (Setting.plugin_redmine_git_hosting != valuehash) Setting.plugin_redmine_git_hosting = valuehash - rescue - # ignore problems if table doesn't exist yet.... - end - end + say "Removed redmine_git_hosting settings: 'gitRecycleBasePath', 'getRecycleExpireTime', 'getLockWaitTime'" + end + Setting.plugin_redmine_git_hosting = valuehash + rescue + # ignore problems if table doesn't exist yet.... + end + end end diff --git a/db/migrate/20111220055819_add_settings_to_plugin_2.rb b/db/migrate/20111220055819_add_settings_to_plugin_2.rb index 6906c6cf0..4eca6100b 100644 --- a/db/migrate/20111220055819_add_settings_to_plugin_2.rb +++ b/db/migrate/20111220055819_add_settings_to_plugin_2.rb @@ -1,47 +1,47 @@ class AddSettingsToPlugin2 < ActiveRecord::Migration - def self.up + def self.up begin - # Add some new settings to settings page, if they don't exist - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash['httpServerSubdir'] ||= '' - valuehash['gitRedmineSubdir'] ||= '' - valuehash['gitRepositoryHierarchy'] ||= 'true' - - # Fix httpServer by removing directory components - valuehash['httpServer'] = (valuehash['httpServer'][/^[^\/]*/]) + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['httpServerSubdir'] ||= '' + valuehash['gitRedmineSubdir'] ||= '' + valuehash['gitRepositoryHierarchy'] ||= 'true' - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Added redmine_git_hosting settings: 'httpServerSubdir', 'gitRedmineSubdir', 'gitRepositoryHierarchy'" - if (Setting.plugin_redmine_git_hosting['httpServer'] != valuehash['httpServer']) - say "Updated 'httpServer' from '#{Setting.plugin_redmine_git_hosting['httpServer']}' to '#{valuehash['httpServer']}'." - end - Setting.plugin_redmine_git_hosting = valuehash - end - rescue => e - # ignore problems if plugin settings don't exist yet - end - end + # Fix httpServer by removing directory components + valuehash['httpServer'] = (valuehash['httpServer'][/^[^\/]*/]) - def self.down - begin - # Remove above settings from plugin page - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash.delete('httpServerSubdir') - valuehash.delete('gitRedmineSubdir') - valuehash.delete('gitRepositoryHierarchy') + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Added redmine_git_hosting settings: 'httpServerSubdir', 'gitRedmineSubdir', 'gitRepositoryHierarchy'" + if (Setting.plugin_redmine_git_hosting['httpServer'] != valuehash['httpServer']) + say "Updated 'httpServer' from '#{Setting.plugin_redmine_git_hosting['httpServer']}' to '#{valuehash['httpServer']}'." + end + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if plugin settings don't exist yet + end + end - # Restore redmine root directory to httpServer (remove trailing '/') - valuehash['httpServer'] = GitHosting.my_root_url + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('httpServerSubdir') + valuehash.delete('gitRedmineSubdir') + valuehash.delete('gitRepositoryHierarchy') + + # Restore redmine root directory to httpServer (remove trailing '/') + valuehash['httpServer'] = GitHosting.my_root_url - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Removed redmine_git_hosting settings: 'httpServerSubdir', 'gitRedmineSubdir', 'gitRepositoryHierarchy'" - if (Setting.plugin_redmine_git_hosting['httpServer'] != valuehash['httpServer']) - say "Updated 'httpServer' from '#{Setting.plugin_redmine_git_hosting['httpServer']}' to '#{valuehash['httpServer']}'." - end - Setting.plugin_redmine_git_hosting = valuehash - end - rescue => e - # ignore problems if table doesn't exist yet.... - end - end + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Removed redmine_git_hosting settings: 'httpServerSubdir', 'gitRedmineSubdir', 'gitRepositoryHierarchy'" + if (Setting.plugin_redmine_git_hosting['httpServer'] != valuehash['httpServer']) + say "Updated 'httpServer' from '#{Setting.plugin_redmine_git_hosting['httpServer']}' to '#{valuehash['httpServer']}'." + end + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if table doesn't exist yet.... + end + end end diff --git a/db/migrate/20120226013750_add_settings_to_plugin_3.rb b/db/migrate/20120226013750_add_settings_to_plugin_3.rb index 1763ae267..8dd75949a 100644 --- a/db/migrate/20120226013750_add_settings_to_plugin_3.rb +++ b/db/migrate/20120226013750_add_settings_to_plugin_3.rb @@ -1,42 +1,42 @@ class AddSettingsToPlugin3 < ActiveRecord::Migration - def self.up + def self.up begin - # Add some new settings to settings page, if they don't exist - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash['gitDaemonDefault'] ||= '1' - valuehash['gitHttpDefault'] ||= '1' - valuehash['gitNotifyCIADefault'] ||= '0' - valuehash['gitTempDataDir'] ||= '/tmp/redmine_git_hosting/' - valuehash['gitScriptDir'] ||= '' - - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Added redmine_git_hosting settings: 'gitDaemonDefault', 'gitHttpDefault', 'gitNotifyCIADefault', 'gitTempDataDir', 'gitScriptDir'" - if (Setting.plugin_redmine_git_hosting['httpServer'] != valuehash['httpServer']) - say "Updated 'httpServer' from '#{Setting.plugin_redmine_git_hosting['httpServer']}' to '#{valuehash['httpServer']}'." - end - Setting.plugin_redmine_git_hosting = valuehash - end - rescue => e - # ignore problems if plugin settings don't exist yet - end - end + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitDaemonDefault'] ||= '1' + valuehash['gitHttpDefault'] ||= '1' + valuehash['gitNotifyCIADefault'] ||= '0' + valuehash['gitTempDataDir'] ||= '/tmp/redmine_git_hosting/' + valuehash['gitScriptDir'] ||= '' - def self.down - begin - # Remove above settings from plugin page - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash.delete('gitDaemonDefault') - valuehash.delete('gitHttpDefault') - valuehash.delete('gitNotifyDIADefault') - valuehash.delete('gitTempDataDir') - valuehash.delete('gitScriptDir') + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Added redmine_git_hosting settings: 'gitDaemonDefault', 'gitHttpDefault', 'gitNotifyCIADefault', 'gitTempDataDir', 'gitScriptDir'" + if (Setting.plugin_redmine_git_hosting['httpServer'] != valuehash['httpServer']) + say "Updated 'httpServer' from '#{Setting.plugin_redmine_git_hosting['httpServer']}' to '#{valuehash['httpServer']}'." + end + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if plugin settings don't exist yet + end + end - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Removed redmine_git_hosting settings: 'gitDaemonDefault', 'gitHttpDefault', 'gitNotifyCIADefault', 'gitTempDataDir', 'gitScriptDir'" - Setting.plugin_redmine_git_hosting = valuehash - end - rescue => e - # ignore problems if table doesn't exist yet.... - end - end + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitDaemonDefault') + valuehash.delete('gitHttpDefault') + valuehash.delete('gitNotifyDIADefault') + valuehash.delete('gitTempDataDir') + valuehash.delete('gitScriptDir') + + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Removed redmine_git_hosting settings: 'gitDaemonDefault', 'gitHttpDefault', 'gitNotifyCIADefault', 'gitTempDataDir', 'gitScriptDir'" + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if table doesn't exist yet.... + end + end end diff --git a/db/migrate/2012052100000_create_repository_post_receive_urls.rb b/db/migrate/2012052100000_create_repository_post_receive_urls.rb index ead700aa1..ae1cd938f 100644 --- a/db/migrate/2012052100000_create_repository_post_receive_urls.rb +++ b/db/migrate/2012052100000_create_repository_post_receive_urls.rb @@ -1,15 +1,15 @@ class CreateRepositoryPostReceiveUrls < ActiveRecord::Migration - def self.up - create_table :repository_post_receive_urls do |t| - t.column :project_id, :integer - t.column :active, :integer, :default => 1 - t.column :url, :string - t.references :project - t.timestamps + def self.up + create_table :repository_post_receive_urls do |t| + t.column :project_id, :integer + t.column :active, :integer, :default => 1 + t.column :url, :string + t.references :project + t.timestamps + end end - end - def self.down - drop_table :repository_post_receive_urls - end + def self.down + drop_table :repository_post_receive_urls + end end diff --git a/db/migrate/2012052100001_set_post_receive_url_role_permissions.rb b/db/migrate/2012052100001_set_post_receive_url_role_permissions.rb index 8865fff5b..9d0de32bc 100644 --- a/db/migrate/2012052100001_set_post_receive_url_role_permissions.rb +++ b/db/migrate/2012052100001_set_post_receive_url_role_permissions.rb @@ -1,38 +1,38 @@ class SetPostReceiveUrlRolePermissions < ActiveRecord::Migration - def self.up + def self.up - begin - GitHostingObserver.set_update_active(false) + begin + GitHostingObserver.set_update_active(false) - manager_role = Role.find_by_name(I18n.t(:default_role_manager)) - manager_role.add_permission! :view_repository_post_receive_urls - manager_role.add_permission! :edit_repository_post_receive_urls - manager_role.add_permission! :create_repository_post_receive_urls - manager_role.save + manager_role = Role.find_by_name(I18n.t(:default_role_manager)) + manager_role.add_permission! :view_repository_post_receive_urls + manager_role.add_permission! :edit_repository_post_receive_urls + manager_role.add_permission! :create_repository_post_receive_urls + manager_role.save - developer_role = Role.find_by_name(I18n.t(:default_role_developer)) - developer_role.add_permission! :view_repository_post_receive_urls - developer_role.save - rescue - end + developer_role = Role.find_by_name(I18n.t(:default_role_developer)) + developer_role.add_permission! :view_repository_post_receive_urls + developer_role.save + rescue + end - end + end - def self.down + def self.down - begin - GitHostingObserver.set_update_active(false) + begin + GitHostingObserver.set_update_active(false) - manager_role = Role.find_by_name(I18n.t(:default_role_manager)) - manager_role.remove_permission! :view_repository_post_receive_urls - manager_role.remove_permission! :edit_repository_post_receive_urls - manager_role.remove_permission! :create_repository_post_receive_urls - manager_role.save + manager_role = Role.find_by_name(I18n.t(:default_role_manager)) + manager_role.remove_permission! :view_repository_post_receive_urls + manager_role.remove_permission! :edit_repository_post_receive_urls + manager_role.remove_permission! :create_repository_post_receive_urls + manager_role.save - developer_role = Role.find_by_name(I18n.t(:default_role_developer)) - developer_role.remove_permission! :view_repository_post_receive_urls - developer_role.save - rescue + developer_role = Role.find_by_name(I18n.t(:default_role_developer)) + developer_role.remove_permission! :view_repository_post_receive_urls + developer_role.save + rescue + end end - end end diff --git a/db/migrate/2012052200000_add_post_receive_url_modes.rb b/db/migrate/2012052200000_add_post_receive_url_modes.rb index 87b29ca4b..c0dff1fca 100644 --- a/db/migrate/2012052200000_add_post_receive_url_modes.rb +++ b/db/migrate/2012052200000_add_post_receive_url_modes.rb @@ -1,9 +1,9 @@ class AddPostReceiveUrlModes < ActiveRecord::Migration - def self.up - add_column :repository_post_receive_urls, :mode, :string, :default => "github" - end + def self.up + add_column :repository_post_receive_urls, :mode, :string, :default => "github" + end - def self.down - remove_column :repository_post_receive_urls, :mode - end -end \ No newline at end of file + def self.down + remove_column :repository_post_receive_urls, :mode + end +end diff --git a/db/migrate/20120708070841_add_settings_to_plugin_4.rb b/db/migrate/20120708070841_add_settings_to_plugin_4.rb index 08c2d3e89..0b1aef145 100644 --- a/db/migrate/20120708070841_add_settings_to_plugin_4.rb +++ b/db/migrate/20120708070841_add_settings_to_plugin_4.rb @@ -1,30 +1,30 @@ class AddSettingsToPlugin4 < ActiveRecord::Migration - def self.up + def self.up begin - # Add some new settings to settings page, if they don't exist - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash['gitForceHooksUpdate'] ||= 'true' - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Added redmine_git_hosting settings: 'gitForceHooksUpdate'." - Setting.plugin_redmine_git_hosting = valuehash - end - rescue => e - # ignore problems if plugin settings don't exist yet - end - end + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitForceHooksUpdate'] ||= 'true' + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Added redmine_git_hosting settings: 'gitForceHooksUpdate'." + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if plugin settings don't exist yet + end + end - def self.down - begin - # Remove above settings from plugin page - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash.delete('gitForceHooksUpdate') + def self.down + begin + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitForceHooksUpdate') - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Removed redmine_git_hosting settings: 'gitForceHooksUpdate'." - Setting.plugin_redmine_git_hosting = valuehash - end - rescue => e - # ignore problems if table doesn't exist yet.... - end - end + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Removed redmine_git_hosting settings: 'gitForceHooksUpdate'." + Setting.plugin_redmine_git_hosting = valuehash + end + rescue => e + # ignore problems if table doesn't exist yet.... + end + end end diff --git a/db/migrate/20120710204007_add_repository_mirror_fields.rb b/db/migrate/20120710204007_add_repository_mirror_fields.rb index 1bd104890..d8e8f6109 100644 --- a/db/migrate/20120710204007_add_repository_mirror_fields.rb +++ b/db/migrate/20120710204007_add_repository_mirror_fields.rb @@ -1,15 +1,15 @@ class AddRepositoryMirrorFields < ActiveRecord::Migration - def self.up - add_column :repository_mirrors, :push_mode, :integer, :default => 0 - add_column :repository_mirrors, :include_all_branches, :boolean, :default => false - add_column :repository_mirrors, :include_all_tags, :boolean, :default => false - add_column :repository_mirrors, :explicit_refspec, :string, :default => "" - end + def self.up + add_column :repository_mirrors, :push_mode, :integer, :default => 0 + add_column :repository_mirrors, :include_all_branches, :boolean, :default => false + add_column :repository_mirrors, :include_all_tags, :boolean, :default => false + add_column :repository_mirrors, :explicit_refspec, :string, :default => "" + end - def self.down - remove_column :repository_mirrors, :push_mode - remove_column :repository_mirrors, :include_all_branches - remove_column :repository_mirrors, :include_all_tags - remove_column :repository_mirrors, :explicit_refspec - end + def self.down + remove_column :repository_mirrors, :push_mode + remove_column :repository_mirrors, :include_all_branches + remove_column :repository_mirrors, :include_all_tags + remove_column :repository_mirrors, :explicit_refspec + end end diff --git a/db/migrate/20120724211806_add_settings_to_plugin_5.rb b/db/migrate/20120724211806_add_settings_to_plugin_5.rb index 89efb0861..0ee946201 100644 --- a/db/migrate/20120724211806_add_settings_to_plugin_5.rb +++ b/db/migrate/20120724211806_add_settings_to_plugin_5.rb @@ -1,33 +1,33 @@ class AddSettingsToPlugin5 < ActiveRecord::Migration - def self.up + def self.up begin - # Add some new settings to settings page, if they don't exist - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash['gitConfigFile'] ||= 'gitolite.conf' - valuehash['gitConfigHasAdminKey'] || 'true' + # Add some new settings to settings page, if they don't exist + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash['gitConfigFile'] ||= 'gitolite.conf' + valuehash['gitConfigHasAdminKey'] || 'true' - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Added redmine_git_hosting settings: 'gitConfigFile', 'gitConfigHasAdminKey'" - Setting.plugin_redmine_git_hosting = valuehash - end + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Added redmine_git_hosting settings: 'gitConfigFile', 'gitConfigHasAdminKey'" + Setting.plugin_redmine_git_hosting = valuehash + end rescue => e - # ignore problems if plugin settings don't exist yet + # ignore problems if plugin settings don't exist yet end - end + end - def self.down + def self.down begin - # Remove above settings from plugin page - valuehash = (Setting.plugin_redmine_git_hosting).clone - valuehash.delete('gitConfigFile') - valuehash.delete('gitConfigHasAdminKey') + # Remove above settings from plugin page + valuehash = (Setting.plugin_redmine_git_hosting).clone + valuehash.delete('gitConfigFile') + valuehash.delete('gitConfigHasAdminKey') - if (Setting.plugin_redmine_git_hosting != valuehash) - say "Removed redmine_git_hosting settings: 'gitConfigFile', 'gitConfigHasAdminKey" - Setting.plugin_redmine_git_hosting = valuehash - end + if (Setting.plugin_redmine_git_hosting != valuehash) + say "Removed redmine_git_hosting settings: 'gitConfigFile', 'gitConfigHasAdminKey" + Setting.plugin_redmine_git_hosting = valuehash + end rescue => e - # ignore problems if table doesn't exist yet.... + # ignore problems if table doesn't exist yet.... end - end + end end diff --git a/init.rb b/init.rb index 88990ef31..f8d32aabd 100755 --- a/init.rb +++ b/init.rb @@ -5,80 +5,80 @@ require File.join(File.dirname(__FILE__), 'app', 'models', 'git_cia_notification') Redmine::Plugin.register :redmine_git_hosting do - name 'Redmine Git Hosting Plugin' - author 'Eric Bishop, Pedro Algarvio, Christian Käser, Zsolt Parragi, Yunsang Choi, Joshua Hogendorn, Jan Schulz-Hofen, John Kubiatowicz and others' - description 'Enables Redmine / ChiliProject to control hosting of git repositories' - version '0.4.5x' - url 'https://github.com/ericpaulbishop/redmine_git_hosting' + name 'Redmine Git Hosting Plugin' + author 'Eric Bishop, Pedro Algarvio, Christian Käser, Zsolt Parragi, Yunsang Choi, Joshua Hogendorn, Jan Schulz-Hofen, John Kubiatowicz and others' + description 'Enables Redmine / ChiliProject to control hosting of git repositories' + version '0.4.5x' + url 'https://github.com/ericpaulbishop/redmine_git_hosting' - settings :default => { - 'httpServer' => 'localhost', - 'httpServerSubdir' => '', - 'gitServer' => 'localhost', - 'gitUser' => 'git', - 'gitConfigPath' => 'gitolite.conf', # Redmine gitolite config file - 'gitConfigHasAdminKey' => 'true', # Conf file should have admin key - 'gitRepositoryBasePath' => 'repositories/', - 'gitRedmineSubdir' => '', - 'gitRepositoryHierarchy' => 'true', - 'gitRecycleBasePath' => 'recycle_bin/', - 'gitRecycleExpireTime' => '24.0', - 'gitLockWaitTime' => '10', - 'gitoliteIdentityFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa', - 'gitoliteIdentityPublicKeyFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa.pub', - 'allProjectsUseGit' => 'false', - 'gitDaemonDefault' => '1', # Default is Daemon enabled - 'gitHttpDefault' => '1', # Default is HTTP_ONLY - 'gitNotifyCIADefault' => '0', # Default is CIA Notification disabled - 'deleteGitRepositories' => 'false', - 'gitRepositoriesShowUrl' => 'true', - 'gitCacheMaxTime' => '-1', - 'gitCacheMaxElements' => '100', - 'gitCacheMaxSize' => '16', - 'gitHooksDebug' => 'false', - 'gitHooksAreAsynchronous' => 'true', - 'gitTempDataDir' => '/tmp/redmine_git_hosting/', - 'gitScriptDir' => '', - 'gitForceHooksUpdate' => 'true' - }, - :partial => 'redmine_git_hosting' - project_module :repository do - permission :create_repository_mirrors, :repository_mirrors => :create - permission :view_repository_mirrors, :repository_mirrors => :index - permission :edit_repository_mirrors, :repository_mirrors => :edit - permission :create_repository_post_receive_urls, :repository_post_receive_urls => :create - permission :view_repository_post_receive_urls, :repository_post_receive_urls => :index - permission :edit_repository_post_receive_urls, :repository_post_receive_urls => :edit - end + settings :default => { + 'httpServer' => 'localhost', + 'httpServerSubdir' => '', + 'gitServer' => 'localhost', + 'gitUser' => 'git', + 'gitConfigPath' => 'gitolite.conf', # Redmine gitolite config file + 'gitConfigHasAdminKey' => 'true', # Conf file should have admin key + 'gitRepositoryBasePath' => 'repositories/', + 'gitRedmineSubdir' => '', + 'gitRepositoryHierarchy' => 'true', + 'gitRecycleBasePath' => 'recycle_bin/', + 'gitRecycleExpireTime' => '24.0', + 'gitLockWaitTime' => '10', + 'gitoliteIdentityFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa', + 'gitoliteIdentityPublicKeyFile' => RAILS_ROOT + '/.ssh/gitolite_admin_id_rsa.pub', + 'allProjectsUseGit' => 'false', + 'gitDaemonDefault' => '1', # Default is Daemon enabled + 'gitHttpDefault' => '1', # Default is HTTP_ONLY + 'gitNotifyCIADefault' => '0', # Default is CIA Notification disabled + 'deleteGitRepositories' => 'false', + 'gitRepositoriesShowUrl' => 'true', + 'gitCacheMaxTime' => '-1', + 'gitCacheMaxElements' => '100', + 'gitCacheMaxSize' => '16', + 'gitHooksDebug' => 'false', + 'gitHooksAreAsynchronous' => 'true', + 'gitTempDataDir' => '/tmp/redmine_git_hosting/', + 'gitScriptDir' => '', + 'gitForceHooksUpdate' => 'true' + }, + :partial => 'redmine_git_hosting' + project_module :repository do + permission :create_repository_mirrors, :repository_mirrors => :create + permission :view_repository_mirrors, :repository_mirrors => :index + permission :edit_repository_mirrors, :repository_mirrors => :edit + permission :create_repository_post_receive_urls, :repository_post_receive_urls => :create + permission :view_repository_post_receive_urls, :repository_post_receive_urls => :index + permission :edit_repository_post_receive_urls, :repository_post_receive_urls => :edit + end end # Set up autoload of patches Dir[File.dirname(__FILE__)+"/lib/git_hosting/patches/*.rb"].each do |patch| - require_dependency 'git_hosting/patches/'+File.basename(patch,".rb") + require_dependency 'git_hosting/patches/'+File.basename(patch,".rb") end # initialize hooks class GitProjectShowHook < Redmine::Hook::ViewListener - render_on :view_projects_show_left, :partial => 'git_urls' + render_on :view_projects_show_left, :partial => 'git_urls' end class GitRepoUrlHook < Redmine::Hook::ViewListener - render_on :view_repositories_show_contextual, :partial => 'git_urls' + render_on :view_repositories_show_contextual, :partial => 'git_urls' end # initialize observer config.after_initialize do - if config.action_controller.perform_caching - ActiveRecord::Base.observers = ActiveRecord::Base.observers << GitHostingObserver - ActiveRecord::Base.observers = ActiveRecord::Base.observers << GitHostingSettingsObserver + if config.action_controller.perform_caching + ActiveRecord::Base.observers = ActiveRecord::Base.observers << GitHostingObserver + ActiveRecord::Base.observers = ActiveRecord::Base.observers << GitHostingSettingsObserver - ActionController::Dispatcher.to_prepare(:git_hosting_observer_reload) do - GitHostingObserver.instance.reload_this_observer - end - ActionController::Dispatcher.to_prepare(:git_hosting_settings_observer_reload) do - GitHostingSettingsObserver.instance.reload_this_observer - end + ActionController::Dispatcher.to_prepare(:git_hosting_observer_reload) do + GitHostingObserver.instance.reload_this_observer end + ActionController::Dispatcher.to_prepare(:git_hosting_settings_observer_reload) do + GitHostingSettingsObserver.instance.reload_this_observer + end + end end diff --git a/lib/git_adapter_hooks.rb b/lib/git_adapter_hooks.rb index bc39840cb..f6db8a6ca 100755 --- a/lib/git_adapter_hooks.rb +++ b/lib/git_adapter_hooks.rb @@ -2,225 +2,225 @@ require_dependency 'redmine/scm/adapters/git_adapter' module GitHosting - class GitAdapterHooks - - @@check_hooks_installed_stamp = nil - @@check_hooks_installed_cached = nil - @@post_receive_hook_path = nil - def self.check_hooks_installed - if not @@check_hooks_installed_cached.nil? and (Time.new - @@check_hooks_installed_stamp <= 0.5) - return @@check_hooks_installed_cached + class GitAdapterHooks + + @@check_hooks_installed_stamp = nil + @@check_hooks_installed_cached = nil + @@post_receive_hook_path = nil + def self.check_hooks_installed + if not @@check_hooks_installed_cached.nil? and (Time.new - @@check_hooks_installed_stamp <= 0.5) + return @@check_hooks_installed_cached + end + + @@post_receive_hook_path ||= File.join(gitolite_hooks_dir, 'post-receive') + post_receive_exists = (%x[#{GitHosting.git_user_runner} test -r '#{@@post_receive_hook_path}' && echo 'yes' || echo 'no']).match(/yes/) + post_receive_length_is_zero = false + if post_receive_exists + post_receive_length_is_zero= "0" == (%x[echo 'wc -c #{@@post_receive_hook_path}' | #{GitHosting.git_user_runner} "bash" ]).chomp.strip.split(/[\t ]+/)[0] + end + + if (!post_receive_exists) || post_receive_length_is_zero + begin + logger.info "\"post-receive\" not handled by gitolite, installing it..." + install_hook("post-receive.redmine_gitolite.rb") + logger.info "\"post-receive.redmine_gitolite\ installed" + logger.info "Running \"gl-setup\" on the gitolite install..." + GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] + update_global_hook_params + logger.info "Finished installing hooks in the gitolite install..." + @@check_hooks_installed_stamp = Time.new + @@check_hooks_installed_cached = true + rescue + logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" + end + return @@check_hooks_installed_cached + else + contents = %x[#{GitHosting.git_user_runner} 'cat #{@@post_receive_hook_path}'] + digest = Digest::MD5.hexdigest(contents) + + logger.debug "Installed hook digest: #{digest}" + if rgh_hook_digest == digest + logger.info "Our hook is already installed" + @@check_hooks_installed_stamp = Time.new + @@check_hooks_installed_cached = true + return @@check_hooks_installed_cached + else + error_msg = "\"post-receive\" is already present but it's not ours!" + logger.warn error_msg + @@check_hooks_installed_cached = error_msg + if Setting.plugin_redmine_git_hosting['gitForceHooksUpdate']!='false' + begin + logger.info "Restoring \"post-receive\" hook since forceInstallHook==true" + install_hook("post-receive.redmine_gitolite.rb") + logger.info "\"post-receive.redmine_gitolite\ installed" + logger.info "Running \"gl-setup\" on the gitolite install..." + GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] + update_global_hook_params + logger.info "Finished installing hooks in the gitolite install..." + @@check_hooks_installed_cached = true + rescue + logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" end + end + @@check_hooks_installed_stamp = Time.new + return @@check_hooks_installed_cached + end + end + end - @@post_receive_hook_path ||= File.join(gitolite_hooks_dir, 'post-receive') - post_receive_exists = (%x[#{GitHosting.git_user_runner} test -r '#{@@post_receive_hook_path}' && echo 'yes' || echo 'no']).match(/yes/) - post_receive_length_is_zero = false - if post_receive_exists - post_receive_length_is_zero= "0" == (%x[echo 'wc -c #{@@post_receive_hook_path}' | #{GitHosting.git_user_runner} "bash" ]).chomp.strip.split(/[\t ]+/)[0] - end + def self.setup_hooks(projects=nil) + check_hooks_installed - if (!post_receive_exists) || post_receive_length_is_zero - begin - logger.info "\"post-receive\" not handled by gitolite, installing it..." - install_hook("post-receive.redmine_gitolite.rb") - logger.info "\"post-receive.redmine_gitolite\ installed" - logger.info "Running \"gl-setup\" on the gitolite install..." - GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] - update_global_hook_params - logger.info "Finished installing hooks in the gitolite install..." - @@check_hooks_installed_stamp = Time.new - @@check_hooks_installed_cached = true - rescue - logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" - end - return @@check_hooks_installed_cached - else - contents = %x[#{GitHosting.git_user_runner} 'cat #{@@post_receive_hook_path}'] - digest = Digest::MD5.hexdigest(contents) - - logger.debug "Installed hook digest: #{digest}" - if rgh_hook_digest == digest - logger.info "Our hook is already installed" - @@check_hooks_installed_stamp = Time.new - @@check_hooks_installed_cached = true - return @@check_hooks_installed_cached - else - error_msg = "\"post-receive\" is already present but it's not ours!" - logger.warn error_msg - @@check_hooks_installed_cached = error_msg - if Setting.plugin_redmine_git_hosting['gitForceHooksUpdate']!='false' - begin - logger.info "Restoring \"post-receive\" hook since forceInstallHook==true" - install_hook("post-receive.redmine_gitolite.rb") - logger.info "\"post-receive.redmine_gitolite\ installed" - logger.info "Running \"gl-setup\" on the gitolite install..." - GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] - update_global_hook_params - logger.info "Finished installing hooks in the gitolite install..." - @@check_hooks_installed_cached = true - rescue - logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" - end - end - @@check_hooks_installed_stamp = Time.new - return @@check_hooks_installed_cached - end - end + if projects.nil? + projects = Project.visible.find(:all).select{|p| p.repository.is_a?(Repository::Git)} + elsif projects.instance_of? Project + projects = [projects] + end + setup_hooks_params(projects) + end + + def self.setup_hooks_params(projects=[]) + return if projects.empty? + + update_global_hook_params + + local_config_map = get_local_config_map + projects.each do |project| + setup_hooks_for_project(project,local_config_map[GitHosting.repository_path(project)]) + end + end + + @@hook_url = nil + def self.update_global_hook_params + cur_values = get_global_config_params + begin + @@hook_url ||= "http://" + File.join(GitHosting.my_root_url,"/githooks/post-receive") + if cur_values["hooks.redmine_gitolite.url"] != @@hook_url + logger.warn "Updating Hook URL: #{@@hook_url}" + GitHosting.shell %[#{GitHosting.git_exec} config --global hooks.redmine_gitolite.url "#{@@hook_url}"] end - def self.setup_hooks(projects=nil) - check_hooks_installed + debug_hook = Setting.plugin_redmine_git_hosting['gitHooksDebug'] + if cur_values["hooks.redmine_gitolite.debug"] != debug_hook + logger.warn "Updating Debug Hook: #{debug_hook}" + GitHosting.shell %[#{GitHosting.git_exec} config --global --bool hooks.redmine_gitolite.debug "#{debug_hook}"] + end - if projects.nil? - projects = Project.visible.find(:all).select{|p| p.repository.is_a?(Repository::Git)} - elsif projects.instance_of? Project - projects = [projects] - end - setup_hooks_params(projects) + asynch_hook = Setting.plugin_redmine_git_hosting['gitHooksAreAsynchronous'] + if cur_values["hooks.redmine_gitolite.asynch"] != asynch_hook + logger.warn "Updating Hooks Are Asynchronous: #{asynch_hook}" + GitHosting.shell %[#{GitHosting.git_exec} config --global --bool hooks.redmine_gitolite.asynch "#{asynch_hook}"] end + rescue + logger.error "update_global_hook_params(): Problems updating hook parameters!" + end + end - def self.setup_hooks_params(projects=[]) - return if projects.empty? + private - update_global_hook_params + def self.logger + return GitHosting::logger + end - local_config_map = get_local_config_map - projects.each do |project| - setup_hooks_for_project(project,local_config_map[GitHosting.repository_path(project)]) - end - end - - @@hook_url = nil - def self.update_global_hook_params - cur_values = get_global_config_params - begin - @@hook_url ||= "http://" + File.join(GitHosting.my_root_url,"/githooks/post-receive") - if cur_values["hooks.redmine_gitolite.url"] != @@hook_url - logger.warn "Updating Hook URL: #{@@hook_url}" - GitHosting.shell %[#{GitHosting.git_exec} config --global hooks.redmine_gitolite.url "#{@@hook_url}"] - end - - debug_hook = Setting.plugin_redmine_git_hosting['gitHooksDebug'] - if cur_values["hooks.redmine_gitolite.debug"] != debug_hook - logger.warn "Updating Debug Hook: #{debug_hook}" - GitHosting.shell %[#{GitHosting.git_exec} config --global --bool hooks.redmine_gitolite.debug "#{debug_hook}"] - end - - asynch_hook = Setting.plugin_redmine_git_hosting['gitHooksAreAsynchronous'] - if cur_values["hooks.redmine_gitolite.asynch"] != asynch_hook - logger.warn "Updating Hooks Are Asynchronous: #{asynch_hook}" - GitHosting.shell %[#{GitHosting.git_exec} config --global --bool hooks.redmine_gitolite.asynch "#{asynch_hook}"] - end - rescue - logger.error "update_global_hook_params(): Problems updating hook parameters!" - end - end + def self.gitolite_hooks_dir + return '~/.gitolite/hooks/common' + end - private + @@cached_hooks_dir = nil + def self.package_hooks_dir + @@cached_hooks_dir ||= File.join(File.dirname(File.dirname(__FILE__)), 'contrib', 'hooks') + end - def self.logger - return GitHosting::logger - end + @@cached_hook_digest = nil + def self.rgh_hook_digest(recreate=false) + if @@cached_hook_digest.nil? || recreate + logger.info "Creating MD5 digests for Redmine Git Hosting hook" + hook_file = "post-receive.redmine_gitolite.rb" + digest = Digest::MD5.hexdigest(File.read(File.join(package_hooks_dir, hook_file))) + logger.info "Digest for #{hook_file}: #{digest}" + @@cached_hook_digest = digest + end + @@cached_hook_digest + end - def self.gitolite_hooks_dir - return '~/.gitolite/hooks/common' - end + def self.install_hook(hook_name) + begin + hook_source_path = File.join(package_hooks_dir, hook_name) + hook_dest_path = File.join(gitolite_hooks_dir, hook_name.split('.')[0]) + logger.info "Installing \"#{hook_name}\" from #{hook_source_path} to #{hook_dest_path}" + git_user = Setting.plugin_redmine_git_hosting['gitUser'] + GitHosting.shell %[ cat #{hook_source_path} | #{GitHosting.git_user_runner} 'cat - > #{hook_dest_path}'] + GitHosting.shell %[#{GitHosting.git_user_runner} 'chown #{git_user} #{hook_dest_path}'] + GitHosting.shell %[#{GitHosting.git_user_runner} 'chmod 700 #{hook_dest_path}'] + rescue + logger.error "install_hook(): Problems installing hook from #{hook_source_path} to #{hook_dest_path}." + end + end - @@cached_hooks_dir = nil - def self.package_hooks_dir - @@cached_hooks_dir ||= File.join(File.dirname(File.dirname(__FILE__)), 'contrib', 'hooks') + # Return a hash with all of the local config parameters for all existing repositories. We do this with a single sudo call to find. + def self.get_local_config_map + local_config_map=Hash.new{|hash, key| hash[key] = {}} # default -- empty hash + + lines = %x[#{GitHosting.git_user_runner} 'find #{GitHosting.repository_base} -type d -name "*.git" -prune -print -exec git config -f {}/config --get-regexp hooks.redmine_gitolite \\;'].chomp.split("\n") + filesplit = /(\.\/)*(#{GitHosting.repository_base}.*?[^\/]+\.git)/ + cur_repo_path = nil + lines.each do |nextline| + if filesplit =~ nextline + cur_repo_path = $2 + elsif cur_repo_path + pair = nextline.split(' ') + local_config_map[cur_repo_path][pair[0]] = pair[1] end + end + local_config_map + end - @@cached_hook_digest = nil - def self.rgh_hook_digest(recreate=false) - if @@cached_hook_digest.nil? || recreate - logger.info "Creating MD5 digests for Redmine Git Hosting hook" - hook_file = "post-receive.redmine_gitolite.rb" - digest = Digest::MD5.hexdigest(File.read(File.join(package_hooks_dir, hook_file))) - logger.info "Digest for #{hook_file}: #{digest}" - @@cached_hook_digest = digest - end - @@cached_hook_digest - end + # Return a hash with local config parameters for a single project + def self.get_local_config_params(project) + value_hash = {} + repo_path = GitHosting.repository_path(project) + params = %x[#{GitHosting.git_exec} config -f '#{repo_path}/config' --get-regexp hooks.redmine_gitolite].split("\n").each do |valuepair| + pair=valuepair.split(' ') + value_hash[pair[0]]=pair[1] + end + value_hash + end - def self.install_hook(hook_name) - begin - hook_source_path = File.join(package_hooks_dir, hook_name) - hook_dest_path = File.join(gitolite_hooks_dir, hook_name.split('.')[0]) - logger.info "Installing \"#{hook_name}\" from #{hook_source_path} to #{hook_dest_path}" - git_user = Setting.plugin_redmine_git_hosting['gitUser'] - GitHosting.shell %[ cat #{hook_source_path} | #{GitHosting.git_user_runner} 'cat - > #{hook_dest_path}'] - GitHosting.shell %[#{GitHosting.git_user_runner} 'chown #{git_user} #{hook_dest_path}'] - GitHosting.shell %[#{GitHosting.git_user_runner} 'chmod 700 #{hook_dest_path}'] - rescue - logger.error "install_hook(): Problems installing hook from #{hook_source_path} to #{hook_dest_path}." - end - end + # Return a hash with global config parameters. + def self.get_global_config_params + value_hash = {} + params = %x[#{GitHosting.git_exec} config -f '.gitconfig' --get-regexp hooks.redmine_gitolite].split("\n").each do |valuepair| + pair=valuepair.split(' ') + value_hash[pair[0]]=pair[1] + end + value_hash + end - # Return a hash with all of the local config parameters for all existing repositories. We do this with a single sudo call to find. - def self.get_local_config_map - local_config_map=Hash.new{|hash, key| hash[key] = {}} # default -- empty hash - - lines = %x[#{GitHosting.git_user_runner} 'find #{GitHosting.repository_base} -type d -name "*.git" -prune -print -exec git config -f {}/config --get-regexp hooks.redmine_gitolite \\;'].chomp.split("\n") - filesplit = /(\.\/)*(#{GitHosting.repository_base}.*?[^\/]+\.git)/ - cur_repo_path = nil - lines.each do |nextline| - if filesplit =~ nextline - cur_repo_path = $2 - elsif cur_repo_path - pair = nextline.split(' ') - local_config_map[cur_repo_path][pair[0]] = pair[1] - end - end - local_config_map - end - - # Return a hash with local config parameters for a single project - def self.get_local_config_params(project) - value_hash = {} - repo_path = GitHosting.repository_path(project) - params = %x[#{GitHosting.git_exec} config -f '#{repo_path}/config' --get-regexp hooks.redmine_gitolite].split("\n").each do |valuepair| - pair=valuepair.split(' ') - value_hash[pair[0]]=pair[1] - end - value_hash - end - - # Return a hash with global config parameters. - def self.get_global_config_params - value_hash = {} - params = %x[#{GitHosting.git_exec} config -f '.gitconfig' --get-regexp hooks.redmine_gitolite].split("\n").each do |valuepair| - pair=valuepair.split(' ') - value_hash[pair[0]]=pair[1] - end - value_hash - end - - def self.setup_hooks_for_project(project,value_hash=nil) - if project.repository.nil? - logger.error "Cannot setup hooks for project #{project.identifier}. Repository is not yet created." - return - end - - # if no value_hash given, go fetch - value_hash = get_local_config_params(project) if value_hash.nil? - hook_key = project.repository.extra.key - if value_hash["hooks.redmine_gitolite.key"] != hook_key || value_hash["hooks.redmine_gitolite.projectid"] != project.identifier - if value_hash["hooks.redmine_gitolite.key"] - logger.info "Repairing hooks for project '#{project.identifier}' (in gitolite repository at '#{GitHosting.repository_path(project)}')" - else - logger.info "Setting up hooks for project '#{project.identifier}' (in gitolite repository at '#{GitHosting.repository_path(project)}')" - end - - begin - repo_path = GitHosting.repository_path(project) - GitHosting.shell %[#{GitHosting.git_exec} --git-dir='#{repo_path}' config hooks.redmine_gitolite.key "#{hook_key}"] - GitHosting.shell %[#{GitHosting.git_exec} --git-dir='#{repo_path}' config hooks.redmine_gitolite.projectid "#{project.identifier}"] - rescue - logger.error "setup_hooks_for_project(#{project.id}) failed!" - end - end + def self.setup_hooks_for_project(project,value_hash=nil) + if project.repository.nil? + logger.error "Cannot setup hooks for project #{project.identifier}. Repository is not yet created." + return + end + + # if no value_hash given, go fetch + value_hash = get_local_config_params(project) if value_hash.nil? + hook_key = project.repository.extra.key + if value_hash["hooks.redmine_gitolite.key"] != hook_key || value_hash["hooks.redmine_gitolite.projectid"] != project.identifier + if value_hash["hooks.redmine_gitolite.key"] + logger.info "Repairing hooks for project '#{project.identifier}' (in gitolite repository at '#{GitHosting.repository_path(project)}')" + else + logger.info "Setting up hooks for project '#{project.identifier}' (in gitolite repository at '#{GitHosting.repository_path(project)}')" end + begin + repo_path = GitHosting.repository_path(project) + GitHosting.shell %[#{GitHosting.git_exec} --git-dir='#{repo_path}' config hooks.redmine_gitolite.key "#{hook_key}"] + GitHosting.shell %[#{GitHosting.git_exec} --git-dir='#{repo_path}' config hooks.redmine_gitolite.projectid "#{project.identifier}"] + rescue + logger.error "setup_hooks_for_project(#{project.id}) failed!" + end + end end + + end end diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index 5c52e3c55..27801f739 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -10,1104 +10,1103 @@ module GitHosting - LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) - REPOSITORY_IF_UNDEF = "repositories/" # In case settings not migrated (normally from settings) - REDMINE_SUBDIR = "" # In case settings not migrated (normally from settings) - REDMINE_HIERARCHICAL = "true" # In case settings not migrated (normally from settings) - HTTP_SERVER_SUBDIR = "" # In case settings not migrated (normally from settings) - TEMP_DATA_DIR = "/tmp/redmine_git_hosting" # In case settings not migrated (normally from settings) - SCRIPT_DIR = "" # In case settings not migrated (normally from settings) - SCRIPT_PARENT = "bin" - - # Used to register errors when pulling and pushing the conf file - class GitHostingException < StandardError + LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) + REPOSITORY_IF_UNDEF = "repositories/" # In case settings not migrated (normally from settings) + REDMINE_SUBDIR = "" # In case settings not migrated (normally from settings) + REDMINE_HIERARCHICAL = "true" # In case settings not migrated (normally from settings) + HTTP_SERVER_SUBDIR = "" # In case settings not migrated (normally from settings) + TEMP_DATA_DIR = "/tmp/redmine_git_hosting" # In case settings not migrated (normally from settings) + SCRIPT_DIR = "" # In case settings not migrated (normally from settings) + SCRIPT_PARENT = "bin" + + # Used to register errors when pulling and pushing the conf file + class GitHostingException < StandardError + end + + # Time in seconds to wait before giving up on acquiring the lock + def self.lock_wait_time + Setting.plugin_redmine_git_hosting['gitLockWaitTime'].to_i || LOCK_WAIT_IF_UNDEF + end + + # Configuration file (relative to git conf directory) + def self.gitolite_conf + GitoliteConfig.gitolite_conf + end + + # Repository base path (relative to git user home directory) + def self.repository_base + Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] || REPOSITORY_IF_UNDEF + end + + # Redmine subdirectory path (relative to Repository base path + def self.repository_redmine_subdir + Setting.plugin_redmine_git_hosting['gitRedmineSubdir'] || REDMINE_SUBDIR + end + + # Redmine repositories in hierarchy + def self.repository_hierarchy + (Setting.plugin_redmine_git_hosting['gitRepositoryHierarchy'] || REDMINE_HIERARCHICAL) != "false" + end + + def self.http_server_subdir + Setting.plugin_redmine_git_hosting['httpServerSubdir'] || HTTP_SERVER_SUBDIR + end + + # This is the file portion of the url used when talking through ssh to the repository. + def self.git_access_url project + return "#{repository_name(project)}" + end + + # This is the relative portion of the url (below the rails_root) used when talking through httpd to the repository + # Note that this differs from the git_access_url in not including 'repository_redmine_subdir' as part of the path. + def self.http_access_url project + return "#{http_server_subdir}#{redmine_name(project)}" + end + + # Server path (minus protocol) + def self.my_root_url + # Remove any path from httpServer in case they are leftover from previous installations. + # No trailing /. + my_root_path = Redmine::Utils::relative_url_root + File.join(Setting.plugin_redmine_git_hosting['httpServer'][/^[^\/]*/],my_root_path,"/")[0..-2] + end + + @@logger = nil + def self.logger + @@logger ||= MyLogger.new + end + + @@web_user = nil + def self.web_user + if @@web_user.nil? + @@web_user = (%x[whoami]).chomp.strip end - - # Time in seconds to wait before giving up on acquiring the lock - def self.lock_wait_time - Setting.plugin_redmine_git_hosting['gitLockWaitTime'].to_i || LOCK_WAIT_IF_UNDEF + return @@web_user + end + + def self.web_user=(setuser) + @@web_user = setuser + end + + def self.git_user + Setting.plugin_redmine_git_hosting['gitUser'] + end + + @@mirror_pubkey = nil + def self.mirror_push_public_key + if @@mirror_pubkey.nil? + + %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityFile']}' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/gitolite_admin_id_rsa ' ] + %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/gitolite_admin_id_rsa.pub ' ] + %x[ #{GitHosting.git_user_runner} 'chmod 600 ~/.ssh/gitolite_admin_id_rsa' ] + %x[ #{GitHosting.git_user_runner} 'chmod 644 ~/.ssh/gitolite_admin_id_rsa.pub' ] + + pubk = ( %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}' ] ).chomp.strip + git_user_dir = ( %x[ #{GitHosting.git_user_runner} "cd ~ ; pwd" ] ).chomp.strip + %x[ #{GitHosting.git_user_runner} 'echo "#{pubk}" > ~/.ssh/gitolite_admin_id_rsa.pub ' ] + %x[ echo '#!/bin/sh' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/run_gitolite_admin_ssh'] + %x[ echo 'exec ssh -o BatchMode=yes -o StrictHostKeyChecking=no -i #{git_user_dir}/.ssh/gitolite_admin_id_rsa "$@"' | #{GitHosting.git_user_runner} "cat >> ~/.ssh/run_gitolite_admin_ssh" ] + %x[ #{GitHosting.git_user_runner} 'chmod 644 ~/.ssh/gitolite_admin_id_rsa.pub' ] + %x[ #{GitHosting.git_user_runner} 'chmod 600 ~/.ssh/gitolite_admin_id_rsa'] + %x[ #{GitHosting.git_user_runner} 'chmod 700 ~/.ssh/run_gitolite_admin_ssh'] + + @@mirror_pubkey = pubk.split(/[\t ]+/)[0].to_s + " " + pubk.split(/[\t ]+/)[1].to_s + + #settings = Setting["plugin_redmine_git_hosting"] + #settings["gitMirrorPushPublicKey"] = publicKey + #Setting["plugin_redmine_git_hosting"] = settings end + @@mirror_pubkey + end - # Configuration file (relative to git conf directory) - def self.gitolite_conf - GitoliteConfig.gitolite_conf - end - # Repository base path (relative to git user home directory) - def self.repository_base - Setting.plugin_redmine_git_hosting['gitRepositoryBasePath'] || REPOSITORY_IF_UNDEF + @@sudo_git_to_web_user_stamp = nil + @@sudo_git_to_web_user_cached = nil + def self.sudo_git_to_web_user + if not @@sudo_git_to_web_user_cached.nil? and (Time.new - @@sudo_git_to_web_user_stamp <= 0.5) + return @@sudo_git_to_web_user_cached end - - # Redmine subdirectory path (relative to Repository base path - def self.repository_redmine_subdir - Setting.plugin_redmine_git_hosting['gitRedmineSubdir'] || REDMINE_SUBDIR + logger.info "Testing if git user(\"#{git_user}\") can sudo to web user(\"#{web_user}\")" + if git_user == web_user + @@sudo_git_to_web_user_cached = true + @@sudo_git_to_web_user_stamp = Time.new + return @@sudo_git_to_web_user_cached end - - # Redmine repositories in hierarchy - def self.repository_hierarchy - (Setting.plugin_redmine_git_hosting['gitRepositoryHierarchy'] || REDMINE_HIERARCHICAL) != "false" + test = %x[#{GitHosting.git_user_runner} sudo -nu #{web_user} echo "yes" ] + if test.match(/yes/) + @@sudo_git_to_web_user_cached = true + @@sudo_git_to_web_user_stamp = Time.new + return @@sudo_git_to_web_user_cached end - - def self.http_server_subdir - Setting.plugin_redmine_git_hosting['httpServerSubdir'] || HTTP_SERVER_SUBDIR + logger.warn "Error while testing sudo_git_to_web_user: #{test}" + @@sudo_git_to_web_user_cached = test + @@sudo_git_to_web_user_stamp = Time.new + return @@sudo_git_to_web_user_cached + end + + @@sudo_web_to_git_user_stamp = nil + @@sudo_web_to_git_user_cached = nil + def self.sudo_web_to_git_user + if not @@sudo_web_to_git_user_cached.nil? and (Time.new - @@sudo_web_to_git_user_stamp <= 0.5) + return @@sudo_web_to_git_user_cached end - - # This is the file portion of the url used when talking through ssh to the repository. - def self.git_access_url project - return "#{repository_name(project)}" + logger.info "Testing if web user(\"#{web_user}\") can sudo to git user(\"#{git_user}\")" + if git_user == web_user + @@sudo_web_to_git_user_cached = true + @@sudo_web_to_git_user_stamp = Time.new + return @@sudo_web_to_git_user_cached end - - # This is the relative portion of the url (below the rails_root) used when talking through httpd to the repository - # Note that this differs from the git_access_url in not including 'repository_redmine_subdir' as part of the path. - def self.http_access_url project - return "#{http_server_subdir}#{redmine_name(project)}" + test = %x[#{GitHosting.git_user_runner} echo "yes"] + if test.match(/yes/) + @@sudo_web_to_git_user_cached = true + @@sudo_web_to_git_user_stamp = Time.new + return @@sudo_web_to_git_user_cached end - - # Server path (minus protocol) - def self.my_root_url - # Remove any path from httpServer in case they are leftover from previous installations. - # No trailing /. - my_root_path = Redmine::Utils::relative_url_root - File.join(Setting.plugin_redmine_git_hosting['httpServer'][/^[^\/]*/],my_root_path,"/")[0..-2] + logger.warn "Error while testing sudo_web_to_git_user: #{test}" + @@sudo_web_to_git_user_cached = test + @@sudo_web_to_git_user_stamp = Time.new + return @@sudo_web_to_git_user_cached + end + + def self.get_full_parent_path(project, is_file_path) + return "" if !project.parent || !repository_hierarchy + parent_parts = []; + p = project + while p.parent + parent_id = p.parent.identifier.to_s + parent_parts.unshift(parent_id) + p = p.parent end - - @@logger = nil - def self.logger - @@logger ||= MyLogger.new + return is_file_path ? File.join(parent_parts) : parent_parts.join("/") + end + + def self.redmine_name(project) + return File.expand_path(File.join("./",get_full_parent_path(project, false),project.identifier),"/")[1..-1] + end + def self.repository_name(project) + return File.expand_path(File.join("./",repository_redmine_subdir,get_full_parent_path(project, false),project.identifier),"/")[1..-1] + end + def self.repository_path projectId + repo_name = projectId.is_a?(String) ? projectId : repository_name(projectId) + return File.join(repository_base, repo_name) + ".git" + end + + @@git_hosting_tmp_dir = nil + @@previous_git_tmp_dir = nil + def self.get_tmp_dir + tmp_dir = (Setting.plugin_redmine_git_hosting['gitTempDataDir'] || TEMP_DATA_DIR) + if (@@previous_git_tmp_dir != tmp_dir) + @@previous_git_tmp_dir = tmp_dir + @@git_hosting_tmp_dir = File.join(tmp_dir,git_user) + "/" end - - @@web_user = nil - def self.web_user - if @@web_user.nil? - @@web_user = (%x[whoami]).chomp.strip - end - return @@web_user + if !File.directory?(@@git_hosting_tmp_dir) + %x[mkdir -p "#{@@git_hosting_tmp_dir}"] + %x[chmod 700 "#{@@git_hosting_tmp_dir}"] + %x[chown #{web_user} "#{@@git_hosting_tmp_dir}"] end - - def self.web_user=(setuser) - @@web_user = setuser + return @@git_hosting_tmp_dir + end + + @@git_hosting_bin_dir = nil + @@previous_git_script_dir = nil + def self.get_bin_dir + script_dir = Setting.plugin_redmine_git_hosting['gitScriptDir'] || SCRIPT_DIR + if @@previous_git_script_dir != script_dir + @@previous_git_script_dir = script_dir + @@git_bin_dir_writeable = nil + + # Directory for binaries includes 'SCRIPT_PARENT' at the end. + # Further, absolute path adds additional 'git_user' component for multi-gitolite installations. + if script_dir[0,1] == "/" + @@git_hosting_bin_dir = File.join(script_dir,git_user,SCRIPT_PARENT) + "/" + else + @@git_hosting_bin_dir = Rails.root.join("vendor/plugins/redmine_git_hosting",script_dir,SCRIPT_PARENT).to_s+"/" + end end - - def self.git_user - Setting.plugin_redmine_git_hosting['gitUser'] + if !File.directory?(@@git_hosting_bin_dir) + logger.info "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" + %x[mkdir -p "#{@@git_hosting_bin_dir}"] + %x[chmod 750 "#{@@git_hosting_bin_dir}"] + %x[chown #{web_user} "#{@@git_hosting_bin_dir}"] + + if !File.directory?(@@git_hosting_bin_dir) + logger.error "Cannot create bin directory: #{@@git_hosting_bin_dir}" + end end - - @@mirror_pubkey = nil - def self.mirror_push_public_key - if @@mirror_pubkey.nil? - - %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityFile']}' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/gitolite_admin_id_rsa ' ] - %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/gitolite_admin_id_rsa.pub ' ] - %x[ #{GitHosting.git_user_runner} 'chmod 600 ~/.ssh/gitolite_admin_id_rsa' ] - %x[ #{GitHosting.git_user_runner} 'chmod 644 ~/.ssh/gitolite_admin_id_rsa.pub' ] - - pubk = ( %x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}' ] ).chomp.strip - git_user_dir = ( %x[ #{GitHosting.git_user_runner} "cd ~ ; pwd" ] ).chomp.strip - %x[ #{GitHosting.git_user_runner} 'echo "#{pubk}" > ~/.ssh/gitolite_admin_id_rsa.pub ' ] - %x[ echo '#!/bin/sh' | #{GitHosting.git_user_runner} 'cat > ~/.ssh/run_gitolite_admin_ssh'] - %x[ echo 'exec ssh -o BatchMode=yes -o StrictHostKeyChecking=no -i #{git_user_dir}/.ssh/gitolite_admin_id_rsa "$@"' | #{GitHosting.git_user_runner} "cat >> ~/.ssh/run_gitolite_admin_ssh" ] - %x[ #{GitHosting.git_user_runner} 'chmod 644 ~/.ssh/gitolite_admin_id_rsa.pub' ] - %x[ #{GitHosting.git_user_runner} 'chmod 600 ~/.ssh/gitolite_admin_id_rsa'] - %x[ #{GitHosting.git_user_runner} 'chmod 700 ~/.ssh/run_gitolite_admin_ssh'] - - @@mirror_pubkey = pubk.split(/[\t ]+/)[0].to_s + " " + pubk.split(/[\t ]+/)[1].to_s - - #settings = Setting["plugin_redmine_git_hosting"] - #settings["gitMirrorPushPublicKey"] = publicKey - #Setting["plugin_redmine_git_hosting"] = settings + return @@git_hosting_bin_dir + end + + @@git_bin_dir_writeable = nil + def self.bin_dir_writeable?(*option) + @@git_bin_dir_writeable = nil if option.length > 0 && option[0] == :reset + if @@git_bin_dir_writeable == nil + mybindir = get_bin_dir + mytestfile = "#{mybindir}/writecheck" + if (!File.directory?(mybindir)) + @@git_bin_dir_writeable = false + else + %x[touch "#{mytestfile}"] + if (!File.exists?("#{mytestfile}")) + @@git_bin_dir_writeable = false + else + %x[rm "#{mytestfile}"] + @@git_bin_dir_writeable = true end - @@mirror_pubkey + end end + @@git_bin_dir_writeable + end + def self.git_exec_path + return File.join(get_bin_dir, "run_git_as_git_user") + end - @@sudo_git_to_web_user_stamp = nil - @@sudo_git_to_web_user_cached = nil - def self.sudo_git_to_web_user - if not @@sudo_git_to_web_user_cached.nil? and (Time.new - @@sudo_git_to_web_user_stamp <= 0.5) - return @@sudo_git_to_web_user_cached - end - logger.info "Testing if git user(\"#{git_user}\") can sudo to web user(\"#{web_user}\")" - if git_user == web_user - @@sudo_git_to_web_user_cached = true - @@sudo_git_to_web_user_stamp = Time.new - return @@sudo_git_to_web_user_cached - end - test = %x[#{GitHosting.git_user_runner} sudo -nu #{web_user} echo "yes" ] - if test.match(/yes/) - @@sudo_git_to_web_user_cached = true - @@sudo_git_to_web_user_stamp = Time.new - return @@sudo_git_to_web_user_cached - end - logger.warn "Error while testing sudo_git_to_web_user: #{test}" - @@sudo_git_to_web_user_cached = test - @@sudo_git_to_web_user_stamp = Time.new - return @@sudo_git_to_web_user_cached - end + def self.gitolite_ssh_path + return File.join(get_bin_dir, "gitolite_admin_ssh") + end + def self.git_user_runner_path + return File.join(get_bin_dir, "run_as_git_user") + end - @@sudo_web_to_git_user_stamp = nil - @@sudo_web_to_git_user_cached = nil - def self.sudo_web_to_git_user - if not @@sudo_web_to_git_user_cached.nil? and (Time.new - @@sudo_web_to_git_user_stamp <= 0.5) - return @@sudo_web_to_git_user_cached - end - logger.info "Testing if web user(\"#{web_user}\") can sudo to git user(\"#{git_user}\")" - if git_user == web_user - @@sudo_web_to_git_user_cached = true - @@sudo_web_to_git_user_stamp = Time.new - return @@sudo_web_to_git_user_cached - end - test = %x[#{GitHosting.git_user_runner} echo "yes"] - if test.match(/yes/) - @@sudo_web_to_git_user_cached = true - @@sudo_web_to_git_user_stamp = Time.new - return @@sudo_web_to_git_user_cached - end - logger.warn "Error while testing sudo_web_to_git_user: #{test}" - @@sudo_web_to_git_user_cached = test - @@sudo_web_to_git_user_stamp = Time.new - return @@sudo_web_to_git_user_cached - end - - def self.get_full_parent_path(project, is_file_path) - return "" if !project.parent || !repository_hierarchy - parent_parts = []; - p = project - while p.parent - parent_id = p.parent.identifier.to_s - parent_parts.unshift(parent_id) - p = p.parent - end - return is_file_path ? File.join(parent_parts) : parent_parts.join("/") - end - def self.redmine_name(project) - return File.expand_path(File.join("./",get_full_parent_path(project, false),project.identifier),"/")[1..-1] + def self.git_exec + if !File.exists?(git_exec_path()) + update_git_exec end - def self.repository_name(project) - return File.expand_path(File.join("./",repository_redmine_subdir,get_full_parent_path(project, false),project.identifier),"/")[1..-1] + return git_exec_path() + end + def self.gitolite_ssh + if !File.exists?(gitolite_ssh_path()) + update_git_exec end - def self.repository_path projectId - repo_name = projectId.is_a?(String) ? projectId : repository_name(projectId) - return File.join(repository_base, repo_name) + ".git" + return gitolite_ssh_path() + end + def self.git_user_runner + if !File.exists?(git_user_runner_path()) + update_git_exec end + return git_user_runner_path() + end - @@git_hosting_tmp_dir = nil - @@previous_git_tmp_dir = nil - def self.get_tmp_dir - tmp_dir = (Setting.plugin_redmine_git_hosting['gitTempDataDir'] || TEMP_DATA_DIR) - if (@@previous_git_tmp_dir != tmp_dir) - @@previous_git_tmp_dir = tmp_dir - @@git_hosting_tmp_dir = File.join(tmp_dir,git_user) + "/" - end - if !File.directory?(@@git_hosting_tmp_dir) - %x[mkdir -p "#{@@git_hosting_tmp_dir}"] - %x[chmod 700 "#{@@git_hosting_tmp_dir}"] - %x[chown #{web_user} "#{@@git_hosting_tmp_dir}"] - end - return @@git_hosting_tmp_dir - end - @@git_hosting_bin_dir = nil - @@previous_git_script_dir = nil - def self.get_bin_dir - script_dir = Setting.plugin_redmine_git_hosting['gitScriptDir'] || SCRIPT_DIR - if @@previous_git_script_dir != script_dir - @@previous_git_script_dir = script_dir - @@git_bin_dir_writeable = nil - - # Directory for binaries includes 'SCRIPT_PARENT' at the end. - # Further, absolute path adds additional 'git_user' component for multi-gitolite installations. - if script_dir[0,1] == "/" - @@git_hosting_bin_dir = File.join(script_dir,git_user,SCRIPT_PARENT) + "/" - else - @@git_hosting_bin_dir = Rails.root.join("vendor/plugins/redmine_git_hosting",script_dir,SCRIPT_PARENT).to_s+"/" - end - end - if !File.directory?(@@git_hosting_bin_dir) - logger.info "Creating bin directory: #{@@git_hosting_bin_dir}, Owner #{web_user}" - %x[mkdir -p "#{@@git_hosting_bin_dir}"] - %x[chmod 750 "#{@@git_hosting_bin_dir}"] - %x[chown #{web_user} "#{@@git_hosting_bin_dir}"] - - if !File.directory?(@@git_hosting_bin_dir) - logger.error "Cannot create bin directory: #{@@git_hosting_bin_dir}" - end - end - return @@git_hosting_bin_dir - end + def self.update_git_exec + logger.info "Setting up #{get_bin_dir}" + gitolite_key=Setting.plugin_redmine_git_hosting['gitoliteIdentityFile'] - @@git_bin_dir_writeable = nil - def self.bin_dir_writeable?(*option) - @@git_bin_dir_writeable = nil if option.length > 0 && option[0] == :reset - if @@git_bin_dir_writeable == nil - mybindir = get_bin_dir - mytestfile = "#{mybindir}/writecheck" - if (!File.directory?(mybindir)) - @@git_bin_dir_writeable = false - else - %x[touch "#{mytestfile}"] - if (!File.exists?("#{mytestfile}")) - @@git_bin_dir_writeable = false - else - %x[rm "#{mytestfile}"] - @@git_bin_dir_writeable = true - end - end - end - @@git_bin_dir_writeable - end + File.open(gitolite_ssh_path(), "w") do |f| + f.puts "#!/bin/sh" + f.puts "exec ssh -o BatchMode=yes -o StrictHostKeyChecking=no -i #{gitolite_key} \"$@\"" + end if !File.exists?(gitolite_ssh_path()) - def self.git_exec_path - return File.join(get_bin_dir, "run_git_as_git_user") + ############################################################################################################################## + # So... older versions of sudo are completely different than newer versions of sudo + # Try running sudo -i [user] 'ls -l' on sudo > 1.7.4 and you get an error that command 'ls -l' doesn't exist + # do it on version < 1.7.3 and it runs just fine. Different levels of escaping are necessary depending on which + # version of sudo you are using... which just completely CRAZY, but I don't know how to avoid it + # + # Note: I don't know whether the switch is at 1.7.3 or 1.7.4, the switch is between ubuntu 10.10 which uses 1.7.2 + # and ubuntu 11.04 which uses 1.7.4. I have tested that the latest 1.8.1p2 seems to have identical behavior to 1.7.4 + ############################################################################################################################## + sudo_version_str=%x[ sudo -V 2>&1 | head -n1 | sed 's/^.* //g' | sed 's/[a-z].*$//g' ] + split_version = sudo_version_str.split(/\./) + sudo_version = 100*100*(split_version[0].to_i) + 100*(split_version[1].to_i) + split_version[2].to_i + sudo_version_switch = (100*100*1) + (100 * 7) + 3 + + File.open(git_exec_path(), "w") do |f| + f.puts '#!/bin/sh' + f.puts "if [ \"\$(whoami)\" = \"#{git_user}\" ] ; then" + f.puts ' cmd=$(printf "\\"%s\\" " "$@")' + f.puts ' cd ~' + f.puts ' eval "git $cmd"' + f.puts "else" + if sudo_version < sudo_version_switch + f.puts ' cmd=$(printf "\\\\\\"%s\\\\\\" " "$@")' + f.puts " sudo -u #{git_user} -i eval \"git $cmd\"" + else + f.puts ' cmd=$(printf "\\"%s\\" " "$@")' + f.puts " sudo -u #{git_user} -i eval \"git $cmd\"" + end + f.puts 'fi' + end if !File.exists?(git_exec_path()) + + # use perl script for git_user_runner so we can + # escape output more easily + File.open(git_user_runner_path(), "w") do |f| + f.puts '#!/usr/bin/perl' + f.puts '' + f.puts 'my $command = join(" ", @ARGV);' + f.puts '' + f.puts 'my $user = `whoami`;' + f.puts 'chomp $user;' + f.puts 'if ($user eq "' + git_user + '")' + f.puts '{' + f.puts ' exec("cd ~ ; $command");' + f.puts '}' + f.puts 'else' + f.puts '{' + f.puts ' $command =~ s/\\\\/\\\\\\\\/g;' + # Previous line turns \; => \\; + # If old sudo, turn \\; => "\\;" to protect ';' from loss as command separator during eval + if sudo_version < sudo_version_switch + f.puts ' $command =~ s/(\\\\\\\\;)/"$1"/g;' + end + f.puts ' $command =~ s/"/\\\\"/g;' + f.puts ' exec("sudo -u ' + git_user + ' -i eval \"$command\"");' + f.puts '}' + end if !File.exists?(git_user_runner_path()) + + File.chmod(0550, git_exec_path()) + File.chmod(0550, gitolite_ssh_path()) + File.chmod(0550, git_user_runner_path()) + %x[chown #{web_user} -R "#{get_bin_dir}"] + end + + @@lock_file = nil + def self.lock(retries) + is_locked = false + if @@lock_file.nil? + @@lock_file=File.new(File.join(get_tmp_dir,'redmine_git_hosting_lock'),File::CREAT|File::RDONLY) end - def self.gitolite_ssh_path - return File.join(get_bin_dir, "gitolite_admin_ssh") - end - def self.git_user_runner_path - return File.join(get_bin_dir, "run_as_git_user") + while retries > 0 + is_locked = @@lock_file.flock(File::LOCK_EX|File::LOCK_NB) + retries-=1 + if (!is_locked) && retries > 0 + sleep 1 + end end + return is_locked + end - - def self.git_exec - if !File.exists?(git_exec_path()) - update_git_exec - end - return git_exec_path() + def self.unlock + if !@@lock_file.nil? + @@lock_file.flock(File::LOCK_UN) end - def self.gitolite_ssh - if !File.exists?(gitolite_ssh_path()) - update_git_exec - end - return gitolite_ssh_path() + end + + def self.shell(command) + begin + my_command = "#{command} 2>&1" + result = %x[#{my_command}].chomp + code = $?.exitstatus + rescue Exception => e + result=e.message + code = -1 end - def self.git_user_runner - if !File.exists?(git_user_runner_path()) - update_git_exec + if code != 0 + logger.error "Command failed (return #{code}): #{command}" + message = " "+result.split("\n").join("\n ") + logger.error message + raise GitHostingException, "Shell Error" + end + end + + # Try to get a cloned version of gitolite-admin repository. + # + # This code tries to recover from a variety of errors which have been observed + # in the field, including a loss of the admin key and an empty top-level directory + # + # Return: false => have uncommitted changes + # true => directory on master + # + # This routine must only be called after acquisition of the lock + # + # John Kubiatowicz, 11/15/11 + # + # This routine will no-longer merge in changes, since this can cause weird behavior + # when interacting with cron-jobs that clean up /tmp. + # + # John Kubiatowicz, 04/23/12 + # + def self.clone_or_pull_gitolite_admin(resync_all_flag) + # clone/pull from admin repo + repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) + + # If preexisting directory exists, try to clone and merge.... + if (File.exists? "#{repo_dir}") && (File.exists? "#{repo_dir}/.git") && (File.exists? "#{repo_dir}/keydir") && (File.exists? "#{repo_dir}/conf") + begin + logger.info "Fetching changes from gitolite-admin repository to #{repo_dir}" + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' fetch] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' merge FETCH_HEAD] + + # unmerged changes=> non-empty return + return_val = %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' status --short].empty? + + if (return_val) + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup + GitAdapterHooks.check_hooks_installed + return return_val + else + # The attempt to merge can cause a weird failure mode when interacting with cron jobs that clean out old + # files in /tmp. The issue is that keys in the keydir can go idle and get deleted. Then, when we merge we + # create an admin repo minus those keys (including the admin key!). Only a RESYNC_ALL operation will + # actually fix. Thus, we never return "have uncommitted changes", but instead fail the merge and reclone. + # + # 04/23/12 + # --KUBI-- + logger.error "Seems to be unmerged changes! Going to delete and reclone for safety." + logger.error "May need to execute RESYNC_ALL to fix whatever caused pending changes." unless resync_all_flag end - return git_user_runner_path() + rescue + logger.error "Repository fetch and merge failed -- trying to delete and reclone repository." + end end - - def self.update_git_exec - logger.info "Setting up #{get_bin_dir}" - gitolite_key=Setting.plugin_redmine_git_hosting['gitoliteIdentityFile'] - - File.open(gitolite_ssh_path(), "w") do |f| - f.puts "#!/bin/sh" - f.puts "exec ssh -o BatchMode=yes -o StrictHostKeyChecking=no -i #{gitolite_key} \"$@\"" - end if !File.exists?(gitolite_ssh_path()) - - ############################################################################################################################## - # So... older versions of sudo are completely different than newer versions of sudo - # Try running sudo -i [user] 'ls -l' on sudo > 1.7.4 and you get an error that command 'ls -l' doesn't exist - # do it on version < 1.7.3 and it runs just fine. Different levels of escaping are necessary depending on which - # version of sudo you are using... which just completely CRAZY, but I don't know how to avoid it - # - # Note: I don't know whether the switch is at 1.7.3 or 1.7.4, the switch is between ubuntu 10.10 which uses 1.7.2 - # and ubuntu 11.04 which uses 1.7.4. I have tested that the latest 1.8.1p2 seems to have identical behavior to 1.7.4 - ############################################################################################################################## - sudo_version_str=%x[ sudo -V 2>&1 | head -n1 | sed 's/^.* //g' | sed 's/[a-z].*$//g' ] - split_version = sudo_version_str.split(/\./) - sudo_version = 100*100*(split_version[0].to_i) + 100*(split_version[1].to_i) + split_version[2].to_i - sudo_version_switch = (100*100*1) + (100 * 7) + 3 - - File.open(git_exec_path(), "w") do |f| - f.puts '#!/bin/sh' - f.puts "if [ \"\$(whoami)\" = \"#{git_user}\" ] ; then" - f.puts ' cmd=$(printf "\\"%s\\" " "$@")' - f.puts ' cd ~' - f.puts ' eval "git $cmd"' - f.puts "else" - if sudo_version < sudo_version_switch - f.puts ' cmd=$(printf "\\\\\\"%s\\\\\\" " "$@")' - f.puts " sudo -u #{git_user} -i eval \"git $cmd\"" - else - f.puts ' cmd=$(printf "\\"%s\\" " "$@")' - f.puts " sudo -u #{git_user} -i eval \"git $cmd\"" - end - f.puts 'fi' - end if !File.exists?(git_exec_path()) - - # use perl script for git_user_runner so we can - # escape output more easily - File.open(git_user_runner_path(), "w") do |f| - f.puts '#!/usr/bin/perl' - f.puts '' - f.puts 'my $command = join(" ", @ARGV);' - f.puts '' - f.puts 'my $user = `whoami`;' - f.puts 'chomp $user;' - f.puts 'if ($user eq "' + git_user + '")' - f.puts '{' - f.puts ' exec("cd ~ ; $command");' - f.puts '}' - f.puts 'else' - f.puts '{' - f.puts ' $command =~ s/\\\\/\\\\\\\\/g;' - # Previous line turns \; => \\; - # If old sudo, turn \\; => "\\;" to protect ';' from loss as command separator during eval - if sudo_version < sudo_version_switch - f.puts ' $command =~ s/(\\\\\\\\;)/"$1"/g;' - end - f.puts ' $command =~ s/"/\\\\"/g;' - f.puts ' exec("sudo -u ' + git_user + ' -i eval \"$command\"");' - f.puts '}' - end if !File.exists?(git_user_runner_path()) - - File.chmod(0550, git_exec_path()) - File.chmod(0550, gitolite_ssh_path()) - File.chmod(0550, git_user_runner_path()) - %x[chown #{web_user} -R "#{get_bin_dir}"] + begin + logger.info "Cloning gitolite-admin repository to #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] + shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup + GitAdapterHooks.check_hooks_installed + + return true # On master (fresh clone) + rescue + begin + # Try to repair admin access. + fixup_gitolite_admin + + logger.info "Recloning gitolite-admin repository to #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] + shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] + shell %[chmod 700 "#{repo_dir}" ] + # Make sure we have our hooks setup + GitAdapterHooks.check_hooks_installed + + return true # On master (fresh clone) + rescue + logger.error "Cannot clone administrative repository. Requires human intervention!!!" + end end - - @@lock_file = nil - def self.lock(retries) - is_locked = false - if @@lock_file.nil? - @@lock_file=File.new(File.join(get_tmp_dir,'redmine_git_hosting_lock'),File::CREAT|File::RDONLY) - end - - while retries > 0 - is_locked = @@lock_file.flock(File::LOCK_EX|File::LOCK_NB) - retries-=1 - if (!is_locked) && retries > 0 - sleep 1 - end - end - return is_locked + end + + # Recover from failure to clone repository. + # + # This routine attempts to recover from a failure to clone by reestablishing the gitolite + # key. It does so by directly cloning the gitolite-admin repository and editing the configuration + # file (gitolite.conf). If we ever try to allow gitolite services on a separate server from Redmine, + # we will have to turn this into a stand-alone script. + # + # Ideally, we have gitolite >= 2.0.3 so that we have 'gl-admin-push'. If not, we try to use gl-setup + # which has some quirks and is not as good. + # + # We try to: + # (1) figure out what the proper name is for the access key by first looking in the conf file, then + # looking for a matching keyname in the keydir. + # (2) delete any keys in the keydir that match our key + # (3) reestablish the keyname in the conf file and the key in the keydir + # (4) push the result back to the admin repo. + # + # Most of this activity is all done as the git user, hence the long command lines. Only parsing of the + # conf file is done as the redmine user (hence the need for the separate "tmp_conf_dir". + # + # Return: on success, returns "Success!". On Failure, throws a GitHostingException. + # + # Consider this the "nuclear" option.... + def self.fixup_gitolite_admin + unless GitoliteConfig.has_admin_key? + raise GitHostingException, "Cannot repair Admin Key: Key not managed by Redmine!" end - - def self.unlock - if !@@lock_file.nil? - @@lock_file.flock(File::LOCK_UN) - end + logger.warn "Attempting to restore repository access key:" + begin + repo_dir = File.join(Dir.tmpdir,"fixrepo",git_user,GitHosting::GitoliteConfig::ADMIN_REPO) + conf_file = File.join(repo_dir,"conf",gitolite_conf) + keydir = File.join(repo_dir, 'keydir') + + tmp_conf_dir = File.join(Dir.tmpdir,"fixconf",git_user) + tmp_conf_file = File.join(tmp_conf_dir,gitolite_conf) + + logger.warn " Cloning administrative repo directly as #{git_user} in #{repo_dir}" + shell %[rm -rf "#{repo_dir}"] if File.exists?(repo_dir) + admin_repo = "#{GitHosting.repository_base}/#{GitHosting::GitoliteConfig::ADMIN_REPO}" + shell %[#{GitHosting.git_user_runner} git clone #{admin_repo} #{repo_dir}] + + # Load up existing conf file + shell %[mkdir -p #{tmp_conf_dir}] + shell %[#{GitHosting.git_user_runner} 'cat #{conf_file}' | cat > #{tmp_conf_file}] + conf = GitoliteConfig.new(tmp_conf_file) + + # copy key into home directory... + shell %[cat #{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']} | #{GitHosting.git_user_runner} 'cat > ~/id_rsa.pub'] + + # Locate any keys that match new key -- save first one + admin_key_matches = %x[#{GitHosting.git_user_runner} 'find #{keydir} -type f -exec cmp -s ~/id_rsa.pub {} \\; -print'].chomp.split("\n").map do |name| + my_basename = File.basename(name,".pub") + shell %[#{GitHosting.git_user_runner} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{my_basename}.pub] + my_basename + end + + unless admin_key_matches.empty? + oldkeys = admin_key_matches.join(", ") + logger.warn " Deleting old keys in keydir that match gitoliteIdentityPublicKeyFile: #{oldkeys}" + end + + # Grab admin key name out of conf file (if it exists) + my_admin_key = conf.get_admin_key + + # Try to deduce administrative key name first from conf file, then from keydir, then use default. + # Remove all extraneous ".pub" from end, in case something crept through. + new_admin_key_name = (/^(.*?)(\.pub)*$/.match(my_admin_key || admin_key_matches.first || GitHosting::GitoliteConfig::DEFAULT_ADMIN_KEY_NAME))[1] + if my_admin_key + logger.warn " Using admin key name from conf file: '#{new_admin_key_name}'" + else + logger.warn " Using '#{new_admin_key_name}' as the admin key name" + end + + conf.set_admin_key new_admin_key_name + conf.save + shell %[cat #{tmp_conf_file} | #{GitHosting.git_user_runner} 'cat > #{conf_file}'] + shell %[#{GitHosting.git_user_runner} 'mv ~/id_rsa.pub #{keydir}/#{new_admin_key_name}.pub'] + + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/#{gitolite_conf}"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'"] + shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -m 'Updated by Redmine: Emergency repair of gitolite admin key'"] + begin + logger.warn " Pushing fixes using gl-admin-push" + shell %[#{GitHosting.git_user_runner} "cd #{repo_dir}; gl-admin-push -f"] + logger.warn "Successfully reestablished gitolite admin key!" + rescue + logger.error "gl-admin-push failed (pre 2.0.3 gitolite?). Trying 'gl-setup #{keydir}/#{new_admin_key_name}.pub'" + shell %[#{GitHosting.git_user_runner} "gl-setup #{keydir}/#{new_admin_key_name}.pub"] + logger.warn "Hopefully we have successfully reestablished gitolite admin key." + end + %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] + %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] + "Success!" + rescue + logger.error "Failed to reestablish gitolite admin key." + %x[#{GitHosting.git_user_runner} 'rm -f ~/id_rsa.pub'] + %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] + %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] + raise GitHostingException, "Failure to Repair Gitolite Admin Key" + end + end + + # Commit Changes to the gitolite-admin repository. This assumes that repository exists + # (i.e. that a clone_or_fetch_gitolite_admin has already be called). + # + # This routine must only be called after acquisition of the lock + # + # John Kubiatowicz, 11/15/11 + def self.commit_gitolite_admin(*args) + resyncing = args && args.first + + # create tmp dir, return cleanly if, for some reason, we don't have proper permissions + repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) + + # commit / push changes to gitolite admin repo + begin + if (!resyncing) + logger.info "Committing changes to gitolite-admin repository" + message = "Updated by Redmine" + else + logger.info "Committing corrections to gitolite-admin repository" + message = "Updated by Redmine: Corrections discovered during RESYNC_ALL" + end + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/#{gitolite_conf}] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -a -m '#{message}'] + shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' push ] + rescue + logger.error "Problems committing changes to gitolite-admin repository!! Probably requires human intervention" + raise GitHostingException, "Gitlite-admin Commit Failure" end + end + + # Update keys for all members of projects of interest + # + # This code is entirely self-correcting for keys owned by users of the specified + # projects. It should work regardless of the history of steps that got us here. + # + # Note that this code has changed from the original. Now, we look at all keys owned + # by users in the specified projects to make sure that they are still live. We + # do this with a single pass through the keydir and do not rely on the "inactive" + # status to tell us that a key should be deleted. The reason is that weird + # synchronization issues (not entirely understood) can cause phantom keys to get left + # in the keydir which can really mess up gitolite. + # + # As of the latest release, we also recover from a variety of aborted MOVE and DELETE + # operations. Further, we better handle movement of complete trees of projects. + # + # Also, when performing :resync_all, if the 'deleteGitRepositories' setting is 'true', + # then we will remove repositories in the configuration file (gitolite.conf) that are + # identifiable as "redmine managed" (because they have one or more keys of the right form) + # but which are nolonger live for some reason (probably because the project was deleted). + # + # John Kubiatowicz, 01/04/12 + # + # Usage: + # + # 1) update_repositories(project) => update for specified project + # 2) update_repositories([list of projects]) => update all projects + # 3) update_repositories(:flag1=>true, :flag2 => false) + # + # Current flags: + # :resync_all => go through all redmine-maintained gitolite repos, + # clean up keydir, delete unused keys, clean up gitolite.conf + # :delete => Clean up keydir, delete unused keys, remove redmine-maintaind + # gitolite entries and repositories unassociated with live projects. + # Unlike :resync_all, do not fix up live projects + # :descendants => for every given project, update self and all decendants + # :archive => Project is being archived -- remove keys from gitolite.conf, + # and possibly keydir if not used by any other project + # + @@recursionCheck = false + def self.update_repositories(*args) + flags = {} + args.each {|arg| flags.merge!(arg) if arg.is_a?(Hash)} + if flags[:resync_all] + logger.info "Executing RESYNC_ALL operation on gitolite configuration" + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + elsif flags[:delete] + # When delete, want to recompute users, so need to go through all projects + logger.info "Executing DELETE operation (resync keys, remove dead repositories)" + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + elsif flags[:archive] + # When archive, want to recompute users, so need to go through all projects + logger.info "Executing ARCHIVE operation (remove keys)" + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + elsif flags[:descendants] + if Project.method_defined?(:self_and_descendants) + projects = (args.flatten.select{|p| p.is_a?(Project)}).collect{|p| p.self_and_descendants}.flatten + else + projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + end + else + projects = args.flatten.select{|p| p.is_a?(Project)} + end + git_projects = projects.uniq.select{|p| p.repository.is_a?(Repository::Git) } + return if git_projects.empty? + + if(defined?(@@recursionCheck)) + if(@@recursionCheck) + # This shouldn't happen any more -- log as error + logger.error "git_hosting: update_repositories() exited with positive recursionCheck flag!" + return + end + end + @@recursionCheck = true - def self.shell(command) - begin - my_command = "#{command} 2>&1" - result = %x[#{my_command}].chomp - code = $?.exitstatus - rescue Exception => e - result=e.message - code = -1 - end - if code != 0 - logger.error "Command failed (return #{code}): #{command}" - message = " "+result.split("\n").join("\n ") - logger.error message - raise GitHostingException, "Shell Error" - end + # Grab actual lock + if !lock(lock_wait_time) + logger.error "git_hosting: update_repositories() exited without acquiring lock!" + @@recursionCheck = false + return end - # Try to get a cloned version of gitolite-admin repository. - # - # This code tries to recover from a variety of errors which have been observed - # in the field, including a loss of the admin key and an empty top-level directory - # - # Return: false => have uncommitted changes - # true => directory on master - # - # This routine must only be called after acquisition of the lock - # - # John Kubiatowicz, 11/15/11 - # - # This routine will no-longer merge in changes, since this can cause weird behavior - # when interacting with cron-jobs that clean up /tmp. - # - # John Kubiatowicz, 04/23/12 - # - def self.clone_or_pull_gitolite_admin(resync_all_flag) - # clone/pull from admin repo - repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) - - # If preexisting directory exists, try to clone and merge.... - if (File.exists? "#{repo_dir}") && (File.exists? "#{repo_dir}/.git") && (File.exists? "#{repo_dir}/keydir") && (File.exists? "#{repo_dir}/conf") - begin - logger.info "Fetching changes from gitolite-admin repository to #{repo_dir}" - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' fetch] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' merge FETCH_HEAD] - - # unmerged changes=> non-empty return - return_val = %x[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' status --short].empty? - - if (return_val) - shell %[chmod 700 "#{repo_dir}" ] - # Make sure we have our hooks setup - GitAdapterHooks.check_hooks_installed - return return_val - else - # The attempt to merge can cause a weird failure mode when interacting with cron jobs that clean out old - # files in /tmp. The issue is that keys in the keydir can go idle and get deleted. Then, when we merge we - # create an admin repo minus those keys (including the admin key!). Only a RESYNC_ALL operation will - # actually fix. Thus, we never return "have uncommitted changes", but instead fail the merge and reclone. - # - # 04/23/12 - # --KUBI-- - logger.error "Seems to be unmerged changes! Going to delete and reclone for safety." - logger.error "May need to execute RESYNC_ALL to fix whatever caused pending changes." unless resync_all_flag - end - rescue - logger.error "Repository fetch and merge failed -- trying to delete and reclone repository." - end + begin + # Make sure we have gitoite-admin cloned. + # If have uncommitted changes, reflect in "changed" flag. + changed = !clone_or_pull_gitolite_admin(flags[:resync_all]) + + # Get directory for the gitolite-admin + repo_dir = File.join(get_tmp_dir,"gitolite-admin") + + # logger.info "Updating keydirectory for projects: #{git_projects.join ', '}" + keydir = File.join(repo_dir,"keydir") + old_keyhash = {} + Dir.foreach(keydir) do |keyfile| + user_token = GitolitePublicKey.ident_to_user_token(keyfile) + if !user_token.nil? + old_keyhash[user_token] ||= [] + old_keyhash[user_token] << keyfile end - - begin - logger.info "Cloning gitolite-admin repository to #{repo_dir}" - shell %[rm -rf "#{repo_dir}"] - shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] - shell %[chmod 700 "#{repo_dir}" ] - # Make sure we have our hooks setup - GitAdapterHooks.check_hooks_installed - - return true # On master (fresh clone) - rescue - begin - # Try to repair admin access. - fixup_gitolite_admin - - logger.info "Recloning gitolite-admin repository to #{repo_dir}" - shell %[rm -rf "#{repo_dir}"] - shell %[env GIT_SSH=#{gitolite_ssh()} git clone ssh://#{git_user}@#{Setting.plugin_redmine_git_hosting['gitServer']}/gitolite-admin.git #{repo_dir}] - shell %[chmod 700 "#{repo_dir}" ] - # Make sure we have our hooks setup - GitAdapterHooks.check_hooks_installed - - return true # On master (fresh clone) - rescue - logger.error "Cannot clone administrative repository. Requires human intervention!!!" - end + end + + # Collect relevant users into hash with user as key and activity (in some active project) as value + git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq.each do |cur_user| + active_keys = cur_user.gitolite_public_keys.active || [] + + # Remove old keys that happen to be left around + cur_token = GitolitePublicKey.user_to_user_token(cur_user) + + # Current filenames + old_keynames = old_keyhash[cur_token] || [] + cur_keynames = [] + + # Get list of active keys that SHOULD be in the keydir + active_keys.each do |key| + key_id = key.identifier + key_token = GitolitePublicKey.ident_to_user_token(key_id) + if key_token != cur_token + # Rare case -- user login changed. Fix it. + key_id = key.reset_identifier + + # Add all key filenames with this (incorrect) token into the set of names + old_keynames += (old_keyhash[key_token] || []) + old_keyhash.delete(key_token) + end + cur_keynames << "#{key_id}.pub" end - end - # Recover from failure to clone repository. - # - # This routine attempts to recover from a failure to clone by reestablishing the gitolite - # key. It does so by directly cloning the gitolite-admin repository and editing the configuration - # file (gitolite.conf). If we ever try to allow gitolite services on a separate server from Redmine, - # we will have to turn this into a stand-alone script. - # - # Ideally, we have gitolite >= 2.0.3 so that we have 'gl-admin-push'. If not, we try to use gl-setup - # which has some quirks and is not as good. - # - # We try to: - # (1) figure out what the proper name is for the access key by first looking in the conf file, then - # looking for a matching keyname in the keydir. - # (2) delete any keys in the keydir that match our key - # (3) reestablish the keyname in the conf file and the key in the keydir - # (4) push the result back to the admin repo. - # - # Most of this activity is all done as the git user, hence the long command lines. Only parsing of the - # conf file is done as the redmine user (hence the need for the separate "tmp_conf_dir". - # - # Return: on success, returns "Success!". On Failure, throws a GitHostingException. - # - # Consider this the "nuclear" option.... - def self.fixup_gitolite_admin - unless GitoliteConfig.has_admin_key? - raise GitHostingException, "Cannot repair Admin Key: Key not managed by Redmine!" + (old_keynames - cur_keynames).each do |keyname| + filename = File.join(keydir,"#{keyname}") + logger.warn "Removing gitolite key: #{keyname}" + %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] + changed = true end - logger.warn "Attempting to restore repository access key:" - begin - repo_dir = File.join(Dir.tmpdir,"fixrepo",git_user,GitHosting::GitoliteConfig::ADMIN_REPO) - conf_file = File.join(repo_dir,"conf",gitolite_conf) - keydir = File.join(repo_dir, 'keydir') - - tmp_conf_dir = File.join(Dir.tmpdir,"fixconf",git_user) - tmp_conf_file = File.join(tmp_conf_dir,gitolite_conf) - - logger.warn " Cloning administrative repo directly as #{git_user} in #{repo_dir}" - shell %[rm -rf "#{repo_dir}"] if File.exists?(repo_dir) - admin_repo = "#{GitHosting.repository_base}/#{GitHosting::GitoliteConfig::ADMIN_REPO}" - shell %[#{GitHosting.git_user_runner} git clone #{admin_repo} #{repo_dir}] - - # Load up existing conf file - shell %[mkdir -p #{tmp_conf_dir}] - shell %[#{GitHosting.git_user_runner} 'cat #{conf_file}' | cat > #{tmp_conf_file}] - conf = GitoliteConfig.new(tmp_conf_file) - - # copy key into home directory... - shell %[cat #{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']} | #{GitHosting.git_user_runner} 'cat > ~/id_rsa.pub'] - - # Locate any keys that match new key -- save first one - admin_key_matches = %x[#{GitHosting.git_user_runner} 'find #{keydir} -type f -exec cmp -s ~/id_rsa.pub {} \\; -print'].chomp.split("\n").map do |name| - my_basename = File.basename(name,".pub") - shell %[#{GitHosting.git_user_runner} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{my_basename}.pub] - my_basename - end - - unless admin_key_matches.empty? - oldkeys = admin_key_matches.join(", ") - logger.warn " Deleting old keys in keydir that match gitoliteIdentityPublicKeyFile: #{oldkeys}" - end - # Grab admin key name out of conf file (if it exists) - my_admin_key = conf.get_admin_key - - # Try to deduce administrative key name first from conf file, then from keydir, then use default. - # Remove all extraneous ".pub" from end, in case something crept through. - new_admin_key_name = (/^(.*?)(\.pub)*$/.match(my_admin_key || admin_key_matches.first || GitHosting::GitoliteConfig::DEFAULT_ADMIN_KEY_NAME))[1] - if my_admin_key - logger.warn " Using admin key name from conf file: '#{new_admin_key_name}'" - else - logger.warn " Using '#{new_admin_key_name}' as the admin key name" - end - - conf.set_admin_key new_admin_key_name - conf.save - shell %[cat #{tmp_conf_file} | #{GitHosting.git_user_runner} 'cat > #{conf_file}'] - shell %[#{GitHosting.git_user_runner} 'mv ~/id_rsa.pub #{keydir}/#{new_admin_key_name}.pub'] - - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/#{gitolite_conf}"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'"] - shell %[#{GitHosting.git_user_runner} "git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -m 'Updated by Redmine: Emergency repair of gitolite admin key'"] - begin - logger.warn " Pushing fixes using gl-admin-push" - shell %[#{GitHosting.git_user_runner} "cd #{repo_dir}; gl-admin-push -f"] - logger.warn "Successfully reestablished gitolite admin key!" - rescue - logger.error "gl-admin-push failed (pre 2.0.3 gitolite?). Trying 'gl-setup #{keydir}/#{new_admin_key_name}.pub'" - shell %[#{GitHosting.git_user_runner} "gl-setup #{keydir}/#{new_admin_key_name}.pub"] - logger.warn "Hopefully we have successfully reestablished gitolite admin key." - end - %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] - %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] - "Success!" - rescue - logger.error "Failed to reestablish gitolite admin key." - %x[#{GitHosting.git_user_runner} 'rm -f ~/id_rsa.pub'] - %x[#{GitHosting.git_user_runner} 'rm -rf "#{File.join(Dir.tmpdir,'fixrepo')}"'] - %x[rm -rf "#{File.join(Dir.tmpdir,'fixconf')}"] - raise GitHostingException, "Failure to Repair Gitolite Admin Key" + # Remove inactive keys (will already be deleted by above code) + cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} + + # Add missing keys to the keydir + active_keys.each do |key| + keyname = "#{key.identifier}.pub" + unless old_keynames.index(keyname) + filename = File.join(keydir,"#{keyname}") + logger.info "Adding gitolite key: #{keyname}" + File.open(filename, 'w') {|f| f.write(key.key.gsub(/\n/,'')) } + changed = true + end end - end - # Commit Changes to the gitolite-admin repository. This assumes that repository exists - # (i.e. that a clone_or_fetch_gitolite_admin has already be called). - # - # This routine must only be called after acquisition of the lock - # - # John Kubiatowicz, 11/15/11 - def self.commit_gitolite_admin(*args) - resyncing = args && args.first - - # create tmp dir, return cleanly if, for some reason, we don't have proper permissions - repo_dir = File.join(get_tmp_dir,GitHosting::GitoliteConfig::ADMIN_REPO) - - # commit / push changes to gitolite admin repo - begin - if (!resyncing) - logger.info "Committing changes to gitolite-admin repository" - message = "Updated by Redmine" - else - logger.info "Committing corrections to gitolite-admin repository" - message = "Updated by Redmine: Corrections discovered during RESYNC_ALL" - end - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add keydir/*] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' add conf/#{gitolite_conf}] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.email '#{Setting.mail_from}'] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' config user.name 'Redmine'] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' commit -a -m '#{message}'] - shell %[env GIT_SSH=#{gitolite_ssh()} git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' push ] - rescue - logger.error "Problems committing changes to gitolite-admin repository!! Probably requires human intervention" - raise GitHostingException, "Gitlite-admin Commit Failure" + # In preparation for resync_all, below + old_keyhash.delete(cur_token) + end + + # Remove keys for deleted users + orphanString=flags[:resync_all] ? "orphan " : "" + if flags[:resync_all] || flags[:delete] || flags[:archive] + # All keys left in old_keyhash should be for users nolonger authorized for gitolite repos + old_keyhash.each_value do |keyset| + keyset.each do |keyname| + filename = File.join(keydir,"#{keyname}") + logger.warn "Removing #{orphanString}gitolite key: #{keyname}" + %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] + changed = true + end end - end - - # Update keys for all members of projects of interest - # - # This code is entirely self-correcting for keys owned by users of the specified - # projects. It should work regardless of the history of steps that got us here. - # - # Note that this code has changed from the original. Now, we look at all keys owned - # by users in the specified projects to make sure that they are still live. We - # do this with a single pass through the keydir and do not rely on the "inactive" - # status to tell us that a key should be deleted. The reason is that weird - # synchronization issues (not entirely understood) can cause phantom keys to get left - # in the keydir which can really mess up gitolite. - # - # As of the latest release, we also recover from a variety of aborted MOVE and DELETE - # operations. Further, we better handle movement of complete trees of projects. - # - # Also, when performing :resync_all, if the 'deleteGitRepositories' setting is 'true', - # then we will remove repositories in the configuration file (gitolite.conf) that are - # identifiable as "redmine managed" (because they have one or more keys of the right form) - # but which are nolonger live for some reason (probably because the project was deleted). - # - # John Kubiatowicz, 01/04/12 - # - # Usage: - # - # 1) update_repositories(project) => update for specified project - # 2) update_repositories([list of projects]) => update all projects - # 3) update_repositories(:flag1=>true, :flag2 => false) - # - # Current flags: - # :resync_all => go through all redmine-maintained gitolite repos, - # clean up keydir, delete unused keys, clean up gitolite.conf - # :delete => Clean up keydir, delete unused keys, remove redmine-maintaind - # gitolite entries and repositories unassociated with live projects. - # Unlike :resync_all, do not fix up live projects - # :descendants => for every given project, update self and all decendants - # :archive => Project is being archived -- remove keys from gitolite.conf, - # and possibly keydir if not used by any other project - # - @@recursionCheck = false - def self.update_repositories(*args) - flags = {} - args.each {|arg| flags.merge!(arg) if arg.is_a?(Hash)} - if flags[:resync_all] - logger.info "Executing RESYNC_ALL operation on gitolite configuration" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - elsif flags[:delete] - # When delete, want to recompute users, so need to go through all projects - logger.info "Executing DELETE operation (resync keys, remove dead repositories)" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - elsif flags[:archive] - # When archive, want to recompute users, so need to go through all projects - logger.info "Executing ARCHIVE operation (remove keys)" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - elsif flags[:descendants] - if Project.method_defined?(:self_and_descendants) - projects = (args.flatten.select{|p| p.is_a?(Project)}).collect{|p| p.self_and_descendants}.flatten - else - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) - end + end + + conf = GitoliteConfig.new(File.join(repo_dir, 'conf', gitolite_conf)) + + # Current redmine repositories (basename=>[repo_name1,repo_name2]) + redmine_repos = conf.redmine_repo_map + + # The set of actual repositories (basename=>[repo_name1,repo_name2]) + actual_repos = GitoliteConfig.gitolite_repository_map + + # Set of all entries in gitolite.conf file (name1=>1, name2=>2) + total_entries = conf.all_repos + + # Projects for which we want to update hooks + new_projects = [] + + # Regenerate configuration file for projects of interest + # Also, try to match up actual repositories with projects (being somewhat conservative + # when a project might be out of control of redmine. + # + # Note that we go through all projects, including archived ones, since we may need to + # find orphaned repos. Archived projects get left with a "ARCHIVED_REDMINE_KEY". + git_projects.each do |proj| + repo_name = repository_name(proj) + + # Common case: these are nil or lists of one element. + my_entries = redmine_repos[proj.identifier] + my_repos = actual_repos[proj.identifier] + + # We have one or more gitolite.conf entries with the right base name. Pick one with + # closest name (will pick one with 'repo_name' if it exists. + closest_entry = closest_path(my_entries,repo_name) + if !closest_entry + # CREATION case. + if !total_entries[repo_name] + logger.warn "Creating new entry '#{repo_name}' in #{gitolite_conf}" + else + logger.warn "Utilizing existing non-redmine entry '#{repo_name}' in #{gitolite_conf}" + end + elsif closest_entry != repo_name + # MOVE case. + logger.warn "Moving entry '#{closest_entry}' to '#{repo_name}' in #{gitolite_conf}." + conf.rename_repo(closest_entry,repo_name) else - projects = args.flatten.select{|p| p.is_a?(Project)} + # NORMAL case. Have entry with correct name. + if !my_repos.index(repo_name) + logger.warn "Missing or misnamed repository for existing gitolite entry '#{repo_name}'." + end end - git_projects = projects.uniq.select{|p| p.repository.is_a?(Repository::Git) } - return if git_projects.empty? - - if(defined?(@@recursionCheck)) - if(@@recursionCheck) - # This shouldn't happen any more -- log as error - logger.error "git_hosting: update_repositories() exited with positive recursionCheck flag!" - return + new_projects << proj unless my_repos.index(closest_entry) # Reinit hooks unless NORMAL or MOVE case + my_entries.delete closest_entry # Claimed this one => don't need to delete later + + if my_repos.empty? + # This is the normal CREATION case. No repositories with matching basenames + # Attempt to recover repository from recycle_bin, if present. Else, create new repository. + if !GitoliteRecycle.recover_repository_if_present repo_name + logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" + end + elsif my_repos.index(closest_entry) + # We have a repository that matches the entry we used above. Move this one to match if necessary + # If closest_entry == repo_name, this is a NORMAL case (do nothing!) + # If closest_entry != repo_name, this is the MOVE case. + move_physical_repo(closest_entry,repo_name) if closest_entry != repo_name + elsif my_repos.index(repo_name) + # Existing repo with right name. We know that there wasn't a corresponding gitolite.conf entry.... + logger.warn " Using existing repository: '#{repository_path(repo_name)}'" + else + # Of the repos in my_repo with a matching base name, only steal away those not already controlled + # by gitolite.conf. The one reasonable case here is if (for some reason) a move was properly + # executed in gitolite.conf but the repo didn't get moved. + closest_repo = closest_path((my_repos - total_entries.keys),repo_name) + if !closest_repo + logger.error "One or more repositories with matching base name '#{proj.identifier}' exist, but already have entries in gitolite.conf" + logger.error "They are: #{my_repos.join(', ')}" + # Attempt to recover repository from recycle_bin, if present. Else, create new repository. + if !GitoliteRecycle.recover_repository_if_present repo_name + logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" end + else + logger.warn " Claiming orphan repository '#{repository_path(closest_repo)}' in gitolite repository." + move_physical_repo(closest_repo,repo_name) + end end - @@recursionCheck = true - # Grab actual lock - if !lock(lock_wait_time) - logger.error "git_hosting: update_repositories() exited without acquiring lock!" - @@recursionCheck = false - return + # Update repository url and root_url if necessary + myrepo = proj.repository + target_url = repository_path(proj) + if myrepo.url != target_url || myrepo.root_url != target_url + # logger.warn " Updating internal access path to '#{target_url}'." + myrepo.url = myrepo.root_url = target_url + proj.repository.save end - begin - # Make sure we have gitoite-admin cloned. - # If have uncommitted changes, reflect in "changed" flag. - changed = !clone_or_pull_gitolite_admin(flags[:resync_all]) - - # Get directory for the gitolite-admin - repo_dir = File.join(get_tmp_dir,"gitolite-admin") - - # logger.info "Updating keydirectory for projects: #{git_projects.join ', '}" - keydir = File.join(repo_dir,"keydir") - old_keyhash = {} - Dir.foreach(keydir) do |keyfile| - user_token = GitolitePublicKey.ident_to_user_token(keyfile) - if !user_token.nil? - old_keyhash[user_token] ||= [] - old_keyhash[user_token] << keyfile - end - end - - # Collect relevant users into hash with user as key and activity (in some active project) as value - git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq.each do |cur_user| - active_keys = cur_user.gitolite_public_keys.active || [] - - # Remove old keys that happen to be left around - cur_token = GitolitePublicKey.user_to_user_token(cur_user) - - # Current filenames - old_keynames = old_keyhash[cur_token] || [] - cur_keynames = [] - - # Get list of active keys that SHOULD be in the keydir - active_keys.each do |key| - key_id = key.identifier - key_token = GitolitePublicKey.ident_to_user_token(key_id) - if key_token != cur_token - # Rare case -- user login changed. Fix it. - key_id = key.reset_identifier - - # Add all key filenames with this (incorrect) token into the set of names - old_keynames += (old_keyhash[key_token] || []) - old_keyhash.delete(key_token) - end - cur_keynames << "#{key_id}.pub" - end - - (old_keynames - cur_keynames).each do |keyname| - filename = File.join(keydir,"#{keyname}") - logger.warn "Removing gitolite key: #{keyname}" - %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] - changed = true - end - - # Remove inactive keys (will already be deleted by above code) - cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} - - # Add missing keys to the keydir - active_keys.each do |key| - keyname = "#{key.identifier}.pub" - unless old_keynames.index(keyname) - filename = File.join(keydir,"#{keyname}") - logger.info "Adding gitolite key: #{keyname}" - File.open(filename, 'w') {|f| f.write(key.key.gsub(/\n/,'')) } - changed = true - end - end - - # In preparation for resync_all, below - old_keyhash.delete(cur_token) - end - - # Remove keys for deleted users - orphanString=flags[:resync_all] ? "orphan " : "" - if flags[:resync_all] || flags[:delete] || flags[:archive] - # All keys left in old_keyhash should be for users nolonger authorized for gitolite repos - old_keyhash.each_value do |keyset| - keyset.each do |keyname| - filename = File.join(keydir,"#{keyname}") - logger.warn "Removing #{orphanString}gitolite key: #{keyname}" - %x[git --git-dir='#{repo_dir}/.git' --work-tree='#{repo_dir}' rm keydir/#{keyname}] - changed = true - end - end - end - - conf = GitoliteConfig.new(File.join(repo_dir, 'conf', gitolite_conf)) - - # Current redmine repositories (basename=>[repo_name1,repo_name2]) - redmine_repos = conf.redmine_repo_map - - # The set of actual repositories (basename=>[repo_name1,repo_name2]) - actual_repos = GitoliteConfig.gitolite_repository_map - - # Set of all entries in gitolite.conf file (name1=>1, name2=>2) - total_entries = conf.all_repos - - # Projects for which we want to update hooks - new_projects = [] - - # Regenerate configuration file for projects of interest - # Also, try to match up actual repositories with projects (being somewhat conservative - # when a project might be out of control of redmine. - # - # Note that we go through all projects, including archived ones, since we may need to - # find orphaned repos. Archived projects get left with a "ARCHIVED_REDMINE_KEY". - git_projects.each do |proj| - repo_name = repository_name(proj) - - # Common case: these are nil or lists of one element. - my_entries = redmine_repos[proj.identifier] - my_repos = actual_repos[proj.identifier] - - # We have one or more gitolite.conf entries with the right base name. Pick one with - # closest name (will pick one with 'repo_name' if it exists. - closest_entry = closest_path(my_entries,repo_name) - if !closest_entry - # CREATION case. - if !total_entries[repo_name] - logger.warn "Creating new entry '#{repo_name}' in #{gitolite_conf}" - else - logger.warn "Utilizing existing non-redmine entry '#{repo_name}' in #{gitolite_conf}" - end - elsif closest_entry != repo_name - # MOVE case. - logger.warn "Moving entry '#{closest_entry}' to '#{repo_name}' in #{gitolite_conf}." - conf.rename_repo(closest_entry,repo_name) - else - # NORMAL case. Have entry with correct name. - if !my_repos.index(repo_name) - logger.warn "Missing or misnamed repository for existing gitolite entry '#{repo_name}'." - end - end - new_projects << proj unless my_repos.index(closest_entry) # Reinit hooks unless NORMAL or MOVE case - my_entries.delete closest_entry # Claimed this one => don't need to delete later - - if my_repos.empty? - # This is the normal CREATION case. No repositories with matching basenames - # Attempt to recover repository from recycle_bin, if present. Else, create new repository. - if !GitoliteRecycle.recover_repository_if_present repo_name - logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" - end - elsif my_repos.index(closest_entry) - # We have a repository that matches the entry we used above. Move this one to match if necessary - # If closest_entry == repo_name, this is a NORMAL case (do nothing!) - # If closest_entry != repo_name, this is the MOVE case. - move_physical_repo(closest_entry,repo_name) if closest_entry != repo_name - elsif my_repos.index(repo_name) - # Existing repo with right name. We know that there wasn't a corresponding gitolite.conf entry.... - logger.warn " Using existing repository: '#{repository_path(repo_name)}'" - else - # Of the repos in my_repo with a matching base name, only steal away those not already controlled - # by gitolite.conf. The one reasonable case here is if (for some reason) a move was properly - # executed in gitolite.conf but the repo didn't get moved. - closest_repo = closest_path((my_repos - total_entries.keys),repo_name) - if !closest_repo - logger.error "One or more repositories with matching base name '#{proj.identifier}' exist, but already have entries in gitolite.conf" - logger.error "They are: #{my_repos.join(', ')}" - # Attempt to recover repository from recycle_bin, if present. Else, create new repository. - if !GitoliteRecycle.recover_repository_if_present repo_name - logger.warn " Letting gitolite create empty repository: '#{repository_path(repo_name)}'" - end - else - logger.warn " Claiming orphan repository '#{repository_path(closest_repo)}' in gitolite repository." - move_physical_repo(closest_repo,repo_name) - end - end - - # Update repository url and root_url if necessary - myrepo = proj.repository - target_url = repository_path(proj) - if myrepo.url != target_url || myrepo.root_url != target_url - # logger.warn " Updating internal access path to '#{target_url}'." - myrepo.url = myrepo.root_url = target_url - proj.repository.save - end - - # If this is an active (non-archived) project, then update gitolite entry. Add GIT_DAEMON_KEY. - if proj.active? - # fetch users - users = proj.member_principals.map(&:user).compact.uniq - write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } - read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } - - # update users - read_user_keys = [] - write_user_keys = [] - - read_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| - read_user_keys.push key.identifier - end - write_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| - write_user_keys.push key.identifier - end - - #git daemon support - if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public - read_user_keys.push GitoliteConfig::GIT_DAEMON_KEY - end - - # Remove previous redmine keys, then add new keys - # By doing things this way, we leave non-redmine keys alone - # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys - # (to be put back as above, when appropriate). - conf.delete_redmine_keys repo_name - conf.add_read_user repo_name, read_user_keys - conf.add_write_user repo_name, write_user_keys - - # If no redmine keys, mark with dummy key - if (read_user_keys+write_user_keys).empty? - conf.mark_with_dummy_key repo_name - end - else - # Must be an archived project! Clear out redmine keys. Mark as an archived project. - conf.delete_redmine_keys repo_name - conf.mark_archived repo_name - end - end - - # If resyncing or deleting, check for orphan repositories which still have redmine keys... - # At this point, redmine_repos contains all repositories in original gitolite.conf - # which have redmine keys but are not part of an active redmine project. - # There are three possibilities: - # - # 1) They have both redmine keys and other (non-redmine) keys => remove redmine keys - # 2) They have only redmine keys, but repository delete is not enabled - # => remove redmine keys (will leave redmine_dummy_key when we save) - # 3) They have only redmine keys and repository delete is enabled => delete repository - if flags[:resync_all] || flags[:delete] - if flags[:delete] - # Get rid of all live repos from redmine_repos - proj_ids = git_projects.map{|proj| proj.identifier} - redmine_repos.delete_if{|basename,values| proj_ids.index(basename)} - end - redmine_repos.values.flatten.each do |repo_name| - # First, delete redmine keys for this repository - conf.delete_redmine_keys repo_name - if (Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true") - if conf.repo_has_no_keys? repo_name - logger.warn "Deleting #{orphanString}entry '#{repo_name}' from #{gitolite_conf}" - conf.delete_repo repo_name - GitoliteRecycle.move_repository_to_recycle repo_name - else - logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" - if git_repository_exists? repo_name - logger.info " Not removing #{repo_name}.git from gitolite repository, because non-redmine keys remain." - end - end - else - logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" - end - end - # Delete expired files from recycle bin. - GitoliteRecycle.delete_expired_files - end - - if conf.changed? - conf.save - changed = true - end - - if changed - # Have changes. Commit / push changes to gitolite admin repo - commit_gitolite_admin flags[:resync_all] - end - - # Set post receive hooks for new projects (or check all repositories on :resync_all). - # We need to do this AFTER push, otherwise necessary repos may not be created yet. - GitAdapterHooks.setup_hooks_params((flags[:resync_all]||flags[:resync_hooks]) ? git_projects : new_projects) - - rescue GitHostingException - logger.error "git_hosting: update_repositories() failed" - rescue => e - logger.error e.message - logger.error e.backtrace[0..4].join("\n") - logger.error "git_hosting: update_repositories() failed" + # If this is an active (non-archived) project, then update gitolite entry. Add GIT_DAEMON_KEY. + if proj.active? + # fetch users + users = proj.member_principals.map(&:user).compact.uniq + write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } + read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } + + # update users + read_user_keys = [] + write_user_keys = [] + + read_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| + read_user_keys.push key.identifier + end + write_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| + write_user_keys.push key.identifier + end + + #git daemon support + if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public + read_user_keys.push GitoliteConfig::GIT_DAEMON_KEY + end + + # Remove previous redmine keys, then add new keys + # By doing things this way, we leave non-redmine keys alone + # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys + # (to be put back as above, when appropriate). + conf.delete_redmine_keys repo_name + conf.add_read_user repo_name, read_user_keys + conf.add_write_user repo_name, write_user_keys + + # If no redmine keys, mark with dummy key + if (read_user_keys+write_user_keys).empty? + conf.mark_with_dummy_key repo_name + end + else + # Must be an archived project! Clear out redmine keys. Mark as an archived project. + conf.delete_redmine_keys repo_name + conf.mark_archived repo_name end - - unlock() - @@recursionCheck = false - end - - # This routine moves a repository in the gitolite repository structure. - def self.move_physical_repo(old_name,new_name) - begin - logger.warn " Moving gitolite repository from '#{old_name}.git' to '#{new_name}.git'" - - if git_repository_exists? new_name - logger.error "Repository already exists at #{new_name}.git! Moving to recycle bin to avoid overwrite." - GitoliteRecycle.move_repository_to_recycle new_name - end - - # physicaly move the repo BEFORE committing/pushing conf changes to gitolite admin repo - prefix = new_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' - if prefix - # Has subdirectory. Must construct destination directory - repo_prefix = File.join(repository_base, prefix) - GitHosting.shell %[#{git_user_runner} mkdir -p '#{repo_prefix}'] - end - old_path = repository_path(old_name) - new_path = repository_path(new_name) - shell %[#{git_user_runner} 'mv "#{old_path}" "#{new_path}"'] - - # If any empty directories left behind, try to delete them. Ignore failure. - old_prefix = old_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' - if old_prefix - repo_subpath = File.join(repository_base, old_prefix) - result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") - result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} + end + + # If resyncing or deleting, check for orphan repositories which still have redmine keys... + # At this point, redmine_repos contains all repositories in original gitolite.conf + # which have redmine keys but are not part of an active redmine project. + # There are three possibilities: + # + # 1) They have both redmine keys and other (non-redmine) keys => remove redmine keys + # 2) They have only redmine keys, but repository delete is not enabled + # => remove redmine keys (will leave redmine_dummy_key when we save) + # 3) They have only redmine keys and repository delete is enabled => delete repository + if flags[:resync_all] || flags[:delete] + if flags[:delete] + # Get rid of all live repos from redmine_repos + proj_ids = git_projects.map{|proj| proj.identifier} + redmine_repos.delete_if{|basename,values| proj_ids.index(basename)} + end + redmine_repos.values.flatten.each do |repo_name| + # First, delete redmine keys for this repository + conf.delete_redmine_keys repo_name + if (Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true") + if conf.repo_has_no_keys? repo_name + logger.warn "Deleting #{orphanString}entry '#{repo_name}' from #{gitolite_conf}" + conf.delete_repo repo_name + GitoliteRecycle.move_repository_to_recycle repo_name + else + logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" + if git_repository_exists? repo_name + logger.info " Not removing #{repo_name}.git from gitolite repository, because non-redmine keys remain." + end end - rescue GitHostingException - logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" - rescue => e - logger.error e.message - logger.error e.backtrace[0..4].join("\n") - logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" + else + logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" + end end + # Delete expired files from recycle bin. + GitoliteRecycle.delete_expired_files + end + + if conf.changed? + conf.save + changed = true + end + + if changed + # Have changes. Commit / push changes to gitolite admin repo + commit_gitolite_admin flags[:resync_all] + end + + # Set post receive hooks for new projects (or check all repositories on :resync_all). + # We need to do this AFTER push, otherwise necessary repos may not be created yet. + GitAdapterHooks.setup_hooks_params((flags[:resync_all]||flags[:resync_hooks]) ? git_projects : new_projects) + + rescue GitHostingException + logger.error "git_hosting: update_repositories() failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: update_repositories() failed" end - # Check to see if the given repository exists or not... - # Need to work a bit, since we have to su to figure it out... - def self.git_repository_exists?(repo_name) - file_exists?(repository_path(repo_name)) - end - - # Check to see if the given file exists off the git user's homedirectory. - # Need to work a bit, since we have to su to figure it out... - def self.file_exists?(filename) - (%x[#{GitHosting.git_user_runner} test -r '#{filename}' && echo 'yes' || echo 'no']).match(/yes/) ? true : false + unlock() + @@recursionCheck = false + end + + # This routine moves a repository in the gitolite repository structure. + def self.move_physical_repo(old_name,new_name) + begin + logger.warn " Moving gitolite repository from '#{old_name}.git' to '#{new_name}.git'" + + if git_repository_exists? new_name + logger.error "Repository already exists at #{new_name}.git! Moving to recycle bin to avoid overwrite." + GitoliteRecycle.move_repository_to_recycle new_name + end + + # physicaly move the repo BEFORE committing/pushing conf changes to gitolite admin repo + prefix = new_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' + if prefix + # Has subdirectory. Must construct destination directory + repo_prefix = File.join(repository_base, prefix) + GitHosting.shell %[#{git_user_runner} mkdir -p '#{repo_prefix}'] + end + old_path = repository_path(old_name) + new_path = repository_path(new_name) + shell %[#{git_user_runner} 'mv "#{old_path}" "#{new_path}"'] + + # If any empty directories left behind, try to delete them. Ignore failure. + old_prefix = old_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' + if old_prefix + repo_subpath = File.join(repository_base, old_prefix) + result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") + result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} + end + rescue GitHostingException + logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" + rescue => e + logger.error e.message + logger.error e.backtrace[0..4].join("\n") + logger.error "git_hosting: move_physical_repo(#{old_name},#{new_name}) failed" end - - # Takes a list of path names and a path name and attempts to find the item in the list that matches - # in the most components. Assume at least one element in list - def self.closest_path(path_list,target_path) - path_list.sort do |x, y| - # sort by length of match, then dictionary - lmatchx = longest_match_length(x,target_path) - lmatchy = longest_match_length(y,target_path) - if (lmatchx < lmatchy) - -1 - elsif (lmatchy < lmatchx) - 1 - else - # in case of equivalence, want earliest alphabetical - y <=> x - end - end.last + end + + # Check to see if the given repository exists or not... + # Need to work a bit, since we have to su to figure it out... + def self.git_repository_exists?(repo_name) + file_exists?(repository_path(repo_name)) + end + + # Check to see if the given file exists off the git user's homedirectory. + # Need to work a bit, since we have to su to figure it out... + def self.file_exists?(filename) + (%x[#{GitHosting.git_user_runner} test -r '#{filename}' && echo 'yes' || echo 'no']).match(/yes/) ? true : false + end + + # Takes a list of path names and a path name and attempts to find the item in the list that matches + # in the most components. Assume at least one element in list + def self.closest_path(path_list,target_path) + path_list.sort do |x, y| + # sort by length of match, then dictionary + lmatchx = longest_match_length(x,target_path) + lmatchy = longest_match_length(y,target_path) + if (lmatchx < lmatchy) + -1 + elsif (lmatchy < lmatchx) + 1 + else + # in case of equivalence, want earliest alphabetical + y <=> x + end + end.last + end + + # return the number of characters that match between str1 and str2 + def self.longest_match_length(str1,str2) + for result in 0..str1.length-1 + # note that str2[result]=nil if beyond end of string, which works fine! + if str1[result] != str2[result] + return result + end end + str1.length + end - # return the number of characters that match between str1 and str2 - def self.longest_match_length(str1,str2) - for result in 0..str1.length-1 - # note that str2[result]=nil if beyond end of string, which works fine! - if str1[result] != str2[result] - return result - end - end - str1.length + def self.clear_cache_for_project(project) + if project.is_a?(Project) + project = project.identifier end - - def self.clear_cache_for_project(project) - if project.is_a?(Project) - project = project.identifier - end - # Clear cache - old_cached=GitCache.find_all_by_proj_identifier(project) - if old_cached != nil - old_ids = old_cached.collect(&:id) - GitCache.destroy(old_ids) - end + # Clear cache + old_cached=GitCache.find_all_by_proj_identifier(project) + if old_cached != nil + old_ids = old_cached.collect(&:id) + GitCache.destroy(old_ids) end + end - def self.check_hooks_installed - installed = false - if lock(5) - installed = GitAdapterHooks.check_hooks_installed - unlock() - end - installed + def self.check_hooks_installed + installed = false + if lock(5) + installed = GitAdapterHooks.check_hooks_installed + unlock() end - def self.setup_hooks(projects=nil) - if lock(5) - GitAdapterHooks.setup_hooks(projects) - unlock() - end + installed + end + def self.setup_hooks(projects=nil) + if lock(5) + GitAdapterHooks.setup_hooks(projects) + unlock() end - def self.update_global_hook_params - if lock(5) - GitAdapterHooks.update_global_hook_params - unlock() - end + end + def self.update_global_hook_params + if lock(5) + GitAdapterHooks.update_global_hook_params + unlock() + end + end + + class MyLogger + # Prefix to error messages + ERROR_PREFIX = "***> " + + # For errors, add our prefix to all messages + def error(*progname, &block) + if block_given? + Rails.logger.error(*progname) { "#{ERROR_PREFIX}#{yield}".gsub(/\n/,"\n#{ERROR_PREFIX}") } + else + Rails.logger.error "#{ERROR_PREFIX}#{progname}".gsub(/\n/,"\n#{ERROR_PREFIX}") + end end - class MyLogger - # Prefix to error messages - ERROR_PREFIX = "***> " - - # For errors, add our prefix to all messages - def error(*progname, &block) - if block_given? - Rails.logger.error(*progname) { "#{ERROR_PREFIX}#{yield}".gsub(/\n/,"\n#{ERROR_PREFIX}") } - else - Rails.logger.error "#{ERROR_PREFIX}#{progname}".gsub(/\n/,"\n#{ERROR_PREFIX}") - end - end - - # Handle everything else with base object - def method_missing(m, *args, &block) - Rails.logger.send m, *args, &block - end + # Handle everything else with base object + def method_missing(m, *args, &block) + Rails.logger.send m, *args, &block end + end end - diff --git a/lib/git_hosting/patches/git_adapter_patch.rb b/lib/git_hosting/patches/git_adapter_patch.rb index f09d181b6..256131ac4 100644 --- a/lib/git_hosting/patches/git_adapter_patch.rb +++ b/lib/git_hosting/patches/git_adapter_patch.rb @@ -5,113 +5,113 @@ require_dependency 'redmine/scm/adapters/git_adapter' module GitHosting - module Patches - module GitAdapterPatch - - def self.included(base) - base.class_eval do - unloadable - end - - begin - base.send(:alias_method_chain, :scm_cmd, :sudo) - rescue Exception =>e - end - - base.extend(ClassMethods) - base.class_eval do - class << self - begin - alias_method_chain :sq_bin, :sudo - begin - alias_method_chain :client_command, :sudo - rescue Exception =>e - end - rescue Exception => e - # Hm.... Might be Redmine version < 1.2 (i.e. 1.1). Try redefining GIT_BIN. - GitHosting.logger.warn "Seems to be early version of Redmine(1.1?), try redefining GIT_BIN." - Redmine::Scm::Adapters::GitAdapter::GIT_BIN = GitHosting::git_exec() - end - end - end - end + module Patches + module GitAdapterPatch + def self.included(base) + base.class_eval do + unloadable + end - module ClassMethods - def sq_bin_with_sudo - return Redmine::Scm::Adapters::GitAdapter::shell_quote(GitHosting::git_exec()) - end - def client_command_with_sudo - return GitHosting::git_exec() - end + begin + base.send(:alias_method_chain, :scm_cmd, :sudo) + rescue Exception =>e + end + + base.extend(ClassMethods) + base.class_eval do + class << self + begin + alias_method_chain :sq_bin, :sudo + begin + alias_method_chain :client_command, :sudo + rescue Exception =>e + end + rescue Exception => e + # Hm.... Might be Redmine version < 1.2 (i.e. 1.1). Try redefining GIT_BIN. + GitHosting.logger.warn "Seems to be early version of Redmine(1.1?), try redefining GIT_BIN." + Redmine::Scm::Adapters::GitAdapter::GIT_BIN = GitHosting::git_exec() end + end + end + end + + + module ClassMethods + def sq_bin_with_sudo + return Redmine::Scm::Adapters::GitAdapter::shell_quote(GitHosting::git_exec()) + end + def client_command_with_sudo + return GitHosting::git_exec() + end + end - def scm_cmd_with_sudo(*args, &block) - - max_cache_time = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i # in seconds, default = 60 - max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i # default = 100 - max_cache_size = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024 # In MB, default = 16MB, converted to bytes - - repo_path = root_url || url - full_args = [GitHosting::git_exec(), '--git-dir', repo_path] - if self.class.client_version_above?([1, 7, 2]) - full_args << '-c' << 'core.quotepath=false' - full_args << '-c' << 'log.decorate=no' - end - full_args += args - - cmd_str=full_args.map { |e| shell_quote e.to_s }.join(' ') - out=nil - retio = nil - cached=GitCache.find_by_command(cmd_str) - if cached != nil - cur_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now - if cur_time.to_i - cached.created_at.to_i < max_cache_time || max_cache_time < 0 - out = cached.command_output == nil ? "" : cached.command_output - #File.open("/tmp/command_output.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") } - else - GitCache.destroy(cached.id) - end - end - if out == nil - shellout(cmd_str) do |io| - out = io.read(max_cache_size + 1) - end - out = out == nil ? "" : out - - if $? && $?.exitstatus != 0 - raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" - elsif out.length <= max_cache_size - proj_id=repo_path.gsub(/\.git$/, "").gsub(/^.*\//, "") - gitc = GitCache.create( :command=>cmd_str, :command_output=>out, :proj_identifier=>proj_id ) - gitc.save - if GitCache.count > max_cache_elements && max_cache_elements >= 0 - oldest = GitCache.find(:last, :order => "created_at DESC") - GitCache.destroy(oldest.id) - end - #File.open("/tmp/non_cached.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") } - else - retio = shellout(cmd_str, &block) - if $? && $?.exitstatus != 0 - raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" - end - - end - end - - if retio == nil - retio = StringIO.new(string=out) - if block_given? - block.call(retio) - end - end - retio + def scm_cmd_with_sudo(*args, &block) + + max_cache_time = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i # in seconds, default = 60 + max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i # default = 100 + max_cache_size = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024 # In MB, default = 16MB, converted to bytes + + repo_path = root_url || url + full_args = [GitHosting::git_exec(), '--git-dir', repo_path] + if self.class.client_version_above?([1, 7, 2]) + full_args << '-c' << 'core.quotepath=false' + full_args << '-c' << 'log.decorate=no' + end + full_args += args + + cmd_str=full_args.map { |e| shell_quote e.to_s }.join(' ') + out=nil + retio = nil + cached=GitCache.find_by_command(cmd_str) + if cached != nil + cur_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now + if cur_time.to_i - cached.created_at.to_i < max_cache_time || max_cache_time < 0 + out = cached.command_output == nil ? "" : cached.command_output + #File.open("/tmp/command_output.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") } + else + GitCache.destroy(cached.id) + end + end + if out == nil + shellout(cmd_str) do |io| + out = io.read(max_cache_size + 1) + end + out = out == nil ? "" : out + + if $? && $?.exitstatus != 0 + raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" + elsif out.length <= max_cache_size + proj_id=repo_path.gsub(/\.git$/, "").gsub(/^.*\//, "") + gitc = GitCache.create( :command=>cmd_str, :command_output=>out, :proj_identifier=>proj_id ) + gitc.save + if GitCache.count > max_cache_elements && max_cache_elements >= 0 + oldest = GitCache.find(:last, :order => "created_at DESC") + GitCache.destroy(oldest.id) + end + #File.open("/tmp/non_cached.txt", "a") { |f| f.write("COMMAND:#{cmd_str}\n#{out}\n") } + else + retio = shellout(cmd_str, &block) + if $? && $?.exitstatus != 0 + raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" end + end + end + if retio == nil + retio = StringIO.new(string=out) + if block_given? + block.call(retio) + end end + retio + end + + end + end end # Patch in changes diff --git a/lib/git_hosting/patches/git_repository_patch.rb b/lib/git_hosting/patches/git_repository_patch.rb index 60027d79d..b2d4563b5 100644 --- a/lib/git_hosting/patches/git_repository_patch.rb +++ b/lib/git_hosting/patches/git_repository_patch.rb @@ -5,41 +5,41 @@ require_dependency 'repository/git' module GitHosting - module Patches - module GitRepositoryPatch - - def report_last_commit_with_always_true - true - end - def extra_report_last_commit_with_always_true - true - end - - def fetch_changesets_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - fetch_changesets_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - - def self.included(base) - base.class_eval do - unloadable - end - begin - base.send(:alias_method_chain, :report_last_commit, :always_true) - base.send(:alias_method_chain, :extra_report_last_commit, :always_true) - base.send(:alias_method_chain, :fetch_changesets, :disable_update) - rescue - end - - end + module Patches + module GitRepositoryPatch + + def report_last_commit_with_always_true + true + end + def extra_report_last_commit_with_always_true + true + end + + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + fetch_changesets_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + def self.included(base) + base.class_eval do + unloadable end + begin + base.send(:alias_method_chain, :report_last_commit, :always_true) + base.send(:alias_method_chain, :extra_report_last_commit, :always_true) + base.send(:alias_method_chain, :fetch_changesets, :disable_update) + rescue + end + + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/groups_controller_patch.rb b/lib/git_hosting/patches/groups_controller_patch.rb index 5f8df39a8..7ed9ee3fd 100644 --- a/lib/git_hosting/patches/groups_controller_patch.rb +++ b/lib/git_hosting/patches/groups_controller_patch.rb @@ -4,29 +4,29 @@ require_dependency 'groups_controller' module GitHosting - module Patches - module GroupsControllerPatch + module Patches + module GroupsControllerPatch - @@original_projects = [] + @@original_projects = [] - def disable_git_observer_updates - GitHostingObserver.set_update_active(false) - end + def disable_git_observer_updates + GitHostingObserver.set_update_active(false) + end - def do_single_update - GitHostingObserver.set_update_active(true) - end + def do_single_update + GitHostingObserver.set_update_active(true) + end - def self.included(base) - base.class_eval do - unloadable - end - base.send(:before_filter, :disable_git_observer_updates, :only=>[:update, :destroy, :add_users, :remove_user, :edit_membership, :destroy_membership]) - base.send(:after_filter, :do_single_update, :only=>[:update, :destroy, :add_users, :remove_user, :edit_membership, :destroy_membership]) - end + def self.included(base) + base.class_eval do + unloadable end + base.send(:before_filter, :disable_git_observer_updates, :only=>[:update, :destroy, :add_users, :remove_user, :edit_membership, :destroy_membership]) + base.send(:after_filter, :do_single_update, :only=>[:update, :destroy, :add_users, :remove_user, :edit_membership, :destroy_membership]) + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/members_controller_patch.rb b/lib/git_hosting/patches/members_controller_patch.rb index 57ab30493..085181a51 100644 --- a/lib/git_hosting/patches/members_controller_patch.rb +++ b/lib/git_hosting/patches/members_controller_patch.rb @@ -4,52 +4,52 @@ require_dependency 'members_controller' module GitHosting - module Patches - module MembersControllerPatch - def new_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - new_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - def edit_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - edit_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - destroy_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(:delete => true); - end - - def self.included(base) - base.class_eval do - unloadable - end - begin - base.send(:alias_method_chain, :new, :disable_update) - base.send(:alias_method_chain, :edit, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - rescue - end - end + module Patches + module MembersControllerPatch + def new_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + new_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def edit_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + edit_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + destroy_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(:delete => true); + end + + def self.included(base) + base.class_eval do + unloadable end + begin + base.send(:alias_method_chain, :new, :disable_update) + base.send(:alias_method_chain, :edit, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/my_controller_patch.rb b/lib/git_hosting/patches/my_controller_patch.rb index 9dfd582cb..f5ccc53ef 100644 --- a/lib/git_hosting/patches/my_controller_patch.rb +++ b/lib/git_hosting/patches/my_controller_patch.rb @@ -4,37 +4,37 @@ require_dependency 'my_controller' module GitHosting - module Patches - module MyControllerPatch - # Add in values for viewing public keys: - def account_with_public_keys - # Previous routine - account_without_public_keys + module Patches + module MyControllerPatch + # Add in values for viewing public keys: + def account_with_public_keys + # Previous routine + account_without_public_keys - @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") - @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} - if @gitolite_public_key.nil? - if params[:public_key_id] - # public_key specified that doesn't belong to @user. Kill off public_key_id and try again - redirect_to :public_key_id => nil, :tab => nil - return - else - @gitolite_public_key = GitolitePublicKey.new - end - end - end + @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") + @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} + if @gitolite_public_key.nil? + if params[:public_key_id] + # public_key specified that doesn't belong to @user. Kill off public_key_id and try again + redirect_to :public_key_id => nil, :tab => nil + return + else + @gitolite_public_key = GitolitePublicKey.new + end + end + end - def self.included(base) - base.class_eval do - unloadable - end - begin - base.send(:alias_method_chain, :account, :public_keys) - rescue - end - end + def self.included(base) + base.class_eval do + unloadable + end + begin + base.send(:alias_method_chain, :account, :public_keys) + rescue end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/project_patch.rb b/lib/git_hosting/patches/project_patch.rb index 231054aef..4d7dec39e 100644 --- a/lib/git_hosting/patches/project_patch.rb +++ b/lib/git_hosting/patches/project_patch.rb @@ -4,25 +4,25 @@ require_dependency 'project' module GitHosting - module Patches - module ProjectPatch - def self.included(base) - base.class_eval do - unloadable + module Patches + module ProjectPatch + def self.included(base) + base.class_eval do + unloadable - named_scope :archived, { :conditions => {:status => "#{Project::STATUS_ARCHIVED}"}} - named_scope :active_or_archived, { :conditions => "status=#{Project::STATUS_ACTIVE} OR status=#{Project::STATUS_ARCHIVED}" } + named_scope :archived, { :conditions => {:status => "#{Project::STATUS_ARCHIVED}"}} + named_scope :active_or_archived, { :conditions => "status=#{Project::STATUS_ACTIVE} OR status=#{Project::STATUS_ARCHIVED}" } - # initialize association from project -> repository mirrors - has_many :repository_mirrors, :dependent => :destroy + # initialize association from project -> repository mirrors + has_many :repository_mirrors, :dependent => :destroy - # initialize association from project -> repository post receive urls - has_many :repository_post_receive_urls, :dependent => :destroy + # initialize association from project -> repository post receive urls + has_many :repository_post_receive_urls, :dependent => :destroy - end - end end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/projects_controller_patch.rb b/lib/git_hosting/patches/projects_controller_patch.rb index 3862cb6fa..6228635b8 100644 --- a/lib/git_hosting/patches/projects_controller_patch.rb +++ b/lib/git_hosting/patches/projects_controller_patch.rb @@ -4,122 +4,122 @@ require_dependency 'projects_controller' module GitHosting - module Patches - module ProjectsControllerPatch - def git_repo_init - users = @project.member_principals.map(&:user).compact.uniq - if users.length == 0 - membership = Member.new( - :principal=>User.current, - :project_id=>@project.id, - :role_ids=>[3] - ) - membership.save - end - if @project.module_enabled?('repository') && Setting.plugin_redmine_git_hosting['allProjectsUseGit'] == "true" - # Create new repository - repo = Repository.factory("Git") - # Set urls - repo.url = repo.root_url = GitHosting.repository_path(@project) - @project.repository = repo - repo.save - end - end - - def disable_git_daemon_if_not_public - if @project.repository != nil - if @project.repository.is_a?(Repository::Git) - if @project.repository.extra.git_daemon == 1 && (not @project.is_public ) - @project.repository.extra.git_daemon = 0; - @project.repository.extra.save - @project.repository.save # Trigger update_repositories - end - end - end + module Patches + module ProjectsControllerPatch + def git_repo_init + users = @project.member_principals.map(&:user).compact.uniq + if users.length == 0 + membership = Member.new( + :principal=>User.current, + :project_id=>@project.id, + :role_ids=>[3] + ) + membership.save + end + if @project.module_enabled?('repository') && Setting.plugin_redmine_git_hosting['allProjectsUseGit'] == "true" + # Create new repository + repo = Repository.factory("Git") + # Set urls + repo.url = repo.root_url = GitHosting.repository_path(@project) + @project.repository = repo + repo.save + end + end + + def disable_git_daemon_if_not_public + if @project.repository != nil + if @project.repository.is_a?(Repository::Git) + if @project.repository.extra.git_daemon == 1 && (not @project.is_public ) + @project.repository.extra.git_daemon = 0; + @project.repository.extra.save + @project.repository.save # Trigger update_repositories end + end + end + end - def create_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - create_without_disable_update + # Do actual update + create_without_disable_update - # Fix up repository - git_repo_init + # Fix up repository + git_repo_init - # Adjust daemon status - disable_git_daemon_if_not_public + # Adjust daemon status + disable_git_daemon_if_not_public - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end - def update_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - update_without_disable_update + # Do actual update + update_without_disable_update - # Adjust daemon status - disable_git_daemon_if_not_public + # Adjust daemon status + disable_git_daemon_if_not_public - myrepo = @project.repository - if myrepo.is_a?(Repository::Git) && (myrepo.url != GitHosting::repository_path(@project) || myrepo.url != myrepo.root_url) - # Hm... something about parent hierarchy changed. Update us and our children - GitHostingObserver.set_update_active(@project, :descendants) - else - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - end + myrepo = @project.repository + if myrepo.is_a?(Repository::Git) && (myrepo.url != GitHosting::repository_path(@project) || myrepo.url != myrepo.root_url) + # Hm... something about parent hierarchy changed. Update us and our children + GitHostingObserver.set_update_active(@project, :descendants) + else + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - destroy_without_disable_update + # Do actual update + destroy_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end - def archive_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def archive_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - archive_without_disable_update + # Do actual update + archive_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(@project, :archive); - end + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(@project, :archive); + end - def unarchive_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def unarchive_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - unarchive_without_disable_update + # Do actual update + unarchive_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(@project); - end + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(@project); + end - def self.included(base) - base.class_eval do - unloadable - end - base.send(:alias_method_chain, :create, :disable_update) - base.send(:alias_method_chain, :update, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - base.send(:alias_method_chain, :archive, :disable_update) - base.send(:alias_method_chain, :unarchive, :disable_update) - end + def self.included(base) + base.class_eval do + unloadable end + base.send(:alias_method_chain, :create, :disable_update) + base.send(:alias_method_chain, :update, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + base.send(:alias_method_chain, :archive, :disable_update) + base.send(:alias_method_chain, :unarchive, :disable_update) + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/repositories_controller_patch.rb b/lib/git_hosting/patches/repositories_controller_patch.rb index 64dadb462..c5b6a8faf 100644 --- a/lib/git_hosting/patches/repositories_controller_patch.rb +++ b/lib/git_hosting/patches/repositories_controller_patch.rb @@ -4,71 +4,71 @@ require_dependency 'repositories_controller' module GitHosting - module Patches - module RepositoriesControllerPatch - def show_with_git_instructions - if @repository.is_a?(Repository::Git) and @repository.entries(@path, @rev).blank? - render :action => 'git_instructions' - else - show_without_git_instructions - end - end + module Patches + module RepositoriesControllerPatch + def show_with_git_instructions + if @repository.is_a?(Repository::Git) and @repository.entries(@path, @rev).blank? + render :action => 'git_instructions' + else + show_without_git_instructions + end + end - def edit_with_scm_settings - GitHosting.logger.debug "On edit_with_scm_settings" + def edit_with_scm_settings + GitHosting.logger.debug "On edit_with_scm_settings" - # Turn off updates during repository update - GitHostingObserver.set_update_active(false); - params[:repository] ||= {} + # Turn off updates during repository update + GitHostingObserver.set_update_active(false); + params[:repository] ||= {} - if params[:repository_scm] == "Git" - params[:repository][:url] = GitHosting.repository_path(@project) - end + if params[:repository_scm] == "Git" + params[:repository][:url] = GitHosting.repository_path(@project) + end - if params[:repository_scm] == "Git" || @project.repository.is_a?(Repository::Git) - #Evidently the ONLY way to update the repository.extra table is to basically copy/paste the existing controller code - #the update line needs to go in the dead center of it. - @repository = @project.repository - if !@repository - @repository = Repository.factory(params[:repository_scm]) - @repository.project = @project if @repository - end - if request.post? && @repository - @repository.attributes = params[:repository] - if !params[:extra].nil? - @repository.extra.update_attributes(params[:extra]) - end - @repository.save - end + if params[:repository_scm] == "Git" || @project.repository.is_a?(Repository::Git) + #Evidently the ONLY way to update the repository.extra table is to basically copy/paste the existing controller code + #the update line needs to go in the dead center of it. + @repository = @project.repository + if !@repository + @repository = Repository.factory(params[:repository_scm]) + @repository.project = @project if @repository + end + if request.post? && @repository + @repository.attributes = params[:repository] + if !params[:extra].nil? + @repository.extra.update_attributes(params[:extra]) + end + @repository.save + end - render(:update) do |page| - page.replace_html "tab-content-repository", :partial => 'projects/settings/repository' - if @repository && !@project.repository - @project.reload #needed to reload association - page.replace_html "main-menu", render_main_menu(@project) - end - end + render(:update) do |page| + page.replace_html "tab-content-repository", :partial => 'projects/settings/repository' + if @repository && !@project.repository + @project.reload #needed to reload association + page.replace_html "main-menu", render_main_menu(@project) + end + end - if !@project.repository.nil? - GitHostingObserver.bracketed_update_repositories(@project) - end - else - edit_without_scm_settings - end + if !@project.repository.nil? + GitHostingObserver.bracketed_update_repositories(@project) + end + else + edit_without_scm_settings + end - GitHostingObserver.set_update_active(true); - end + GitHostingObserver.set_update_active(true); + end - def self.included(base) - base.class_eval do - unloadable - end - base.send(:alias_method_chain, :show, :git_instructions) - base.send(:alias_method_chain, :edit, :scm_settings) - end + def self.included(base) + base.class_eval do + unloadable end + base.send(:alias_method_chain, :show, :git_instructions) + base.send(:alias_method_chain, :edit, :scm_settings) + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/repository_cia_filters.rb b/lib/git_hosting/patches/repository_cia_filters.rb index 839a05040..325d91949 100644 --- a/lib/git_hosting/patches/repository_cia_filters.rb +++ b/lib/git_hosting/patches/repository_cia_filters.rb @@ -1,32 +1,32 @@ module GitHosting - module Patches - module RepositoryCiaFilters - module FilterMethods - def notified(scmid) - scmid = scmid.scmid if scmid.instance_of?(Changeset) - #self.cia_notifications.push GitCiaNotification.new(:scmid => scmid) - self.push GitCiaNotification.new(:scmid => scmid) - end + module Patches + module RepositoryCiaFilters + module FilterMethods + def notified(scmid) + scmid = scmid.scmid if scmid.instance_of?(Changeset) + #self.cia_notifications.push GitCiaNotification.new(:scmid => scmid) + self.push GitCiaNotification.new(:scmid => scmid) + end - def notified?(scmid) - #GitHosting.logger.debug "1On has_notified? processing smcid: #{scmid}" - scmid = scmid.scmid if scmid.instance_of?(Changeset) - #GitHosting.logger.debug "2On has_notified? processing smcid: #{scmid}" - #q = find(:first, :conditions => ["scmid = ?", scmid]) - #GitHosting.logger.debug "Query result: #{q}, NIL? #{q.nil?}" - #return !q.nil? - #return !self.cia_notifications.find(:first, :conditions => ["scmid = ?", scmid_]).nil? - return !find(:first, :conditions => ["scmid = ?", scmid]).nil? - end - end - def self.included(base) - base.class_eval do - unloadable - end - base.extend(FilterMethods) - end + def notified?(scmid) + #GitHosting.logger.debug "1On has_notified? processing smcid: #{scmid}" + scmid = scmid.scmid if scmid.instance_of?(Changeset) + #GitHosting.logger.debug "2On has_notified? processing smcid: #{scmid}" + #q = find(:first, :conditions => ["scmid = ?", scmid]) + #GitHosting.logger.debug "Query result: #{q}, NIL? #{q.nil?}" + #return !q.nil? + #return !self.cia_notifications.find(:first, :conditions => ["scmid = ?", scmid_]).nil? + return !find(:first, :conditions => ["scmid = ?", scmid]).nil? + end + end + def self.included(base) + base.class_eval do + unloadable end + base.extend(FilterMethods) + end end + end end diff --git a/lib/git_hosting/patches/repository_patch.rb b/lib/git_hosting/patches/repository_patch.rb index c422119da..9855f48f0 100644 --- a/lib/git_hosting/patches/repository_patch.rb +++ b/lib/git_hosting/patches/repository_patch.rb @@ -4,88 +4,88 @@ require_dependency 'repository' module GitHosting - module Patches - module RepositoryPatch - module ClassMethods - def fetch_changesets_for_project(proj_identifier) - p = Project.find_by_identifier(proj_identifier) - if p - if p.repository - begin - p.repository.fetch_changesets - rescue Redmine::Scm::Adapters::CommandFailed => e - logger.error "scm: error during fetching changesets: #{e.message}" - end - end - end - end + module Patches + module RepositoryPatch + module ClassMethods + def fetch_changesets_for_project(proj_identifier) + p = Project.find_by_identifier(proj_identifier) + if p + if p.repository + begin + p.repository.fetch_changesets + rescue Redmine::Scm::Adapters::CommandFailed => e + logger.error "scm: error during fetching changesets: #{e.message}" + end + end + end + end - def factory_with_git_extra_init(klass_name, *args) - new_repo = factory_without_git_extra_init(klass_name, *args) - if new_repo.is_a?(Repository::Git) - if new_repo.extra.nil? - # Note that this autoinitializes default values and hook key - GitHosting.logger.error "Automatic initialization of git_repository_extra failed for #{self.project.to_s}" - end - end - return new_repo - end - def fetch_changesets_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + def factory_with_git_extra_init(klass_name, *args) + new_repo = factory_without_git_extra_init(klass_name, *args) + if new_repo.is_a?(Repository::Git) + if new_repo.extra.nil? + # Note that this autoinitializes default values and hook key + GitHosting.logger.error "Automatic initialization of git_repository_extra failed for #{self.project.to_s}" + end + end + return new_repo + end + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - fetch_changesets_without_disable_update + # Do actual update + fetch_changesets_without_disable_update - # Reenable updates to perform a sync of all projects - GitHostingObserver.set_update_active(:resync_all); - end - end + # Reenable updates to perform a sync of all projects + GitHostingObserver.set_update_active(:resync_all); + end + end - module InstanceMethods - # New version of extra() -- construct extra association if missing - def extra - retval = self.git_extra - if retval.nil? - # Construct new extra structure, followed by updating hooks (if necessary) - GitHostingObserver.set_update_active(false); + module InstanceMethods + # New version of extra() -- construct extra association if missing + def extra + retval = self.git_extra + if retval.nil? + # Construct new extra structure, followed by updating hooks (if necessary) + GitHostingObserver.set_update_active(false); - retval = GitRepositoryExtra.new() - self.git_extra = retval # Should save object... + retval = GitRepositoryExtra.new() + self.git_extra = retval # Should save object... - # If self.project != nil, trigger repair of hooks - GitHostingObserver.set_update_active(true, self.project, :resync_hooks => true) - end - retval - end + # If self.project != nil, trigger repair of hooks + GitHostingObserver.set_update_active(true, self.project, :resync_hooks => true) + end + retval + end - def extra=(new_extra_struct) - self.git_extra=(new_extra_struct) - end - end + def extra=(new_extra_struct) + self.git_extra=(new_extra_struct) + end + end - def self.included(base) - base.class_eval do - unloadable + def self.included(base) + base.class_eval do + unloadable - extend(ClassMethods) - class << self - alias_method_chain :factory, :git_extra_init - alias_method_chain :fetch_changesets, :disable_update - end + extend(ClassMethods) + class << self + alias_method_chain :factory, :git_extra_init + alias_method_chain :fetch_changesets, :disable_update + end - # initialize association from git repository -> git_extra - has_one :git_extra, :foreign_key =>'repository_id', :class_name => 'GitRepositoryExtra', :dependent => :destroy + # initialize association from git repository -> git_extra + has_one :git_extra, :foreign_key =>'repository_id', :class_name => 'GitRepositoryExtra', :dependent => :destroy - # initialize association from git repository -> cia_notifications - has_many :cia_notifications, :foreign_key =>'repository_id', :class_name => 'GitCiaNotification', :dependent => :destroy, :extend => GitHosting::Patches::RepositoryCiaFilters::FilterMethods + # initialize association from git repository -> cia_notifications + has_many :cia_notifications, :foreign_key =>'repository_id', :class_name => 'GitCiaNotification', :dependent => :destroy, :extend => GitHosting::Patches::RepositoryCiaFilters::FilterMethods - include(InstanceMethods) - end - end + include(InstanceMethods) end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/roles_controller_patch.rb b/lib/git_hosting/patches/roles_controller_patch.rb index 73b0be3a5..0caef703d 100644 --- a/lib/git_hosting/patches/roles_controller_patch.rb +++ b/lib/git_hosting/patches/roles_controller_patch.rb @@ -4,40 +4,40 @@ require_dependency 'roles_controller' module GitHosting - module Patches - module RolesControllerPatch - def edit_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + module Patches + module RolesControllerPatch + def edit_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - edit_without_disable_update + # Do actual update + edit_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - destroy_without_disable_update + # Do actual update + destroy_without_disable_update - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - def self.included(base) - base.class_eval do - unloadable - end - begin - base.send(:alias_method_chain, :edit, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - rescue - end - end + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def self.included(base) + base.class_eval do + unloadable end + begin + base.send(:alias_method_chain, :edit, :disable_update) + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/sys_controller_patch.rb b/lib/git_hosting/patches/sys_controller_patch.rb index 0eddce9ed..e86a99bd6 100644 --- a/lib/git_hosting/patches/sys_controller_patch.rb +++ b/lib/git_hosting/patches/sys_controller_patch.rb @@ -4,30 +4,30 @@ require_dependency 'sys_controller' module GitHosting - module Patches - module SysControllerPatch - def fetch_changesets_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); + module Patches + module SysControllerPatch + def fetch_changesets_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); - # Do actual update - fetch_changesets_without_disable_update + # Do actual update + fetch_changesets_without_disable_update - # Perform the updating process on all projects - GitHostingObserver.set_update_active(:resync_all); - end + # Perform the updating process on all projects + GitHostingObserver.set_update_active(:resync_all); + end - def self.included(base) - base.class_eval do - unloadable - end - begin - base.send(:alias_method_chain, :fetch_changesets, :disable_update) - rescue - end - end + def self.included(base) + base.class_eval do + unloadable end + begin + base.send(:alias_method_chain, :fetch_changesets, :disable_update) + rescue + end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/user_patch.rb b/lib/git_hosting/patches/user_patch.rb index e749e6cfb..8b66f3077 100644 --- a/lib/git_hosting/patches/user_patch.rb +++ b/lib/git_hosting/patches/user_patch.rb @@ -3,18 +3,18 @@ require_dependency 'git_hosting' module GitHosting - module Patches - module UserPatch - def self.included(base) - base.class_eval do - unloadable + module Patches + module UserPatch + def self.included(base) + base.class_eval do + unloadable - # initialize association from user -> gitolite_public_keys - has_many :gitolite_public_keys, :dependent => :destroy - end - end + # initialize association from user -> gitolite_public_keys + has_many :gitolite_public_keys, :dependent => :destroy end + end end + end end # Patch in changes diff --git a/lib/git_hosting/patches/users_controller_patch.rb b/lib/git_hosting/patches/users_controller_patch.rb index 0b283447e..7bb0524bc 100644 --- a/lib/git_hosting/patches/users_controller_patch.rb +++ b/lib/git_hosting/patches/users_controller_patch.rb @@ -4,94 +4,94 @@ require_dependency 'users_controller' module GitHosting - module Patches - module UsersControllerPatch - def create_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - create_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - def update_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Set public key values for view - set_public_key_values - - # Do actual update - update_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - def destroy_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - destroy_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(:delete); - end - def edit_membership_with_disable_update - # Turn of updates during repository update - GitHostingObserver.set_update_active(false); - - # Do actual update - edit_membership_without_disable_update - - # Reenable updates to perform a single update - GitHostingObserver.set_update_active(true); - end - - # Add in values for viewing public keys: - def edit_with_public_keys - # Set public key values for view - set_public_key_values - - # Previous routine - edit_without_public_keys - end - - # Add in values for viewing public keys: - def set_public_key_values - @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") - @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} - if @gitolite_public_key.nil? - if params[:public_key_id] - # public_key specified that doesn't belong to @user. Kill off public_key_id and try again - redirect_to :public_key_id => nil, :tab => params[:tab] - return - else - @gitolite_public_key = GitolitePublicKey.new - end - end - end - - - def self.included(base) - base.class_eval do - unloadable - end - # Edit adds new functionality, so don't silently fail! - base.send(:alias_method_chain, :edit, :public_keys) - begin - base.send(:alias_method_chain, :create, :disable_update) - base.send(:alias_method_chain, :update, :disable_update) - base.send(:alias_method_chain, :edit_membership, :disable_update) - # Put this last, since Redmine 1.1 doesn't have it.... - base.send(:alias_method_chain, :destroy, :disable_update) - rescue - end - end + module Patches + module UsersControllerPatch + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + create_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Set public key values for view + set_public_key_values + + # Do actual update + update_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + destroy_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(:delete); + end + def edit_membership_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + edit_membership_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + # Add in values for viewing public keys: + def edit_with_public_keys + # Set public key values for view + set_public_key_values + + # Previous routine + edit_without_public_keys + end + + # Add in values for viewing public keys: + def set_public_key_values + @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") + @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} + if @gitolite_public_key.nil? + if params[:public_key_id] + # public_key specified that doesn't belong to @user. Kill off public_key_id and try again + redirect_to :public_key_id => nil, :tab => params[:tab] + return + else + @gitolite_public_key = GitolitePublicKey.new + end end + end + + + def self.included(base) + base.class_eval do + unloadable + end + # Edit adds new functionality, so don't silently fail! + base.send(:alias_method_chain, :edit, :public_keys) + begin + base.send(:alias_method_chain, :create, :disable_update) + base.send(:alias_method_chain, :update, :disable_update) + base.send(:alias_method_chain, :edit_membership, :disable_update) + # Put this last, since Redmine 1.1 doesn't have it.... + base.send(:alias_method_chain, :destroy, :disable_update) + rescue + end + end end + end end # Patch in changes diff --git a/lib/gitolite_conf.rb b/lib/gitolite_conf.rb index 5edd7ba18..fc38a0b24 100755 --- a/lib/gitolite_conf.rb +++ b/lib/gitolite_conf.rb @@ -1,253 +1,253 @@ module GitHosting - class GitoliteConfig - DUMMY_REDMINE_KEY="redmine_dummy_key" - ARCHIVED_REDMINE_KEY="redmine_archived_project" - GIT_DAEMON_KEY="daemon" - ADMIN_REPO = "gitolite-admin" - PRIMARY_CONF_FILE = "gitolite.conf" - DEFAULT_ADMIN_KEY_NAME = "id_rsa" - - def self.gitolite_conf - Setting.plugin_redmine_git_hosting['gitConfigFile'] || PRIMARY_CONF_FILE - end + class GitoliteConfig + DUMMY_REDMINE_KEY="redmine_dummy_key" + ARCHIVED_REDMINE_KEY="redmine_archived_project" + GIT_DAEMON_KEY="daemon" + ADMIN_REPO = "gitolite-admin" + PRIMARY_CONF_FILE = "gitolite.conf" + DEFAULT_ADMIN_KEY_NAME = "id_rsa" + + def self.gitolite_conf + Setting.plugin_redmine_git_hosting['gitConfigFile'] || PRIMARY_CONF_FILE + end - def self.default_conf? - gitolite_conf == PRIMARY_CONF_FILE - end + def self.default_conf? + gitolite_conf == PRIMARY_CONF_FILE + end - # Need admin key if conf file is default file or if there was an admin key. - def self.has_admin_key? - Setting.plugin_redmine_git_hosting['gitConfigHasAdminKey'] != 'false' || default_conf? - end + # Need admin key if conf file is default file or if there was an admin key. + def self.has_admin_key? + Setting.plugin_redmine_git_hosting['gitConfigHasAdminKey'] != 'false' || default_conf? + end - def initialize file_path - @path = file_path - load - end + def initialize file_path + @path = file_path + load + end - def logger - return GitHosting.logger - end + def logger + return GitHosting.logger + end - def save - begin - File.open(@path, "w") do |f| - f.puts content - end - @original_content = content - rescue => e - GitHosting.logger.error "Error trying to write config file: #{e.to_s}" - end + def save + begin + File.open(@path, "w") do |f| + f.puts content end + @original_content = content + rescue => e + GitHosting.logger.error "Error trying to write config file: #{e.to_s}" + end + end - def add_write_user repo_name, users - repository(repo_name).add "RW+", users.sort - end + def add_write_user repo_name, users + repository(repo_name).add "RW+", users.sort + end - def set_write_user repo_name, users - repository(repo_name).set "RW+", users.sort - end + def set_write_user repo_name, users + repository(repo_name).set "RW+", users.sort + end - def add_read_user repo_name, users - repository(repo_name).add "R", users.sort - end + def add_read_user repo_name, users + repository(repo_name).add "R", users.sort + end - def set_read_user repo_name, users - repository(repo_name).set "R", users.sort - end + def set_read_user repo_name, users + repository(repo_name).set "R", users.sort + end - def mark_with_dummy_key repo_name - add_read_user repo_name, [DUMMY_REDMINE_KEY] - end + def mark_with_dummy_key repo_name + add_read_user repo_name, [DUMMY_REDMINE_KEY] + end - def mark_archived repo_name - add_read_user repo_name, [ARCHIVED_REDMINE_KEY] - end + def mark_archived repo_name + add_read_user repo_name, [ARCHIVED_REDMINE_KEY] + end - # Grab admin key (assuming it exists) - def get_admin_key - return (repository(ADMIN_REPO).get "RW+").first - end + # Grab admin key (assuming it exists) + def get_admin_key + return (repository(ADMIN_REPO).get "RW+").first + end - def set_admin_key new_name - repository(ADMIN_REPO).set "RW+", new_name - end + def set_admin_key new_name + repository(ADMIN_REPO).set "RW+", new_name + end - def delete_repo repo_name - @repositories.delete(repo_name) - end + def delete_repo repo_name + @repositories.delete(repo_name) + end - def rename_repo old_name, new_name - if @repositories.has_key?(old_name) - perms = @repositories.delete(old_name) - @repositories[new_name] = perms - end - end + def rename_repo old_name, new_name + if @repositories.has_key?(old_name) + perms = @repositories.delete(old_name) + @repositories[new_name] = perms + end + end - # A repository is a "redmine" repository if it has redmine keys or no keys - # (latter case is checked, since we end up adding the DUMMY_REDMINE_KEY to - # a repository with no keys anyway.... - def is_redmine_repo? repo_name - repository(repo_name).rights.detect {|perm, users| users.detect {|key| is_redmine_key? key}} || (repo_has_no_keys? repo_name) - end + # A repository is a "redmine" repository if it has redmine keys or no keys + # (latter case is checked, since we end up adding the DUMMY_REDMINE_KEY to + # a repository with no keys anyway.... + def is_redmine_repo? repo_name + repository(repo_name).rights.detect {|perm, users| users.detect {|key| is_redmine_key? key}} || (repo_has_no_keys? repo_name) + end - # Delete all of the redmine keys from a repository - # In addition, if there are any redmine keys, delete the GIT_DAEMON_KEY as well, - # since we assume this under control of redmine server. - def delete_redmine_keys repo_name - return unless @repositories[repo_name] && is_redmine_repo?(repo_name) + # Delete all of the redmine keys from a repository + # In addition, if there are any redmine keys, delete the GIT_DAEMON_KEY as well, + # since we assume this under control of redmine server. + def delete_redmine_keys repo_name + return unless @repositories[repo_name] && is_redmine_repo?(repo_name) - repository(repo_name).rights.each do |perm, users| - users.delete_if {|key| ((is_redmine_key? key) || (is_daemon_key? key))} - end - end - - def repo_has_no_keys? repo_name - !repository(repo_name).rights.detect {|perm, users| users.length > 0} - end + repository(repo_name).rights.each do |perm, users| + users.delete_if {|key| ((is_redmine_key? key) || (is_daemon_key? key))} + end + end - def is_redmine_key? keyname - (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY || keyname == ARCHIVED_REDMINE_KEY) ? true : false - end + def repo_has_no_keys? repo_name + !repository(repo_name).rights.detect {|perm, users| users.length > 0} + end - def is_daemon_key? keyname - (keyname == GIT_DAEMON_KEY) - end + def is_redmine_key? keyname + (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY || keyname == ARCHIVED_REDMINE_KEY) ? true : false + end - def changed? - @original_content != content - end + def is_daemon_key? keyname + (keyname == GIT_DAEMON_KEY) + end - def all_repos - repos={} - @repositories.each do |repo, rights| - repos[repo] = 1 - end - return repos - end + def changed? + @original_content != content + end - # For redmine repos, return map of basename (unique for redmine repos) => repo path - def redmine_repo_map - redmine_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list - @repositories.each do |repo, rights| - if is_redmine_repo? repo - # Represents bug in conf file, but must allow more than one - mybase = File.basename(repo) - redmine_repos[mybase] << repo - end - end - return redmine_repos - end + def all_repos + repos={} + @repositories.each do |repo, rights| + repos[repo] = 1 + end + return repos + end - def self.gitolite_repository_map - gitolite_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list - myfiles = %x[#{GitHosting.git_user_runner} 'find #{GitHosting.repository_base} -type d -name "*.git" -prune -print'].chomp.split("\n") - filesplit = /(\.\/)*#{GitHosting.repository_base}(.*?)([^\/]+)\.git/ - myfiles.each do |nextfile| - if filesplit =~ nextfile - gitolite_repos[$3] << "#{$2}#{$3}" - end - end - gitolite_repos - end + # For redmine repos, return map of basename (unique for redmine repos) => repo path + def redmine_repo_map + redmine_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list + @repositories.each do |repo, rights| + if is_redmine_repo? repo + # Represents bug in conf file, but must allow more than one + mybase = File.basename(repo) + redmine_repos[mybase] << repo + end + end + return redmine_repos + end - private - def load - @original_content = [] - @repositories = ActiveSupport::OrderedHash.new - begin - cur_repo_name = nil - File.open(@path).each_line do |line| - @original_content << line - tokens = line.strip.split - if tokens.first == 'repo' - cur_repo_name = tokens.last - @repositories[cur_repo_name] = GitoliteAccessRights.new - next - end - cur_repo_right = @repositories[cur_repo_name] - if cur_repo_right and tokens[1] == '=' - cur_repo_right.add tokens.first, tokens[2..-1] - end - end - # If no admin key in repo, delete any residual - @repositories.delete(ADMIN_REPO) unless self.class.has_admin_key? - - @original_content = @original_content.join - rescue => e - GitHosting.logger.error "Error trying to read config file: #{e.to_s}" - end + def self.gitolite_repository_map + gitolite_repos=Hash.new{|hash, key| hash[key] = []} # default -- empty list + myfiles = %x[#{GitHosting.git_user_runner} 'find #{GitHosting.repository_base} -type d -name "*.git" -prune -print'].chomp.split("\n") + filesplit = /(\.\/)*#{GitHosting.repository_base}(.*?)([^\/]+)\.git/ + myfiles.each do |nextfile| + if filesplit =~ nextfile + gitolite_repos[$3] << "#{$2}#{$3}" end + end + gitolite_repos + end - def repository repo_name - @repositories[repo_name] ||= GitoliteAccessRights.new - end + private + def load + @original_content = [] + @repositories = ActiveSupport::OrderedHash.new + begin + cur_repo_name = nil + File.open(@path).each_line do |line| + @original_content << line + tokens = line.strip.split + if tokens.first == 'repo' + cur_repo_name = tokens.last + @repositories[cur_repo_name] = GitoliteAccessRights.new + next + end + cur_repo_right = @repositories[cur_repo_name] + if cur_repo_right and tokens[1] == '=' + cur_repo_right.add tokens.first, tokens[2..-1] + end + end + # If no admin key in repo, delete any residual + @repositories.delete(ADMIN_REPO) unless self.class.has_admin_key? + + @original_content = @original_content.join + rescue => e + GitHosting.logger.error "Error trying to read config file: #{e.to_s}" + end + end - def content - content = [] - - if self.class.has_admin_key? - # Make sure that admin repo is first - content << "repo\t#{ADMIN_REPO}" - admin_key = @repositories[ADMIN_REPO] && (@repositories[ADMIN_REPO].get "RW+").first - unless admin_key.nil? - # Put in a single admin key - content << "\tRW+\t=\t#{admin_key}" - else - # If no repo, put in a default -- will try to fix later if problem. - content << "\tRW+\t=\t#{DEFAULT_ADMIN_KEY_NAME}" - end - content << "" - end + def repository repo_name + @repositories[repo_name] ||= GitoliteAccessRights.new + end - @repositories.each do |repo, rights| - unless repo == ADMIN_REPO - content << "repo\t#{repo}" - has_users=false - rights.each do |perm, users| - if users.length > 0 - has_users=true - content << "\t#{perm}\t=\t#{users.join(' ')}" - end - end - if !has_users - # If no users, use dummy key to make sure repo created - content << "\tR\t=\t#{DUMMY_REDMINE_KEY}" - end - content << "" - end + def content + content = [] + + if self.class.has_admin_key? + # Make sure that admin repo is first + content << "repo\t#{ADMIN_REPO}" + admin_key = @repositories[ADMIN_REPO] && (@repositories[ADMIN_REPO].get "RW+").first + unless admin_key.nil? + # Put in a single admin key + content << "\tRW+\t=\t#{admin_key}" + else + # If no repo, put in a default -- will try to fix later if problem. + content << "\tRW+\t=\t#{DEFAULT_ADMIN_KEY_NAME}" + end + content << "" + end + + @repositories.each do |repo, rights| + unless repo == ADMIN_REPO + content << "repo\t#{repo}" + has_users=false + rights.each do |perm, users| + if users.length > 0 + has_users=true + content << "\t#{perm}\t=\t#{users.join(' ')}" end - return content.join("\n") - end + end + if !has_users + # If no users, use dummy key to make sure repo created + content << "\tR\t=\t#{DUMMY_REDMINE_KEY}" + end + content << "" + end + end + return content.join("\n") end + end - class GitoliteAccessRights - def initialize - @rights = ActiveSupport::OrderedHash.new - end + class GitoliteAccessRights + def initialize + @rights = ActiveSupport::OrderedHash.new + end - def rights - @rights - end + def rights + @rights + end - def add perm, users - @rights[perm.to_sym] ||= [] - @rights[perm.to_sym] << users - @rights[perm.to_sym].flatten! - @rights[perm.to_sym].uniq! - end + def add perm, users + @rights[perm.to_sym] ||= [] + @rights[perm.to_sym] << users + @rights[perm.to_sym].flatten! + @rights[perm.to_sym].uniq! + end - def set perm, users - @rights[perm.to_sym] = [] - add perm, users - end + def set perm, users + @rights[perm.to_sym] = [] + add perm, users + end - def get perm - @rights[perm.to_sym] || [] - end + def get perm + @rights[perm.to_sym] || [] + end - def each - @rights.each {|k,v| yield k, v} - end + def each + @rights.each {|k,v| yield k, v} end + end end diff --git a/lib/gitolite_recycle.rb b/lib/gitolite_recycle.rb index 161579006..2224038e7 100755 --- a/lib/gitolite_recycle.rb +++ b/lib/gitolite_recycle.rb @@ -1,114 +1,114 @@ module GitHosting - # This class implements a basic recycle bit for repositories deleted from the gitolite repository - # - # Whenever repositories are deleted, we rename them and place them in the recycle_bin. - # Assuming that GitoliteRecycle.delete_expired_files is called regularly, files in the recycle_bin - # older than 'preserve_time' will be deleted. Both the path for the recycle_bin and the preserve_time - # are settable as settings. - # - # John Kubiatowicz, 11/21/11 - class GitoliteRecycle - TRASH_DIR_SEP = "__" # Separator character(s) used to replace '/' in name + # This class implements a basic recycle bit for repositories deleted from the gitolite repository + # + # Whenever repositories are deleted, we rename them and place them in the recycle_bin. + # Assuming that GitoliteRecycle.delete_expired_files is called regularly, files in the recycle_bin + # older than 'preserve_time' will be deleted. Both the path for the recycle_bin and the preserve_time + # are settable as settings. + # + # John Kubiatowicz, 11/21/11 + class GitoliteRecycle + TRASH_DIR_SEP = "__" # Separator character(s) used to replace '/' in name - RECYCLE_BIN_IF_UNDEF = "recycle_bin/" # In case settings not migrated (normally from settings) - PRESERVE_TIME_IF_UNDEF = 1440 # In case settings not migrated (normally from settings) + RECYCLE_BIN_IF_UNDEF = "recycle_bin/" # In case settings not migrated (normally from settings) + PRESERVE_TIME_IF_UNDEF = 1440 # In case settings not migrated (normally from settings) - def self.logger - return GitHosting.logger - end + def self.logger + return GitHosting.logger + end - # Recycle bin base path (relative to git user home directory) - def self.recycle_bin - Setting.plugin_redmine_git_hosting['gitRecycleBasePath'] || RECYCLE_BIN_IF_UNDEF - end + # Recycle bin base path (relative to git user home directory) + def self.recycle_bin + Setting.plugin_redmine_git_hosting['gitRecycleBasePath'] || RECYCLE_BIN_IF_UNDEF + end - # Recycle preservation time (in minutes) - def self.preserve_time - (Setting.plugin_redmine_git_hosting['gitRecycleExpireTime'].to_f * 60).to_i || PRESERVE_TIME_IF_UNDEF - end - - # Scan through the recyclebin and delete files older than 'preserve_time' minutes - def self.delete_expired_files - return unless GitHosting.file_exists?(recycle_bin) + # Recycle preservation time (in minutes) + def self.preserve_time + (Setting.plugin_redmine_git_hosting['gitRecycleExpireTime'].to_f * 60).to_i || PRESERVE_TIME_IF_UNDEF + end - result = %x[#{GitHosting.git_user_runner} find '#{recycle_bin}' -type d -regex '.*\.git' -cmin +#{preserve_time} -prune -print].chomp.split("\n") - if result.length > 0 - logger.warn "Garbage-collecting expired file#{(result.length != 1) ? "s" : ""} from recycle bin:" - result.each do |filename| - begin - GitHosting.shell %[#{GitHosting.git_user_runner} rm -r #{filename}] - logger.warn " Deleting #{filename}" - rescue - logger.error "GitoliteRecycle.delete_expired_files() failed trying to delete repository #{filename}!" - end - end + # Scan through the recyclebin and delete files older than 'preserve_time' minutes + def self.delete_expired_files + return unless GitHosting.file_exists?(recycle_bin) - # Optionally remove recycle_bin (but only if empty). Ignore error if non-empty - %x[#{GitHosting.git_user_runner} rmdir #{recycle_bin}] - end - end + result = %x[#{GitHosting.git_user_runner} find '#{recycle_bin}' -type d -regex '.*\.git' -cmin +#{preserve_time} -prune -print].chomp.split("\n") + if result.length > 0 + logger.warn "Garbage-collecting expired file#{(result.length != 1) ? "s" : ""} from recycle bin:" + result.each do |filename| + begin + GitHosting.shell %[#{GitHosting.git_user_runner} rm -r #{filename}] + logger.warn " Deleting #{filename}" + rescue + logger.error "GitoliteRecycle.delete_expired_files() failed trying to delete repository #{filename}!" + end + end - def self.move_repository_to_recycle repo_name - # Only bother if actually exists! - return unless GitHosting.git_repository_exists?(repo_name) - - repo_path = GitHosting.repository_path(repo_name) - new_path = File.join(recycle_bin,"#{Time.now.to_i.to_s}#{TRASH_DIR_SEP}#{name_to_recycle_name(repo_name)}.git") - begin - GitHosting.shell %[#{GitHosting.git_user_runner} mkdir -p '#{recycle_bin}'] - GitHosting.shell %[#{GitHosting.git_user_runner} chmod 770 '#{recycle_bin}'] - GitHosting.shell %[#{GitHosting.git_user_runner} mv '#{repo_path}' '#{new_path}'] - logger.warn " Moving '#{repo_name}' from gitolite repository => '#{new_path}'. Will remain for at least #{preserve_time/60.0} hours" - # If any empty directories left behind, try to delete them. Ignore failure. - old_prefix = repo_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' - if old_prefix - repo_subpath = File.join(GitHosting.repository_base, old_prefix) - result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") - result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} - end - return true - rescue - logger.error "Attempt to move repository '#{repo_name}.git' to recycle bin failed" - return false - end - end + # Optionally remove recycle_bin (but only if empty). Ignore error if non-empty + %x[#{GitHosting.git_user_runner} rmdir #{recycle_bin}] + end + end - def self.recover_repository_if_present repo_name - # Pull up any matching repositories. Sort them (beginning is representation of time) - myregex = File.join(recycle_bin,"[0-9]+#{TRASH_DIR_SEP}#{name_to_recycle_name(repo_name)}.git") - files = %x[#{GitHosting.git_user_runner} find '#{recycle_bin}' -type d -regex '#{myregex}' -prune].chomp.split("\n").sort {|x,y| y <=> x } - if files.length > 0 - # Found something! - logger.warn " Restoring '#{repo_name}.git' to gitolite repository from recycle bin (#{files.first})" - begin - prefix = repo_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' - if prefix - repo_prefix = File.join(GitHosting.repository_base, prefix) - # Has subdirectory. Must reconstruct directory - GitHosting.shell %[#{GitHosting.git_user_runner} mkdir -p '#{repo_prefix}'] - end - repo_path = GitHosting.repository_path(repo_name) - GitHosting.shell %[#{GitHosting.git_user_runner} mv '#{files.first}' '#{repo_path}'] + def self.move_repository_to_recycle repo_name + # Only bother if actually exists! + return unless GitHosting.git_repository_exists?(repo_name) - # Optionally remove recycle_bin (but only if empty). Ignore error if non-empty - %x[#{GitHosting.git_user_runner} rmdir #{recycle_bin}] - return true - rescue - logger.error "Attempt to recover '#{repo_name}.git' failed" - return false - end - else - false - end - end + repo_path = GitHosting.repository_path(repo_name) + new_path = File.join(recycle_bin,"#{Time.now.to_i.to_s}#{TRASH_DIR_SEP}#{name_to_recycle_name(repo_name)}.git") + begin + GitHosting.shell %[#{GitHosting.git_user_runner} mkdir -p '#{recycle_bin}'] + GitHosting.shell %[#{GitHosting.git_user_runner} chmod 770 '#{recycle_bin}'] + GitHosting.shell %[#{GitHosting.git_user_runner} mv '#{repo_path}' '#{new_path}'] + logger.warn " Moving '#{repo_name}' from gitolite repository => '#{new_path}'. Will remain for at least #{preserve_time/60.0} hours" + # If any empty directories left behind, try to delete them. Ignore failure. + old_prefix = repo_name[/.*?(?=\/)/] # Top-level old directory without trailing '/' + if old_prefix + repo_subpath = File.join(GitHosting.repository_base, old_prefix) + result = %x[#{GitHosting.git_user_runner} find '#{repo_subpath}' -type d ! -regex '.*\.git/.*' -empty -depth -delete -print].chomp.split("\n") + result.each { |dir| logger.warn " Removing empty repository subdirectory: #{dir}"} + end + return true + rescue + logger.error "Attempt to move repository '#{repo_name}.git' to recycle bin failed" + return false + end + end - # This routine takes a name and turns it into a name for the recycle bit, - # where we have a 1-level directory full of deleted repositories which - # we keep for 'preserve_time'. - def self.name_to_recycle_name repo_name - new_trash_name = "#{repo_name}".gsub(/\//,"#{TRASH_DIR_SEP}") - end + def self.recover_repository_if_present repo_name + # Pull up any matching repositories. Sort them (beginning is representation of time) + myregex = File.join(recycle_bin,"[0-9]+#{TRASH_DIR_SEP}#{name_to_recycle_name(repo_name)}.git") + files = %x[#{GitHosting.git_user_runner} find '#{recycle_bin}' -type d -regex '#{myregex}' -prune].chomp.split("\n").sort {|x,y| y <=> x } + if files.length > 0 + # Found something! + logger.warn " Restoring '#{repo_name}.git' to gitolite repository from recycle bin (#{files.first})" + begin + prefix = repo_name[/.*(?=\/)/] # Complete directory path (if exists) without trailing '/' + if prefix + repo_prefix = File.join(GitHosting.repository_base, prefix) + # Has subdirectory. Must reconstruct directory + GitHosting.shell %[#{GitHosting.git_user_runner} mkdir -p '#{repo_prefix}'] + end + repo_path = GitHosting.repository_path(repo_name) + GitHosting.shell %[#{GitHosting.git_user_runner} mv '#{files.first}' '#{repo_path}'] + # Optionally remove recycle_bin (but only if empty). Ignore error if non-empty + %x[#{GitHosting.git_user_runner} rmdir #{recycle_bin}] + return true + rescue + logger.error "Attempt to recover '#{repo_name}.git' failed" + return false + end + else + false + end + end + # This routine takes a name and turns it into a name for the recycle bit, + # where we have a 1-level directory full of deleted repositories which + # we keep for 'preserve_time'. + def self.name_to_recycle_name repo_name + new_trash_name = "#{repo_name}".gsub(/\//,"#{TRASH_DIR_SEP}") end + + + end end diff --git a/test/unit/gitolite_public_key_test.rb b/test/unit/gitolite_public_key_test.rb index 6c90dbf6d..bd712fc9d 100644 --- a/test/unit/gitolite_public_key_test.rb +++ b/test/unit/gitolite_public_key_test.rb @@ -1,10 +1,10 @@ require File.dirname(__FILE__) + '/../test_helper' class GitolitePublicKeyTest < ActiveSupport::TestCase - fixtures :gitolite_public_keys + fixtures :gitolite_public_keys - # Replace this with your real tests. - def test_truth - assert true - end + # Replace this with your real tests. + def test_truth + assert true + end end From b46a84f6dc77790d9693a6d7e2eb1a1d3512aba7 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Fri, 17 Aug 2012 14:34:23 -0700 Subject: [PATCH 061/107] First take on implementation of deployment credentials. Deployment credentials are constructed on the repository settings page for a repository and consist of combinations of public keys and permissions (currently either "R" or "RW+". The public keys for deployment credentials are owned by users with 'manager' permissions. The administrator can modify (add/update/delete) all deployment credentials. Also included with this commit is a functioning validation on the public key display window (this wasn't working originally). --- .../deployment_credentials_controller.rb | 259 ++++++++++++++++++ .../gitolite_public_keys_controller.rb | 77 +++++- .../repository_mirrors_controller.rb | 21 +- ...repository_post_receive_urls_controller.rb | 21 +- app/helpers/application_helper.rb | 10 + app/helpers/git_hosting_helper.rb | 17 ++ app/helpers/gitolite_public_keys_helper.rb | 15 + app/models/deployment_credential.rb | 83 ++++++ app/models/gitolite_public_key.rb | 53 +++- .../_form_with_key.html.erb | 54 ++++ .../create_with_key.html.erb | 7 + .../deployment_credentials/destroy.html.erb | 16 ++ .../deployment_credentials/edit.html.erb | 11 + .../deployment_credentials/form_error.rjs | 3 + app/views/gitolite_public_keys/_view.html.erb | 96 ++++++- app/views/gitolite_public_keys/form_error.rjs | 9 + app/views/my/account.html.erb | 3 +- .../projects/settings/_repository.html.erb | 111 +++++++- app/views/repository_mirrors/_form.html.erb | 2 +- assets/stylesheets/application.css | 2 +- config/locales/bg.yml | 33 ++- config/locales/bs.yml | 33 ++- config/locales/ca.yml | 33 ++- config/locales/cs.yml | 33 ++- config/locales/da.yml | 33 ++- config/locales/de.yml | 33 ++- config/locales/el.yml | 33 ++- config/locales/en.yml | 33 ++- config/locales/es.yml | 33 ++- config/locales/fi.yml | 33 ++- config/locales/fr.yml | 33 ++- config/locales/gl.yml | 33 ++- config/locales/he.yml | 33 ++- config/locales/hu.yml | 33 ++- config/locales/id.yml | 33 ++- config/locales/it.yml | 33 ++- config/locales/ja.yml | 33 ++- config/locales/ko.yml | 33 ++- config/locales/lt.yml | 33 ++- config/locales/nl.yml | 33 ++- config/locales/no.yml | 33 ++- config/locales/pl.yml | 33 ++- config/locales/pt-BR.yml | 33 ++- config/locales/pt.yml | 37 ++- config/locales/ro.yml | 33 ++- config/locales/ru.yml | 33 ++- config/locales/sk.yml | 33 ++- config/locales/sl.yml | 33 ++- config/locales/sr.yml | 33 ++- config/locales/sv.yml | 33 ++- config/locales/th.yml | 33 ++- config/locales/tr.yml | 33 ++- config/locales/uk.yml | 33 ++- config/locales/vi.yml | 33 ++- config/locales/zh-TW.yml | 33 ++- config/locales/zh.yml | 33 ++- config/routes.rb | 6 + ...803043256_create_deployment_credentials.rb | 53 ++++ init.rb | 3 + lib/git_hosting.rb | 48 ++-- .../patches/members_controller_patch.rb | 18 ++ .../patches/my_controller_patch.rb | 6 +- lib/git_hosting/patches/repository_patch.rb | 2 + .../patches/users_controller_patch.rb | 9 +- 64 files changed, 1978 insertions(+), 229 deletions(-) create mode 100644 app/controllers/deployment_credentials_controller.rb create mode 100644 app/models/deployment_credential.rb create mode 100644 app/views/deployment_credentials/_form_with_key.html.erb create mode 100644 app/views/deployment_credentials/create_with_key.html.erb create mode 100644 app/views/deployment_credentials/destroy.html.erb create mode 100644 app/views/deployment_credentials/edit.html.erb create mode 100644 app/views/deployment_credentials/form_error.rjs create mode 100644 app/views/gitolite_public_keys/form_error.rjs create mode 100644 db/migrate/20120803043256_create_deployment_credentials.rb diff --git a/app/controllers/deployment_credentials_controller.rb b/app/controllers/deployment_credentials_controller.rb new file mode 100644 index 000000000..13dfc74dc --- /dev/null +++ b/app/controllers/deployment_credentials_controller.rb @@ -0,0 +1,259 @@ +class DeploymentCredentialsController < ApplicationController + + before_filter :require_login + before_filter :set_user_variable + before_filter :set_project_variable + before_filter :set_repository_variable + + before_filter :can_create_credentials, :only => [:create, :create_with_key] + before_filter :can_edit_credentials, :only => [:edit, :update, :destroy] + + before_filter :check_xhr_request + before_filter :find_deployment_credential, :except => [:index, :new, :create, :create_with_key] + before_filter :find_key, :except => [:index, :new, :create, :create_with_key] + + helper :gitolite_public_keys + include GitolitePublicKeysHelper + + layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'base' } + + def index + render_404 + end + + def create + render_404 + end + + def create_with_key + @cred = DeploymentCredential.new(params[:deployment_credentials]) + @key = nil + if params[:gitolite_public_key] && params[:gitolite_public_key][:id] + @key = GitolitePublicKey.find_by_id(params[:gitolite_public_key][:id]) + if @key && !User.current.admin? && (@key.user != User.current) + # Key not owned by current user -- cannot be used + @key = nil + end + end + @key = GitolitePublicKey.new(params[:gitolite_public_key]) if @key.nil? + if request.get? + # display create_with_key view. Find preexisting keys to offer to user + @user_keys = GitolitePublicKey.active.deploy_key.find_all_by_user_id(User.current.id, :order => "title ASC") + @other_keys = [] + if User.current.admin? + # Admin can use other's deploy keys as well + deploy_users = @project.users.find(:all, :order =>"login ASC").select {|x| x != User.current && x.allowed_to?(:create_deployment_keys,@project)} + @other_keys = deploy_users.map {|user| user.gitolite_public_keys.active.deploy_key.find(:all, :order => "title ASC")}.flatten + end + @disabled_keys = @repository.deployment_credentials.active.map(&:gitolite_public_key) + if @key.new_record? + @key.title = suggested_title + end + else + if @key.new_record? + @key.key_type = GitolitePublicKey::KEY_TYPE_DEPLOY + @key.user = User.current + elsif @key.key_type != GitolitePublicKey::KEY_TYPE_DEPLOY + # Should never happen through normal interface... + render_403 + return + end + @cred.repository = @repository + # If admin, let credential be owned by owner of key... + if User.current.admin? + @cred.user=@key.user + else + @cred.user = User.current + end + + # Make sure that cred will validate even if key is new. + @cred.gitolite_public_key = @key + + GitHostingObserver.set_update_active(false); + @key.valid? # set error messages on key (in case cred is invalid) + if @cred.valid? && @key.save && @cred.save + flash[:notice] = l(:notice_deployment_credential_added, :title=>keylabel(@key),:perm=>@cred[:perm]) + + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @project.id, + :tab => 'repository') + + respond_to do |format| + format.html { + redirect_to redirect_url + } + format.js { + render :update do |page| + page.redirect_to redirect_url + end + } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:error_deployment_credential_create_failed) + render :action => "create_with_key" + } + format.js { + render :action => "form_error" + } + end + end + GitHostingObserver.set_update_active(@project); + end + end + + def edit + # Credential should already be set. + end + + def update + GitHostingObserver.set_update_active(false); + + # Can only alter the permissions + if @cred.update_attributes(params[:deployment_credentials]) + flash[:notice] = l(:notice_deployment_credential_updated, :title=>keylabel(@key),:perm=>@cred[:perm]) + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @project.id, + :tab => 'repository') + + respond_to do |format| + format.html { + redirect_to redirect_url + } + format.js { + render :update do |page| + page.redirect_to redirect_url + end + } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:error_deployment_credential_update_failed) + render :action => "edit" + } + format.js { + render :action => "form_error" + } + end + end + GitHostingObserver.set_update_active(@project); + end + + def destroy + key = @cred.gitolite_public_key + @will_delete_key = key.deploy_key? && key.delete_when_unused && key.deployment_credentials.count == 1 + if request.get? + # display confirmation view + else + GitHostingObserver.set_update_active(false); + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @project.id, + :tab => 'repository') + if params[:confirm] + key = @cred.gitolite_public_key + @cred.destroy + if @will_delete_key && key.deployment_credentials.empty? + # Key no longer used -- delete it! + key.destroy + flash[:notice] = l(:notice_deployment_credential_deleted_with_key, :title=>keylabel(@key),:perm=>@cred[:perm]) + else + flash[:notice] = l(:notice_deployment_credential_deleted, :title=>keylabel(@key),:perm=>@cred[:perm]) + + end + end + respond_to do |format| + format.html {redirect_to(redirect_url)} + end + GitHostingObserver.set_update_active(@project); + end + end + + def settings + end + + protected + + + def can_view_credentials + render_403 unless GitHostingHelper.can_view_deployment_keys(@project) + end + + def can_create_credentials + render_403 unless GitHostingHelper.can_create_deployment_keys(@project) + end + + def can_edit_credentials + render_403 unless GitHostingHelper.can_edit_deployment_keys(@project) + end + + def set_user_variable + @user = User.current + end + + def set_project_variable + @project = Project.find_by_identifier(params[:project_id]) + if !@project + render_404 + end + end + + def set_repository_variable + @repository = @project.repository + if !@repository + render_404 + end + end + + def find_deployment_credential + cred = DeploymentCredential.find_by_id(params[:id]) + if cred && cred.user && cred.repository && (User.current.admin? || cred.user == User.current) + @cred = cred + elsif cred + render_403 + else + render_404 + end + end + + def find_key + key = @cred.gitolite_public_key + if key && key.user && (User.current.admin? || key.user == User.current) + @key = key + elsif key + render_403 + else + render_404 + end + end + + # Suggest title for new one-of deployment key + def suggested_title + # Base of suggested title + default_title = "#{@project.name} Deploy Key" + + # Find number of keys or max default deploy key that matches + maxnum = @repository.deployment_credentials.map(&:gitolite_public_key).uniq.count + @repository.deployment_credentials.each do |cred| + if matches = cred.gitolite_public_key.title.match(/#{default_title} (\d+)$/) + maxnum = [maxnum,matches[1].to_i].max + end + end + # Also, check for uniqueness for current user + @user.gitolite_public_keys.each do |key| + if matches = key.title.match(/#{default_title} (\d+)$/) + maxnum = [maxnum,matches[1].to_i].max + end + end + + "#{default_title} #{maxnum+1}" + end + + def check_xhr_request + @is_xhr ||= request.xhr? + end +end diff --git a/app/controllers/gitolite_public_keys_controller.rb b/app/controllers/gitolite_public_keys_controller.rb index 6b4887669..9ac3a501f 100644 --- a/app/controllers/gitolite_public_keys_controller.rb +++ b/app/controllers/gitolite_public_keys_controller.rb @@ -3,8 +3,14 @@ class GitolitePublicKeysController < ApplicationController before_filter :require_login before_filter :set_user_variable - before_filter :find_gitolite_public_key, :except => [:index, :new, :create] + before_filter :set_users_keys, :except => [:index, :new, :reset_rss_key] + before_filter :find_gitolite_public_key, :except => [:index, :new, :reset_rss_key, :create] + helper :issues + helper :users + helper :custom_fields + helper :gitolite_public_keys + include GitolitePublicKeysHelper def edit redirect_to url_for(:controller=>'my', :action=>'account', :public_key_id => @gitolite_public_key[:id]) @@ -22,26 +28,67 @@ def destroy def update GitHostingObserver.set_update_active(false) if !request.get? - if params[:save_button] && @gitolite_public_key.update_attributes(params[:public_key]) - @gitolite_public_key.save - flash[:notice] = l(:notice_public_key_updated, :title=>@gitolite_public_key[:title]) - elsif params[:delete_button] - destroy_key + if params[:save_button] + if @gitolite_public_key.update_attributes(params[:public_key]) + flash[:notice] = l(:notice_public_key_updated, :title=>keylabel(@gitolite_public_key)) + + respond_to do |format| + format.html { redirect_to @redirect_url } + format.js { render :update do |page| page.redirect_to @redirect_url end } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:error_public_key_create_failed) + # This doesn't give back validation errors (messy!) + redirect_to @redirect_url + } + format.js { + render :action => "form_error" + } + end + end + else + destroy_key if params[:delete_button] + + respond_to do |format| + format.html { redirect_to @redirect_url } + format.js { render :update do |page| page.redirect_to @redirect_url end } + end end end - redirect_to @redirect_url GitHostingObserver.set_update_active(true) end def create GitHostingObserver.set_update_active(false) @gitolite_public_key = GitolitePublicKey.new(params[:public_key].merge(:user => @user)) - if @gitolite_public_key.save - flash[:notice] = l(:notice_public_key_added, :title=>@gitolite_public_key[:title]) + if params[:create_button] + if @gitolite_public_key.save + flash[:notice] = l(:notice_public_key_added, :title=>keylabel(@gitolite_public_key)) + + respond_to do |format| + format.html { redirect_to @redirect_url } + format.js { render :update do |page| page.redirect_to @redirect_url end } + end + else + respond_to do |format| + format.html { + flash[:error] = l(:error_public_key_create_failed) + # This doesn't give back validation errors (messy!) + redirect_to @redirect_url + } + format.js { + render :action => "form_error" + } + end + end else - @gitolite_public_key = GitolitePublicKey.new(:user => @user) + respond_to do |format| + format.html { redirect_to @redirect_url } + format.js { render :update do |page| page.redirect_to @redirect_url end } + end end - redirect_to @redirect_url GitHostingObserver.set_update_active(true) end @@ -61,6 +108,12 @@ def set_user_variable end end + def set_users_keys + @gitolite_user_keys = @user.gitolite_public_keys.active.user_key.find(:all,:order => 'title ASC, created_at ASC') + @gitolite_deploy_keys = @user.gitolite_public_keys.active.deploy_key.find(:all,:order => 'title ASC, created_at ASC') + @gitolite_public_keys = @gitolite_user_keys + @gitolite_deploy_keys + end + def find_gitolite_public_key key = GitolitePublicKey.find_by_id(params[:id]) if key and (@user == key.user || @user.admin?) @@ -75,6 +128,6 @@ def find_gitolite_public_key def destroy_key @gitolite_public_key[:active] = 0 @gitolite_public_key.save - flash[:notice] = l(:notice_public_key_deleted, :title=>@gitolite_public_key[:title]) + flash[:notice] = l(:notice_public_key_deleted, :title=>keylabel(@gitolite_public_key)) end end diff --git a/app/controllers/repository_mirrors_controller.rb b/app/controllers/repository_mirrors_controller.rb index efbaf5500..439bd79a3 100644 --- a/app/controllers/repository_mirrors_controller.rb +++ b/app/controllers/repository_mirrors_controller.rb @@ -94,15 +94,20 @@ def destroy if request.get? # display confirmation view else + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @mirror.project.identifier, + :tab => 'repository') if params[:confirm] - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') @mirror.destroy - respond_to do |format| - format.html {redirect_to(redirect_url, :notice => l(:mirror_notice_deleted))} - end + + flash[:notice] = l(:mirror_notice_deleted) + end + + respond_to do |format| + format.html { + redirect_to(redirect_url) + } end end end @@ -126,7 +131,7 @@ def set_user_variable end def set_project_variable - @project = Project.find(:first, :conditions => ["identifier = ?", params[:project_id]]) + @project = Project.find_by_identifier(params[:project_id]) end def find_repository_mirror diff --git a/app/controllers/repository_post_receive_urls_controller.rb b/app/controllers/repository_post_receive_urls_controller.rb index bdb2fc25b..631d86085 100644 --- a/app/controllers/repository_post_receive_urls_controller.rb +++ b/app/controllers/repository_post_receive_urls_controller.rb @@ -94,15 +94,20 @@ def destroy if request.get? # display confirmation view else + redirect_url = url_for(:controller => 'projects', + :action => 'settings', + :id => @prurl.project.identifier, + :tab => 'repository') if params[:confirm] - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') @prurl.destroy - respond_to do |format| - format.html {redirect_to(redirect_url, :notice => l(:post_receive_url_notice_deleted))} - end + + flash[:notice] = l(:post_receive_url_notice_deleted) + end + + respond_to do |format| + format.html { + redirect_to(redirect_url) + } end end end @@ -118,7 +123,7 @@ def set_user_variable end def set_project_variable - @project = Project.find(:first, :conditions => ["identifier = ?", params[:project_id]]) + @project = Project.find_by_identifier(params[:project_id]) end def find_repository_post_receive_url diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 99d583e3b..34084cdd1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -11,6 +11,7 @@ module ApplicationHelper # and override properly defined versions). Note that Redmine 1.4+ removes # lib/tabular_form_builder.rb but defines these functions using new # builder functions... + # 8/10/12 Added "labelled_fields_for", since that seems to be coming in 1.4 if !defined?(labelled_form_for) && File.exists?(Rails.root.join("lib/tabular_form_builder.rb")) def labelled_form_for(*args, &proc) args << {} unless args.last.is_a?(Hash) @@ -29,6 +30,15 @@ def labelled_remote_form_for(*args, &proc) end end + if !defined?(labelled_fields_for) && File.exists?(Rails.root.join("lib/tabular_form_builder.rb")) + def labelled_fields_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => TabularFormBuilder,:lang => current_language}) + fields_for(*args, &proc) + end + end + # Generic helper functions def reldir_add_dotslash(path) # Is this a relative path? diff --git a/app/helpers/git_hosting_helper.rb b/app/helpers/git_hosting_helper.rb index df822121a..3406b6423 100644 --- a/app/helpers/git_hosting_helper.rb +++ b/app/helpers/git_hosting_helper.rb @@ -53,6 +53,23 @@ def self.can_edit_post_receive_urls(project) return User.current.allowed_to?(:edit_repository_post_receive_urls, project) end + def self.can_create_deployment_keys(project) + return User.current.admin? || User.current.allowed_to?(:create_deployment_keys, project) + end + def self.can_view_deployment_keys(project) + return User.current.admin? || User.current.allowed_to?(:view_deployment_keys, project) + end + def self.can_edit_deployment_keys(project) + return User.current.admin? || User.current.allowed_to?(:edit_deployment_keys, project) + end + def self.can_create_deployment_keys_for_some_project(theuser=User.current) + return true if theuser.admin? + theuser.projects_by_role.each_key do |role| + return true if role.allowed_to?(:create_deployment_keys) + end + false + end + @@file_actions = { "a" => "add", "m" => "modify", diff --git a/app/helpers/gitolite_public_keys_helper.rb b/app/helpers/gitolite_public_keys_helper.rb index a31fcf3e9..e50482a3b 100644 --- a/app/helpers/gitolite_public_keys_helper.rb +++ b/app/helpers/gitolite_public_keys_helper.rb @@ -6,4 +6,19 @@ def gitolite_public_keys_status_options_for_select(user, selected) ["#{l(:status_locked)} (#{key_count_by_active[false].to_i})", GitolitePublicKey::STATUS_LOCKED]], selected) end + def keylabel(key) + if key.user == User.current + "\"#{key.title}\"" + else + "\"#{key.user.login}@#{key.title}\"" + end + end + + def keylabel_text(key) + if key.user == User.current + "#{key.title}" + else + "#{key.user.login}@#{key.title}" + end + end end diff --git a/app/models/deployment_credential.rb b/app/models/deployment_credential.rb new file mode 100644 index 000000000..708a76373 --- /dev/null +++ b/app/models/deployment_credential.rb @@ -0,0 +1,83 @@ +class DeploymentCredential < ActiveRecord::Base + STATUS_ACTIVE = 1 + STATUS_INACTIVE = 0 + + belongs_to :repository + belongs_to :gitolite_public_key + belongs_to :user + + attr_accessible :perm + validates_presence_of :perm, :user, :repository + validates_presence_of :gitolite_public_key + validate :correct_key_type, :correct_perm_type, :no_duplicate_creds + validate :owner_matches_key + + named_scope :active, {:conditions => {:active => STATUS_ACTIVE}} + named_scope :inactive, {:conditions => {:active => STATUS_INACTIVE}} + + def perm= (value) + write_attribute(:perm, (value.upcase rescue nil)) + end + + # Provide a role-like interface. + # Support :commit_access and :view_changesets + @@equivalence = nil + def allowed_to?( cred ) + @@equivalence ||= { + :view_changesets => ["R","RW+"], + :commit_access => ["RW+"] + } + return false unless honored? + + # Deployment Credentials equivalence matrix + return false unless @@equivalence[cred] && @@equivalence[cred].index(perm) + true + end + + # Deployment Credentials ignored unless created by someone who still has permission to create them + def honored? + user.admin? || user.allowed_to?(:create_deployment_keys,repository.project) + end + + def self.valid_perms + ["R", "RW+"] + end + + def self.default_perm + "RW+" + end + + def to_s + return File.join("Deploy Key: #{repository.identifier}-#{gitolite_public_key.identifier}: #{mode.to_s}") + end + + protected + + def correct_key_type + if gitolite_public_key && gitolite_public_key.key_type != GitolitePublicKey::KEY_TYPE_DEPLOY + errors.add_to_base("Public Key Must Be a Deployment Key") + end + end + + def correct_perm_type + if !self.class.valid_perms.index(perm) + errors.add(:perm, "must be one of #{self.class.valid_perms.join(',')}") + end + end + + def owner_matches_key + return if user.nil? || gitolite_public_key.nil? + if user != gitolite_public_key.user + errors.add_to_base("Credential owner cannot be different than owner of Key.") + end + end + + def no_duplicate_creds + return if !new_record? || repository.nil? || gitolite_public_key.nil? + repository.deployment_credentials.each do |cred| + if cred.gitolite_public_key == gitolite_public_key + errors.add_to_base("This Public Key has already been used in a Deployment Credential for this repository.") + end + end + end +end diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 74dc5d31b..2de728ad9 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -2,34 +2,69 @@ class GitolitePublicKey < ActiveRecord::Base STATUS_ACTIVE = 1 STATUS_LOCKED = 0 + KEY_TYPE_USER = 0 + KEY_TYPE_DEPLOY = 1 + + DEPLOY_PSEUDO_USER = "_deploy_key_" + belongs_to :user validates_uniqueness_of :title, :scope => :user_id - validates_uniqueness_of :identifier, :score => :user_id - validates_presence_of :title, :key, :identifier + validates_uniqueness_of :identifier, :scope => :user_id + validates_presence_of :title, :key, :identifier, :key_type + + has_many :deployment_credentials, :dependent => :destroy + def validate_associated_records_for_deployment_credentials() end named_scope :active, {:conditions => {:active => GitolitePublicKey::STATUS_ACTIVE}} named_scope :inactive, {:conditions => {:active => GitolitePublicKey::STATUS_LOCKED}} + named_scope :user_key, {:conditions => {:key_type => GitolitePublicKey::KEY_TYPE_USER}} + named_scope :deploy_key, {:conditions => {:key_type => GitolitePublicKey::KEY_TYPE_DEPLOY}} + validate :has_not_been_changed + validates_inclusion_of :key_type, :in => [KEY_TYPE_USER, KEY_TYPE_DEPLOY] before_validation :set_identifier before_validation :remove_control_characters def has_not_been_changed unless new_record? - %w(identifier key user_id).each do |attribute| + %w(identifier key user_id key_type).each do |attribute| errors.add(attribute, 'may not be changed') unless changes[attribute].blank? end end end def set_identifier - # add "redmine_" as a prefix to the username, and then the current date - # this helps ensure uniqueness of each key identifier - # - # also, it ensures that it is very, very unlikely to conflict with any - # existing key name if gitolite config is also being edited manually - self.identifier ||= "redmine_#{self.user.login.underscore}_#{Time.now.to_i.to_s}_#{Time.now.usec.to_s}".gsub(/[^0-9a-zA-Z\-]/,'_') + self.identifier ||= + begin + my_time = Time.now + time_tag = "#{my_time.to_i.to_s}_#{my_time.usec.to_s}" + case key_type + when KEY_TYPE_USER + # add "redmine_" as a prefix to the username, and then the current date + # this helps ensure uniqueness of each key identifier + # + # also, it ensures that it is very, very unlikely to conflict with any + # existing key name if gitolite config is also being edited manually + "redmine_#{self.user.login.underscore}_#{time_tag}".gsub(/[^0-9a-zA-Z\-]/,'_') + when KEY_TYPE_DEPLOY + # add "redmine_deploy_key_" as a prefix, and then the current date + # to help ensure uniqueness of each key identifier + "redmine_#{DEPLOY_PSEUDO_USER}_#{time_tag}" + else + nil + end + end + end + + # Key type checking functions + def user_key? + key_type == KEY_TYPE_USER + end + + def deploy_key? + key_type == KEY_TYPE_DEPLOY end # Make sure that current identifier is consistent with current user login. diff --git a/app/views/deployment_credentials/_form_with_key.html.erb b/app/views/deployment_credentials/_form_with_key.html.erb new file mode 100644 index 000000000..fee87f946 --- /dev/null +++ b/app/views/deployment_credentials/_form_with_key.html.erb @@ -0,0 +1,54 @@ +
<%= error_messages_for 'cred' %>
+ +
+ +<% labelled_fields_for :gitolite_public_key, @key do |k| %> +
+ <% if !@user_keys.empty? || !@other_keys.empty? %> + <% option_array = [[l(:select_create_new_key),-1]] %> + <% option_array += (@user_keys.map {|key| [keylabel_text(key),key.id]}) %> + <% if !@other_keys.empty? %> + <% option_array2 = (@other_keys.map {|key| [keylabel_text(key), key.id]}) %> + <% maxlen = (option_array+option_array2).map{|x|x.first.length}.max %> + <% extra = ([maxlen - l(:select_other_keys).length - 2,6].max)/2 %> + <% option_array += [[("-"*extra)+" "+l(:select_other_keys)+" "+"-"*extra, -2]] %> + <% option_array += option_array2 %> + <% end %> +

<%= k.select :id, options_for_select(option_array, :selected => -1, :disabled => [-2]+@disabled_keys.map(&:id)), :required => true, :label => :label_which_deploy_key %>

+ <% end %> +

<%= f.select :perm, options_for_select(DeploymentCredential::valid_perms,DeploymentCredential::default_perm), :required => true, :label => :label_deploy_perm %>

+
+
+
<%= l(:label_public_key_new) %> +

<%= k.text_field :title, :label => :label_identifier_can_be_arbitrary, :required => true, :style => 'width:99%;' %>

+

<%= k.check_box :delete_when_unused, :required => true, :label => :field_delete_when_unused %>

+

<%= k.text_area :key, :required => true, :label => :label_cut_and_paste, + :style => "width:99%;height:125px;overflow:auto;", + :cols => nil, :rows => nil %>

+
+
+<% end %> +
+ + + diff --git a/app/views/deployment_credentials/create_with_key.html.erb b/app/views/deployment_credentials/create_with_key.html.erb new file mode 100644 index 000000000..ed337c871 --- /dev/null +++ b/app/views/deployment_credentials/create_with_key.html.erb @@ -0,0 +1,7 @@ +<% if not @is_xhr %>

<%=l(:label_deployment_credential_create)%>

<% end %> +<% labelled_remote_form_for :deployment_credentials, @cred, + :url => { :controller => 'deployment_credentials', :action => 'create_with_key', :project_id => @project, :id => @cred.id }, + :html => {:method => :post} do |f| %> +<%= render :partial => 'form_with_key', :locals => { :f => f } %> +<%= submit_tag l(:button_save) %> +<% end %> diff --git a/app/views/deployment_credentials/destroy.html.erb b/app/views/deployment_credentials/destroy.html.erb new file mode 100644 index 000000000..1fabd6c59 --- /dev/null +++ b/app/views/deployment_credentials/destroy.html.erb @@ -0,0 +1,16 @@ +<% if not @is_xhr %>

<%=l(:label_deployment_credential_delete)%>

<% end %> +
+

+ <%= "#{keylabel(@cred.gitolite_public_key)} => #{@cred.perm}".html_safe %>
+ Are you sure you want to delete this deployment credential? + <% if @will_delete_key %> +
(Will delete corresponding key: <%= "#{keylabel(@cred.gitolite_public_key)}".html_safe %>) + <% end %> +

+ <% form_tag url_for(:controller => 'deployment_credentials', :action => 'destroy', :project_id => @project, :id => @cred.id ), :method => :delete do %> +

+ + <%= submit_tag l(:button_delete) %> +

+ <% end %> +
diff --git a/app/views/deployment_credentials/edit.html.erb b/app/views/deployment_credentials/edit.html.erb new file mode 100644 index 000000000..8a24dee9a --- /dev/null +++ b/app/views/deployment_credentials/edit.html.erb @@ -0,0 +1,11 @@ +<% if not @is_xhr %>

<%=l(:label_deployment_credential_edit)%>

<% end %> +
<%= error_messages_for 'cred' %>
+<% remote_form_for :deployment_credentials, @cred, + :url => { :controller => 'deployment_credentials', :action => 'update', :project_id => @project, :id => @cred.id }, + :html => {:method => :post} do |f| %> +
+<%= "#{keylabel(@key)} => ".html_safe %> +<%= f.select :perm, options_for_select(DeploymentCredential::valid_perms,@cred.perm), :required => true %> +
+<%= submit_tag l(:button_save) %> +<% end %> diff --git a/app/views/deployment_credentials/form_error.rjs b/app/views/deployment_credentials/form_error.rjs new file mode 100644 index 000000000..90ce2b17e --- /dev/null +++ b/app/views/deployment_credentials/form_error.rjs @@ -0,0 +1,3 @@ +page.replace_html 'validation_messages', ((error_messages_for 'cred')+(error_messages_for 'key')) +page << "if (typeof(Modalbox) != 'undefined' ) { Modalbox.resizeToContent(); }" + diff --git a/app/views/gitolite_public_keys/_view.html.erb b/app/views/gitolite_public_keys/_view.html.erb index a5002990c..dafe4d647 100644 --- a/app/views/gitolite_public_keys/_view.html.erb +++ b/app/views/gitolite_public_keys/_view.html.erb @@ -2,43 +2,121 @@
<%=l(:label_public_keys)%> -
<%= l(:label_current_public_keys)%> +<% if !@gitolite_user_keys.empty? || @gitolite_deploy_keys.empty? %> +
<%= @gitolite_deploy_keys.empty? ? l(:label_current_public_keys) : l(:label_current_user_keys) %> - <% if @gitolite_public_keys.empty? %> + <% if @gitolite_user_keys.empty? %> <%end %> - <% @gitolite_public_keys.each do |key| %> + <% @gitolite_user_keys.each do |key| %> <% if params[:id] %> - + <% end %> <% end %>
<%= h(key) %><%= "keydir/#{key.identifier}.pub" %><%= "keydir/#{key.identifier}.pub" %> <%= link_to(l(:button_edit), { :public_key_id => key.id, :tab => params[:id]&&'keys'}, :class => 'icon icon-edit') %> - <%= link_to(l(:button_delete), public_key_path(key, :user_id=>params[:id]), :method => 'delete', :class => 'icon icon-del', :confirm=>l(:text_gitolite_key_destroy_confirmation, :title=>key[:title])) %> + <%= link_to(l(:button_delete), public_key_path(key, :user_id=>params[:id]), :method => 'delete', :class => 'icon icon-del', :confirm=>l(:text_gitolite_key_destroy_confirmation, :title=>keylabel(key))) %>

+<% end %> + +<% if !@gitolite_deploy_keys.empty? %> +
<%= l(:label_current_deploy_keys)%> + + <% if @gitolite_deploy_keys.empty? %> + + <%end %> + <% @gitolite_deploy_keys.each do |key| %> + + + <% if params[:id] %> + + <% end %> + + + <% end %> +
<%= h(key) %><%= "keydir/#{key.identifier}.pub" %> + <%= link_to(l(:button_edit), { :public_key_id => key.id, :tab => params[:id]&&'keys'}, :class => 'icon icon-edit') %> + <%= link_to(l(:button_delete), public_key_path(key, :user_id=>params[:id]), :method => 'delete', :class => 'icon icon-del', :confirm=>l(:text_gitolite_key_destroy_confirmation, :title=>keylabel(key))) %> +
+

+<% end %> <% @new_key = @gitolite_public_key.new_record? %>
<%= @new_key ? l(:label_public_key_new) : l(:label_public_key_edit) %> -<% labelled_form_for :public_key, @gitolite_public_key, +
<%= error_messages_for 'gitolite_public_key' %>
+<% labelled_remote_form_for :public_key, @gitolite_public_key, :url=>{:controller=>'gitolite_public_keys', :action=>@new_key ? 'create' : 'update', :id=>@gitolite_public_key.id, :user_id=>params[:id], :tab => params[:id]&&'keys'}, :html => {:method=>(@new_key ? :post : :put)} do |f| %>

<%= f.text_field :title, :label => :label_identifier_can_be_arbitrary, :required => true, :style => 'width:99%;' %>

+ <% if @gitolite_public_key.key_type==1 || GitHostingHelper.can_create_deployment_keys_for_some_project(@user) %> +
+

+ <%= f.select :key_type, options_for_select([[l(:label_user_key),0],[l(:label_deploy_key),1]], :selected => @gitolite_public_key.key_type, :disabled => (@new_key ? [] : [0,1])), :required => true, :label => :field_key_type %> +

+ +
+ <% end %>

<%= f.text_area :key, :label => (@new_key? :label_cut_and_paste : :field_public_key), :required => true, :disabled => !@new_key, - :style => "width:99%;height:#{params[:id]?150:250}px;overflow:auto;", + :style => "width:99%;height:#{params[:id]?100:200}px;overflow:auto;", :cols => nil, :rows => nil %> + <%= hidden_field_tag :button_submit_field, "true" %> <% if !@new_key%> <%= l(:label_key_cannot_be_changed_please_create_new_key) %>

<%= submit_tag l(:button_save), :name=>'save_button' %> - <%= submit_tag l(:button_delete), :name=>'delete_button', :confirm => l(:text_gitolite_key_destroy_confirmation,:title=>@gitolite_public_key[:title]) %> + <%= submit_tag l(:button_delete), :name=>'delete_button', :confirm => l(:text_gitolite_key_destroy_confirmation,:title=>(@gitolite_public_key[:title].blank? ? l(:text_this_key) : keylabel(@gitolite_public_key))) %> <%= submit_tag l(:button_cancel), :name=>'cancel_button' %> <% else %> -

<%= submit_tag l(:button_create) %> +

+ <%= submit_tag l(:button_create), :name=>'create_button' %> + <% mystyle = (@gitolite_public_key.errors.any? ? '' : 'style="display:none;"') %> + > + <%= submit_tag l(:button_cancel), :name=>'cancel_button' %> + <% end %> <% end %>
+ + diff --git a/app/views/gitolite_public_keys/form_error.rjs b/app/views/gitolite_public_keys/form_error.rjs new file mode 100644 index 000000000..a47fe8cbf --- /dev/null +++ b/app/views/gitolite_public_keys/form_error.rjs @@ -0,0 +1,9 @@ +page.replace_html 'validation_messages', (error_messages_for 'gitolite_public_key') +page.select('div.notice').each do |d| + page.replace d, nil +end +page[:cancel_button_div].show + + + + diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index 8f8e46b49..e4b58c702 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -4,7 +4,6 @@

<%=l(:label_my_account)%>

<%= error_messages_for 'user' %> -<%= error_messages_for 'gitolite_public_key' %>
<% labelled_form_for :user, @user, :url => { :action => "account" }, :html => { :id => 'my_account_form' } do |f| %> @@ -40,6 +39,6 @@
<% content_for :sidebar do %> - <%= render :partial => 'sidebar' %> + <%= render :partial => 'my/sidebar' %> <% end %> <% html_title(l(:label_my_account)) -%> diff --git a/app/views/projects/settings/_repository.html.erb b/app/views/projects/settings/_repository.html.erb index 524965b68..7729f60da 100644 --- a/app/views/projects/settings/_repository.html.erb +++ b/app/views/projects/settings/_repository.html.erb @@ -1,4 +1,3 @@ - <% content_for :header_tags do %> <%= stylesheet_link_tag('zero_clipboard', :plugin => 'redmine_git_hosting') %> <%= javascript_include_tag('ZeroClipboard', :plugin => 'redmine_git_hosting') %> @@ -79,6 +78,66 @@ + <% if @repository && @repository.is_a?(Repository::Git) && GitHostingHelper.can_view_deployment_keys(@repository.project) %> + + + + +
+

Deployment Credentials

+ + <% if GitHostingHelper.can_create_deployment_keys(@repository.project) %> +
+ <%= link_to("Add Deployment Credential", url_for(:controller => 'deployment_credentials', :action => 'create_with_key', :project_id => @repository.project.identifier), :class => 'icon icon-add add-deployment-credential' ) %> +
+ <% end %> + + <% if @repository.deployment_credentials.active.any? %> +
+ + + + + + + + + + + + + <% @repository.deployment_credentials.active.sort_by {|x| [x.user.login, x.gitolite_public_key.title]}.each do |cred| %> + + + + + + + + + <% end %> + +
OwnerPublic Key NamePublic Key FilePermHonored? 
<%= cred.user.login %> + <% if cred.user == User.current %> + <%= link_to(cred.gitolite_public_key.title, url_for(:controller => 'my', :action => 'account', :public_key_id => cred.gitolite_public_key.id)) %> + <% elsif User.current.admin? %> + <%= link_to(cred.gitolite_public_key.title, url_for(:controller => 'users', :action => 'edit', :id => cred.gitolite_public_key.user.id, :public_key_id => cred.gitolite_public_key.id, :tab => 'keys')) %> + <% else %> + <%= cred.gitolite_public_key.title %> + <% end %> + <%= "keydir/#{cred.gitolite_public_key.identifier}.pub" %><%= cred.perm %><%= checked_image cred.honored? %> + <% if GitHostingHelper.can_edit_deployment_keys(@repository.project) && (User.current.admin?|| User.current == cred.user) %> + <%= link_to(l(:button_edit), url_for(:controller => 'deployment_credentials', :action => 'edit', :project_id => @repository.project.identifier, :id => cred.id), :class => 'icon icon-edit edit-deployment-credential') %> + <%= link_to(l(:button_delete), url_for(:controller => 'deployment_credentials', :action => 'destroy', :project_id => @repository.project.identifier, :id => cred.id), :class => 'icon icon-del delete-deployment-credential') %> + <% end %> +
+
+ <% else %> +

No Deployment Credentials Defined

+ <% end %> + +
+ <% end %> <% if @repository && @repository.is_a?(Repository::Git) && GitHostingHelper.can_view_post_receive_urls(@repository.project) %> @@ -108,10 +167,10 @@ <% @repository.project.repository_post_receive_urls.each do |prurl| %> <%= link_to(h(prurl.url), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'edit-post-receive-url') %> - <%= post_receive_mode prurl %>
<%= checked_image prurl.active? %> - + <%= post_receive_mode prurl %>
<%= checked_image prurl.active? %> + <% if GitHostingHelper.can_edit_post_receive_urls(@repository.project) %> - <%= link_to(l(:button_edit), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'icon icon-edit edit-post-receive-url') %>
+ <%= link_to(l(:button_edit), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'icon icon-edit edit-post-receive-url') %> <%= link_to(l(:button_delete), url_for(:controller => 'repository_post_receive_urls', :action => 'destroy', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'icon icon-del delete-post-receive-url') %> <% end %> @@ -153,13 +212,13 @@ <%= link_to(h(mirror.url), url_for(:controller => 'repository_mirrors', :action => 'edit', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'edit-mirror') %> <%= refspec mirror, 64 %> - <%= mirror_mode mirror %>
<%= checked_image mirror.active? %> - + <%= mirror_mode mirror %>
<%= checked_image mirror.active? %> + <% if GitHostingHelper.can_edit_mirrors(@repository.project) %> <% if mirror.active==1 -%> - <%= link_to(l(:button_push), url_for(:controller => 'repository_mirrors', :action => 'push', :project_id => @repository.project.identifier, :id => mirror.id), :title => l(:button_push_title), :alt => l(:button_push_title), :class => 'icon icon-push push-mirror') %>
+ <%= link_to(l(:button_push), url_for(:controller => 'repository_mirrors', :action => 'push', :project_id => @repository.project.identifier, :id => mirror.id), :title => l(:button_push_title), :alt => l(:button_push_title), :class => 'icon icon-push push-mirror') %> <% end %> - <%= link_to(l(:button_edit), url_for(:controller => 'repository_mirrors', :action => 'edit', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'icon icon-edit edit-mirror') %>
+ <%= link_to(l(:button_edit), url_for(:controller => 'repository_mirrors', :action => 'edit', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'icon icon-edit edit-mirror') %> <%= link_to(l(:button_delete), url_for(:controller => 'repository_mirrors', :action => 'destroy', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'icon icon-del delete-mirror') %> <% end %> @@ -292,6 +351,42 @@ return false; }) }) + $$(".add-deployment-credential").each(function(elem) { + Event.observe(elem, 'click', function(event) { + Event.stop(event); + Modalbox.show( + elem.href, { + title: "<%=l(:label_deployment_credential_create)%>", + width: getWidth() + } + ) + return false; + }) + }) + $$(".edit-deployment-credential").each(function(elem) { + Event.observe(elem, 'click', function(event) { + Event.stop(event); + Modalbox.show( + elem.href, { + title: "<%=l(:label_deployment_credential_edit)%>", + width: getWidth() + } + ) + return false; + }) + }) + $$(".delete-deployment-credential").each(function(elem) { + Event.observe(elem, 'click', function(event) { + Event.stop(event); + Modalbox.show( + elem.href, { + title: "<%=l(:label_deployment_credential_delete)%>", + width: getWidth() + } + ) + return false; + }) + }) }) diff --git a/app/views/repository_mirrors/_form.html.erb b/app/views/repository_mirrors/_form.html.erb index e1bac8800..99d582859 100644 --- a/app/views/repository_mirrors/_form.html.erb +++ b/app/views/repository_mirrors/_form.html.erb @@ -8,8 +8,8 @@

<%= f.check_box :include_all_branches, :label => :field_include_all_branches %>

<%= f.check_box :include_all_tags, :label => :field_include_all_tags %>

<%= f.text_field :explicit_refspec, :size => 65 %>

-

<%= f.check_box :active %>

+

<%= f.check_box :active %>

diff --git a/assets/stylesheets/application.css b/assets/stylesheets/application.css index 8166c27b5..1227d6cca 100644 --- a/assets/stylesheets/application.css +++ b/assets/stylesheets/application.css @@ -45,7 +45,7 @@ padding:5px; .public_key_view label { font-weight: bold; -text-align: left; +text-align: right; } .public_key_view .myhead { diff --git a/config/locales/bg.yml b/config/locales/bg.yml index bac4065ef..72b772751 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -62,20 +62,45 @@ bg: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/bs.yml b/config/locales/bs.yml index 7158f7e3d..acb112115 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -62,20 +62,45 @@ bs: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/ca.yml b/config/locales/ca.yml index ed842f7a9..3675b3f32 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -62,20 +62,45 @@ ca: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/cs.yml b/config/locales/cs.yml index e1fa96dde..57b8e66ba 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -62,20 +62,45 @@ cs: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/da.yml b/config/locales/da.yml index 973630c8d..9fe0a79f2 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -62,20 +62,45 @@ da: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/de.yml b/config/locales/de.yml index 5e5a4b949..55cbc2d64 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -62,20 +62,45 @@ de: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/el.yml b/config/locales/el.yml index be4529f04..d525128b1 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -62,20 +62,45 @@ el: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/en.yml b/config/locales/en.yml index cb36bc86c..368b9b327 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -62,20 +62,45 @@ en: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/es.yml b/config/locales/es.yml index 573b7f209..0d64f8331 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -62,20 +62,45 @@ es: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/fi.yml b/config/locales/fi.yml index d63be5e6d..e4b50c459 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -62,20 +62,45 @@ fi: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 4d2ac0a49..bc0a2849a 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -62,20 +62,45 @@ fr: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/gl.yml b/config/locales/gl.yml index d03af4042..10eae6611 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -62,20 +62,45 @@ gl: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/he.yml b/config/locales/he.yml index 02252b0e6..66ece3a8e 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -62,20 +62,45 @@ he: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/hu.yml b/config/locales/hu.yml index e6c37c6cc..73fe1aa3b 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -62,20 +62,45 @@ label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/id.yml b/config/locales/id.yml index 8e63e11b1..7033e1721 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -62,20 +62,45 @@ id: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/it.yml b/config/locales/it.yml index 6be58efd4..aabcbecad 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -62,20 +62,45 @@ it: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 0e6ae9c4f..b978f96ef 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -62,20 +62,45 @@ ja: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 6cb43503e..3db715414 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -62,20 +62,45 @@ ko: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 1f621b474..c91b0e9cf 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -62,20 +62,45 @@ lt: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/nl.yml b/config/locales/nl.yml index a1a64f9cc..0b9f633e2 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -62,20 +62,45 @@ nl: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/no.yml b/config/locales/no.yml index 5b01171c6..97b4a9062 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -62,20 +62,45 @@ label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 8c2fc822a..45085fa2d 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -62,20 +62,45 @@ pl: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index d36ceb9bd..520a23764 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -60,20 +60,45 @@ pt-BR: label_public_keys: 'Chaves públicas' label_current_public_keys: 'As chaves públicas atuais' + label_current_user_keys: 'Atuais Chaves Usuário' + label_current_deploy_keys: 'Atuais Chaves Deployment' + label_user_key: 'Chaves Usuário' + label_deploy_key: 'Chaves Deployment' + field_key_type: 'Tipo de Chave Pública' + field_delete_when_unused: 'Excluir chave quando não for utilizado' label_identifier_can_be_arbitrary: 'Identificador (Qualquer texto significativa)' label_cut_and_paste: 'Chave (cortar e colar arquivo .pub ficheiro)' label_public_key_new: 'Nova chave pública' label_public_key_edit: 'Editar a chave pública existentes' + field_key: Chave field_public_key: Chave - notice_public_key_updated: 'A chave pública "%{title}" foi actualizada com sucesso.' - notice_public_key_added: 'A chave pública "%{title}" foi adicionada com sucesso.' - notice_public_key_deleted: 'A chave pública "%{title}" foi excluído com sucesso.' + field_title: Identifier + notice_public_key_updated: 'A chave pública %{title} foi actualizada com sucesso.' + notice_public_key_added: 'A chave pública %{title} foi adicionada com sucesso.' + notice_public_key_deleted: 'A chave pública %{title} foi excluído com sucesso.' + error_public_key_create_failed: 'Não Conseguiu Criar Chave Pública' + error_public_key_update_failed: 'Não Conseguiu Atualizar Chave Pública' label_key_cannot_be_changed_please_create_new_key: 'A chave já não pode ser alterada. No entanto, pode excluí-lo e criar uma nova.' activerecord: errors: messages: 'não pode ser mudada': 'não pode ser mudada' - text_gitolite_key_destroy_confirmation: 'Tem certeza de que quer destruir "%{title}"?' + text_gitolite_key_destroy_confirmation: 'Tem certeza de que quer destruir %{title}?' + text_this_key: esta chave + label_deploy_perm: 'Permissões de Acesso' + select_create_new_key: 'Criar Nova Chave (Abaixo)' + select_other_keys: 'Outras Chaves' + label_deployment_credential_create: 'Criar Credencial Deployment Novo' + label_deployment_credential_edit: 'Editar Credencial Deployment' + label_deployment_credential_delete: 'Excluir Credencial Deployment' + label_which_deploy_key: 'Selecione a Chave Deployment' + notice_deployment_credential_updated: "Credencial Deployment %{title} => %{perm} foi atualizado com sucesso." + notice_deployment_credential_added: "Credencial Deployment %{title} => %{perm} foi adicionado com sucesso." + notice_deployment_credential_deleted: "Credencial Deployment %{title} => %{perm} excluído com sucesso." + notice_deployment_credential_deleted_with_key: "Ambos os Credencial Deployment %{title} => %{perm} and Key %{title} foram excluído com sucesso." + error_deployment_credential_create_failed: 'Não Conseguiu Criar Credencial Deployment.' + error_deployment_credential_update_failed: 'Não Conseguiu Atualizar Credencial Deployment.' + field_perm: 'Permissões' label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/pt.yml b/config/locales/pt.yml index c420e5a0d..2361afcbc 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -6,8 +6,8 @@ pt: label_git_user: Nome de Utilizador Git label_gitolite_identity_public_key_file: Gitolite SSH Identity File (Public Key) label_gitolite_identity_file: Ficheiro de identidade SSH Gitolite (Chave Privada) - label_gitolite_config_file: Arquivo de configuração Gitolite
(relativo ao diretório conf) - label_gitolite_config_has_admin_key: Arquivo de configuração Gitolite tem admin chave + label_gitolite_config_file: 'Arquivo de configuração Gitolite
(relativo ao diretório conf)' + label_gitolite_config_has_admin_key: 'Arquivo de configuração Gitolite tem admin chave' label_git_repository_base_path: 'Directório Git base (Relativo à "home" do utilizador git)' label_git_recycle_base_path: 'Directório Recycle Bin base (Relativo à "home" do utilizador git)' label_git_recycle_expire_time: 'expirar o tempo para Recycle Bin (em horas)' @@ -60,20 +60,45 @@ pt: label_public_keys: 'Chaves públicas' label_current_public_keys: 'As chaves públicas atuais' + label_current_user_keys: 'Atuais Chaves Usuário' + label_current_deploy_keys: 'Atuais Chaves Deployment' + label_user_key: 'Chaves Usuário' + label_deploy_key: 'Chaves Deployment' + field_key_type: 'Tipo de Chave Pública' + field_delete_when_unused: 'Excluir chave quando não for utilizado' label_identifier_can_be_arbitrary: 'Identificador (Qualquer texto significativa)' label_cut_and_paste: 'Chave (cortar e colar arquivo .pub ficheiro)' label_public_key_new: 'Nova chave pública' label_public_key_edit: 'Editar a chave pública existentes' + field_key: Chave field_public_key: Chave - notice_public_key_updated: 'A chave pública "%{title}" foi actualizada com sucesso.' - notice_public_key_added: 'A chave pública "%{title}" foi adicionada com sucesso.' - notice_public_key_deleted: 'A chave pública "%{title}" foi excluído com sucesso.' + field_title: Identifier + notice_public_key_updated: 'A chave pública %{title} foi actualizada com sucesso.' + notice_public_key_added: 'A chave pública %{title} foi adicionada com sucesso.' + notice_public_key_deleted: 'A chave pública %{title} foi excluído com sucesso.' + error_public_key_create_failed: 'Não Conseguiu Criar Chave Pública' + error_public_key_update_failed: 'Não Conseguiu Atualizar Chave Pública' label_key_cannot_be_changed_please_create_new_key: 'A chave já não pode ser alterada. No entanto, pode excluí-lo e criar uma nova.' activerecord: errors: messages: 'não pode ser mudada': 'não pode ser mudada' - text_gitolite_key_destroy_confirmation: 'Tem certeza de que quer destruir "%{title}"?' + text_gitolite_key_destroy_confirmation: 'Tem certeza de que quer destruir %{title}?' + text_this_key: esta chave + label_deploy_perm: 'Permissões de Acesso' + select_create_new_key: 'Criar Nova Chave (Abaixo)' + select_other_keys: 'Outras Chaves' + label_deployment_credential_create: 'Criar Credencial Deployment Novo' + label_deployment_credential_edit: 'Editar Credencial Deployment' + label_deployment_credential_delete: 'Excluir Credencial Deployment' + label_which_deploy_key: 'Selecione a Chave Deployment' + notice_deployment_credential_updated: "Credencial Deployment %{title} => %{perm} foi atualizado com sucesso." + notice_deployment_credential_added: "Credencial Deployment %{title} => %{perm} foi adicionado com sucesso." + notice_deployment_credential_deleted: "Credencial Deployment %{title} => %{perm} excluído com sucesso." + notice_deployment_credential_deleted_with_key: "Ambos os Credencial Deployment %{title} => %{perm} and Key %{title} foram excluído com sucesso." + error_deployment_credential_create_failed: 'Não Conseguiu Criar Credencial Deployment.' + error_deployment_credential_update_failed: 'Não Conseguiu Atualizar Credencial Deployment.' + field_perm: 'Permissões' label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 6c2978bc4..ea8d13ec0 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -62,20 +62,45 @@ ro: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/ru.yml b/config/locales/ru.yml index cd6852f21..22cc8f614 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -62,20 +62,45 @@ ru: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 0c18347e9..bcfea1cfe 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -62,20 +62,45 @@ sk: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 5ea76e91d..ef3716b74 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -62,20 +62,45 @@ sl: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 1b7388206..058101a22 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -62,20 +62,45 @@ sr: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/sv.yml b/config/locales/sv.yml index bc039bd38..b5f529517 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -62,20 +62,45 @@ sv: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/th.yml b/config/locales/th.yml index 4621d24a1..ac5bf7b90 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -62,20 +62,45 @@ th: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 752100704..93b237ecc 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -62,20 +62,45 @@ tr: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/uk.yml b/config/locales/uk.yml index a84293b29..37b654927 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -62,20 +62,45 @@ uk: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 6dd5c2963..c9610a808 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -62,20 +62,45 @@ vi: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 61031f104..91897c6e2 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -62,20 +62,45 @@ label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/locales/zh.yml b/config/locales/zh.yml index d86b3eba9..0e1492b1e 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -62,20 +62,45 @@ zh: label_public_keys: Public keys label_current_public_keys: Current Public keys + label_current_user_keys: Current User Keys + label_current_deploy_keys: Current Deployment Keys + label_user_key: User Key + label_deploy_key: Deploy Key + field_key_type: Public Key Type + field_delete_when_unused: Delete Key When Unused label_identifier_can_be_arbitrary: Identifier (can be any meaningful string) label_cut_and_paste: Key (cut and paste entire .pub file) label_public_key_new: Enter new Public key label_public_key_edit: Edit existing Public key + field_key: Key field_public_key: Key - notice_public_key_updated: Public key '%{title}' was successfully updated. - notice_public_key_added: Public key '%{title}' was successfully added. - notice_public_key_deleted: Public key '%{title}' was successfully deleted. + field_title: Identifier + notice_public_key_updated: Public key %{title} was successfully updated. + notice_public_key_added: Public key %{title} was successfully added. + notice_public_key_deleted: Public key %{title} was successfully deleted. + error_public_key_create_failed: Failed to create public key. + error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' activerecord: errors: messages: 'may not be changed': 'may not be changed' - text_gitolite_key_destroy_confirmation: Are you sure you want to destroy '%{title}'? + text_gitolite_key_destroy_confirmation: Are you sure you want to destroy %{title}? + text_this_key: this key + label_deploy_perm: Access Permissions + select_create_new_key: Create New Key (Below) + select_other_keys: 'Other Keys' + label_deployment_credential_create: Create New Deployment Credential + label_deployment_credential_edit: Edit Deployment Credential + label_deployment_credential_delete: Delete Deployment Credential + label_which_deploy_key: Select Deployment Key + notice_deployment_credential_updated: Deployment Credential %{title} => %{perm} was successfully updated. + notice_deployment_credential_added: Deployment Credential %{title} => %{perm} was successfully added. + notice_deployment_credential_deleted: Deployment Credential %{title} =>%{perm} was successfully deleted. + notice_deployment_credential_deleted_with_key: Both Deployment Credential %{title} => %{perm} and Key %{title} were successfully deleted. + error_deployment_credential_create_failed: Failed to create Deployment Credential. + error_deployment_credential_update_failed: Failed to update Deployment Credential. + field_perm: Permissions label_git_cache_max_time: Max Cache Time label_git_cache_max_elements: Max Cache Elements diff --git a/config/routes.rb b/config/routes.rb index 0fd926585..1708754e8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,6 +27,12 @@ def install_redmine_git_hosting_routes(map) project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/update/:id', :action => 'update', :conditions => {:method => :post} project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} end + project_mapper.with_options :controller => 'deployment_credentials' do |project_views| + project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/new', :action => 'create_with_key', :conditions => {:method => [:get, :post]} + project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/edit/:id', :action => 'edit' + project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/update/:id', :action => 'update', :conditions => {:method => :post} + project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} + end end end diff --git a/db/migrate/20120803043256_create_deployment_credentials.rb b/db/migrate/20120803043256_create_deployment_credentials.rb new file mode 100644 index 000000000..2c3396583 --- /dev/null +++ b/db/migrate/20120803043256_create_deployment_credentials.rb @@ -0,0 +1,53 @@ +class CreateDeploymentCredentials < ActiveRecord::Migration + def self.up + begin + create_table :deployment_credentials do |t| + t.references :repository + t.references :gitolite_public_key + t.references :user + t.column :active, :integer, :default => 1 + t.column :perm, :string, :null => false + end + add_index :deployment_credentials, :repository_id + add_index :deployment_credentials, :gitolite_public_key_id + + add_column :gitolite_public_keys, :key_type, :integer, :default => GitolitePublicKey::KEY_TYPE_USER + add_column :gitolite_public_keys, :delete_when_unused, :boolean, :default => true + + GitHostingObserver.set_update_active(false) + + manager_role = Role.find_by_name(I18n.t(:default_role_manager)) + manager_role.add_permission! :view_deployment_keys + manager_role.add_permission! :edit_deployment_keys + manager_role.add_permission! :create_deployment_keys + manager_role.save + + developer_role = Role.find_by_name(I18n.t(:default_role_developer)) + developer_role.add_permission! :view_deployment_keys + developer_role.save + rescue => e + puts "#{e}" + end + end + + def self.down + begin + drop_table :deployment_credentials + remove_column :gitolite_public_keys, :key_type + remove_column :gitolite_public_keys, :delete_when_unused + + GitHostingObserver.set_update_active(false) + manager_role = Role.find_by_name(I18n.t(:default_role_manager)) + manager_role.remove_permission! :view_deployment_keys + manager_role.remove_permission! :edit_deployment_keys + manager_role.remove_permission! :create_deployment_keys + manager_role.save + + developer_role = Role.find_by_name(I18n.t(:default_role_developer)) + developer_role.remove_permission! :view_deployment_keys + developer_role.save + rescue => e + puts "#{e}" + end + end +end diff --git a/init.rb b/init.rb index f8d32aabd..838133f80 100755 --- a/init.rb +++ b/init.rb @@ -49,6 +49,9 @@ permission :create_repository_post_receive_urls, :repository_post_receive_urls => :create permission :view_repository_post_receive_urls, :repository_post_receive_urls => :index permission :edit_repository_post_receive_urls, :repository_post_receive_urls => :edit + permission :create_deployment_keys, :deployment_credentials => :create_with_key + permission :view_deployment_keys, :deployment_credentials => :index + permission :edit_deployment_keys, :deployment_credentials => :edit end end diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index 27801f739..f5b3d1bba 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -10,13 +10,13 @@ module GitHosting - LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) - REPOSITORY_IF_UNDEF = "repositories/" # In case settings not migrated (normally from settings) - REDMINE_SUBDIR = "" # In case settings not migrated (normally from settings) - REDMINE_HIERARCHICAL = "true" # In case settings not migrated (normally from settings) - HTTP_SERVER_SUBDIR = "" # In case settings not migrated (normally from settings) + LOCK_WAIT_IF_UNDEF = 10 # In case settings not migrated (normally from settings) + REPOSITORY_IF_UNDEF = "repositories/" # In case settings not migrated (normally from settings) + REDMINE_SUBDIR = "" # In case settings not migrated (normally from settings) + REDMINE_HIERARCHICAL = "true" # In case settings not migrated (normally from settings) + HTTP_SERVER_SUBDIR = "" # In case settings not migrated (normally from settings) TEMP_DATA_DIR = "/tmp/redmine_git_hosting" # In case settings not migrated (normally from settings) - SCRIPT_DIR = "" # In case settings not migrated (normally from settings) + SCRIPT_DIR = "" # In case settings not migrated (normally from settings) SCRIPT_PARENT = "bin" # Used to register errors when pulling and pushing the conf file @@ -719,11 +719,20 @@ def self.update_repositories(*args) end # Collect relevant users into hash with user as key and activity (in some active project) as value - git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq.each do |cur_user| - active_keys = cur_user.gitolite_public_keys.active || [] + (git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq << GitolitePublicKey::DEPLOY_PSEUDO_USER).each do |cur_user| + if cur_user == GitolitePublicKey::DEPLOY_PSEUDO_USER + active_keys = DeploymentCredential.active.select(&:honored?).map(&:gitolite_public_key).uniq + cur_token = cur_user - # Remove old keys that happen to be left around - cur_token = GitolitePublicKey.user_to_user_token(cur_user) + # Remove inactive Deployment Credentials + DeploymentCredential.inactive.each {|cred| DeploymentCredential.destroy(cred.id)} + else + active_keys = cur_user.gitolite_public_keys.active.select{|x| x.user_key?} || [] + cur_token = GitolitePublicKey.user_to_user_token(cur_user) + + # Remove inactive keys (will be deleted below) + cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} + end # Current filenames old_keynames = old_keyhash[cur_token] || [] @@ -751,9 +760,6 @@ def self.update_repositories(*args) changed = true end - # Remove inactive keys (will already be deleted by above code) - cur_user.gitolite_public_keys.inactive.each {|key| GitolitePublicKey.destroy(key.id)} - # Add missing keys to the keydir active_keys.each do |key| keyname = "#{key.identifier}.pub" @@ -876,19 +882,19 @@ def self.update_repositories(*args) # If this is an active (non-archived) project, then update gitolite entry. Add GIT_DAEMON_KEY. if proj.active? + # Get deployment keys (could be empty) + write_user_keys = myrepo.deployment_credentials.active.select{|cred| cred.honored? && cred.allowed_to?(:commit_access)}.map{|x| x.gitolite_public_key.identifier} + read_user_keys = myrepo.deployment_credentials.active.select{|cred| cred.honored? && cred.allowed_to?(:view_changesets) && !cred.allowed_to?(:commit_access)}.map{|x| x.gitolite_public_key.identifier} + # fetch users users = proj.member_principals.map(&:user).compact.uniq write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } - # update users - read_user_keys = [] - write_user_keys = [] - - read_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| + read_users.map{|u| u.gitolite_public_keys.active.user_key}.flatten.compact.uniq.each do |key| read_user_keys.push key.identifier end - write_users.map{|u| u.gitolite_public_keys.active}.flatten.compact.uniq.each do |key| + write_users.map{|u| u.gitolite_public_keys.active.user_key}.flatten.compact.uniq.each do |key| write_user_keys.push key.identifier end @@ -902,8 +908,8 @@ def self.update_repositories(*args) # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys # (to be put back as above, when appropriate). conf.delete_redmine_keys repo_name - conf.add_read_user repo_name, read_user_keys - conf.add_write_user repo_name, write_user_keys + conf.add_read_user repo_name, read_user_keys.uniq + conf.add_write_user repo_name, write_user_keys.uniq # If no redmine keys, mark with dummy key if (read_user_keys+write_user_keys).empty? diff --git a/lib/git_hosting/patches/members_controller_patch.rb b/lib/git_hosting/patches/members_controller_patch.rb index 085181a51..92c356caa 100644 --- a/lib/git_hosting/patches/members_controller_patch.rb +++ b/lib/git_hosting/patches/members_controller_patch.rb @@ -37,14 +37,32 @@ def destroy_with_disable_update GitHostingObserver.set_update_active(:delete => true); end + # Need to make sure that we can re-render the repository settings page + def render_with_trigger_refresh(*options, &myblock) + doing_update = options.detect {|x| x==:update || (x.is_a?(Hash) && x[:update])} + if !doing_update + render_without_trigger_refresh(*options, &myblock) + else + # For repository partial + @repository ||= @project.repository + render_without_trigger_refresh *options do |page| + yield page + page.replace_html "tab-content-repository", :partial => 'projects/settings/repository' + end + end + end + def self.included(base) base.class_eval do unloadable + + helper :repositories end begin base.send(:alias_method_chain, :new, :disable_update) base.send(:alias_method_chain, :edit, :disable_update) base.send(:alias_method_chain, :destroy, :disable_update) + base.send(:alias_method_chain, :render, :trigger_refresh) rescue end end diff --git a/lib/git_hosting/patches/my_controller_patch.rb b/lib/git_hosting/patches/my_controller_patch.rb index f5ccc53ef..6dfcb22ce 100644 --- a/lib/git_hosting/patches/my_controller_patch.rb +++ b/lib/git_hosting/patches/my_controller_patch.rb @@ -11,7 +11,9 @@ def account_with_public_keys # Previous routine account_without_public_keys - @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") + @gitolite_user_keys = @user.gitolite_public_keys.active.user_key.find(:all,:order => 'title ASC, created_at ASC') + @gitolite_deploy_keys = @user.gitolite_public_keys.active.deploy_key.find(:all,:order => 'title ASC, created_at ASC') + @gitolite_public_keys = @gitolite_user_keys + @gitolite_deploy_keys @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} if @gitolite_public_key.nil? if params[:public_key_id] @@ -27,6 +29,8 @@ def account_with_public_keys def self.included(base) base.class_eval do unloadable + + helper :gitolite_public_keys end begin base.send(:alias_method_chain, :account, :public_keys) diff --git a/lib/git_hosting/patches/repository_patch.rb b/lib/git_hosting/patches/repository_patch.rb index 9855f48f0..28f95302f 100644 --- a/lib/git_hosting/patches/repository_patch.rb +++ b/lib/git_hosting/patches/repository_patch.rb @@ -81,6 +81,8 @@ class << self # initialize association from git repository -> cia_notifications has_many :cia_notifications, :foreign_key =>'repository_id', :class_name => 'GitCiaNotification', :dependent => :destroy, :extend => GitHosting::Patches::RepositoryCiaFilters::FilterMethods + has_many :deployment_credentials, :dependent => :destroy + include(InstanceMethods) end end diff --git a/lib/git_hosting/patches/users_controller_patch.rb b/lib/git_hosting/patches/users_controller_patch.rb index 7bb0524bc..c257584a9 100644 --- a/lib/git_hosting/patches/users_controller_patch.rb +++ b/lib/git_hosting/patches/users_controller_patch.rb @@ -61,12 +61,14 @@ def edit_with_public_keys # Add in values for viewing public keys: def set_public_key_values - @gitolite_public_keys = @user.gitolite_public_keys.all(:order => 'active DESC, created_at DESC', :conditions => "active=1") + @gitolite_user_keys = @user.gitolite_public_keys.all(:order => 'title ASC, created_at ASC', :conditions => "active=1 && key_type=#{GitolitePublicKey::KEY_TYPE_USER}") + @gitolite_deploy_keys = @user.gitolite_public_keys.all(:order => 'title ASC, created_at ASC', :conditions => "active=1 && key_type=#{GitolitePublicKey::KEY_TYPE_DEPLOY}") + @gitolite_public_keys = @gitolite_user_keys + @gitolite_deploy_keys @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} if @gitolite_public_key.nil? if params[:public_key_id] # public_key specified that doesn't belong to @user. Kill off public_key_id and try again - redirect_to :public_key_id => nil, :tab => params[:tab] + redirect_to :public_key_id => nil, :tab => nil return else @gitolite_public_key = GitolitePublicKey.new @@ -74,10 +76,11 @@ def set_public_key_values end end - def self.included(base) base.class_eval do unloadable + + helper :gitolite_public_keys end # Edit adds new functionality, so don't silently fail! base.send(:alias_method_chain, :edit, :public_keys) From f0884bdd5f6c7e3fa53da0fa19cfbd7e85fa8263 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Fri, 17 Aug 2012 14:48:39 -0700 Subject: [PATCH 062/107] Fix formatting on "my account" page to match more recent changes. --- app/views/my/account.html.erb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index e4b58c702..f65083e19 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -6,7 +6,10 @@ <%= error_messages_for 'user' %>
- <% labelled_form_for :user, @user, :url => { :action => "account" }, :html => { :id => 'my_account_form' } do |f| %> + <% form_for :user, @user, :url => { :action => "account" }, + :builder => TabularFormBuilder, + :lang => current_language, + :html => { :id => 'my_account_form' } do |f| %>
<%=l(:label_information_plural)%>

<%= f.text_field :firstname, :required => true %>

@@ -21,7 +24,7 @@ <% end %> <%= call_hook(:view_my_account, :user => @user, :form => f) %>
-
+
<%=l(:field_mail_notification)%> <%= render :partial => 'users/mail_notifications' %>
From 7f766a254f6219b257c71656b39161c8fc29f04a Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sun, 19 Aug 2012 12:48:03 -0700 Subject: [PATCH 063/107] Validate global uniqueness of public keys. --- app/helpers/gitolite_public_keys_helper.rb | 12 ++ app/models/gitolite_public_key.rb | 131 +++++++++++++++++++-- config/locales/bg.yml | 9 ++ config/locales/bs.yml | 9 ++ config/locales/ca.yml | 9 ++ config/locales/cs.yml | 9 ++ config/locales/da.yml | 9 ++ config/locales/de.yml | 9 ++ config/locales/el.yml | 9 ++ config/locales/en.yml | 9 ++ config/locales/es.yml | 9 ++ config/locales/fi.yml | 9 ++ config/locales/fr.yml | 9 ++ config/locales/gl.yml | 9 ++ config/locales/he.yml | 9 ++ config/locales/hu.yml | 9 ++ config/locales/id.yml | 9 ++ config/locales/it.yml | 9 ++ config/locales/ja.yml | 9 ++ config/locales/ko.yml | 9 ++ config/locales/lt.yml | 9 ++ config/locales/nl.yml | 9 ++ config/locales/no.yml | 9 ++ config/locales/pl.yml | 9 ++ config/locales/pt-BR.yml | 9 ++ config/locales/pt.yml | 9 ++ config/locales/ro.yml | 9 ++ config/locales/ru.yml | 9 ++ config/locales/sk.yml | 9 ++ config/locales/sl.yml | 9 ++ config/locales/sr.yml | 9 ++ config/locales/sv.yml | 9 ++ config/locales/th.yml | 9 ++ config/locales/tr.yml | 9 ++ config/locales/uk.yml | 9 ++ config/locales/vi.yml | 9 ++ config/locales/zh-TW.yml | 9 ++ config/locales/zh.yml | 9 ++ 38 files changed, 454 insertions(+), 13 deletions(-) diff --git a/app/helpers/gitolite_public_keys_helper.rb b/app/helpers/gitolite_public_keys_helper.rb index e50482a3b..6b6e29531 100644 --- a/app/helpers/gitolite_public_keys_helper.rb +++ b/app/helpers/gitolite_public_keys_helper.rb @@ -21,4 +21,16 @@ def keylabel_text(key) "#{key.user.login}@#{key.title}" end end + + def wrap_and_join(in_array,my_or="or") + my_array = in_array.map{|x| "\"#{x}\""} + length = my_array.length + return my_array if length < 2 + my_array[length-1] = my_or+" "+my_array[length-1] + if length == 2 + my_array.join(' ') + else + my_array.join(', ') + end + end end diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 2de728ad9..3b6457eac 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -1,3 +1,6 @@ +require 'base64' +include GitolitePublicKeysHelper + class GitolitePublicKey < ActiveRecord::Base STATUS_ACTIVE = 1 STATUS_LOCKED = 0 @@ -7,6 +10,10 @@ class GitolitePublicKey < ActiveRecord::Base DEPLOY_PSEUDO_USER = "_deploy_key_" + # These two constants are related -- don't change one without the other + KEY_FORMATS = ['ssh-rsa', 'ssh-dss'] + KEY_NUM_COMPONENTS = [3,5] + belongs_to :user validates_uniqueness_of :title, :scope => :user_id validates_uniqueness_of :identifier, :scope => :user_id @@ -23,18 +30,12 @@ def validate_associated_records_for_deployment_credentials() end validate :has_not_been_changed validates_inclusion_of :key_type, :in => [KEY_TYPE_USER, KEY_TYPE_DEPLOY] + validate :key_format_and_uniqueness before_validation :set_identifier + before_validation :strip_whitespace before_validation :remove_control_characters - def has_not_been_changed - unless new_record? - %w(identifier key user_id key_type).each do |attribute| - errors.add(attribute, 'may not be changed') unless changes[attribute].blank? - end - end - end - def set_identifier self.identifier ||= begin @@ -81,11 +82,6 @@ def reset_identifier self.identifier end - # Remove control characters from key - def remove_control_characters - self.key=key.gsub(/[\a\r\n\t]/,'') - end - def to_s ; title ; end @@myregular = /^redmine_(.*)_\d*_\d*(.pub)?$/ @@ -97,4 +93,113 @@ def self.ident_to_user_token(identifier) def self.user_to_user_token(user) user.login.underscore.gsub(/[^0-9a-zA-Z\-]/,'_') end + + protected + + # Strip leading and trailing whitespace + def strip_whitespace + self.title = title.strip + self.key = key.strip + end + + # Remove control characters from key + def remove_control_characters + # First -- let the first control char or space stand (to divide key type from key) + # Really, this is catching a special case in which there is a \n between type and key. + # Most common case turns first space back into space.... + self.key=key.sub(/[ \r\n\t]/,' ') + + # Next, if comment divided from key by control char, let that one stand as well + # We can only tell this if there is an "=" in the key. So, won't help 1/3 times. + self.key=key.sub(/=[ \r\n\t]/,'= ') + + # Delete any remaining control characters.... + self.key=key.gsub(/[\a\r\n\t]/,'').strip + end + + def has_not_been_changed + unless new_record? + %w(identifier key user_id key_type).each do |attribute| + errors.add(attribute, 'may not be changed') unless changes[attribute].blank? + end + end + end + + def key_format_and_uniqueness + return if key.blank? || !new_record? + + # First, check that key crypto type is present and of correct form. Also, decode base64 and see if key + # crypto type matches. Note that we ignore presence of comment! + keypieces = key.match(/^(\S+)\s+(\S+)/) + if !keypieces || keypieces[1].length > 10 # Probably has key as first component + errors.add(:key,l(:error_key_needs_two_components)) + return + end + + if !(KEY_FORMATS.index(keypieces[1])) + errors.add(:key,l(:error_key_bad_type,:types=>wrap_and_join(KEY_FORMATS,l(:word_or)))) + return + end + + # Make sure that key has proper number of characters (divisible by 4) and no more than 2 '=' + if (keypieces[2].length % 4) != 0 || !(keypieces[2].match(/^[a-zA-Z0-9\+\/]+={0,2}$/)) + Rails.logger.error "Key error: #{keypieces[2].length % 4}" + errors.add(:key,l(:error_key_corrupted)) + return + end + + deckey = Base64.decode64(keypieces[2]) + piecearray=[] + while deckey.length >= 4 + length = 0 + deckey.slice!(0..3).bytes do |byte| + length = length * 256 + byte + end + if deckey.length < length + errors.add(:key,l(:error_key_corrupted)) + return + end + piecearray << deckey.slice!(0..length-1) + end + if deckey.length != 0 + errors.add(:key,l(:error_key_corrupted)) + return + end + + if piecearray[0] != keypieces[1] + errors.add(:key,l(:error_key_type_mismatch,:type1=>keypieces[1],:type2=>piecearray[0])) + return + end + + if piecearray.length != KEY_NUM_COMPONENTS[KEY_FORMATS.index(piecearray[0])] + errors.add(:key,l(:error_key_corrupted)) + return + end + + + # First version of uniqueness check -- simply check all keys... + + # Check against the gitolite administrator key file (owned by noone). + allkeys = [GitolitePublicKey.new({ :user=>nil, :key=>%x[cat '#{Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']}'] })] + # Check all active keys + allkeys += (GitolitePublicKey.active.all) + + allkeys.each do |existingkey| + existingpieces = existingkey.key.match(/^(\S+)\s+(\S+)/) + if existingpieces && (existingpieces[2] == keypieces[2]) + # Hm.... have a duplicate key! + if existingkey.user == User.current + errors.add(:key,l(:error_key_in_use_by_you,:name=>existingkey.title)) + elsif User.current.admin? + if existingkey.user + errors.add(:key,l(:error_key_in_use_by_other,:login=>existingkey.user.login,:name=>existingkey.title)) + else + errors.add(:key,l(:error_key_in_use_by_admin)) + end + else + errors.add(:key,l(:error_key_in_use_by_someone)) + end + end + end + end end diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 72b772751..d62a2872f 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -81,6 +81,15 @@ bg: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/bs.yml b/config/locales/bs.yml index acb112115..edbfc9016 100644 --- a/config/locales/bs.yml +++ b/config/locales/bs.yml @@ -81,6 +81,15 @@ bs: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 3675b3f32..5be176a96 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -81,6 +81,15 @@ ca: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 57b8e66ba..3e6ee5afa 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -81,6 +81,15 @@ cs: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/da.yml b/config/locales/da.yml index 9fe0a79f2..e615c4ebe 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -81,6 +81,15 @@ da: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/de.yml b/config/locales/de.yml index 55cbc2d64..fec8dccea 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -81,6 +81,15 @@ de: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/el.yml b/config/locales/el.yml index d525128b1..0e9fa6c40 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -81,6 +81,15 @@ el: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/en.yml b/config/locales/en.yml index 368b9b327..8b069ae81 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -81,6 +81,15 @@ en: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/es.yml b/config/locales/es.yml index 0d64f8331..069431f15 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -81,6 +81,15 @@ es: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/fi.yml b/config/locales/fi.yml index e4b50c459..64986bb6e 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -81,6 +81,15 @@ fi: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index bc0a2849a..7573ff07e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -81,6 +81,15 @@ fr: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 10eae6611..7484e58d5 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -81,6 +81,15 @@ gl: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/he.yml b/config/locales/he.yml index 66ece3a8e..1f296dcb6 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -81,6 +81,15 @@ he: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 73fe1aa3b..b1181dc62 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -81,6 +81,15 @@ error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/id.yml b/config/locales/id.yml index 7033e1721..fd8df3976 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -81,6 +81,15 @@ id: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/it.yml b/config/locales/it.yml index aabcbecad..7167c17e3 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -81,6 +81,15 @@ it: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b978f96ef..b3b436804 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -81,6 +81,15 @@ ja: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 3db715414..1e24f4faf 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -81,6 +81,15 @@ ko: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/lt.yml b/config/locales/lt.yml index c91b0e9cf..426485d67 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -81,6 +81,15 @@ lt: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 0b9f633e2..5043c0971 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -81,6 +81,15 @@ nl: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/no.yml b/config/locales/no.yml index 97b4a9062..d13f16f69 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -81,6 +81,15 @@ error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 45085fa2d..377d0a852 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -81,6 +81,15 @@ pl: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 520a23764..128679ec3 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -79,6 +79,15 @@ pt-BR: error_public_key_create_failed: 'Não Conseguiu Criar Chave Pública' error_public_key_update_failed: 'Não Conseguiu Atualizar Chave Pública' label_key_cannot_be_changed_please_create_new_key: 'A chave já não pode ser alterada. No entanto, pode excluí-lo e criar uma nova.' + error_key_corrupted: parece ser corrompido. + error_key_needs_two_components: 'precisa de pelo menos dois componentes: a tecla [tipo] e [valor-chave].' + error_key_bad_type: 'tem um tipo ruim. Os tipos válidos são %{types}.' + word_or: ou + error_key_type_mismatch: tipo (%{type1}) não corresponde ao tipo dentro do corpo da chave (%{type2}). + error_key_in_use_by_you: 'já está em uso por você como "%{name}".' + error_key_in_use_by_other: 'já está em uso por "%{login}" usuário como "%{name}".' + error_key_in_use_by_admin: 'já está em uso como a chave administrador gitolite.' + error_key_in_use_by_someone: 'já está em uso. Ela pertence a outro usuário (perguntar ao administrador para detalhes).' activerecord: errors: messages: diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 2361afcbc..2c564336c 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -79,6 +79,15 @@ pt: error_public_key_create_failed: 'Não Conseguiu Criar Chave Pública' error_public_key_update_failed: 'Não Conseguiu Atualizar Chave Pública' label_key_cannot_be_changed_please_create_new_key: 'A chave já não pode ser alterada. No entanto, pode excluí-lo e criar uma nova.' + error_key_corrupted: parece ser corrompido. + error_key_needs_two_components: 'precisa de pelo menos dois componentes: a tecla [tipo] e [valor-chave].' + error_key_bad_type: 'tem um tipo ruim. Os tipos válidos são %{types}.' + word_or: ou + error_key_type_mismatch: tipo (%{type1}) não corresponde ao tipo dentro do corpo da chave (%{type2}). + error_key_in_use_by_you: 'já está em uso por você como "%{name}".' + error_key_in_use_by_other: 'já está em uso por "%{login}" usuário como "%{name}".' + error_key_in_use_by_admin: 'já está em uso como a chave administrador gitolite.' + error_key_in_use_by_someone: 'já está em uso. Ela pertence a outro usuário (perguntar ao administrador para detalhes).' activerecord: errors: messages: diff --git a/config/locales/ro.yml b/config/locales/ro.yml index ea8d13ec0..ea20b6076 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -81,6 +81,15 @@ ro: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 22cc8f614..fd0d538f0 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -81,6 +81,15 @@ ru: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index bcfea1cfe..2166dddd8 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -81,6 +81,15 @@ sk: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/sl.yml b/config/locales/sl.yml index ef3716b74..862d43743 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -81,6 +81,15 @@ sl: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 058101a22..ef8dd9d17 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -81,6 +81,15 @@ sr: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index b5f529517..fb331d1f4 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -81,6 +81,15 @@ sv: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/th.yml b/config/locales/th.yml index ac5bf7b90..eb06ed302 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -81,6 +81,15 @@ th: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 93b237ecc..6762b1be3 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -81,6 +81,15 @@ tr: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 37b654927..1e3a58642 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -81,6 +81,15 @@ uk: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/vi.yml b/config/locales/vi.yml index c9610a808..14443e0e4 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -81,6 +81,15 @@ vi: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 91897c6e2..e16e54dca 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -81,6 +81,15 @@ error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: diff --git a/config/locales/zh.yml b/config/locales/zh.yml index 0e1492b1e..eeb2aea6e 100644 --- a/config/locales/zh.yml +++ b/config/locales/zh.yml @@ -81,6 +81,15 @@ zh: error_public_key_create_failed: Failed to create public key. error_public_key_update_failed: Failed to update public key. label_key_cannot_be_changed_please_create_new_key: 'The key cannot be altered anymore. However, you can delete it and create a new one.' + error_key_corrupted: seems to be corrupted. + error_key_needs_two_components: 'needs at least two components: [key-type] and [key-value].' + error_key_bad_type: 'has a bad type. Valid types are %{types}.' + word_or: or + error_key_type_mismatch: type (%{type1}) does not match type within body of key (%{type2}). + error_key_in_use_by_you: 'is already in use by you as "%{name}".' + error_key_in_use_by_other: 'is already in use by user "%{login}" as "%{name}".' + error_key_in_use_by_admin: is already in use as the gitolite administrator key. + error_key_in_use_by_someone: is already in use. It belongs to another user (ask administrator for details). activerecord: errors: messages: From 44cec8ed0bf5d53dffc74ddbb68740b00e2d3bf0 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Mon, 20 Aug 2012 11:49:51 -0700 Subject: [PATCH 064/107] Relax public key validation for old (possibly non-conforming) keys. --- app/controllers/gitolite_public_keys_controller.rb | 5 ++++- app/models/gitolite_public_key.rb | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/gitolite_public_keys_controller.rb b/app/controllers/gitolite_public_keys_controller.rb index 9ac3a501f..874122809 100644 --- a/app/controllers/gitolite_public_keys_controller.rb +++ b/app/controllers/gitolite_public_keys_controller.rb @@ -127,7 +127,10 @@ def find_gitolite_public_key def destroy_key @gitolite_public_key[:active] = 0 - @gitolite_public_key.save + + # Since we are ultimately destroying this key, just force save (since old keys may fail new validations) + @gitolite_public_key.save((Rails::VERSION::STRING.split('.')[0].to_i > 2) ? { :validate => false } : false) + flash[:notice] = l(:notice_public_key_deleted, :title=>keylabel(@gitolite_public_key)) end end diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 3b6457eac..5e6919673 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -98,12 +98,18 @@ def self.user_to_user_token(user) # Strip leading and trailing whitespace def strip_whitespace + # Don't mess with existing keys (since cannot change key text anyway) + return if !new_record? + self.title = title.strip self.key = key.strip end # Remove control characters from key def remove_control_characters + # Don't mess with existing keys (since cannot change key text anyway) + return if !new_record? + # First -- let the first control char or space stand (to divide key type from key) # Really, this is catching a special case in which there is a \n between type and key. # Most common case turns first space back into space.... @@ -126,7 +132,7 @@ def has_not_been_changed end def key_format_and_uniqueness - return if key.blank? || !new_record? + return if key.blank? # First, check that key crypto type is present and of correct form. Also, decode base64 and see if key # crypto type matches. Note that we ignore presence of comment! @@ -185,6 +191,7 @@ def key_format_and_uniqueness allkeys += (GitolitePublicKey.active.all) allkeys.each do |existingkey| + next if existingkey.id == id existingpieces = existingkey.key.match(/^(\S+)\s+(\S+)/) if existingpieces && (existingpieces[2] == keypieces[2]) # Hm.... have a duplicate key! From ec01acdfa8337ece2ffc6dfcca687b8eaef5b0b3 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Mon, 20 Aug 2012 16:02:10 -0700 Subject: [PATCH 065/107] Better handling of projects with a disabled repository module. We now treat this case exactly the same as an archived project/repository. Before, it was handled inconsistently. Three changes: 1) When a repository module is disabled/enabled through settings page call update_repositories to update state. A disabling of the repository module will call update_repositories with :archive. 2) Existing repositories will be left alone and marked with a dummy redmine key of "redmine_disabled_project". Reenabling of the repository module will restore project and keys to accessibility. 3) When removing redmine keys from a gitolite.conf entry, don't report in the log unless removing real readmine keys. This avoids a weird circumstance in which every fetch_changesets operation reports that it is deleting redmine keys (caused when repo entry exists but the "delete_repositories" flag is disabled at the time that the repo is deleted. This fix is entirely about removing confusing messages from the log. --- lib/git_hosting.rb | 87 ++++++++++--------- .../patches/projects_controller_patch.rb | 16 ++++ lib/gitolite_conf.rb | 12 ++- 3 files changed, 75 insertions(+), 40 deletions(-) diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index f5b3d1bba..5ad3edf43 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -662,20 +662,20 @@ def self.update_repositories(*args) args.each {|arg| flags.merge!(arg) if arg.is_a?(Hash)} if flags[:resync_all] logger.info "Executing RESYNC_ALL operation on gitolite configuration" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + projects = Project.active_or_archived.find(:all, :include => :repository) elsif flags[:delete] # When delete, want to recompute users, so need to go through all projects logger.info "Executing DELETE operation (resync keys, remove dead repositories)" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + projects = Project.active_or_archived.find(:all, :include => :repository) elsif flags[:archive] # When archive, want to recompute users, so need to go through all projects logger.info "Executing ARCHIVE operation (remove keys)" - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + projects = Project.active_or_archived.find(:all, :include => :repository) elsif flags[:descendants] if Project.method_defined?(:self_and_descendants) projects = (args.flatten.select{|p| p.is_a?(Project)}).collect{|p| p.self_and_descendants}.flatten else - projects = Project.active_or_archived.has_module(:repository).find(:all, :include => :repository) + projects = Project.active_or_archived.find(:all, :include => :repository) end else projects = args.flatten.select{|p| p.is_a?(Project)} @@ -719,7 +719,7 @@ def self.update_repositories(*args) end # Collect relevant users into hash with user as key and activity (in some active project) as value - (git_projects.select{|proj| proj.active?}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq << GitolitePublicKey::DEPLOY_PSEUDO_USER).each do |cur_user| + (git_projects.select{|proj| proj.active? && proj.module_enabled?(:repository)}.map{|proj| proj.member_principals.map(&:user).compact}.flatten.uniq << GitolitePublicKey::DEPLOY_PSEUDO_USER).each do |cur_user| if cur_user == GitolitePublicKey::DEPLOY_PSEUDO_USER active_keys = DeploymentCredential.active.select(&:honored?).map(&:gitolite_public_key).uniq cur_token = cur_user @@ -882,41 +882,47 @@ def self.update_repositories(*args) # If this is an active (non-archived) project, then update gitolite entry. Add GIT_DAEMON_KEY. if proj.active? - # Get deployment keys (could be empty) - write_user_keys = myrepo.deployment_credentials.active.select{|cred| cred.honored? && cred.allowed_to?(:commit_access)}.map{|x| x.gitolite_public_key.identifier} - read_user_keys = myrepo.deployment_credentials.active.select{|cred| cred.honored? && cred.allowed_to?(:view_changesets) && !cred.allowed_to?(:commit_access)}.map{|x| x.gitolite_public_key.identifier} - - # fetch users - users = proj.member_principals.map(&:user).compact.uniq - write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } - read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } - - read_users.map{|u| u.gitolite_public_keys.active.user_key}.flatten.compact.uniq.each do |key| - read_user_keys.push key.identifier - end - write_users.map{|u| u.gitolite_public_keys.active.user_key}.flatten.compact.uniq.each do |key| - write_user_keys.push key.identifier - end - - #git daemon support - if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public - read_user_keys.push GitoliteConfig::GIT_DAEMON_KEY - end + if proj.module_enabled?(:repository) + # Get deployment keys (could be empty) + write_user_keys = myrepo.deployment_credentials.active.select{|cred| cred.honored? && cred.allowed_to?(:commit_access)}.map{|x| x.gitolite_public_key.identifier} + read_user_keys = myrepo.deployment_credentials.active.select{|cred| cred.honored? && cred.allowed_to?(:view_changesets) && !cred.allowed_to?(:commit_access)}.map{|x| x.gitolite_public_key.identifier} + + # fetch users + users = proj.member_principals.map(&:user).compact.uniq + write_users = users.select{ |user| user.allowed_to?( :commit_access, proj ) } + read_users = users.select{ |user| user.allowed_to?( :view_changesets, proj ) && !user.allowed_to?( :commit_access, proj ) } + + read_users.map{|u| u.gitolite_public_keys.active.user_key}.flatten.compact.uniq.each do |key| + read_user_keys.push key.identifier + end + write_users.map{|u| u.gitolite_public_keys.active.user_key}.flatten.compact.uniq.each do |key| + write_user_keys.push key.identifier + end - # Remove previous redmine keys, then add new keys - # By doing things this way, we leave non-redmine keys alone - # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys - # (to be put back as above, when appropriate). - conf.delete_redmine_keys repo_name - conf.add_read_user repo_name, read_user_keys.uniq - conf.add_write_user repo_name, write_user_keys.uniq + #git daemon support + if (proj.repository.extra.git_daemon == 1 || proj.repository.extra.git_daemon == nil ) && proj.is_public + read_user_keys.push GitoliteConfig::GIT_DAEMON_KEY + end - # If no redmine keys, mark with dummy key - if (read_user_keys+write_user_keys).empty? - conf.mark_with_dummy_key repo_name + # Remove previous redmine keys, then add new keys + # By doing things this way, we leave non-redmine keys alone + # Note -- delete_redmine_keys() will also remove the GIT_DAEMON_KEY for repos with redmine keys + # (to be put back as above, when appropriate). + conf.delete_redmine_keys repo_name + conf.add_read_user repo_name, read_user_keys.uniq + conf.add_write_user repo_name, write_user_keys.uniq + + # If no redmine keys, mark with dummy key + if (read_user_keys+write_user_keys).empty? + conf.mark_with_dummy_key repo_name + end + else + # Must be a project that has repositories disabled. Mark as disabled project. + conf.delete_redmine_keys repo_name + conf.mark_disabled repo_name end else - # Must be an archived project! Clear out redmine keys. Mark as an archived project. + # Must be an archived project! Clear out redmine keys. Mark as an archived project. conf.delete_redmine_keys repo_name conf.mark_archived repo_name end @@ -938,20 +944,23 @@ def self.update_repositories(*args) redmine_repos.delete_if{|basename,values| proj_ids.index(basename)} end redmine_repos.values.flatten.each do |repo_name| - # First, delete redmine keys for this repository + # First, check if there are any redmine keys other than the DUMMY or ARCHIVED key + has_keys = conf.has_actual_redmine_keys? repo_name + + # Next, delete redmine keys for this repository conf.delete_redmine_keys repo_name if (Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true") if conf.repo_has_no_keys? repo_name logger.warn "Deleting #{orphanString}entry '#{repo_name}' from #{gitolite_conf}" conf.delete_repo repo_name GitoliteRecycle.move_repository_to_recycle repo_name - else + elsif has_keys # Something changed when we deleted keys logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" if git_repository_exists? repo_name logger.info " Not removing #{repo_name}.git from gitolite repository, because non-redmine keys remain." end end - else + elsif has_keys # Something changed when we deleted keys logger.info "Deleting redmine keys from #{orphanString}entry '#{repo_name}' in #{gitolite_conf}" end end diff --git a/lib/git_hosting/patches/projects_controller_patch.rb b/lib/git_hosting/patches/projects_controller_patch.rb index 6228635b8..798fb48fa 100644 --- a/lib/git_hosting/patches/projects_controller_patch.rb +++ b/lib/git_hosting/patches/projects_controller_patch.rb @@ -108,6 +108,21 @@ def unarchive_with_disable_update GitHostingObserver.set_update_active(@project); end + def settings_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + settings_without_disable_update + + # Reenable updates to perform a single update + if @project.module_enabled?(:repository) + GitHostingObserver.set_update_active(@project); + else + GitHostingObserver.set_update_active(:archive); + end + end + def self.included(base) base.class_eval do unloadable @@ -117,6 +132,7 @@ def self.included(base) base.send(:alias_method_chain, :destroy, :disable_update) base.send(:alias_method_chain, :archive, :disable_update) base.send(:alias_method_chain, :unarchive, :disable_update) + base.send(:alias_method_chain, :settings, :disable_update) end end end diff --git a/lib/gitolite_conf.rb b/lib/gitolite_conf.rb index fc38a0b24..f15c85457 100755 --- a/lib/gitolite_conf.rb +++ b/lib/gitolite_conf.rb @@ -2,6 +2,7 @@ module GitHosting class GitoliteConfig DUMMY_REDMINE_KEY="redmine_dummy_key" ARCHIVED_REDMINE_KEY="redmine_archived_project" + DISABLED_REDMINE_KEY="redmine_disabled_project" GIT_DAEMON_KEY="daemon" ADMIN_REPO = "gitolite-admin" PRIMARY_CONF_FILE = "gitolite.conf" @@ -64,6 +65,10 @@ def mark_archived repo_name add_read_user repo_name, [ARCHIVED_REDMINE_KEY] end + def mark_disabled repo_name + add_read_user repo_name, [DISABLED_REDMINE_KEY] + end + # Grab admin key (assuming it exists) def get_admin_key return (repository(ADMIN_REPO).get "RW+").first @@ -91,6 +96,11 @@ def is_redmine_repo? repo_name repository(repo_name).rights.detect {|perm, users| users.detect {|key| is_redmine_key? key}} || (repo_has_no_keys? repo_name) end + # Return true if repository has any redmine keys other than the DUMMY_REDMINE_KEY + def has_actual_redmine_keys? repo_name + repository(repo_name).rights.detect {|perm, users| users.detect {|key| GitolitePublicKey::ident_to_user_token(key)}} + end + # Delete all of the redmine keys from a repository # In addition, if there are any redmine keys, delete the GIT_DAEMON_KEY as well, # since we assume this under control of redmine server. @@ -107,7 +117,7 @@ def repo_has_no_keys? repo_name end def is_redmine_key? keyname - (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY || keyname == ARCHIVED_REDMINE_KEY) ? true : false + (GitolitePublicKey::ident_to_user_token(keyname) || keyname == DUMMY_REDMINE_KEY || keyname == ARCHIVED_REDMINE_KEY || keyname == DISABLED_REDMINE_KEY) ? true : false end def is_daemon_key? keyname From 07762126d456a5bdb9236b6d98a44775a334c2f9 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Wed, 22 Aug 2012 17:50:33 -0700 Subject: [PATCH 066/107] Remove excess whitespace before validation. --- app/models/gitolite_public_key.rb | 9 +++++---- app/models/repository_mirror.rb | 8 ++++++++ app/models/repository_post_receive_url.rb | 11 ++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 5e6919673..1bcd286fd 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -98,11 +98,12 @@ def self.user_to_user_token(user) # Strip leading and trailing whitespace def strip_whitespace - # Don't mess with existing keys (since cannot change key text anyway) - return if !new_record? - self.title = title.strip - self.key = key.strip + + # Don't mess with existing keys (since cannot change key text anyway) + if new_record? + self.key = key.strip + end end # Remove control characters from key diff --git a/app/models/repository_mirror.rb b/app/models/repository_mirror.rb index 4bd6e4e94..0fd03834f 100644 --- a/app/models/repository_mirror.rb +++ b/app/models/repository_mirror.rb @@ -16,6 +16,8 @@ class RepositoryMirror < ActiveRecord::Base validate :check_refspec + before_validation :strip_whitespace + named_scope :active, {:conditions => {:active => RepositoryMirror::STATUS_ACTIVE}} named_scope :inactive, {:conditions => {:active => RepositoryMirror::STATUS_INACTIVE}} @@ -77,6 +79,12 @@ def to_s protected + # Strip leading and trailing whitespace + def strip_whitespace + self.url = url.strip + self.explicit_refspec = explicit_refspec.strip + end + # Put backquote in front of crucial characters def dequote(in_string) in_string.gsub(/[$,"\\\n]/) {|x| "\\"+x} diff --git a/app/models/repository_post_receive_url.rb b/app/models/repository_post_receive_url.rb index 620d1ec86..19d1ea3b1 100644 --- a/app/models/repository_post_receive_url.rb +++ b/app/models/repository_post_receive_url.rb @@ -7,10 +7,12 @@ class RepositoryPostReceiveUrl < ActiveRecord::Base attr_accessible :url, :mode, :active validates_uniqueness_of :url, :scope => [:project_id] - validates_presence_of :project_id, :url + validates_presence_of :project_id validates_format_of :url, :with => URI::regexp(%w(http https)) validates_associated :project + before_validation :strip_whitespace + named_scope :active, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_ACTIVE}} named_scope :inactive, {:conditions => {:active => RepositoryPostReceiveUrl::STATUS_INACTIVE}} @@ -25,4 +27,11 @@ def mode= (value) def to_s return File.join("#{project.identifier}-#{url}") end + + protected + + # Strip leading and trailing whitespace + def strip_whitespace + self.url = url.strip + end end From 1c770d88f5a29772b24562a28c85f556afaf19db Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Wed, 22 Aug 2012 18:20:20 -0700 Subject: [PATCH 067/107] Formatting tweak: bigger key text box. --- app/views/gitolite_public_keys/_view.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/gitolite_public_keys/_view.html.erb b/app/views/gitolite_public_keys/_view.html.erb index dafe4d647..38fa83a00 100644 --- a/app/views/gitolite_public_keys/_view.html.erb +++ b/app/views/gitolite_public_keys/_view.html.erb @@ -66,7 +66,7 @@
<% end %>

<%= f.text_area :key, :label => (@new_key? :label_cut_and_paste : :field_public_key), :required => true, :disabled => !@new_key, - :style => "width:99%;height:#{params[:id]?100:200}px;overflow:auto;", + :style => "width:99%;height:200px;overflow:auto;", :cols => nil, :rows => nil %> <%= hidden_field_tag :button_submit_field, "true" %> <% if !@new_key%> From 468ae9895f2b08ec7393e4ee7cd1c098aafcf893 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Wed, 22 Aug 2012 18:23:20 -0700 Subject: [PATCH 068/107] Incremented redmine_git_hosting version from 0.4.5x => 0.4.6x This is a feature release. It includes: 1) Post-Receive URLS 2) Updated Mirror Functionality 3) Deployment Credentials 4) Support for secondary gitolite configuration file It also includes a series of bug fixes. See release notes at: https://github.com/kubitron/redmine_git_hosting/wiki/_history --- README.mkd | 52 ++++++++++++++++++++++++++++++++++++++-------------- init.rb | 2 +- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/README.mkd b/README.mkd index 160ca8aab..7d89b6e4b 100755 --- a/README.mkd +++ b/README.mkd @@ -1,4 +1,4 @@ -# Redmine Git Hosting Plugin (v0.4.5x) +# Redmine Git Hosting Plugin (v0.4.6x) A ChiliProject / Redmine plugin which makes configuring your own git hosting easy. This plugin allows straightforward management of gitolite and associated public keys, the git daemon, and integrates code from Scott Schacon's "grack" utility @@ -354,6 +354,43 @@ This command will install the new values in the database. You should follow up parameter(s)). If you are moving the location of the *gitScriptDir*, you might want to remove the old scripts before executing 'restore_defaults' as described above. +## Deployment Credentials + +As of version 0.4.6x, this plugin provides deployment credentials on a per-repository basis. These credentials are combinations of a public key and access +permissions (R or RW+) which are attached directly to a repository rather than by attaching users to repositories. Deployment credentials may be +added to a repository through the repository settings interface. They may be added by anyone who is a manager for the project or by the administrator. + +Public keys used in this way are called "deploy keys". They are owned by the user who created them and may be edited on the user's public_key page (which is under +"my account" for individual users). Since keys have unique names (per creator), they may be reused in multiple deployment credentials (in multiple repositories), +simply by selecting them from the pulldown menu on the "deployment credentials create" dialog box. + +One typical use-case would be for all deploy keys to be owned by the administrator and attached selectively to various repositories. Note that the "deployment +credentials create" dialog is actually a convenience dialog in that it allows the simultaneous creation of both a deploy key and deployment credential in a single step, +even suggesting a name for the deployment credential, with the eye to deployments that have a separate deploy key for each repository. Reusing a deploy key in +another credential is a simple matter of selecting the key from a drop-down menu. + +## Post-Receive URLs + +As of version 0.4.6x, this plugin supports the inclusion of GitHub-style Post-Receive URLs. Once added, a post-receive URL will be notified when new changes +are pushed to the repository. Two versions of this functionality are available: (1) either a GitHub-style POST operation will include json-encoded information +about the updated branch or (2) an empty GET request will be issued to the given URL. Post-Receive URLs may be added from the repository settings page. + +## Automatic Mirror Updates + +As of version 0.4.0, this plugin can automatically push updates to repository mirrors when new changes are pushed to the repository. Mirrors +must grant access to the public key defined in the gitolite_admin_id_rsa.pub public key file, which is displayed for convenience in the +repository settings tab. + +Note that 0.4.6x introduced the ability to selectively push branches to the mirror (using the git-push refspec mechanism) rather than mirroring all branches and tags. +To utilize this feature, simply select a mirror update mode of "Force Update Remote" or "Fast Forward (unforced)" instead of the default "Complete Mirroring" in the +mirror create/edit dialog. More options will then become available. + + +## Notification to CIA.vc + +As of version 0.4.0, this plugin can notify CIA.vc when changes are pushed to the repository. The project identifier on CIA.vc must match the +project identifier specified in ChiliProject/Redmine exactly + ## "Smart" HTTP Functionality @@ -429,19 +466,6 @@ max_allowed_packet=32M The above example should allow **Max Cache Element Size** == 32M. -## Notification to CIA.vc - -As of version 0.4.0, this plugin can notify CIA.vc when changes are pushed to the repository. The project identifier on CIA.vc must match the -project identifier specified in ChiliProject/Redmine exactly - - -## Automatic Mirror Updates - -As of version 0.4.0, this plugin can automatically push updates to repository mirrors when new changes are pushed to the repository. Mirrors -must grant access to the public key defined in the gitolite_admin_id_rsa.pub public key file, which is displayed for convenience in the -repository settings tab. - - ## Fast Deployment with YourChili Bash Library Instead of installing/configuring by hand, one option for quickly deploying a fully-functional system for hosting diff --git a/init.rb b/init.rb index 838133f80..b9b6c902a 100755 --- a/init.rb +++ b/init.rb @@ -8,7 +8,7 @@ name 'Redmine Git Hosting Plugin' author 'Eric Bishop, Pedro Algarvio, Christian Käser, Zsolt Parragi, Yunsang Choi, Joshua Hogendorn, Jan Schulz-Hofen, John Kubiatowicz and others' description 'Enables Redmine / ChiliProject to control hosting of git repositories' - version '0.4.5x' + version '0.4.6x' url 'https://github.com/ericpaulbishop/redmine_git_hosting' settings :default => { From d512172064161524d18103e48002a5abfc28a07f Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Sun, 26 Aug 2012 14:25:48 -0700 Subject: [PATCH 069/107] Fixed non-standard condition that tripped-up SQLite3. --- lib/git_hosting/patches/users_controller_patch.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/git_hosting/patches/users_controller_patch.rb b/lib/git_hosting/patches/users_controller_patch.rb index c257584a9..cb5b9fe68 100644 --- a/lib/git_hosting/patches/users_controller_patch.rb +++ b/lib/git_hosting/patches/users_controller_patch.rb @@ -61,8 +61,8 @@ def edit_with_public_keys # Add in values for viewing public keys: def set_public_key_values - @gitolite_user_keys = @user.gitolite_public_keys.all(:order => 'title ASC, created_at ASC', :conditions => "active=1 && key_type=#{GitolitePublicKey::KEY_TYPE_USER}") - @gitolite_deploy_keys = @user.gitolite_public_keys.all(:order => 'title ASC, created_at ASC', :conditions => "active=1 && key_type=#{GitolitePublicKey::KEY_TYPE_DEPLOY}") + @gitolite_user_keys = @user.gitolite_public_keys.active.user_key.find(:all,:order => 'title ASC, created_at ASC') + @gitolite_deploy_keys = @user.gitolite_public_keys.active.deploy_key.find(:all,:order => 'title ASC, created_at ASC') @gitolite_public_keys = @gitolite_user_keys + @gitolite_deploy_keys @gitolite_public_key = @gitolite_public_keys.detect{|x| x.id == params[:public_key_id].to_i} if @gitolite_public_key.nil? From 82ac05226e03c1abf9fa867373e8f3bae1d3cd4b Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Mon, 3 Sep 2012 22:37:29 -0700 Subject: [PATCH 070/107] First take at integration (basically works for 1 repo). --- .../deployment_credentials_controller.rb | 43 +- .../repository_mirrors_controller.rb | 41 +- ...repository_post_receive_urls_controller.rb | 41 +- app/models/git_repository_extra.rb | 2 - app/models/gitolite_public_key.rb | 2 +- .../_view_list.html.erb | 111 +++++ .../projects/settings/_repository.html.erb | 444 +++--------------- app/views/repositories/_form.html.erb | 69 +++ .../repository_mirrors/_view_list.html.erb | 157 +++++++ .../_view_list.html.erb | 97 ++++ config/routes.rb | 34 +- lib/git_hosting.rb | 15 + lib/git_hosting/patches/git_adapter_patch.rb | 11 +- .../patches/members_controller_patch.rb | 49 +- .../patches/repositories_controller_patch.rb | 57 ++- .../patches/roles_controller_patch.rb | 53 ++- .../patches/users_controller_patch.rb | 11 + 17 files changed, 770 insertions(+), 467 deletions(-) create mode 100644 app/views/deployment_credentials/_view_list.html.erb create mode 100644 app/views/repositories/_form.html.erb create mode 100644 app/views/repository_mirrors/_view_list.html.erb create mode 100644 app/views/repository_post_receive_urls/_view_list.html.erb diff --git a/app/controllers/deployment_credentials_controller.rb b/app/controllers/deployment_credentials_controller.rb index 13dfc74dc..a94cc1951 100644 --- a/app/controllers/deployment_credentials_controller.rb +++ b/app/controllers/deployment_credentials_controller.rb @@ -2,8 +2,8 @@ class DeploymentCredentialsController < ApplicationController before_filter :require_login before_filter :set_user_variable - before_filter :set_project_variable before_filter :set_repository_variable + before_filter :set_project_variable before_filter :can_create_credentials, :only => [:create, :create_with_key] before_filter :can_edit_credentials, :only => [:edit, :update, :destroy] @@ -74,11 +74,7 @@ def create_with_key if @cred.valid? && @key.save && @cred.save flash[:notice] = l(:notice_deployment_credential_added, :title=>keylabel(@key),:perm=>@cred[:perm]) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @project.id, - :tab => 'repository') - + redirect_url = success_url respond_to do |format| format.html { redirect_to redirect_url @@ -114,11 +110,8 @@ def update # Can only alter the permissions if @cred.update_attributes(params[:deployment_credentials]) flash[:notice] = l(:notice_deployment_credential_updated, :title=>keylabel(@key),:perm=>@cred[:perm]) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @project.id, - :tab => 'repository') + redirect_url = success_url respond_to do |format| format.html { redirect_to redirect_url @@ -150,10 +143,6 @@ def destroy # display confirmation view else GitHostingObserver.set_update_active(false); - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @project.id, - :tab => 'repository') if params[:confirm] key = @cred.gitolite_public_key @cred.destroy @@ -166,6 +155,7 @@ def destroy end end + redirect_url = success_url respond_to do |format| format.html {redirect_to(redirect_url)} end @@ -178,6 +168,19 @@ def settings protected + # This is a success URL to return to basic listing + def success_url + if GitHosting.multi_repos? + url_for(:controller => 'repositories', + :action => 'edit', + :id => @repository.id) + else + url_for(:controller => 'projects', + :action => 'settings', + :id => @project.id, + :tab => 'repository') + end + end def can_view_credentials render_403 unless GitHostingHelper.can_view_deployment_keys(@project) @@ -195,16 +198,16 @@ def set_user_variable @user = User.current end - def set_project_variable - @project = Project.find_by_identifier(params[:project_id]) - if !@project + def set_repository_variable + @repository = Repository.find_by_id(params[:repository_id]) + if !@repository render_404 end end - def set_repository_variable - @repository = @project.repository - if !@repository + def set_project_variable + @project = @repository.project + if !@project render_404 end end diff --git a/app/controllers/repository_mirrors_controller.rb b/app/controllers/repository_mirrors_controller.rb index 439bd79a3..31d0a612b 100644 --- a/app/controllers/repository_mirrors_controller.rb +++ b/app/controllers/repository_mirrors_controller.rb @@ -3,6 +3,7 @@ class RepositoryMirrorsController < ApplicationController before_filter :require_login before_filter :set_user_variable + before_filter :set_repository_variable before_filter :set_project_variable before_filter :check_required_permissions before_filter :check_xhr_request @@ -26,11 +27,8 @@ def create if @mirror.save flash[:notice] = l(:mirror_notice_created) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') + redirect_url = success_url respond_to do |format| format.html { redirect_to redirect_url @@ -61,11 +59,8 @@ def edit def update if @mirror.update_attributes(params[:repository_mirrors]) flash[:notice] = l(:mirror_notice_updated) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') + redirect_url = success_url respond_to do |format| format.html { redirect_to redirect_url @@ -94,16 +89,13 @@ def destroy if request.get? # display confirmation view else - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @mirror.project.identifier, - :tab => 'repository') if params[:confirm] @mirror.destroy flash[:notice] = l(:mirror_notice_deleted) end + redirect_url = success_url respond_to do |format| format.html { redirect_to(redirect_url) @@ -125,13 +117,36 @@ def push protected + # This is a success URL to return to basic listing + def success_url + if GitHosting.multi_repos? + url_for(:controller => 'repositories', + :action => 'edit', + :id => @repository.id) + else + url_for(:controller => 'projects', + :action => 'settings', + :id => @project.id, + :tab => 'repository') + end + end def set_user_variable @user = User.current end + def set_repository_variable + @repository = Repository.find_by_id(params[:repository_id]) + if !@repository + render_404 + end + end + def set_project_variable - @project = Project.find_by_identifier(params[:project_id]) + @project = @repository.project + if !@project + render_404 + end end def find_repository_mirror diff --git a/app/controllers/repository_post_receive_urls_controller.rb b/app/controllers/repository_post_receive_urls_controller.rb index 631d86085..8044855e5 100644 --- a/app/controllers/repository_post_receive_urls_controller.rb +++ b/app/controllers/repository_post_receive_urls_controller.rb @@ -3,6 +3,7 @@ class RepositoryPostReceiveUrlsController < ApplicationController before_filter :require_login before_filter :set_user_variable + before_filter :set_repository_variable before_filter :set_project_variable before_filter :check_required_permissions before_filter :check_xhr_request @@ -26,11 +27,8 @@ def create if @prurl.save flash[:notice] = l(:post_receive_url_notice_created) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') + redirect_url = success_url respond_to do |format| format.html { redirect_to redirect_url @@ -61,11 +59,8 @@ def edit def update if @prurl.update_attributes(params[:repository_post_receive_urls]) flash[:notice] = l(:post_receive_url_notice_updated) - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') + redirect_url = success_url respond_to do |format| format.html { redirect_to redirect_url @@ -94,16 +89,13 @@ def destroy if request.get? # display confirmation view else - redirect_url = url_for(:controller => 'projects', - :action => 'settings', - :id => @prurl.project.identifier, - :tab => 'repository') if params[:confirm] @prurl.destroy flash[:notice] = l(:post_receive_url_notice_deleted) end + redirect_url = success_url respond_to do |format| format.html { redirect_to(redirect_url) @@ -117,13 +109,36 @@ def settings protected + # This is a success URL to return to basic listing + def success_url + if GitHosting.multi_repos? + url_for(:controller => 'repositories', + :action => 'edit', + :id => @repository.id) + else + url_for(:controller => 'projects', + :action => 'settings', + :id => @project.id, + :tab => 'repository') + end + end def set_user_variable @user = User.current end + def set_repository_variable + @repository = Repository.find_by_id(params[:repository_id]) + if !@repository + render_404 + end + end + def set_project_variable - @project = Project.find_by_identifier(params[:project_id]) + @project = @repository.project + if !@project + render_404 + end end def find_repository_post_receive_url diff --git a/app/models/git_repository_extra.rb b/app/models/git_repository_extra.rb index f9ebe12d5..1495d1926 100755 --- a/app/models/git_repository_extra.rb +++ b/app/models/git_repository_extra.rb @@ -33,7 +33,6 @@ def validate_encoded_time(clear_time, encoded_time) def generate if self.key.nil? write_attribute(:key, (0...64+rand(64) ).map{65.+(rand(25)).chr}.join ) - self.save end end @@ -41,7 +40,6 @@ def setup_defaults write_attribute(:git_http,Setting.plugin_redmine_git_hosting['gitHttpDefault']) if Setting.plugin_redmine_git_hosting['gitHttpDefault'] write_attribute(:git_daemon,Setting.plugin_redmine_git_hosting['gitDaemonDefault']) if Setting.plugin_redmine_git_hosting['gitDaemonDefault'] write_attribute(:notify_cia,Setting.plugin_redmine_git_hosting['gitNotifyCIADefault']) if Setting.plugin_redmine_git_hosting['gitNotifyCIADefault'] - self.save end end diff --git a/app/models/gitolite_public_key.rb b/app/models/gitolite_public_key.rb index 1bcd286fd..557a7783d 100755 --- a/app/models/gitolite_public_key.rb +++ b/app/models/gitolite_public_key.rb @@ -77,7 +77,7 @@ def reset_identifier # Need to override the "never change identifier" constraint # Note that Rails 3 has a different calling convention... - self.save((Rails::VERSION::STRING.split('.')[0].to_i > 2) ? { :validate => false } : false) + self.save(GitHosting.rails_3? ? { :validate => false } : false) self.identifier end diff --git a/app/views/deployment_credentials/_view_list.html.erb b/app/views/deployment_credentials/_view_list.html.erb new file mode 100644 index 000000000..651be9ab8 --- /dev/null +++ b/app/views/deployment_credentials/_view_list.html.erb @@ -0,0 +1,111 @@ + +

+ <% if GitHostingHelper.can_create_deployment_keys(@repository.project) %> +
+ <%= link_to("Add Deployment Credential", url_for(:controller => 'deployment_credentials', :action => 'create_with_key', :repository_id => @repository.id), :class => 'icon icon-add add-deployment-credential' ) %> +
+ <% end %> + +

Deployment Credentials

+ + <% if @repository.deployment_credentials.active.any? %> +
+ + + + + + + + + + + + + <% @repository.deployment_credentials.active.sort_by {|x| [x.user.login, x.gitolite_public_key.title]}.each do |cred| %> + + + + + + + + + <% end %> + +
OwnerPublic Key NamePublic Key FilePermHonored? 
<%= cred.user.login %> + <% if cred.user == User.current %> + <%= link_to(cred.gitolite_public_key.title, url_for(:controller => 'my', :action => 'account', :public_key_id => cred.gitolite_public_key.id)) %> + <% elsif User.current.admin? %> + <%= link_to(cred.gitolite_public_key.title, url_for(:controller => 'users', :action => 'edit', :id => cred.gitolite_public_key.user.id, :public_key_id => cred.gitolite_public_key.id, :tab => 'keys')) %> + <% else %> + <%= cred.gitolite_public_key.title %> + <% end %> + <%= "keydir/#{cred.gitolite_public_key.identifier}.pub" %><%= cred.perm %><%= checked_image cred.honored? %> + <% if GitHostingHelper.can_edit_deployment_keys(@repository.project) && (User.current.admin?|| User.current == cred.user) %> + <%= link_to(l(:button_edit), url_for(:controller => 'deployment_credentials', :action => 'edit', :repository_id => @repository.id, :id => cred.id), :class => 'icon icon-edit edit-deployment-credential') %> + <%= link_to(l(:button_delete), url_for(:controller => 'deployment_credentials', :action => 'destroy', :repository_id => @repository.id, :id => cred.id), :class => 'icon icon-del delete-deployment-credential') %> + <% end %> +
+
+ <% else %> + + +
No Deployment Credentials Defined
+ <% end %> +
+ + diff --git a/app/views/projects/settings/_repository.html.erb b/app/views/projects/settings/_repository.html.erb index 7729f60da..f88e12ff9 100644 --- a/app/views/projects/settings/_repository.html.erb +++ b/app/views/projects/settings/_repository.html.erb @@ -1,395 +1,63 @@ <% content_for :header_tags do %> - <%= stylesheet_link_tag('zero_clipboard', :plugin => 'redmine_git_hosting') %> - <%= javascript_include_tag('ZeroClipboard', :plugin => 'redmine_git_hosting') %> - <%= javascript_include_tag('zero_clipboard_setup', :plugin => 'redmine_git_hosting') %> - - - + <%= stylesheet_link_tag('application', :plugin => 'redmine_git_hosting') %> + <%= stylesheet_link_tag('modalbox/modalbox', :plugin => 'redmine_git_hosting') %> + <%= javascript_include_tag('modalbox/lib/builder', :plugin => 'redmine_git_hosting') %> + <%= javascript_include_tag('modalbox/modalbox', :plugin => 'redmine_git_hosting') %> <% end %> <% labelled_remote_form_for :repository, @repository, :url => { :controller => 'repositories', :action => 'edit', :id => @project } do |f| %> - <%= error_messages_for 'repository' %> - -
- <% if @repository && @repository.is_a?(Repository::Git) && GitHostingHelper.can_view_mirrors(@repository.project) %> -

Repository Options

- <% end %> - -

<%= label_tag('repository_scm', l(:label_scm)) %><%= scm_select_tag(@repository) %>

- - <% if @repository && @repository.is_a?(Repository::Git) %> - <% - mirror_pubkey = GitHosting.mirror_push_public_key - extra_vals = {} - [:git_daemon,:git_http,:notify_cia].each {|x| extra_vals[x] = (@repository.extra[x] || 0).to_s } - extra_vals[:git_daemon] = "0" unless @project.is_public - %> -

- <%= label_tag "extra[git_daemon]", l(:field_git_daemon) %> - <%= select_tag "extra[git_daemon]", options_for_select([ [l(:label_disabled), "0"], [l(:label_enabled), "1"]], :selected=>extra_vals[:git_daemon]), :disabled => !@project.is_public %> -

-

- <%= label_tag "extra[git_http]", l(:field_git_http) %> - <%= select_tag "extra[git_http]", options_for_select([ [l(:label_disabled), "0"], [l(:label_https_only), "1"], [l(:label_https_and_http), "2"] ], :selected=>extra_vals[:git_http]) %> -

-

- <%= label_tag "extra[notify_cia]", l(:field_notify_cia) %> - <%= select_tag "extra[notify_cia]", options_for_select([ [l(:label_disabled), "0"], [l(:label_enabled), "1"]], :selected=>extra_vals[:notify_cia]) %> - <% if extra_vals[:notify_cia] == "1" %> - "test", :projectid => @repository.project.identifier) %>"><%= l(:field_notify_cia_test) %> - - <% end %> -

- <%= javascript_include_tag('notify_cia_test', :plugin => 'redmine_git_hosting') %> -
- <% else %> - <%= repository_field_tags(f, @repository) if @repository %> - <% end %> -
- - - - - <% if @repository && @repository.is_a?(Repository::Git) && GitHostingHelper.can_view_deployment_keys(@repository.project) %> - - - - -
-

Deployment Credentials

- - <% if GitHostingHelper.can_create_deployment_keys(@repository.project) %> -
- <%= link_to("Add Deployment Credential", url_for(:controller => 'deployment_credentials', :action => 'create_with_key', :project_id => @repository.project.identifier), :class => 'icon icon-add add-deployment-credential' ) %> -
- <% end %> - - <% if @repository.deployment_credentials.active.any? %> -
- - - - - - - - - - - - - <% @repository.deployment_credentials.active.sort_by {|x| [x.user.login, x.gitolite_public_key.title]}.each do |cred| %> - - - - - - - - - <% end %> - -
OwnerPublic Key NamePublic Key FilePermHonored? 
<%= cred.user.login %> - <% if cred.user == User.current %> - <%= link_to(cred.gitolite_public_key.title, url_for(:controller => 'my', :action => 'account', :public_key_id => cred.gitolite_public_key.id)) %> - <% elsif User.current.admin? %> - <%= link_to(cred.gitolite_public_key.title, url_for(:controller => 'users', :action => 'edit', :id => cred.gitolite_public_key.user.id, :public_key_id => cred.gitolite_public_key.id, :tab => 'keys')) %> - <% else %> - <%= cred.gitolite_public_key.title %> - <% end %> - <%= "keydir/#{cred.gitolite_public_key.identifier}.pub" %><%= cred.perm %><%= checked_image cred.honored? %> - <% if GitHostingHelper.can_edit_deployment_keys(@repository.project) && (User.current.admin?|| User.current == cred.user) %> - <%= link_to(l(:button_edit), url_for(:controller => 'deployment_credentials', :action => 'edit', :project_id => @repository.project.identifier, :id => cred.id), :class => 'icon icon-edit edit-deployment-credential') %> - <%= link_to(l(:button_delete), url_for(:controller => 'deployment_credentials', :action => 'destroy', :project_id => @repository.project.identifier, :id => cred.id), :class => 'icon icon-del delete-deployment-credential') %> - <% end %> -
-
- <% else %> -

No Deployment Credentials Defined

- <% end %> - -
+ <%= error_messages_for 'repository' %> + +
+ <% if @repository && @repository.is_a?(Repository::Git) && (GitHostingHelper.can_view_deployment_keys(@repository.project) || GitHostingHelper.can_view_post_receive_urls(@repository.project) || GitHostingHelper.can_view_mirrors(@repository.project)) %> +

Repository Options

+ <% end %> + +

<%= label_tag('repository_scm', l(:label_scm)) %><%= scm_select_tag(@repository) %>

+ + <% if @repository && @repository.is_a?(Repository::Git) %> +

+ <%= label_tag "extra[git_daemon]", l(:field_git_daemon) %> + <%= select_tag "extra[git_daemon]", options_for_select([ [l(:label_disabled), "0"], [l(:label_enabled), "1"]], :selected=>(@project.is_public ? @repository.extra[:git_daemon].to_s : "0")), :disabled => !@project.is_public %> +

+

+ <%= label_tag "extra[git_http]", l(:field_git_http) %> + <%= select_tag "extra[git_http]", options_for_select([ [l(:label_disabled), "0"], [l(:label_https_only), "1"], [l(:label_https_and_http), "2"] ], :selected=>@repository.extra[:git_http].to_s) %> +

+

+ <%= label_tag "extra[notify_cia]", l(:field_notify_cia) %> + <%= select_tag "extra[notify_cia]", options_for_select([ [l(:label_disabled), "0"], [l(:label_enabled), "1"]], :selected=>@repository.extra[:notify_cia].to_s) %> + <% if @repository.extra[:notify_cia].to_s == "1" %> + "test", :projectid => @repository.project.identifier) %>"><%= l(:field_notify_cia_test) %> + <% end %> - <% if @repository && @repository.is_a?(Repository::Git) && GitHostingHelper.can_view_post_receive_urls(@repository.project) %> - - - - -

- -

Post Receive URLs

+

+ <%= javascript_include_tag('notify_cia_test', :plugin => 'redmine_git_hosting') %> +
+ <% else %> + <%= repository_field_tags(f, @repository) if @repository %> + <% end %> +
+ <% if @repository && !@repository.new_record? %> + <%= link_to(l(:label_user_plural), {:controller => 'repositories', :action => 'committers', :id => @project}, :class => 'icon icon-user') %> + <%= link_to(l(:button_delete), {:controller => 'repositories', :action => 'destroy', :id => @project}, :confirm => l(:text_are_you_sure),:method => :post,:class => 'icon icon-del') %> + <% end %> +
+ <%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled => @repository.nil?) %> +
+ + <% if @repository && @repository.is_a?(Repository::Git) && !(@repository.nil? || @repository.new_record?) %> + <% if GitHostingHelper.can_view_deployment_keys(@repository.project) %> + <%= render :partial => 'deployment_credentials/view_list' %> + <% end %> + + <% if GitHostingHelper.can_view_post_receive_urls(@repository.project) %> + <%= render :partial => 'repository_post_receive_urls/view_list' %> + <% end %> + + <% if GitHostingHelper.can_view_mirrors(@repository.project) %> + <%= render :partial => 'repository_mirrors/view_list' %> + <% end %> + <% end %> - <% if GitHostingHelper.can_create_post_receive_urls(@repository.project) %> -
- <%= link_to("Add Post Receive URL", url_for(:controller => 'repository_post_receive_urls', :action => 'create', :project_id => @repository.project.identifier), :class => 'icon icon-add add-post-receive-url' ) %> -
- <% end %> - - <% if @repository.project.repository_post_receive_urls.any? %> -
- - - - - - - - - - <% @repository.project.repository_post_receive_urls.each do |prurl| %> - - - - - - <% end %> - -
URLMode 
<%= link_to(h(prurl.url), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'edit-post-receive-url') %><%= post_receive_mode prurl %>
<%= checked_image prurl.active? %>
- <% if GitHostingHelper.can_edit_post_receive_urls(@repository.project) %> - <%= link_to(l(:button_edit), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'icon icon-edit edit-post-receive-url') %> - <%= link_to(l(:button_delete), url_for(:controller => 'repository_post_receive_urls', :action => 'destroy', :project_id => @repository.project.identifier, :id => prurl.id), :class => 'icon icon-del delete-post-receive-url') %> - <% end %> -
-
- <% else %> -

No Post Receive URLs Defined

- <% end %> - -
- <% end %> - <% if @repository && @repository.is_a?(Repository::Git) && GitHostingHelper.can_view_mirrors(@repository.project) %> -
-

Repository Mirrors

- - <% if GitHostingHelper.can_create_mirrors(@repository.project) %> -
- <%= link_to("Add Repository Mirror", url_for(:controller => 'repository_mirrors', :action => 'create', :project_id => @repository.project.identifier), :class => 'icon icon-add add-mirror' ) %> -
- <% end %> - - <% if @repository.project.repository_mirrors.any? %> -
- - - - - - - - - - - <% @repository.project.repository_mirrors.each do |mirror| %> - - - - - - - <% end %> - -
URLRefspecMode 
<%= link_to(h(mirror.url), url_for(:controller => 'repository_mirrors', :action => 'edit', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'edit-mirror') %> - <%= refspec mirror, 64 %><%= mirror_mode mirror %>
<%= checked_image mirror.active? %>
- <% if GitHostingHelper.can_edit_mirrors(@repository.project) %> - <% if mirror.active==1 -%> - <%= link_to(l(:button_push), url_for(:controller => 'repository_mirrors', :action => 'push', :project_id => @repository.project.identifier, :id => mirror.id), :title => l(:button_push_title), :alt => l(:button_push_title), :class => 'icon icon-push push-mirror') %> - <% end %> - <%= link_to(l(:button_edit), url_for(:controller => 'repository_mirrors', :action => 'edit', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'icon icon-edit edit-mirror') %> - <%= link_to(l(:button_delete), url_for(:controller => 'repository_mirrors', :action => 'destroy', :project_id => @repository.project.identifier, :id => mirror.id), :class => 'icon icon-del delete-mirror') %> - <% end %> -
-
- <% else %> -

No Mirrors Defined

- <% end %> -
-
-
<%= image_tag 'paste.png', :plugin => 'redmine_git_hosting' %>
-
- Mirrors Must Grant Write Access To The Following Public Key: -
- -
- -
- - -
-
- <% if @repository && !@repository.new_record? %> - <%= link_to(l(:label_user_plural), {:controller => 'repositories', :action => 'committers', :id => @project}, :class => 'icon icon-user') %> - <%= link_to(l(:button_delete), {:controller => 'repositories', :action => 'destroy', :id => @project}, :confirm => l(:text_are_you_sure),:method => :post,:class => 'icon icon-del') %> - <% end %> -
- <%= submit_tag((@repository.nil? || @repository.new_record?) ? l(:button_create) : l(:button_save), :disabled => @repository.nil?) %> - <% content_for :header_tags do %> - <%= stylesheet_link_tag('application', :plugin => 'redmine_git_hosting') %> - <%= stylesheet_link_tag('modalbox/modalbox', :plugin => 'redmine_git_hosting') %> - <%= javascript_include_tag('modalbox/lib/builder', :plugin => 'redmine_git_hosting') %> - <%= javascript_include_tag('modalbox/modalbox', :plugin => 'redmine_git_hosting') %> - - - - <% end %> - <% end %> <% end %> diff --git a/app/views/repositories/_form.html.erb b/app/views/repositories/_form.html.erb new file mode 100644 index 000000000..db2493244 --- /dev/null +++ b/app/views/repositories/_form.html.erb @@ -0,0 +1,69 @@ +<%= error_messages_for 'repository' %> + +<% content_for :header_tags do %> + <%= stylesheet_link_tag('application', :plugin => 'redmine_git_hosting') %> + <%= stylesheet_link_tag('modalbox/modalbox', :plugin => 'redmine_git_hosting') %> + <%= javascript_include_tag('modalbox/lib/builder', :plugin => 'redmine_git_hosting') %> + <%= javascript_include_tag('modalbox/modalbox', :plugin => 'redmine_git_hosting') %> +<% end %> + +
+ <% if @repository && @repository.is_a?(Repository::Git) && (GitHostingHelper.can_view_deployment_keys(@repository.project) || GitHostingHelper.can_view_post_receive_urls(@repository.project) || GitHostingHelper.can_view_mirrors(@repository.project)) %> +

Repository Options

+ <% end %> + +

+ <%= label_tag('repository_scm', l(:label_scm)) %><%= scm_select_tag(@repository) %> + <% if @repository && ! @repository.class.scm_available %> + <%= l(:text_scm_command_not_available) %> + <% end %> +

+ +

<%= f.check_box :is_default, :label => :field_repository_is_default %>

+

<%= f.text_field :identifier %>

+ + <% button_disabled = true %> + <% if @repository %> + <% button_disabled = ! @repository.class.scm_available %> + <% end %> + + <% if @repository && @repository.is_a?(Repository::Git) %> +

+ <%= label_tag "extra[git_daemon]", l(:field_git_daemon) %> + <%= select_tag "extra[git_daemon]", options_for_select([ [l(:label_disabled), "0"], [l(:label_enabled), "1"]], :selected=>(@project.is_public ? @repository.extra[:git_daemon].to_s : "0")), :disabled => !@project.is_public %> +

+

+ <%= label_tag "extra[git_http]", l(:field_git_http) %> + <%= select_tag "extra[git_http]", options_for_select([ [l(:label_disabled), "0"], [l(:label_https_only), "1"], [l(:label_https_and_http), "2"] ], :selected=>@repository.extra[:git_http].to_s) %> +

+

+ <%= label_tag "extra[notify_cia]", l(:field_notify_cia) %> + <%= select_tag "extra[notify_cia]", options_for_select([ [l(:label_disabled), "0"], [l(:label_enabled), "1"]], :selected=>@repository.extra[:notify_cia].to_s) %> + <% if @repository.extra[:notify_cia].to_s == "1" %> + "test", :projectid => @repository.project.identifier) %>"><%= l(:field_notify_cia_test) %> + + <% end %> +

+ <%= javascript_include_tag('notify_cia_test', :plugin => 'redmine_git_hosting') %> +
+ <% else %> + <%= repository_field_tags(f, @repository) if @repository %> + <% end %> + + <%= submit_tag(@repository.new_record? ? l(:button_create) : l(:button_save), :disabled => button_disabled) %> + <%= link_to l(:button_cancel), settings_project_path(@project, :tab => 'repositories') %> +
+ +<% if @repository && @repository.is_a?(Repository::Git) && !(@repository.nil? || @repository.new_record?) %> + <% if GitHostingHelper.can_view_deployment_keys(@repository.project) %> + <%= render :partial => 'deployment_credentials/view_list' %> + <% end %> + + <% if GitHostingHelper.can_view_post_receive_urls(@repository.project) %> + <%= render :partial => 'repository_post_receive_urls/view_list' %> + <% end %> + + <% if GitHostingHelper.can_view_mirrors(@repository.project) %> + <%= render :partial => 'repository_mirrors/view_list' %> + <% end %> +<% end %> diff --git a/app/views/repository_mirrors/_view_list.html.erb b/app/views/repository_mirrors/_view_list.html.erb new file mode 100644 index 000000000..c698c31d0 --- /dev/null +++ b/app/views/repository_mirrors/_view_list.html.erb @@ -0,0 +1,157 @@ + +
+ <% if GitHostingHelper.can_create_mirrors(@repository.project) %> +
+ <%= link_to("Add Repository Mirror", url_for(:controller => 'repository_mirrors', :action => 'create', :repository_id => @repository.id), :class => 'icon icon-add add-mirror' ) %> +
+ <% end %> + +

Repository Mirrors

+ + <% if @repository.project.repository_mirrors.any? %> +
+ + + + + + + + + + + <% @repository.project.repository_mirrors.each do |mirror| %> + + + + + + + <% end %> + +
URLRefspecMode 
<%= link_to(h(mirror.url), url_for(:controller => 'repository_mirrors', :action => 'edit', :repository_id => @repository.id, :id => mirror.id), :class => 'edit-mirror') %> + <%= refspec mirror, 64 %><%= mirror_mode mirror %>
<%= checked_image mirror.active? %>
+ <% if GitHostingHelper.can_edit_mirrors(@repository.project) %> + <% if mirror.active==1 -%> + <%= link_to(l(:button_push), url_for(:controller => 'repository_mirrors', :action => 'push', :repository_id => @repository.id, :id => mirror.id), :title => l(:button_push_title), :alt => l(:button_push_title), :class => 'icon icon-push push-mirror') %> + <% end %> + + <%= link_to(l(:button_edit), url_for(:controller => 'repository_mirrors', :action => 'edit', :repository_id => @repository.id, :id => mirror.id), :class => 'icon icon-edit edit-mirror') %> + <%= link_to(l(:button_delete), url_for(:controller => 'repository_mirrors', :action => 'destroy', :repository_id => @repository.id, :id => mirror.id), :class => 'icon icon-del delete-mirror') %> + <% end %> +
+
+ <% else %> + + +
No Mirrors Defined
+ <% end %> + + <% mirror_pubkey = GitHosting.mirror_push_public_key %> +
+
+
<%= image_tag 'paste.png', :plugin => 'redmine_git_hosting' %>
+
+ Mirrors Must Grant Write Access To The Following Public Key: +
+ +
+ +
+
+ +<% content_for :header_tags do %> + <%= stylesheet_link_tag('zero_clipboard', :plugin => 'redmine_git_hosting') %> + <%= javascript_include_tag('ZeroClipboard', :plugin => 'redmine_git_hosting') %> + <%= javascript_include_tag('zero_clipboard_setup', :plugin => 'redmine_git_hosting') %> +<% end %> + + diff --git a/app/views/repository_post_receive_urls/_view_list.html.erb b/app/views/repository_post_receive_urls/_view_list.html.erb new file mode 100644 index 000000000..55ec3f3f3 --- /dev/null +++ b/app/views/repository_post_receive_urls/_view_list.html.erb @@ -0,0 +1,97 @@ + +
+ <% if GitHostingHelper.can_create_post_receive_urls(@repository.project) %> +
+ <%= link_to("Add Post Receive URL", url_for(:controller => 'repository_post_receive_urls', :action => 'create', :repository_id => @repository.id), :class => 'icon icon-add add-post-receive-url' ) %> +
+ <% end %> + +

Post Receive URLs

+ + <% if @repository.project.repository_post_receive_urls.any? %> +
+ + + + + + + + + + <% @repository.project.repository_post_receive_urls.each do |prurl| %> + + + + + + <% end %> + +
URLMode 
<%= link_to(h(prurl.url), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :repository_id => @repository.id, :id => prurl.id), :class => 'edit-post-receive-url') %><%= post_receive_mode prurl %>
<%= checked_image prurl.active? %>
+ <% if GitHostingHelper.can_edit_post_receive_urls(@repository.project) %> + <%= link_to(l(:button_edit), url_for(:controller => 'repository_post_receive_urls', :action => 'edit', :repository_id => @repository.id, :id => prurl.id), :class => 'icon icon-edit edit-post-receive-url') %> + <%= link_to(l(:button_delete), url_for(:controller => 'repository_post_receive_urls', :action => 'destroy', :repository_id => @repository.id, :id => prurl.id), :class => 'icon icon-del delete-post-receive-url') %> + <% end %> +
+
+ <% else %> + + +
No Post Receive URLs Defined
+ <% end %> +
+ + diff --git a/config/routes.rb b/config/routes.rb index 1708754e8..14aa466e3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,25 +13,25 @@ def install_redmine_git_hosting_routes(map) map.connect 'githooks', :controller => 'gitolite_hooks', :action => 'stub' map.connect 'githooks/post-receive', :controller => 'gitolite_hooks', :action => 'post_receive' map.connect 'githooks/test', :controller => 'gitolite_hooks', :action => 'test' - map.with_options :controller => 'projects' do |project_mapper| - project_mapper.with_options :controller => 'repository_mirrors' do |project_views| - project_views.connect 'projects/:project_id/settings/repository/mirrors/new', :action => 'create', :conditions => {:method => [:get, :post]} - project_views.connect 'projects/:project_id/settings/repository/mirrors/edit/:id', :action => 'edit' - project_views.connect 'projects/:project_id/settings/repository/mirrors/push/:id', :action => 'push' - project_views.connect 'projects/:project_id/settings/repository/mirrors/update/:id', :action => 'update', :conditions => {:method => :post} - project_views.connect 'projects/:project_id/settings/repository/mirrors/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} + map.with_options :controller => 'repositories' do |repo_mapper| + repo_mapper.with_options :controller => 'repository_mirrors' do |mirror_views| + mirror_views.connect 'repositories/:repository_id/mirrors/new', :action => 'create', :conditions => {:method => [:get, :post]} + mirror_views.connect 'repositories/:repository_id/mirrors/edit/:id', :action => 'edit' + mirror_views.connect 'repositories/:repository_id/mirrors/push/:id', :action => 'push' + mirror_views.connect 'repositories/:repository_id/mirrors/update/:id', :action => 'update', :conditions => {:method => :post} + mirror_views.connect 'repositories/:repository_id/mirrors/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} end - project_mapper.with_options :controller => 'repository_post_receive_urls' do |project_views| - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/new', :action => 'create', :conditions => {:method => [:get, :post]} - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/edit/:id', :action => 'edit' - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/update/:id', :action => 'update', :conditions => {:method => :post} - project_views.connect 'projects/:project_id/settings/repository/post-receive-urls/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} + repo_mapper.with_options :controller => 'repository_post_receive_urls' do |post_receive_views| + post_receive_views.connect 'repositories/:repository_id/post-receive-urls/new', :action => 'create', :conditions => {:method => [:get, :post]} + post_receive_views.connect 'repositories/:repository_id/post-receive-urls/edit/:id', :action => 'edit' + post_receive_views.connect 'repositories/:repository_id/post-receive-urls/update/:id', :action => 'update', :conditions => {:method => :post} + post_receive_views.connect 'repositories/:repository_id/post-receive-urls/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} end - project_mapper.with_options :controller => 'deployment_credentials' do |project_views| - project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/new', :action => 'create_with_key', :conditions => {:method => [:get, :post]} - project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/edit/:id', :action => 'edit' - project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/update/:id', :action => 'update', :conditions => {:method => :post} - project_views.connect 'projects/:project_id/settings/repository/deployment-credentials/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} + repo_mapper.with_options :controller => 'deployment_credentials' do |deploy_views| + deploy_views.connect 'repositories/:repository_id/deployment-credentials/new', :action => 'create_with_key', :conditions => {:method => [:get, :post]} + deploy_views.connect 'repositories/:repository_id/deployment-credentials/edit/:id', :action => 'edit' + deploy_views.connect 'repositories/:repository_id/deployment-credentials/update/:id', :action => 'update', :conditions => {:method => :post} + deploy_views.connect 'repositories/:repository_id/deployment-credentials/delete/:id', :action => 'destroy', :conditions => {:method => [:get, :delete]} end end end diff --git a/lib/git_hosting.rb b/lib/git_hosting.rb index 5ad3edf43..24b303f7f 100755 --- a/lib/git_hosting.rb +++ b/lib/git_hosting.rb @@ -23,6 +23,21 @@ module GitHosting class GitHostingException < StandardError end + # Are we in the multiple-repositories-per-project version of Redmine? + @@multi_repos = nil + def self.multi_repos? + # Simple -- if Project.repositories exists, it will be an array. Otherwise + # will throw an exception. + @@multi_repos ||= Project.new.repositories.is_a?(Array) rescue false + end + + # Are we in RAILS 3 mode? + @@rails_3 = nil + def self.rails_3? + # Grab major mode from version string.... + @@rails_3 ||= (Rails::VERSION::STRING.split('.')[0].to_i > 2) + end + # Time in seconds to wait before giving up on acquiring the lock def self.lock_wait_time Setting.plugin_redmine_git_hosting['gitLockWaitTime'].to_i || LOCK_WAIT_IF_UNDEF diff --git a/lib/git_hosting/patches/git_adapter_patch.rb b/lib/git_hosting/patches/git_adapter_patch.rb index 256131ac4..f867fe72d 100644 --- a/lib/git_hosting/patches/git_adapter_patch.rb +++ b/lib/git_hosting/patches/git_adapter_patch.rb @@ -14,8 +14,10 @@ def self.included(base) end begin - base.send(:alias_method_chain, :scm_cmd, :sudo) - rescue Exception =>e + base.send(:alias_method_chain, :git_cmd, :sudo) + rescue + # Hm... might be pre-1.4, where :git_cmd => :scm_cmd + base.send(:alias_method_chain, :scm_cmd, :sudo) rescue nil end base.extend(ClassMethods) @@ -47,8 +49,13 @@ def client_command_with_sudo end + # Pre-1.4 command syntax def scm_cmd_with_sudo(*args, &block) + git_cmd_with_sudo(args, &block) + end + # Post-1.4 command syntax + def git_cmd_with_sudo(args, options = {}, &block) max_cache_time = (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i # in seconds, default = 60 max_cache_elements = (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i # default = 100 max_cache_size = (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024 # In MB, default = 16MB, converted to bytes diff --git a/lib/git_hosting/patches/members_controller_patch.rb b/lib/git_hosting/patches/members_controller_patch.rb index 92c356caa..a963dd8e9 100644 --- a/lib/git_hosting/patches/members_controller_patch.rb +++ b/lib/git_hosting/patches/members_controller_patch.rb @@ -6,6 +6,7 @@ module GitHosting module Patches module MembersControllerPatch + # pre-1.4 (Non RESTfull) def new_with_disable_update # Turn of updates during repository update GitHostingObserver.set_update_active(false); @@ -16,6 +17,18 @@ def new_with_disable_update # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); end + # post-1.4 (RESTfull) + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + create_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + # pre-1.4 (Non RESTfull) def edit_with_disable_update # Turn of updates during repository update GitHostingObserver.set_update_active(false); @@ -26,6 +39,17 @@ def edit_with_disable_update # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); end + # post-1.4 (RESTfull) + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + update_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end def destroy_with_disable_update # Turn of updates during repository update GitHostingObserver.set_update_active(false); @@ -38,6 +62,7 @@ def destroy_with_disable_update end # Need to make sure that we can re-render the repository settings page + # (Only for pre-1.4, i.e. single repo/project) def render_with_trigger_refresh(*options, &myblock) doing_update = options.detect {|x| x==:update || (x.is_a?(Hash) && x[:update])} if !doing_update @@ -47,7 +72,7 @@ def render_with_trigger_refresh(*options, &myblock) @repository ||= @project.repository render_without_trigger_refresh *options do |page| yield page - page.replace_html "tab-content-repository", :partial => 'projects/settings/repository' + Page.replace_html "tab-content-repository", :partial => 'projects/settings/repository' end end end @@ -59,11 +84,25 @@ def self.included(base) helper :repositories end begin - base.send(:alias_method_chain, :new, :disable_update) - base.send(:alias_method_chain, :edit, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) - base.send(:alias_method_chain, :render, :trigger_refresh) + # RESTfull (post-1.4) + base.send(:alias_method_chain, :create, :scm_settings) rescue + # Not RESTfull (pre-1.4) + base.send(:alias_method_chain, :new, :scm_settings) rescue nil + end + begin + # RESTfull (post-1.4) + base.send(:alias_method_chain, :update, :scm_settings) + rescue + # Not RESTfull (pre-1.4) + base.send(:alias_method_chain, :edit, :scm_settings) rescue nil + end + base.send(:alias_method_chain, :destroy, :disable_update) rescue nil + + # This patch only needed when repository settings in same set + # if tabs as members (i.e. pre-1.4, single repo) + if !GitHosting.multi_repos? + base.send(:alias_method_chain, :render, :trigger_refresh) rescue nil end end end diff --git a/lib/git_hosting/patches/repositories_controller_patch.rb b/lib/git_hosting/patches/repositories_controller_patch.rb index c5b6a8faf..44bb72af4 100644 --- a/lib/git_hosting/patches/repositories_controller_patch.rb +++ b/lib/git_hosting/patches/repositories_controller_patch.rb @@ -14,6 +14,7 @@ def show_with_git_instructions end end + # This patch is only for pre-1.4 Redmine (since they made this controller RESTful def edit_with_scm_settings GitHosting.logger.debug "On edit_with_scm_settings" @@ -33,7 +34,7 @@ def edit_with_scm_settings @repository = Repository.factory(params[:repository_scm]) @repository.project = @project if @repository end - if request.post? && @repository + if request.post? @repository.attributes = params[:repository] if !params[:extra].nil? @repository.extra.update_attributes(params[:extra]) @@ -41,7 +42,6 @@ def edit_with_scm_settings @repository.save end - render(:update) do |page| page.replace_html "tab-content-repository", :partial => 'projects/settings/repository' if @repository && !@project.repository @@ -54,18 +54,67 @@ def edit_with_scm_settings GitHostingObserver.bracketed_update_repositories(@project) end else - edit_without_scm_settings + edit_without_scm_settings_old end GitHostingObserver.set_update_active(true); end + # Post-1.4, all creation is done by create (rather than edit) + def create_with_scm_settings + GitHostingObserver.set_update_active(false) + + # FixME XXXXXXXXXXXXXXXXXXXXXX + if params[:repository_scm] == "Git" + params[:repository][:url] = GitHosting.repository_path(@project) + end + + # Must create repository first + create_without_scm_settings + + # Update repository extras + if @repository && !params[:extra].nil? + @repository.extra.update_attributes(params[:extra]) + end + + GitHostingObserver.set_update_active(@project) + end + + # Post-1.4, all of the updates are done by update (rather than edit with post) + def update_with_scm_settings + GitHostingObserver.set_update_active(false) + + # FixME XXXXXXXXXXXXXXXXXXXXXX + if params[:repository_scm] == "Git" + params[:repository][:url] = GitHosting.repository_path(@project) + end + + update_without_scm_settings + + # Update repository extras + if @repository && !params[:extra].nil? + @repository.extra.update_attributes(params[:extra]) + end + + GitHostingObserver.set_update_active(@project) + end + def self.included(base) base.class_eval do unloadable end base.send(:alias_method_chain, :show, :git_instructions) - base.send(:alias_method_chain, :edit, :scm_settings) + + # RESTful (post-1.4). + base.send(:alias_method_chain, :create, :scm_settings) rescue nil + + begin + # RESTfull (post-1.4) + base.send(:alias_method_chain, :update, :scm_settings) + rescue + # Not RESTfull (pre-1.4) + base.send(:alias_method_chain, :edit, :scm_settings) rescue nil + end end end end diff --git a/lib/git_hosting/patches/roles_controller_patch.rb b/lib/git_hosting/patches/roles_controller_patch.rb index 0caef703d..78b9532ea 100644 --- a/lib/git_hosting/patches/roles_controller_patch.rb +++ b/lib/git_hosting/patches/roles_controller_patch.rb @@ -6,6 +6,31 @@ module GitHosting module Patches module RolesControllerPatch + # Pre-1.4 (Not RESTfull) + def new_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + new_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + # Post-1.4 (RESTfull) + def create_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + create_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + + # Pre-1.4 (Not RESTfull) def edit_with_disable_update # Turn of updates during repository update GitHostingObserver.set_update_active(false); @@ -16,6 +41,19 @@ def edit_with_disable_update # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); end + + # Post-1.4 (RESTfull) + def update_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + update_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end + def destroy_with_disable_update # Turn of updates during repository update GitHostingObserver.set_update_active(false); @@ -31,10 +69,21 @@ def self.included(base) unloadable end begin - base.send(:alias_method_chain, :edit, :disable_update) - base.send(:alias_method_chain, :destroy, :disable_update) + # RESTfull (post-1.4) + base.send(:alias_method_chain, :create, :disable_update) rescue + # Not RESTfull (pre-1.4) + base.send(:alias_method_chain, :new, :disable_update) rescue nil end + begin + # RESTfull (post-1.4) + base.send(:alias_method_chain, :update, :disable_update) + rescue + # Not RESTfull (pre-1.4) + base.send(:alias_method_chain, :edit, :disable_update) rescue nil + end + + base.send(:alias_method_chain, :destroy, :disable_update) rescue nil end end end diff --git a/lib/git_hosting/patches/users_controller_patch.rb b/lib/git_hosting/patches/users_controller_patch.rb index cb5b9fe68..44d9bb58e 100644 --- a/lib/git_hosting/patches/users_controller_patch.rb +++ b/lib/git_hosting/patches/users_controller_patch.rb @@ -49,6 +49,16 @@ def edit_membership_with_disable_update # Reenable updates to perform a single update GitHostingObserver.set_update_active(true); end + def destroy_membership_with_disable_update + # Turn of updates during repository update + GitHostingObserver.set_update_active(false); + + # Do actual update + destroy_membership_without_disable_update + + # Reenable updates to perform a single update + GitHostingObserver.set_update_active(true); + end # Add in values for viewing public keys: def edit_with_public_keys @@ -88,6 +98,7 @@ def self.included(base) base.send(:alias_method_chain, :create, :disable_update) base.send(:alias_method_chain, :update, :disable_update) base.send(:alias_method_chain, :edit_membership, :disable_update) + base.send(:alias_method_chain, :destroy_membership, :disable_update) # Put this last, since Redmine 1.1 doesn't have it.... base.send(:alias_method_chain, :destroy, :disable_update) rescue From da534037f99f3397631d9e961fc2f449e7c73297 Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Fri, 14 Sep 2012 09:10:28 -0700 Subject: [PATCH 071/107] Working version of cached_shell_redirector, still with debugging. --- lib/cached_shell_redirector.rb | 373 +++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 lib/cached_shell_redirector.rb diff --git a/lib/cached_shell_redirector.rb b/lib/cached_shell_redirector.rb new file mode 100644 index 000000000..8b54f54af --- /dev/null +++ b/lib/cached_shell_redirector.rb @@ -0,0 +1,373 @@ +require 'stringio' + +# Rewritten version of caching functionality to accommodate Redmine 1.4+ +# When the shell is called with options[:write_stdin], then part of the +# argument on which caching is based is written to the input stream of the shell. +# Thus, we may need to wait for this write to occur before checking the cache. +# +# The basic mechanism here is a duck-typed IO stream (the CachedShellRedirector) which +# intercepts the output of git and places it in the cache. In addition, this mechanism +# can intercept the stdin heading toward git so as to have a complete key for examining +# the cache. +# +# Primary calling sequence is to use the "execute" method which will allocate a new +# CachedShellRedirector only if required: +# +class CachedShellRedirector + # Redirector states + WAIT_TO_CHECK=0 + RUNNING_SHELL=1 + STRING_IO = 2 + DEAD = 3 + + # Primary interface: execute given command and send IO to block + # options[:write_stdin] will derive caching key from data that block writes to io stream + def self.execute(cmd_str, repo_id, options={}, &block) + if max_cache_time == 0 + # Disabled cache, simply launch shell, don't redirect + retio = options.empty? ? shellout(cmd_str, &block) : shellout(cmd_str, options, &block) + status = $? + elsif !options[:write_stdin] && out = self.check_cache(cmd_str) + # Simple case -- have cached result that depends only on cmd_str + block.call(out) + status = nil + retio = out + else + # Create redirector stream and call block + redirector = self.new(cmd_str, repo_id, options) + block.call(redirector) + (retio,status) = redirector.exit_shell + end + + if status && status.exitstatus != 0 + raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}" + end + retio + end + + ############################################### + # Duck-typing of an IO interface # + ############################################### + def respond_to?(my_method) + !IO.instance_methods.index(my_method.to_s).nil? || super(my_method, *args, &block) + end + + # Catch any extra args placed into stdin. We explicitly code the + # output (write) functions here. Below, 'method_missing' traps the + # read functions (since there are a lot of them) and any control functions + # and dynamically defines them as needed. + def puts(*args) + @my_extra_args << args.join("\n") + "\n" + end + + def putc(obj) + @my_extra_args << retval_to_s(obj) + end + + def write(obj) + @my_extra_args << obj.to_s + end + + # Ignore this -- must handle it before we have chosen output stream + def binmode + end + + def close_write + # Ok -- now have all the extra args... Check cache + out = self.class.check_cache(@my_cmd_str,@my_extra_args) + if out + # Match in the cache! + @state = STRING_IO + @my_read_stream = @retio = out + else + startup_shell + end + end + + # On-the-fly compilation of any missing functions, including all of the + # read functions (with and without blocks), which we divert into the buffer + # for potential caching. Other functions are compiled as "proxies", which + # simply call the corresponding functions on the current read stream (@my_read_stream). + # In this way, we pretty much get a complete I/O interface which diverts the + # returns from reads. + # + # Note that missing I/O functions are of 3 classes here: + # 1) Those that take a block and/or return enumerators + # 2) Those that returns Array, String, or Integer + # 3) Everything else + # + # The little bit of trickery with "class_eval" below is to compile custom functions + # for each encountered missing function (so that method_missing only gets called + # once for each function. Note that we don't use define_method here, since + # Ruby 1.8 define_method doesn't work with blocks. + def method_missing(my_method, *args, &block) + # Only handle IO methods! + if IO.instance_methods.index(my_method.to_s).nil? + return super(my_method, *args, &block) + end + + if @my_read_stream.nil? + # Shouldn't happen, but might be problem + raise Redmine::Scm::Adapters::GitAdapter::ScmCommandAborted, "call to #{my_method.to_s} before IO-handlers wrapped." + end + + # Buffer up results from read operations. Proxy everything else directly to IO stream. + Rails.logger.error "method_missing handling: #{my_method}" + my_name = my_method.to_s + if my_name =~ /^(each|bytes)/ + # Handle Enumerator read functions (Class #1) + Rails.logger.error "Defining new function: #{my_method}" + self.class.class_eval <<-EOF, __FILE__, __LINE__ + def #{my_method}(*args, &block) + Rails.logger.error "Enumerator Diverter (#{my_method})" + if @state == RUNNING_SHELL + # Must Divert results into buffer. + if block_given? + @my_read_stream.#{my_method}(*args) {|myvalue| + add_to_buffer(myvalue) + block.call(myvalue) + } + else + myvalue = @my_read_stream.#{my_method}(*args) + EnumerableRedirector.new(myvalue,self) + end + else + Rails.logger.error "Direct forwarding" + @my_read_stream.#{my_method}(*args,&block) + end + end + EOF + Rails.logger.error "Finished definition." + elsif my_name =~ /^(get|read)/ + # Handle "regular" read functions (Class #2) + Rails.logger.error "Defining new function: #{my_method}" + self.class.class_eval <<-EOF, __FILE__, __LINE__ + def #{my_method}(*args, &block) + Rails.logger.error "Normal Diverter" + myvalue = @my_read_stream.#{my_method}(*args) + add_to_buffer(myvalue) if @state == RUNNING_SHELL + myvalue + end + EOF + Rails.logger.error "Finished definition." + else + # Handle every thing else by simply forwarding (Class #3) + Rails.logger.error "Defining new function: #{my_method}" + self.class.class_eval <<-EOF, __FILE__, __LINE__ + def #{my_method}(*args, &block) + Rails.logger.error "Simple Proxy" + @my_read_stream.#{my_method}(*args,&block) + end + EOF + Rails.logger.error "Finished definition." + end + # Call new function once + Rails.logger.error "Calling new function once!" + self.send(my_method,*args,&block) + end + + # This class wraps a given enumerator and produces another one + # that logs all read data into the buffer. + class EnumerableRedirector + include Enumerable + + def initialize(my_enum,my_redirector) + @my_enum=my_enum + @my_redirector=my_redirector + end + + def each + return to_enum :each unless block_given? + + @my_enum.each do |myvalue| + @my_redirector.add_to_buffer(myvalue) + yield myvalue + end + end + end + + def add_to_buffer(invalue) + return if @my_buffer_overfull + if invalue.is_a?(Array) + invalue.each {|nextvalue| push_to_buffer nextvalue} + else + push_to_buffer invalue + end + end + + def push_to_buffer(invalue) + nextchunk = invalue.is_a?(Integer) ? invalue.chr : invalue + if @my_buffer.length + nextchunk.length <= self.class.max_cache_size + @my_buffer << nextchunk + else + @my_buffer_overfull = true + end + end + + ############################################################################## + # The following three functions are the generic versions of what is # + # currently "compiled" into function definitions above in missing_method(). # + ############################################################################## + # Class #1 functions (Read functions with block/enumerator behavior) + def enumerator_diverter(my_method,*args,&block) + Rails.logger.error "Enumerator Diverter" + if @state == RUNNING_SHELL + # Must Divert results into buffer. + if block_given? + @my_read_stream.send(my_method,*args) {|myvalue| + add_to_buffer(myvalue) + block.call(myvalue) + } + else + myvalue = @my_read_stream.send(my_method,*args) + EnumerableRedirector.new(myvalue,self) + end + else + @my_read_stream.send(my_method,*args,&block) + end + end + + # Class #2 functions (Return of Array, String, or Integer) + def normal_diverter(my_method,*args) + myvalue = @my_read_stream.send(my_method,*args) + Rails.logger.error "Normal Diverter: #{myvalue}" + add_to_buffer(myvalue) if @state == RUNNING_SHELL + myvalue + end + + # Class #3 functions (Everything by read functions) + def simple_proxy(my_method,*args,&block) + Rails.logger.error "Simple Proxy" + @my_read_stream.send(my_method,*args,&block) + end + + ############################################### + # Basic redirector methods # + ############################################### + + def initialize(cmd_str, repo_id, options={}) + @my_cmd_str = cmd_str + @my_repo_id = repo_id + @my_options = options + @my_buffer = "" + @my_buffer_overfull = false + @my_extra_args = "" + @my_read_stream = nil + @status = nil + if options[:write_stdin] + @state = WAIT_TO_CHECK + else + startup_shell + end + end + + def startup_shell + Thread.abort_on_exception = true + proxy_started = false + @wrap_thread = Thread.new(@my_cmd_str,@my_options) {|cmd_str,options| + if options[:write_stdin] + @retio = Redmine::Scm::Adapters::AbstractAdapter.shellout(cmd_str,options) {|io| + io.binmode + io.puts(@my_extra_args) + io.close_write + @my_read_stream = io + + proxy_started = true + + # Wait before closing io + Thread.stop + } + else + @retio = Redmine::Scm::Adapters::AbstractAdapter.shellout(cmd_str) {|io| + @my_read_stream = io + + proxy_started = true + + # Wait before closing io + Thread.stop + } + end + @status = $? + } + + # Wait until subthread gets far enough + while !proxy_started + Thread.pass + end + @state = RUNNING_SHELL + end + + def exit_shell + # If shell was running, kill off wrapper thread + if @state == RUNNING_SHELL + @wrap_thread.run + @wrap_thread.join + @state = DEAD + Rails.logger.error "Output from shell#{@my_buffer_overfull ? "(Overflow)" : ""}:\n#{@my_buffer}" + if !@my_buffer_overfull + self.class.set_cache(@repo_id,@my_buffer,@my_cmd_str,@my_extra_args) + # Insert result into cache + end + end + [@retio,@status] + end + + ############################################### + # Caching interface functions # + ############################################### + + def self.max_cache_time + (Setting.plugin_redmine_git_hosting['gitCacheMaxTime']).to_i # in seconds, default = 60 + end + + def self.max_cache_elements + (Setting.plugin_redmine_git_hosting['gitCacheMaxElements']).to_i # default = 100 + end + + def self.max_cache_size + (Setting.plugin_redmine_git_hosting['gitCacheMaxSize']).to_i*1024*1024 # In MB, default = 16MB, converted to bytes + end + + def self.compose_key(key1,key2) + if key2 && !key2.blank? + key1 + "\n" + key2 + else + key1 + end + end + + def self.check_cache(primary_key,secondary_key=nil) + Rails.logger.error "Probing cache with key: #{compose_key(primary_key,secondary_key)}" + out=nil + cached = GitCache.find_by_command(compose_key(primary_key,secondary_key)) + if cached + cur_time = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now + if cur_time.to_i - cached.created_at.to_i < max_cache_time || max_cache_time < 0 + out = cached.command_output == nil ? "" : cached.command_output + else + GitCache.destroy(cached.id) + end + end + if out + Rails.logger.error "********* Matched ***********\n#{out.to_s}" + # Return result as a string stream + StringIO.new(out) + else + Rails.logger.error "********* Failed ************\n" + nil + end + end + + def self.set_cache(repo_id,out_value,primary_key,secondary_key=nil) + Rails.logger.error "Inserting into cache with key: #{compose_key(primary_key,secondary_key)}" + gitc = GitCache.create( :command=>compose_key(primary_key,secondary_key), + :command_output=>out_value, + :proj_identifier=>repo_id ) + gitc.save + if GitCache.count > max_cache_elements && max_cache_elements >= 0 + oldest = GitCache.find(:last, :order => "created_at DESC") + GitCache.destroy(oldest.id) + end + end +end + From b454d86649f68c0bb44f5c111696b5b9695dc53a Mon Sep 17 00:00:00 2001 From: John Kubiatowicz Date: Thu, 20 Sep 2012 12:36:09 -0700 Subject: [PATCH 072/107] Mostly working integration with 1.4. Still with debugging. --- app/controllers/git_http_controller.rb | 28 +- app/controllers/gitolite_hooks_controller.rb | 232 ++++++------ .../repository_mirrors_controller.rb | 6 +- ...repository_post_receive_urls_controller.rb | 6 +- app/models/git_hosting_observer.rb | 2 +- app/models/git_hosting_settings_observer.rb | 13 +- app/models/repository_mirror.rb | 16 +- app/models/repository_post_receive_url.rb | 10 +- app/views/my/account.html.erb | 10 +- app/views/projects/_git_urls.erb | 10 +- .../projects/settings/_repository.html.erb | 23 +- app/views/repositories/_form.html.erb | 20 +- app/views/repositories/_git_urls.erb | 16 +- app/views/repositories/edit.html.erb | 20 ++ .../repositories/git_instructions.html.erb | 23 +- .../repository_mirrors/_view_list.html.erb | 31 +- .../_view_list.html.erb | 4 +- app/views/settings/_display_access.html.erb | 2 +- .../settings/_redmine_git_hosting.html.erb | 7 + config/locales/bg.yml | 9 +- config/locales/bs.yml | 9 +- config/locales/ca.yml | 9 +- config/locales/cs.yml | 9 +- config/locales/da.yml | 9 +- config/locales/de.yml | 9 +- config/locales/el.yml | 9 +- config/locales/en.yml | 9 +- config/locales/es.yml | 9 +- config/locales/fi.yml | 9 +- config/locales/fr.yml | 9 +- config/locales/gl.yml | 9 +- config/locales/he.yml | 9 +- config/locales/hu.yml | 9 +- config/locales/id.yml | 9 +- config/locales/it.yml | 9 +- config/locales/ja.yml | 9 +- config/locales/ko.yml | 9 +- config/locales/lt.yml | 9 +- config/locales/nl.yml | 9 +- config/locales/no.yml | 9 +- config/locales/pl.yml | 9 +- config/locales/pt-BR.yml | 9 +- config/locales/pt.yml | 9 +- config/locales/ro.yml | 9 +- config/locales/ru.yml | 9 +- config/locales/sk.yml | 9 +- config/locales/sl.yml | 9 +- config/locales/sr.yml | 9 +- config/locales/sv.yml | 9 +- config/locales/th.yml | 9 +- config/locales/tr.yml | 9 +- config/locales/uk.yml | 9 +- config/locales/vi.yml | 9 +- config/locales/zh-TW.yml | 9 +- config/locales/zh.yml | 9 +- config/routes.rb | 4 +- .../hooks/post-receive.redmine_gitolite.rb | 9 +- ...20120724211806_add_settings_to_plugin_5.rb | 2 +- ...904060609_update_multi_repo_per_project.rb | 111 ++++++ init.rb | 3 +- lib/cached_shell_redirector.rb | 36 +- lib/git_adapter_hooks.rb | 36 +- lib/git_hosting.rb | 337 +++++++++++------- lib/git_hosting/patches/git_adapter_patch.rb | 73 ++-- .../patches/git_repository_patch.rb | 12 + .../patches/members_controller_patch.rb | 19 +- lib/git_hosting/patches/project_patch.rb | 36 +- .../patches/projects_controller_patch.rb | 27 +- .../patches/repositories_controller_patch.rb | 80 ++--- lib/git_hosting/patches/repository_patch.rb | 139 +++++++- lib/gitolite_conf.rb | 2 +- 71 files changed, 1178 insertions(+), 551 deletions(-) create mode 100644 app/views/repositories/edit.html.erb create mode 100644 db/migrate/20120904060609_update_multi_repo_per_project.rb diff --git a/app/controllers/git_http_controller.rb b/app/controllers/git_http_controller.rb index 6225fa1c8..e776a340f 100644 --- a/app/controllers/git_http_controller.rb +++ b/app/controllers/git_http_controller.rb @@ -1,4 +1,3 @@ - require 'rack/response' require 'rack/utils' require 'time' @@ -11,12 +10,11 @@ class GitHttpController < ApplicationController before_filter :authenticate def index - if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) - proj_id = proj_path_split[2] - project = Project.find_by_identifier(proj_id) - @git_http_repo_path = project_path = proj_path_split[1] + # Have already parsed path and found @repository during authentication + if proj_path_split = (params[:repo_path].match(/^(.*)\.git$/)) + @git_http_repo_path = repo_path = proj_path_split[1] - if GitHosting.http_access_url(project) == project_path + if GitHosting.http_access_url(@repository) == repo_path p1 = params[:path][0] || "" p2 = params[:path][1] || "" p3 = params[:path][2] || "" @@ -52,10 +50,9 @@ def index # repository URL doesn't match render_not_found end - return + else + render_not_found end - # Wrong prefix or :base doesn't end in .git or doesn't at least have one char in base name - render_not_found end private @@ -65,19 +62,17 @@ def authenticate query_valid = false authentication_valid = true - if proj_path_split = (params[:project_path].match(/^(.*?([^\/]+))\.git$/)) - project = Project.find_by_identifier(proj_path_split[2]) - repository = project != nil ? project.repository : nil - if(project != nil && repository !=nil) - if repository.extra[:git_http] == 2 || (repository.extra[:git_http] == 1 && is_ssl?) + if (@repository = Repository.find_by_path(params[:repo_path],:parse_ext=>true)) && @repository.is_a?(Repository::Git) + if @project = @repository.project + if @repository.extra[:git_http] == 2 || (@repository.extra[:git_http] == 1 && is_ssl?) query_valid = true - allow_anonymous_read = project.is_public + allow_anonymous_read = @project.is_public if is_push || (!allow_anonymous_read) authentication_valid = false authenticate_or_request_with_http_basic do |login, password| user = User.find_by_login(login); if user.is_a?(User) - if user.allowed_to?( :commit_access, project ) || ((!is_push) && user.allowed_to?( :view_changesets, project )) + if user.allowed_to?( :commit_access, @project ) || ((!is_push) && user.allowed_to?( :view_changesets, @project )) authentication_valid = user.check_password?(password) end end @@ -206,7 +201,6 @@ def internal_send_file(reqfile, content_type) end def file_exists(reqfile) - cmd="#{run_git_prefix()} if [ -e \"#{reqfile}\" ] ; then echo found ; else echo bad ; fi ' " is_found=%x[#{cmd}] is_found.chomp! diff --git a/app/controllers/gitolite_hooks_controller.rb b/app/controllers/gitolite_hooks_controller.rb index fb48cc4c1..e08c47b40 100644 --- a/app/controllers/gitolite_hooks_controller.rb +++ b/app/controllers/gitolite_hooks_controller.rb @@ -1,7 +1,7 @@ class GitoliteHooksController < ApplicationController skip_before_filter :verify_authenticity_token, :check_if_login_required, :except => :test - before_filter :find_project + before_filter :find_project_and_repository helper :git_hosting include GitHostingHelper @@ -12,103 +12,14 @@ def stub render(:code => 404) end - # Returns an array of GitHub post-receive hook style hashes - # http://help.github.com/post-receive-hooks/ - def post_receive_payloads(refs, project=nil) - project ||= @project - payloads = [] - refs.each do |ref| - oldhead, newhead, refname = ref.split(',') - - # Only pay attention to branch updates - next if not refname.match(/refs\/heads\//) - branch = refname.gsub('refs/heads/', '') - - if newhead.match(/^0{40}$/) - # Deleting a branch - GitHosting.logger.debug "Deleting branch \"#{branch}\"" - next - elsif oldhead.match(/^0{40}$/) - # Creating a branch - GitHosting.logger.debug "Creating branch \"#{branch}\"" - range = newhead - else - range = "#{oldhead}..#{newhead}" - end - - revisions_in_range = %x[#{GitHosting.git_exec} --git-dir='#{GitHosting.repository_path(project)}' rev-list --reverse #{range}] - #GitHosting.logger.debug "Revisions in Range: #{revisions.split().join(' ')}" - - commits = [] - revisions_in_range.split().each do |rev| - revision = project.repository.find_changeset_by_name(rev.strip) - commit = { - :id => revision.revision, - :url => url_for(:controller => "repositories", :action => "revision", - :id => project, :rev => rev, :only_path => false, - :host => Setting['host_name'], :protocol => Setting['protocol'] - ), - :author => { - :name => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), - :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') - }, - :message => revision.comments, - :timestamp => revision.committed_on, - :added => [], - :modified => [], - :removed => [] - } - revision.changes.each do |change| - if change.action == "M" - commit[:modified] << change.path - elsif change.action == "A" - commit[:added] << change.path - elsif change.action == "D" - commit[:removed] << change.path - end - end - commits << commit - end - - payloads << { - :before => oldhead, - :after => newhead, - :ref => refname, - :commits => commits, - :repository => { - :description => project.description, - :fork => false, - :forks => 0, - :homepage => project.homepage, - :name => project.identifier, - :open_issues => project.issues.open.length, - :owner => { - :name => Setting["app_title"], - :email => Setting["mail_from"] - }, - :private => !project.is_public, - :url => url_for(:controller => "repositories", :action => "show", - :id => project, :only_path => false, - :host => Setting["host_name"], :protocol => Setting["protocol"] - ), - :watchers => 0 - } - } - end - payloads - end - def post_receive - - if not @project.repository.extra.validate_encoded_time(params[:clear_time], params[:encoded_time]) + if not @repository.extra.validate_encoded_time(params[:clear_time], params[:encoded_time]) render(:text => "The hook key provided is not valid. Please let your server admin know about it") return end # Clear existing cache - GitHosting::clear_cache_for_project(@project) - - repo_path = GitHosting.repository_path(@project) + CachedShellRedirectory.clear_cache_for_repository(@repository) render :text => Proc.new { |response, output| response.headers["Content-Type"] = "text/plain;" @@ -117,16 +28,20 @@ def post_receive GitHosting.logger.debug "Fetching changesets for #{@project.name}'s repository" output.write("Fetching changesets for #{@project.name}'s repository ... ") output.flush - Repository.fetch_changesets_for_project(@project.identifier) + begin + @repository.fetch_changesets + rescue Redmine::Scm::Adapters::CommandFailed => e + logger.error "scm: error during fetching changesets: #{e.message}" + end output.write("Done\n") output.flush payloads = [] - if @project.repository_mirrors.has_explicit_refspec.any? or @project.repository.extra.notify_cia == 1 or @project.repository_post_receive_urls.any? + if @repository.repository_mirrors.has_explicit_refspec.any? or @repository.extra.notify_cia == 1 or @repository.repository_post_receive_urls.any? payloads = post_receive_payloads(params[:refs]) end - @project.repository_mirrors.all(:order => 'active DESC, created_at ASC', :conditions => "active=1").each {|mirror| + @repository.repository_mirrors.all(:order => 'active DESC, created_at ASC', :conditions => "active=1").each {|mirror| if mirror.needs_push payloads GitHosting.logger.debug "Pushing changes to #{mirror.url} ... " output.write("Pushing changes to mirror #{mirror.url} ... ") @@ -138,10 +53,10 @@ def post_receive output.write(result) output.flush end - } if @project.repository_mirrors.any? + } if @repository.repository_mirrors.any? # Post to each post-receive URL - @project.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each {|prurl| + @repository.repository_post_receive_urls.all(:order => "active DESC, created_at ASC", :conditions => "active=1").each {|prurl| msg = "Posting #{payloads.length} post-receive payloads to #{prurl.url} ... " GitHosting.logger.debug msg output.write msg @@ -158,11 +73,11 @@ def post_receive } output.write "done\n" output.flush - } if @project.repository_post_receive_urls.any? + } if @repository.repository_post_receive_urls.any? # Notify CIA #Thread.abort_on_exception = true - Thread.new(@project, payloads) {|project, payloads| + Thread.new(@repository, payloads) {|repository, payloads| GitHosting.logger.debug "Notifying CIA" output.write("Notifying CIA\n") output.flush @@ -170,15 +85,15 @@ def post_receive payloads.each do |payload| branch = payload[:ref].gsub("refs/heads/", "") payload[:commits].each do |commit| - revision = project.repository.find_changeset_by_name(commit["id"]) - next if project.repository.cia_notifications.notified?(revision) # Already notified about this commit + revision = repository.find_changeset_by_name(commit["id"]) + next if repository.cia_notifications.notified?(revision) # Already notified about this commit GitHosting.logger.info "Notifying CIA: Branch => #{branch} REVISION => #{revision.revision}" CiaNotificationMailer.deliver_notification(revision, branch) - project.repository.cia_notifications.notified(revision) + repository.cia_notifications.notified(revision) end end - } if !params[:refs].nil? && @project.repository.extra.notify_cia==1 + } if !params[:refs].nil? && @repository.extra.notify_cia==1 }, :layout => false end @@ -194,9 +109,9 @@ def test return render(:text => l(:cia_not_enough_permissions), :status => 403) if not_enough_perms # Grab the repository path - repo_path = GitHosting.repository_path(@project) + repo_path = GitHosting.repository_path(@repository) # Get the last revision we have on the database for this project - revision = @project.repository.changesets.find(:first) + revision = @repository.changesets.find(:first) # Find out to which branch this commit belongs to branch = %x[#{GitHosting.git_exec} --git-dir='#{repo_path}' branch --contains #{revision.scmid}].split('\n')[0].strip.gsub(/\* /, '') GitHosting.logger.debug "Revision #{revision.scmid} found on branch #{branch}" @@ -207,12 +122,115 @@ def test render(:text => l(:cia_notification_ok)) end - def find_project + protected + + # Returns an array of GitHub post-receive hook style hashes + # http://help.github.com/post-receive-hooks/ + def post_receive_payloads(refs) + payloads = [] + refs.each do |ref| + oldhead, newhead, refname = ref.split(',') + + # Only pay attention to branch updates + next if not refname.match(/refs\/heads\//) + branch = refname.gsub('refs/heads/', '') + + if newhead.match(/^0{40}$/) + # Deleting a branch + GitHosting.logger.debug "Deleting branch \"#{branch}\"" + next + elsif oldhead.match(/^0{40}$/) + # Creating a branch + GitHosting.logger.debug "Creating branch \"#{branch}\"" + range = newhead + else + range = "#{oldhead}..#{newhead}" + end + + # Grab the repository path + repo_path = GitHosting.repository_path(@repository) + revisions_in_range = %x[#{GitHosting.git_exec} --git-dir='#{repo_path}' rev-list --reverse #{range}] + #GitHosting.logger.debug "Revisions in Range: #{revisions.split().join(' ')}" + + commits = [] + revisions_in_range.split().each do |rev| + revision = @repository.find_changeset_by_name(rev.strip) + commit = { + :id => revision.revision, + :url => url_for(:controller => "repositories", :action => "revision", + :id => @project, :rev => rev, :only_path => false, + :host => Setting['host_name'], :protocol => Setting['protocol'] + ), + :author => { + :name => revision.committer.gsub(/^([^<]+)\s+.*$/, '\1'), + :email => revision.committer.gsub(/^.*<([^>]+)>.*$/, '\1') + }, + :message => revision.comments, + :timestamp => revision.committed_on, + :added => [], + :modified => [], + :removed => [] + } + revision.changes.each do |change| + if change.action == "M" + commit[:modified] << change.path + elsif change.action == "A" + commit[:added] << change.path + elsif change.action == "D" + commit[:removed] << change.path + end + end + commits << commit + end + + payloads << { + :before => oldhead, + :after => newhead, + :ref => refname, + :commits => commits, + :repository => { + :description => @project.description, + :fork => false, + :forks => 0, + :homepage => @project.homepage, + :name => @project.identifier, + :open_issues => @project.issues.open.length, + :owner => { + :name => Setting["app_title"], + :email => Setting["mail_from"] + }, + :private => !@project.is_public, + :url => url_for(:controller => "repositories", :action => "show", + :id => @project, :only_path => false, + :host => Setting["host_name"], :protocol => Setting["protocol"] + ), + :watchers => 0 + } + } + end + payloads + end + + # Locate that actual repository that is in use here. + # Notice that an empty "repositoryid" is assumed to refer to the default repo for a project + def find_project_and_repository @project = Project.find_by_identifier(params[:projectid]) if @project.nil? render(:text => l(:project_not_found, :identifier => params[:projectid])) if @project.nil? return end - end + if GitHosting.multi_repos? + if params[:repositoryid] && !params[:repositoryid].blank? + @repository = @project.repositories.find_by_identifier(params[:repositoryid]) + else + # return default or first repo with blank identifier + @repository = @project.repository || @project.repo_blank_ident + else + @repository = @project.repository # Only repository if redmine < 1.4 + end + if @repository.nil? + render_404 + end + end end diff --git a/app/controllers/repository_mirrors_controller.rb b/app/controllers/repository_mirrors_controller.rb index 31d0a612b..fd4067627 100644 --- a/app/controllers/repository_mirrors_controller.rb +++ b/app/controllers/repository_mirrors_controller.rb @@ -23,7 +23,7 @@ def create # display create view else @mirror.update_attributes(params[:repository_mirrors]) - @mirror.project = @project + @mirror.repository = @repository if @mirror.save flash[:notice] = l(:mirror_notice_created) @@ -152,9 +152,9 @@ def set_project_variable def find_repository_mirror mirror = RepositoryMirror.find_by_id(params[:id]) - @mirrors = @project.repository_mirrors + @mirrors = @repository.repository_mirrors - if mirror and mirror.project == @project + if mirror and mirror.repository == @repository @mirror = mirror elsif mirror render_403 diff --git a/app/controllers/repository_post_receive_urls_controller.rb b/app/controllers/repository_post_receive_urls_controller.rb index 8044855e5..e6dca8f53 100644 --- a/app/controllers/repository_post_receive_urls_controller.rb +++ b/app/controllers/repository_post_receive_urls_controller.rb @@ -23,7 +23,7 @@ def create # display create view else @prurl.update_attributes(params[:repository_post_receive_urls]) - @prurl.project = @project + @prurl.repository = @repository if @prurl.save flash[:notice] = l(:post_receive_url_notice_created) @@ -144,9 +144,9 @@ def set_project_variable def find_repository_post_receive_url prurl = RepositoryPostReceiveUrl.find_by_id(params[:id]) - @prurls = @project.repository_post_receive_urls + @prurls = @repository.repository_post_receive_urls - if prurl and prurl.project == @project + if prurl and prurl.repository == @repository @prurl = prurl elsif prurl render_403 diff --git a/app/models/git_hosting_observer.rb b/app/models/git_hosting_observer.rb index e82fee5c4..0bfb03e1a 100644 --- a/app/models/git_hosting_observer.rb +++ b/app/models/git_hosting_observer.rb @@ -70,7 +70,7 @@ def after_save(object) def after_destroy(object) if object.is_a?(Repository::Git) update_repositories(object,:delete=>true) - GitHosting::clear_cache_for_project(object.project) + CachedShellRedirector::clear_cache_for_repository(object) else update_repositories(object) end diff --git a/app/models/git_hosting_settings_observer.rb b/app/models/git_hosting_settings_observer.rb index 1e64873eb..996bd6b9e 100644 --- a/app/models/git_hosting_settings_observer.rb +++ b/app/models/git_hosting_settings_observer.rb @@ -138,6 +138,16 @@ def before_save(object) end end + # Check to see if we are trying to claim all repository identifiers are unique + if valuehash['gitRepositoryIdentUnique'] == "true" + prev=nil + if Repository.all(:order => 'identifier ASC').map(&:identifier).detect{|x| old_prev=prev; prev=x; old_prev==prev} + # Oops -- have duplication. Force to false. + valuehash['gitRepositoryIdentUnique'] == "false" + end + end + + # Exclude bad expire times (and exclude non-numbers) if valuehash['gitRecycleExpireTime'] if valuehash['gitRecycleExpireTime'].to_f > 0 @@ -188,7 +198,8 @@ def after_save(object) @@old_valuehash['gitRepositoryHierarchy'] != valuehash['gitRepositoryHierarchy'] || @@old_valuehash['gitUser'] != valuehash['gitUser'] || @@old_valuehash['gitConfigFile'] != valuehash['gitConfigFile'] || - @@old_valuehash['gitConfigHasAdminKey'] != valuehash['gitConfigHasAdminKey'] + @@old_valuehash['gitConfigHasAdminKey'] != valuehash['gitConfigHasAdminKey'] || + GitHosting.multi_repos? && @@old_valuehash['gitRepositoryIdentUnique'] != valuehash['gitRepositoryIdentUnique'] # Need to update everyone! GitHostingObserver.bracketed_update_repositories(:resync_all) end diff --git a/app/models/repository_mirror.rb b/app/models/repository_mirror.rb index 0fd03834f..20c1e19cd 100644 --- a/app/models/repository_mirror.rb +++ b/app/models/repository_mirror.rb @@ -6,13 +6,13 @@ class RepositoryMirror < ActiveRecord::Base PUSHMODE_FORCE = 1 PUSHMODE_FAST_FORWARD = 2 - belongs_to :project + belongs_to :repository attr_accessible :url, :push_mode, :include_all_branches, :include_all_tags, :explicit_refspec, :active - validates_uniqueness_of :url, :scope => [:project_id] - validates_presence_of :project_id, :url - validates_associated :project + validates_uniqueness_of :url, :scope => [:repository_id] + validates_presence_of :repository_id, :url + validates_associated :repository validate :check_refspec @@ -23,12 +23,8 @@ class RepositoryMirror < ActiveRecord::Base named_scope :has_explicit_refspec, {:conditions => ['push_mode > 0']} - def to_s - return File.join("#{project.identifier}-#{url}") - end - def push - repo_path = GitHosting.repository_path(project) + repo_path = GitHosting.repository_path(repository) push_args = "" if push_mode == PUSHMODE_MIRROR @@ -74,7 +70,7 @@ def needs_push(payloads=[]) end def to_s - return File.join("#{project.identifier}-#{url}") + return File.join("#{repository.project.identifier}-#{url}") end protected diff --git a/app/models/repository_post_receive_url.rb b/app/models/repository_post_receive_url.rb index 19d1ea3b1..55ba8b784 100644 --- a/app/models/repository_post_receive_url.rb +++ b/app/models/repository_post_receive_url.rb @@ -2,14 +2,14 @@ class RepositoryPostReceiveUrl < ActiveRecord::Base STATUS_ACTIVE = 1 STATUS_INACTIVE = 0 - belongs_to :project + belongs_to :repository attr_accessible :url, :mode, :active - validates_uniqueness_of :url, :scope => [:project_id] - validates_presence_of :project_id + validates_uniqueness_of :url, :scope => [:repository_id] + validates_presence_of :repository_id validates_format_of :url, :with => URI::regexp(%w(http https)) - validates_associated :project + validates_associated :repository before_validation :strip_whitespace @@ -25,7 +25,7 @@ def mode= (value) end def to_s - return File.join("#{project.identifier}-#{url}") + return File.join("#{repository.project.identifier}-#{url}") end protected diff --git a/app/views/my/account.html.erb b/app/views/my/account.html.erb index f65083e19..0caec5486 100644 --- a/app/views/my/account.html.erb +++ b/app/views/my/account.html.erb @@ -6,10 +6,10 @@ <%= error_messages_for 'user' %>
- <% form_for :user, @user, :url => { :action => "account" }, - :builder => TabularFormBuilder, - :lang => current_language, - :html => { :id => 'my_account_form' } do |f| %> + <% labelled_form_for :user, @user, + :url => { :action => "account" }, + :html => { :id => 'my_account_form', + :method => :post } do |f| %>
<%=l(:label_information_plural)%>

<%= f.text_field :firstname, :required => true %>

@@ -34,7 +34,7 @@ <%= submit_tag l(:button_save) %>
- <% end %> + <% end %>
diff --git a/app/views/projects/_git_urls.erb b/app/views/projects/_git_urls.erb index fa9c4388c..392a665c5 100644 --- a/app/views/projects/_git_urls.erb +++ b/app/views/projects/_git_urls.erb @@ -1,4 +1,4 @@ -<% if @project.repository && @project.repository.is_a?(Repository::Git) %> +<% if project.repository && project.repository.is_a?(Repository::Git) %> <% content_for :header_tags do %> <%= stylesheet_link_tag('git_url_display', :plugin => 'redmine_git_hosting') %> @@ -10,16 +10,16 @@ <% end %> - <% if (!User.current.anonymous?) || project.repository.extra[:git_http].to_s != "0" || (project.is_public && project.repository.extra[:git_daemon].to_s != "0" ) %> + <% if (!User.current.anonymous?) || repository.extra[:git_http].to_s != "0" || (project.is_public && repository.extra[:git_daemon].to_s != "0" ) %>

Repository Access Links:

@@ -40,10 +40,10 @@ <% if !User.current.anonymous? %>
  • SSH
  • <% end %> - <% if project.repository.extra[:git_http].to_s != "0" %> + <% if repository.extra[:git_http].to_s != "0" %>
  • HTTP
  • <% end %> - <% if project.is_public && project.repository.extra[:git_daemon].to_s != "0" %> + <% if project.is_public && repository.extra[:git_daemon].to_s != "0" %>
  • Git
  • <% end %> diff --git a/app/views/repositories/edit.html.erb b/app/views/repositories/edit.html.erb new file mode 100644 index 000000000..e028b5e30 --- /dev/null +++ b/app/views/repositories/edit.html.erb @@ -0,0 +1,20 @@ +

    <%= l(:label_repository) %>

    + +<% labelled_form_for :repository, @repository, :url => repository_path(@path), :html => {:method => :put} do |f| %> + <%= render :partial => 'form', :locals => {:f => f} %> +<% end %> + + +<% if @repository && @repository.is_a?(Repository::Git) && !(@repository.nil? || @repository.new_record?) %> + <% if GitHostingHelper.can_view_deployment_keys(@repository.project) %> + <%= render :partial => 'deployment_credentials/view_list' %> + <% end %> + + <% if GitHostingHelper.can_view_post_receive_urls(@repository.project) %> + <%= render :partial => 'repository_post_receive_urls/view_list' %> + <% end %> + + <% if GitHostingHelper.can_view_mirrors(@repository.project) %> + <%= render :partial => 'repository_mirrors/view_list' %> + <% end %> +<% end %> diff --git a/app/views/repositories/git_instructions.html.erb b/app/views/repositories/git_instructions.html.erb index 2664af785..e5dd4ed45 100644 --- a/app/views/repositories/git_instructions.html.erb +++ b/app/views/repositories/git_instructions.html.erb @@ -1,9 +1,8 @@ - <%# This is used to display basic git setup instructions, like on github... %> <% flash.now[:warning] = "Repository is empty. Get started by following the instructions below." %> <% gitSHP = Setting.plugin_redmine_git_hosting['gitServer'].match(/:\d+$/) %> <% git_ssh_url = - "#{gitSHP ? 'ssh://' : ''}#{Setting.plugin_redmine_git_hosting['gitUser']}@#{Setting.plugin_redmine_git_hosting['gitServer']}#{gitSHP ? '/' : ':'}#{GitHosting.repository_name(@project)}.git" %> + "#{gitSHP ? 'ssh://' : ''}#{Setting.plugin_redmine_git_hosting['gitUser']}@#{Setting.plugin_redmine_git_hosting['gitServer']}#{gitSHP ? '/' : ':'}#{GitHosting.repository_name(@repository)}.git" %>
    @@ -13,12 +12,12 @@ git config --global user.email <%= User.current.mail %> <% if User.current.gitolite_public_keys.active.length == 0 %><%= "\t" + (link_to "Upload SSH Public Key", {:controller => 'my', :action => 'account'}) + "\n" %><% else %><%= "" %><% end %>

    Repository Setup:

    -
    	mkdir <%= @project.identifier %>
    -	cd <%= @project.identifier %>
    +
    	mkdir <%= @repository.git_name %>
    +	cd <%= @repository.git_name %>
     	git init
     	touch readme.txt
     	git add readme.txt
    -	git commit -m 'Initializing <%= @project %> repository'
    +	git commit -m 'Initializing <%= @git_label %> repository'
     	git remote add origin <%= git_ssh_url %>
     	git push -u origin master
     
    @@ -28,3 +27,17 @@ git push -u origin master
    + +<% if @repositories.size > 1 %> + <% content_for :sidebar do %> +

    <%= l(:label_repository_plural) %>

    +

    + <%= @repositories.sort.collect {|repo| + link_to h(repo.name), + {:controller => 'repositories', :action => 'show', + :id => @project, :repository_id => repo.identifier_param, :rev => nil, :path => nil}, + :class => 'repository' + (repo == @repository ? ' selected' : '') + }.join('
    ').html_safe %> +

    + <% end %> +<% end %> diff --git a/app/views/repository_mirrors/_view_list.html.erb b/app/views/repository_mirrors/_view_list.html.erb index c698c31d0..6b8b94c22 100644 --- a/app/views/repository_mirrors/_view_list.html.erb +++ b/app/views/repository_mirrors/_view_list.html.erb @@ -8,7 +8,7 @@

    Repository Mirrors

    - <% if @repository.project.repository_mirrors.any? %> + <% if @repository.repository_mirrors.any? %>
    @@ -20,7 +20,7 @@ - <% @repository.project.repository_mirrors.each do |mirror| %> + <% @repository.repository_mirrors.each do |mirror| %> @@ -67,6 +67,9 @@ <% end %>
    <%= link_to(h(mirror.url), url_for(:controller => 'repository_mirrors', :action => 'edit', :repository_id => @repository.id, :id => mirror.id), :class => 'edit-mirror') %>