gitolite 0.0.3.alpha → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,30 +1,40 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitolite (0.0.3.alpha)
5
- grit (~> 2.4.1)
6
- hashery (~> 1.4.0)
4
+ gitolite (0.0.4.alpha)
5
+ grit (~> 2.5.0)
6
+ hashery (~> 1.5.0)
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
- diff-lcs (1.1.2)
11
+ blankslate (2.1.2.4)
12
+ diff-lcs (1.1.3)
12
13
  forgery (0.5.0)
13
- grit (2.4.1)
14
+ grit (2.5.0)
14
15
  diff-lcs (~> 1.1)
15
16
  mime-types (~> 1.15)
16
- hashery (1.4.0)
17
- mime-types (1.16)
18
- rcov (0.9.11)
19
- rdoc (3.9.4)
20
- rspec (2.6.0)
21
- rspec-core (~> 2.6.0)
22
- rspec-expectations (~> 2.6.0)
23
- rspec-mocks (~> 2.6.0)
24
- rspec-core (2.6.0)
25
- rspec-expectations (2.6.0)
26
- diff-lcs (~> 1.1.2)
27
- rspec-mocks (2.6.0)
17
+ posix-spawn (~> 0.3.6)
18
+ hashery (1.5.0)
19
+ blankslate
20
+ json (1.6.6)
21
+ mime-types (1.18)
22
+ multi_json (1.3.2)
23
+ posix-spawn (0.3.6)
24
+ rdoc (3.12)
25
+ json (~> 1.4)
26
+ rspec (2.9.0)
27
+ rspec-core (~> 2.9.0)
28
+ rspec-expectations (~> 2.9.0)
29
+ rspec-mocks (~> 2.9.0)
30
+ rspec-core (2.9.0)
31
+ rspec-expectations (2.9.1)
32
+ diff-lcs (~> 1.1.3)
33
+ rspec-mocks (2.9.0)
34
+ simplecov (0.6.2)
35
+ multi_json (~> 1.3)
36
+ simplecov-html (~> 0.5.3)
37
+ simplecov-html (0.5.3)
28
38
 
29
39
  PLATFORMS
30
40
  ruby
@@ -32,6 +42,6 @@ PLATFORMS
32
42
  DEPENDENCIES
33
43
  forgery (~> 0.5.0)
34
44
  gitolite!
35
- rcov (~> 0.9.11)
36
- rdoc (~> 3.9.4)
37
- rspec (~> 2.6.0)
45
+ rdoc (~> 3.12)
46
+ rspec (~> 2.9.0)
47
+ simplecov (~> 0.6.2)
@@ -0,0 +1,179 @@
1
+ # gitolite #
2
+
3
+ This gem is designed to provide a Ruby interface to the {gitolite}[https://github.com/sitaramc/gitolite] git backend system. I am aiming to provide all management functionality that is available via the gitolite-admin repository (like SSH keys, repository permissions, etc)
4
+
5
+ This gem can still have problems. Please file an issue if you encounter a bug. If you have a feature request, file one please.
6
+
7
+ ## Features ##
8
+ * Allows for the creation and management of repos within gitolite
9
+ * Allows for the creation and deletion of SSH keys within gitolite
10
+ * Allows for the bootstrapping of a gitolite-admin repository
11
+
12
+ ## Issues ##
13
+ * Gem is not thread safe. For now, the gem will change directories in order to perform git operations. It will, however, return to the old working directory once it is finished. I am looking into making the gem thread safe.
14
+
15
+ ## Requirements ##
16
+ * Ruby 1.8.x or 1.9.x
17
+ * a working {gitolite}[https://github.com/sitaramc/gitolite] installation
18
+ * the <tt>gitolite-admin</tt> repository checked out locally
19
+
20
+ ## Installation ##
21
+
22
+ gem install gitolite
23
+
24
+ ## Usage ##
25
+
26
+ ### Load a gitolite-admin repo ###
27
+
28
+ require 'gitolite'
29
+ ga_repo = Gitolite::GitoliteAdmin.new("/path/to/gitolite/admin/repo")
30
+
31
+ This method can only be called on an existing gitolite-admin repo. If you need to create a new gitolite-admin repo, see "Bootstrapping".
32
+
33
+ ### Configuration Files ###
34
+
35
+ conf = ga_repo.config
36
+
37
+ #Empty configs can also be initialized
38
+ conf2 = Config.init # => defaults to a filename of gitolite.conf
39
+ conf2 = Config.init("new_config.conf")
40
+
41
+ #Filename is set to whatever the filename was when the config was created
42
+ conf.filename # => "gitolite.conf"
43
+ conf2.filename # => "new_config.conf")
44
+
45
+ #filename can be changed via the setter
46
+ conf2.filename = "new_config.conf"
47
+
48
+ #to_file will write the config out to the file system
49
+ #using the value of the filename attribute. An alternative
50
+ #filename can also be specified
51
+ conf.to_file("/new/config/path") # => writes /new/config/path/gitolite.conf
52
+ conf.to_file("/new/config/path", "test.conf") # => writes /new/config/path/test.conf
53
+
54
+ ### Repo management ###
55
+
56
+ repo = Gitolite::Config::Repo.new("AwesomeRepo")
57
+
58
+ #For a list of permissions, see http://sitaramc.github.com/gitolite/conf.html#gitolite
59
+ repo.add_permission("RW+", "", "bob", "joe", "susan")
60
+
61
+ #Add repo to config
62
+ conf.add_repo(repo)
63
+
64
+ #Delete repo by object
65
+ conf.rm_repo(repo)
66
+
67
+ #Delete a repo by name
68
+ conf.rm_repo("AwesomeRepo")
69
+ conf.rm_repo(:AwesomeRepo)
70
+
71
+ #Test if repo exists by name
72
+ conf.has_repo?('cool_repo') # => false
73
+ conf.has_repo?(:cool_repo) # => false
74
+
75
+ #Can also pass a Gitolite::Config::Repo object
76
+ repo = Gitolite::Config::Repo.new('cool_repo')
77
+ conf.has_repo?(repo) # => true
78
+
79
+ #Get a repo object from the config
80
+ repo = conf.get_repo('cool_repo')
81
+ repo = conf.get_repo(:cool_repo)
82
+
83
+ ### SSH Key Management ###
84
+
85
+ #Three ways to create keys: manually, from an existing key, or from a string representing a key
86
+ key = Gitolite::SSHKey.new("ssh-rsa", "big-public-key-blob", "email")
87
+ key2 = Gitolite::SSHKey.from_file("/path/to/ssh/key.pub")
88
+
89
+ key_string = File.read("/path/to/ssh/key.pub")
90
+ key3 = Gitolite::SSHKey.from_string(key_string, "owner")
91
+
92
+
93
+ #Add the keys
94
+ ga_repo.add_key(key)
95
+ ga_repo.add_key(key2)
96
+ ga_repo.add_key(key3)
97
+
98
+ #Remove key2
99
+ ga_repo.rm_key(key2)
100
+
101
+ ### Save changes ###
102
+
103
+ ga_repo.save
104
+
105
+ When this method is called, all changes get written to the file system and staged in git. For the time being, gitolite assumes full control of the gitolite-admin repository. This means that any keys in the keydir that are not being tracked will be removed and any human changes to gitolite.conf will be erased.
106
+
107
+ ### Apply changes ###
108
+ ga_repo.apply
109
+
110
+ This method will commit all changes with a generic message (will be improved upon later) and push to <tt>origin master</tt>.
111
+
112
+ ### Save and apply ###
113
+ ga_repo.save_and_apply
114
+
115
+ ### Updating remote changes ###
116
+ #In order to avoid conflicts, this will perform a reset! by default
117
+ #pass :reset => false to disable the reset (Git conflicts will have to be manually fixed)
118
+ ga_repo.update
119
+ ga_repo.update(:reset => false)
120
+
121
+ #Update while performing a rebase
122
+ ga_repo.update(:rebase => true)
123
+
124
+ ### Reloading from the file system ###
125
+ ga_repo.reload!
126
+
127
+ ### Resetting to HEAD, destroying all local changes (including untracked files) ###
128
+ #This will also perform a reload!
129
+ ga_repo.reset!
130
+
131
+ ### Bootstrapping ###
132
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo")
133
+
134
+ This will create the folders <tt>conf</tt> and <tt>keydir</tt> in the supplied path. A config file will also be created in the conf directory. The default configuration supplies RW+ permissions to a user named git for a repo named <tt>gitolite-admin</tt>. You can specify an options hash to change some values:
135
+
136
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo", {:user => "admin", :perm => "RW"})
137
+
138
+ You can also pass a message to be used for the initial bootstrap commit:
139
+
140
+ ga_repo = GitoliteAdmin.bootstrap("/path/to/new/gitolite/repo", {:message => "Bootstrapped new repo"})
141
+
142
+ Please note that while bootstrapping is supported, I highly recommend that the initial gitolite-admin repo be created by gitolite itself.
143
+
144
+ ## Caveats ##
145
+ ### Windows compatibility ###
146
+ The grit gem (which is used for under-the-hood git operations) does not currently support Windows. Until it does, gitolite will be unable to support Windows.
147
+
148
+ ### Group Ordering ###
149
+ When the gitolite backend parses the config file, it does so in one pass. Because of this, groups that are modified after being used do not see those changes reflected in previous uses.
150
+
151
+ For example:
152
+
153
+ @groupa = bob joe sue
154
+ @groupb = jim @groupa
155
+ @groupa = sam
156
+
157
+ Group b will contain the users <tt>jim, bob, joe, and sue</tt>
158
+
159
+ The gitolite gem, on the other hand, will <em>always</em> output groups so that all modifications are represented before it is ever used. For the above example, group b will be output with the following users: <tt>jim, bob, joe, sue, and sam</tt>. The groups in the config file will look like this:
160
+
161
+ @groupa = bob joe sue sam
162
+ @groupb = jim @groupa
163
+
164
+ # Contributing #
165
+ * Tests! If you ask me to pull changes that are not adequately tested, I'm not going to do it.
166
+ * If you introduce new features/public methods on objects, you must update the README.
167
+
168
+ ### Contributors ###
169
+ * Alexander Simonov - [simonoff](https://github.com/simonoff)
170
+
171
+ ## Documentation ##
172
+ * Rdoc is coming eventually
173
+
174
+ ## Future ##
175
+ * support folders in the keydir
176
+ * support include tags
177
+ * cleanup methods to make adding and removing easier (like add_key should accept an array of keys)
178
+ * Make the gem thread safe
179
+ * Rails integration via [gitolite-rails](https://www.github.com/wingrunr21/gitolite-rails)
@@ -14,12 +14,13 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "gitolite"
16
16
 
17
- s.add_development_dependency "rspec", "~> 2.6.0"
17
+ s.add_development_dependency "rspec", "~> 2.9.0"
18
18
  s.add_development_dependency "forgery", "~> 0.5.0"
19
- s.add_development_dependency "rdoc", "~> 3.9.4"
20
- s.add_development_dependency "rcov", "~> 0.9.11"
21
- s.add_dependency "grit", "~> 2.4.1"
22
- s.add_dependency "hashery", "~> 1.4.0"
19
+ s.add_development_dependency "rdoc", "~> 3.12"
20
+ s.add_development_dependency "simplecov", "~> 0.6.2"
21
+ s.add_dependency "grit", "~> 2.5.0"
22
+ s.add_dependency "hashery", "~> 1.5.0"
23
+ s.add_dependency "plexus", "~> 0.5.10"
23
24
 
24
25
  s.files = `git ls-files`.split("\n")
25
26
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -1,6 +1,7 @@
1
1
  module Gitolite
2
2
  require 'grit'
3
3
  require 'hashery'
4
+ require 'plexus'
4
5
  require 'gitolite/ssh_key'
5
6
  require 'gitolite/config'
6
7
  require 'gitolite/gitolite_admin'
@@ -68,7 +68,8 @@ module Gitolite
68
68
  new_conf = File.join(path, filename)
69
69
  File.open(new_conf, "w") do |f|
70
70
  #Output groups
71
- @groups.each_value {|group| f.write group.to_s }
71
+ dep_order = build_groups_depgraph
72
+ dep_order.each {|group| f.write group.to_s }
72
73
 
73
74
  gitweb_descs = []
74
75
  @repos.each do |k, v|
@@ -106,7 +107,7 @@ module Gitolite
106
107
  line = cleanup_config_line(l)
107
108
  next if line.empty? #lines are empty if we killed a comment
108
109
 
109
- case line.strip
110
+ case line
110
111
  #found a repo definition
111
112
  when /^repo (.*)/
112
113
  #Empty our current context
@@ -119,7 +120,7 @@ module Gitolite
119
120
  @repos[r] = Repo.new(r) unless has_repo?(r)
120
121
  end
121
122
  #repo permissions
122
- when /^(-|C|R|RW\+?(?:C?D?|D?C?)) (.* )?= (.+)/
123
+ when /^(-|C|R|RW\+?(?:C?D?|D?C?)M?) (.* )?= (.+)/
123
124
  perm = $1
124
125
  refex = $2 || ""
125
126
  users = $3.split
@@ -199,8 +200,38 @@ module Gitolite
199
200
  end
200
201
  end
201
202
 
203
+ # Builds a dependency tree from the groups in order to ensure all groups
204
+ # are defined before they are used
205
+ def build_groups_depgraph
206
+ dp = ::Plexus::Digraph.new
207
+
208
+ # Add each group to the graph
209
+ @groups.each_value do |group|
210
+ # Select group names from the users
211
+ subgroups = group.users.select {|u| u =~ /^#{Group::PREPEND_CHAR}.*$/}
212
+ .map{|g| get_group g.gsub(Group::PREPEND_CHAR, '') }
213
+
214
+ subgroups.each do |subgroup|
215
+ dp.add_edge! subgroup, group
216
+ end
217
+ end
218
+
219
+ # Figure out if we have a good depedency graph
220
+ dep_order = dp.topsort
221
+
222
+ if dep_order.empty?
223
+ raise GroupDependencyError unless @groups.empty?
224
+ end
225
+
226
+ dep_order
227
+ end
228
+
202
229
  #Raised when something in a config fails to parse properly
203
230
  class ParseError < RuntimeError
204
231
  end
232
+
233
+ # Raised when group dependencies cannot be suitably resolved for output
234
+ class GroupDependencyError < RuntimeError
235
+ end
205
236
  end
206
237
  end
@@ -1,11 +1,9 @@
1
- require 'hashery'
2
-
3
1
  module Gitolite
4
2
  class Config
5
3
  #Represents a repo inside the gitolite configuration. The name, permissions, and git config
6
4
  #options are all encapsulated in this class
7
5
  class Repo
8
- ALLOWED_PERMISSIONS = /-|R|RW+?C?D?/
6
+ ALLOWED_PERMISSIONS = /-|C|R|RW\+?(?:C?D?|D?C?)M?/
9
7
 
10
8
  attr_accessor :permissions, :name, :config, :owner, :description
11
9
 
@@ -6,17 +6,21 @@ module Gitolite
6
6
  CONFDIR = "conf"
7
7
  KEYDIR = "keydir"
8
8
 
9
+ #Gitolite gem's default git commit message
10
+ DEFAULT_COMMIT_MSG = "Committed by the gitolite gem"
11
+
9
12
  # Intialize with the path to
10
13
  # the gitolite-admin repository
11
14
  def initialize(path, options = {})
15
+ @path = path
12
16
  @gl_admin = Grit::Repo.new(path)
13
17
 
14
18
  @conf = options[:conf] || CONF
15
19
  @confdir = options[:confdir] || CONFDIR
16
20
  @keydir = options[:keydir] || KEYDIR
17
21
 
18
- @ssh_keys = load_keys(File.join(path, @keydir))
19
- @config = Config.new(File.join(path, @confdir, @conf))
22
+ # Load the ssh keys and the configuration
23
+ load_data
20
24
  end
21
25
 
22
26
  # This method will bootstrap a gitolite-admin repo
@@ -80,20 +84,50 @@ module Gitolite
80
84
  end
81
85
  end
82
86
 
87
+ # This method will destroy all local tracked changes, resetting the local gitolite
88
+ # git repo to HEAD and reloading the entire repository
89
+ # Note that this will also delete all untracked files
90
+ def reset!
91
+ Dir.chdir(@gl_admin.working_dir) do
92
+ @gl_admin.git.reset({:hard => true}, 'HEAD')
93
+ @gl_admin.git.clean({:d => true, :q => true, :f => true})
94
+ end
95
+ reload!
96
+ end
97
+
98
+ # This method will destroy the in-memory data structures and reload everything
99
+ # from the file system
100
+ def reload!
101
+ load_data
102
+ end
103
+
83
104
  #commits all staged changes and pushes back
84
105
  #to origin
85
106
  #
86
107
  #TODO: generate a better commit message
87
108
  #TODO: add the ability to specify the remote and branch
88
109
  #TODO: detect existance of origin instead of just dying
89
- def apply(commit_message = "Commit by gitolite gem")
110
+ def apply(commit_message = DEFAULT_COMMIT_MSG)
90
111
  @gl_admin.commit_index(commit_message)
91
112
  @gl_admin.git.push({}, "origin", "master")
92
113
  end
93
114
 
94
- def save_and_apply
115
+ def save_and_apply(commit_message = DEFAULT_COMMIT_MSG)
95
116
  self.save
96
- self.apply
117
+ self.apply(commit_message)
118
+ end
119
+
120
+ # Updates the repo with changes from remote master
121
+ def update(options = {})
122
+ options = {:reset => true, :rebase => false }.merge(options)
123
+
124
+ reset! if options[:reset]
125
+
126
+ Dir.chdir(@gl_admin.working_dir) do
127
+ @gl_admin.git.pull({:rebase => options[:rebase]}, "origin", "master")
128
+ end
129
+
130
+ reload!
97
131
  end
98
132
 
99
133
  def add_key(key)
@@ -102,6 +136,7 @@ module Gitolite
102
136
  end
103
137
 
104
138
  def rm_key(key)
139
+ raise "Key must be of type Gitolite::SSHKey!" unless key.instance_of? Gitolite::SSHKey
105
140
  @ssh_keys[key.owner].delete key
106
141
  end
107
142
 
@@ -124,6 +159,11 @@ module Gitolite
124
159
  end
125
160
 
126
161
  private
162
+ def load_data
163
+ @ssh_keys = load_keys(File.join(@path, @keydir))
164
+ @config = Config.new(File.join(@path, @confdir, @conf))
165
+ end
166
+
127
167
  #Loads all .pub files in the gitolite-admin
128
168
  #keydir directory
129
169
  def load_keys(path)