Skip to content

Commit db30d7e

Browse files
committed
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
1 parent 10bfd7d commit db30d7e

50 files changed

Lines changed: 967 additions & 259 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.mkd

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# Redmine Git Hosting Plugin (v0.4.2)
1+
# Redmine Git Hosting Plugin (v0.4.2x)
22

33
A ChiliProject / Redmine plugin which makes configuring your own git hosting easy. This plugin allows straightforward management
44
of gitolite and associated public keys, the git daemon, and integrates code from Scott Schacon's "grack" utility
55
to provide Git Smart HTTP access. Git repositories are automatically created when the repository is created in
6-
redmine. There is also an option to automatically create a git repository for a project, when the project is created.
6+
Redmine. There is also an option to automatically create a git repository for a project, when the project is created.
77
Caching functionality is also implemented to speed page-load times for viewing git repositories.
88

99

@@ -41,6 +41,9 @@ Note that this guide refers to the "web server user" as the user under which Rai
4141
not always) the same as the user that runs the main web server. If you are running Rails under a different user, follow
4242
these instructions using that user, not the one for the main web server.
4343

44+
As of the most recent set of patches, this plugin is compatible with running multiple Redmine installations on the same server, each
45+
with the same *or* different gitolite users/repositories. The later configuration (multiple Redmine installations, each with a different
46+
gitolite installation) is particularly useful for a web-hosting scenario with independent developers.
4447

4548
## Step-By-Step configuration instructions
4649

@@ -101,12 +104,12 @@ variables are set correctly. To adjust these variables, open an editor and edit
101104
Starting on line 22 you will see the settings definitions you should edit.
102105

103106

104-
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
105-
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
107+
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
108+
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
106109

107110

108111
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
109-
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
112+
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
110113
www.my-own-personal-git-host-server.com/path/to/redmine, *gitServer* should be set to www.my-own-personal-git-host-server.com
111114

112115

@@ -146,12 +149,51 @@ you have both git and svn (or hg, or cvs etc.) repositories, this may cause prob
146149

147150
*Delete Git Repository When Project Is Deleted* can be enabled to let this plugin control repository deletion as well as repository creation. By default,
148151
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
149-
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.
152+
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
153+
deleted in ChiliProject/Redmine. Note, however, that even when this feature is enabled, deleted repositories are placed into a "recycle_bin" for a configurable
154+
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.
155+
156+
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. '/'.
157+
Deleted repositories are kept here for up to *gitRecycleExpireTime* hours (configurable, defaults to 24.0 hours).
158+
159+
The *gitLockWaitTime* represents the amount of time that the plugin will wait in attempting to acquire its internal synchronization lock before giving up.
160+
You probably will not need to change this value.
150161

151162
*Show Checkout URLs* can be disabled to hide the git URL bar in the repository tab. It is enabled by default.
152163

153164
See below in the "Caching" section of this readme for more information on caching and how the caching variables should be configured.
154165

166+
## Resychronization of gitolite configuration
167+
168+
Whenever a Redmine `fetch_changesets()` operation is executed (i.e. `http://REDMINE_ROOT/sys/fetch_changesets?key=xxx`), this plugin will check the
169+
gitolite keydir and configuration file for consistency. It will correct any errors that it finds. Further, regular execution of a fetch_changesets operation
170+
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
171+
phantom synchronization problems, it is recommended that you execute `fetch_changesets()` regularly (every 15 to 30 minutes).
172+
173+
Two rake tasks can additionally be used for resynchronization (although these are redundant with executing `fetch_changesets()` through other means).
174+
175+
**(1)** To fixup the gitolite configuration file, fix errors, and delete expired files in the recycle_bin, execute:
176+
177+
RAILS_ENV=production rake gitolite:update_repositories
178+
179+
**(2)** To perform all the above operations while at the same time fetching changesets for all repositories, execute:
180+
181+
RAILS_ENV=production rake gitolite:fetch_changes
182+
183+
**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.**
184+
(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).
185+
186+
## Interaction with non-Redmine gitolite users
187+
188+
This plugin respects gitolite repositories that are managed outside of Redmine or managed by both Redmine and non-Redmine users:
189+
190+
* When performing a *fetch_changesets()* operation, this plugin will delete and reestablish all keys that are of the form "redmine_",
191+
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
192+
is granted for a given repository.
193+
* Keys other than "redmine_" are left untouched and can be in projects by themselves or mixed in with projects managed by redmine.
194+
* When a Redmine-managed project is deleted (with the *Delete Git Repository When Project Is Deleted* option enabled), its corresponding git repository
195+
*will not be deleted/recycled* if there are non-Redmine keys in the gitolite.conf file.
196+
155197
## A Note About PATH variables
156198

157199
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,27 +288,27 @@ This library allows you to quickly deploy ChiliProject, with this plugin to an u
246288
chili\_test.sh script, modifying the variables in those scripts as desired. This library is still under development,
247289
so these instructions may need to be updated in the near future.
248290

249-
## Selinux Configuration for redmine
291+
## Selinux Configuration for Redmine
250292

251293
This plugin can be configured to run with selinux. We have included a rakefile in tasks/selinux.rake to assist
252294
with installing with selinux. You should start by editing init.rb and migrating as described above. Then, you
253-
can execute one of the selinux rake tasks (from the redmine root). For instance, the simplest option installs
254-
a selinux configuration for both redmine and the redmine_git_hosting plugin:
295+
can execute one of the selinux rake tasks (from the Redmine root). For instance, the simplest option installs
296+
a selinux configuration for both Redmine and the redmine_git_hosting plugin:
255297

256298
rake selinux:install RAILS_ENV=production
257299

258300
This will generate the redmine_git_hosting binaries in ./bin, install a selinux policy for these binaries (called
259-
redmine_git.pp), then install a complete context for redmine as follows:
301+
redmine_git.pp), then install a complete context for Redmine as follows:
260302

261-
**(1)** Most of redmine will be marked with "public_content_rw_t".
303+
**(1)** Most of Redmine will be marked with "public_content_rw_t".
262304

263305
**(2)** The dispatch files in Rails.root/public/dispatch.* will be marked with "httpd_sys_script_exec_t"
264306

265307
**(3)** The redmine_git_hosting binaries in Rails.root/vendor/plugins/redmine_git_hosting/bin will be labeled
266308
with "httpd_redmine_git_script_exec_t", which has been crafted to allow the sudo behavior required by these
267309
binaries.
268310

269-
Note that this rake file has additional options. For instance, you can specify redmine roots with regular
311+
Note that this rake file has additional options. For instance, you can specify Redmine roots with regular
270312
expressions (not globbed expessions!) as follows (notice the use of double quotes):
271313

272314
rake selinux:install RAILS_ENV=production ROOT_PATTERN="/source/.*/redmine"
@@ -288,7 +330,8 @@ with apache and fcgi. Other configurations may require slight tweaking.
288330

289331
This plugin has been primarily tested on Ubuntu Server 10.10 and 11.04 (32 and 64 bit) with ChiliProject v1.x,
290332
ChiliProject 2.0.0 and Redmine 1.2.1 with PostgreSQL as the database (July, 2011). It is possible that some
291-
debugging will be necessary for other configurations.
333+
debugging will be necessary for other configurations. Selinux configurations were tested under Redhat Enterprise Linux
334+
version 6.x with apache and fcgi.
292335

293336

294337
## Required gems
@@ -308,7 +351,8 @@ This plugin is based largely on the Gitosis plugin by Jan Schulz-Hofen for http:
308351
were provided by github users untoldwind, tingar and ericpaulbishop. These updates were merged together and
309352
expanded upon by Eric Bishop to create this more comprehensive Git Hosting plugin.
310353

354+
Copyright (c) 2011 John Kubiatowicz (kubitron@cs.berkeley.edu) MIT License.
355+
311356
Copyright (c) 2010-2011 Eric Bishop (ericpaulbishop@gmail.com) MIT License.
312357

313358
Copyright (c) 2009-2010 Jan Schulz-Hofen, ROCKET RENTALS GmbH (http://www.rocket-rentals.de). MIT License.
314-

app/models/git_hosting_observer.rb

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class GitHostingObserver < ActiveRecord::Observer
33

44
@@updating_active = true
55
@@updating_active_stack = 0
6+
@@updating_active_flags = {}
67
@@cached_project_updates = []
78

89
def reload_this_observer
@@ -13,6 +14,12 @@ def reload_this_observer
1314

1415

1516
def self.set_update_active(is_active)
17+
case is_active
18+
when Symbol then @@updating_active_flags[is_active] = true
19+
when Hash then @updating_active_flags.merge(is_active)
20+
when Project then @@cached_project_updates << is_active
21+
end
22+
1623
if !is_active
1724
@@updating_active_stack += 1
1825
else
@@ -23,22 +30,22 @@ def self.set_update_active(is_active)
2330
end
2431

2532
if is_active && @@updating_active_stack == 0
26-
if @@cached_project_updates.length > 0
33+
if @@cached_project_updates.length > 0 || !@@updating_active_flags.empty?
2734
@@cached_project_updates = @@cached_project_updates.flatten.uniq.compact
28-
GitHosting::update_repositories(@@cached_project_updates, false)
35+
GitHosting::update_repositories(@@cached_project_updates, @@updating_active_flags)
2936
@@cached_project_updates = []
37+
@@updating_active_flags = {}
3038
end
3139
@@updating_active = true
3240
else
3341
@@updating_active = false
3442
end
3543
end
3644

37-
3845
def before_destroy(object)
3946
if object.is_a?(Repository::Git)
4047
if Setting.plugin_redmine_git_hosting['deleteGitRepositories'] == "true"
41-
GitHosting::update_repositories(object.project, true)
48+
GitHosting::delete_repository(object.project)
4249
%x[#{GitHosting::git_user_runner} 'rm -rf #{object.url}' ]
4350
end
4451
GitHosting::clear_cache_for_project(object.project)
@@ -63,7 +70,7 @@ def before_save(object)
6370
def after_save(object)
6471
update_repositories(object)
6572
end
66-
73+
6774

6875
def after_destroy(object)
6976
if !object.is_a?(Repository::Git)
@@ -74,9 +81,7 @@ def after_destroy(object)
7481

7582
protected
7683

77-
7884
def update_repositories(object)
79-
8085
projects = []
8186
case object
8287
when Repository::Git then projects.push(object.project)
@@ -87,7 +92,7 @@ def update_repositories(object)
8792
end
8893
if(projects.length > 0)
8994
if (@@updating_active)
90-
GitHosting::update_repositories(projects, false)
95+
GitHosting::update_repositories(projects)
9196
else
9297
@@cached_project_updates.concat(projects)
9398
end
Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,112 @@
11
class GitHostingSettingsObserver < ActiveRecord::Observer
22
observe :setting
33

4-
@@old_hook_debug = Setting.plugin_redmine_git_hosting['gitHooksDebug']
5-
@@old_hook_asynch = Setting.plugin_redmine_git_hosting['gitHooksAreAsynchronous']
6-
@@old_http_server = Setting.plugin_redmine_git_hosting['httpServer']
7-
@@old_git_user = Setting.plugin_redmine_git_hosting['gitUser']
8-
@@old_gitolite_identity = Setting.plugin_redmine_git_hosting['gitoliteIdentityFile']
9-
@@old_gitolite_publickey = Setting.plugin_redmine_git_hosting['gitoliteIdentityPublicKeyFile']
10-
@@old_repo_base = Setting.plugin_redmine_git_hosting['gitRepositoryBasePath']
11-
4+
@@old_valuehash = (Setting.plugin_redmine_git_hosting).clone
125

136
def reload_this_observer
147
observed_classes.each do |klass|
158
klass.name.constantize.add_observer(self)
169
end
1710
end
1811

12+
# There is a long-running bug in ActiveRecord::Observer that prevents us from
13+
# returning from before_save() with false to signal verification failure.
14+
#
15+
# Thus, we can only silently refuse to perform bad changes and/or perform
16+
# slight corrections to badly formatted values.
1917
def before_save(object)
20-
if object.name == "plugin_redmine_git_hosting" && !GitHosting.bin_dir_writeable?
21-
# If bin directory not alterable, don't alow changes to
22-
# Git Username, or Gitolite public or private keys
18+
# Only validate settings for our plugin
19+
if object.name == "plugin_redmine_git_hosting"
2320
valuehash = object.value
24-
valuehash['gitUser'] = @@old_git_user
25-
valuehash['gitoliteIdentityFile'] = @@old_gitolite_identity
26-
valuehash['gitoliteIdentityPublicKeyFile'] = @@old_gitolite_publickey
27-
object.value = valuehash
21+
if !GitHosting.bin_dir_writeable?
22+
# If bin directory not alterable, don't allow changes to
23+
# Git Username, or Gitolite public or private keys
24+
valuehash['gitUser'] = @@old_valuehash['gitUser']
25+
valuehash['gitoliteIdentityFile'] = @@old_valuehash['gitoliteIdentityFile']
26+
valuehash['gitoliteIdentityPublicKeyFile'] = @@old_valuehash['gitoliteIdentityPublicKeyFile']
27+
end
28+
29+
# Normalize Repository path, should be relative and end in '/'
30+
if valuehash['gitRepositoryBasePath']
31+
normalizedFile = File.expand_path(valuehash['gitRepositoryBasePath'].lstrip.rstrip,"/")
32+
if (normalizedFile != "/")
33+
valuehash['gitRepositoryBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/'
34+
else
35+
valuehash['gitRepositoryBasePath'] = @@old_valuehash['gitRepositoryBasePath']
36+
end
37+
end
38+
39+
# Normalize Recycle bin path, should be relative and end in '/'
40+
if valuehash['gitRecycleBasePath']
41+
normalizedFile = File.expand_path(valuehash['gitRecycleBasePath'].lstrip.rstrip,"/")
42+
if (normalizedFile != "/")
43+
valuehash['gitRecycleBasePath'] = normalizedFile[1..-1] + "/" # Clobber leading '/' add trailing '/'
44+
else
45+
valuehash['gitRecycleBasePath'] = @@old_valuehash['gitRecycleBasePath']
46+
end
47+
end
48+
49+
# Exclude bad expire times (and exclude non-numbers)
50+
if valuehash['gitRecycleExpireTime']
51+
if valuehash['gitRecycleExpireTime'].to_f > 0
52+
valuehash['gitRecycleExpireTime'] = "#{(valuehash['gitRecycleExpireTime'].to_f * 10).to_i / 10.0}"
53+
else
54+
valuehash['gitRecycleExpireTime'] = @@old_valuehash['gitRecycleExpireTime']
55+
end
56+
end
57+
58+
# Validate wait time > 0 (and exclude non-numbers)
59+
if valuehash['gitLockWaitTime']
60+
if valuehash['gitLockWaitTime'].to_i > 0
61+
valuehash['gitLockWaitTime'] = "#{valuehash['gitLockWaitTime'].to_i}"
62+
else
63+
valuehash['gitLockWaitTime'] = @@old_valuehash['gitLockWaitTime']
64+
end
65+
end
66+
# Save back results
67+
object.value = valuehash
2868
end
2969
end
3070

3171
def after_save(object)
72+
# Only perform after-actions on settings for our plugin
3273
if object.name == "plugin_redmine_git_hosting"
74+
valuehash = object.value
3375

3476
if GitHosting.bin_dir_writeable?
3577
%x[ rm -rf '#{ GitHosting.get_tmp_dir }' ]
3678
%x[ rm -rf '#{ GitHosting.get_bin_dir }' ]
3779
end
3880

39-
if @@old_repo_base != object.value['gitRepositoryBasePath']
81+
if @@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath']
4082
GitHostingObserver.set_update_active(false)
4183
all_projects = Project.find(:all)
4284
all_projects.each do |p|
4385
if p.repository.is_a?(Repository::Git)
4486
r = p.repository
4587
repo_name= p.parent ? File.join(GitHosting::get_full_parent_path(p, true),p.identifier) : p.identifier
46-
r.url = File.join(object.value['gitRepositoryBasePath'], "#{repo_name}.git")
88+
r.url = File.join(valuehash['gitRepositoryBasePath'], "#{repo_name}.git")
4789
r.root_url = r.url
4890
r.save
4991
end
5092
end
5193
GitHostingObserver.set_update_active(true)
5294
end
5395

54-
if @@old_git_user != object.value['gitUser']
96+
if @@old_valuehash['gitUser'] != valuehash['gitUser']
5597

5698
GitHosting.setup_hooks
57-
GitHosting.update_repositories( Project.find(:all), false)
99+
GitHosting.update_repositories(:resync_all=>true)
58100

59-
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']
101+
elsif @@old_valuehash['httpServer'] != valuehash['httpServer'] ||
102+
@@old_valuehash['gitHooksDebug'] != valuehash['gitHooksDebug'] ||
103+
@@old_valuehash['gitRepositoryBasePath'] != valuehash['gitRepositoryBasePath'] ||
104+
@@old_valuehash['gitHooksAreAsynchronous'] != valuehash['gitHooksAreAsynchronous']
60105

61106
GitHosting.update_global_hook_params
62107
end
63-
@@old_hook_debug = object.value['gitHooksDebug']
64-
@@old_hook_asynch = object.value['gitHooksAreAsynchronous']
65-
@@old_http_server = object.value['httpServer']
66-
@@old_git_user = object.value['gitUser']
67-
@@old_gitolite_identity = object.value['gitoliteIdentityFile']
68-
@@old_gitolite_publickey = object.value['gitoliteIdentityPublicKeyFile']
69-
@@old_repo_base = object.value['gitRepositoryBasePath']
70108

109+
@@old_valuehash = (Setting.plugin_redmine_git_hosting).clone
71110
end
72111
end
73-
74112
end

0 commit comments

Comments
 (0)