right_git 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,368 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_git/git'
25
+
26
+ module RightGit::Git
27
+
28
+ # Provides an API for managing a git repository that is suitable for
29
+ # automation. It is assumed that gestures like creating a new repository,
30
+ # branch or tag are manual tasks beyond the scope of automation so those are
31
+ # not covered here. What is provided are APIs for cloning, fetching, listing
32
+ # and grooming git-related objects.
33
+ class Repository
34
+ COMMIT_SHA1_REGEX = /^commit ([0-9a-fA-F]{40})$/
35
+
36
+ SUBMODULE_STATUS_REGEX = /^([+\- ])([0-9a-fA-F]{40}) (.*) (.*)$/
37
+
38
+ attr_reader :repo_dir, :logger, :shell
39
+
40
+ # @param [String] repo_dir for git actions or '.'
41
+ # @param [Hash] options for repository
42
+ # @option options [Object] :shell for git command execution (default = DefaultShell)
43
+ # @option options [Logger] :logger for logging (default = STDOUT)
44
+ def initialize(repo_dir, options = {})
45
+ options = {
46
+ :shell => nil,
47
+ :logger => nil
48
+ }.merge(options)
49
+ if repo_dir && ::File.directory?(repo_dir)
50
+ @repo_dir = ::File.expand_path(repo_dir)
51
+ else
52
+ raise ::ArgumentError.new('A valid repo_dir is required')
53
+ end
54
+ @shell = options[:shell] || ::RightGit::Shell::Default
55
+ @logger = options[:logger] || ::RightGit::Shell::Default.default_logger
56
+ end
57
+
58
+ # Factory method to clone the repo given by URL to the given destination and
59
+ # return a new Repository object.
60
+ #
61
+ # Note that cloning to the default working directory-relative location is
62
+ # not currently supported.
63
+ #
64
+ # @param [String] repo_url to clone
65
+ # @param [String] destination path where repo is cloned
66
+ # @param [Hash] options for repository
67
+ #
68
+ # @return [Repository] new repository
69
+ def self.clone_to(repo_url, destination, options = {})
70
+ destination = ::File.expand_path(destination)
71
+ git_args = ['clone', '--', repo_url, destination]
72
+ expected_git_dir = ::File.join(destination, '.git')
73
+ if ::File.directory?(expected_git_dir)
74
+ raise ::ArgumentError,
75
+ "Destination is already a git repository: #{destination.inspect}"
76
+ end
77
+ repo = self.new('.', options)
78
+ repo.vet_output(git_args)
79
+ if ::File.directory?(expected_git_dir)
80
+ repo.instance_variable_set(:@repo_dir, destination)
81
+ else
82
+ raise GitError,
83
+ "Failed to clone #{repo_url.inspect} to #{destination.inspect}"
84
+ end
85
+ repo
86
+ end
87
+
88
+ # Fetches using the given options, if any.
89
+ #
90
+ # @param [Array] args for fetch
91
+ #
92
+ # @return [TrueClass] always true
93
+ def fetch(*args)
94
+ vet_output(['fetch', args])
95
+ true
96
+ end
97
+
98
+ # Fetches branch and tag information from remote origin.
99
+ #
100
+ # @param [Hash] options for fetch all
101
+ # @option options [TrueClass|FalseClass] :prune as true to prune dead branches
102
+ #
103
+ # @return [TrueClass] always true
104
+ def fetch_all(options = {})
105
+ options = { :prune => false }.merge(options)
106
+ git_args = ['--all']
107
+ git_args << '--prune' if options[:prune]
108
+ fetch(git_args)
109
+ fetch('--tags') # need a separate call for tags or else you don't get all the tags
110
+ true
111
+ end
112
+
113
+ # Factory method for a branch object referencing this repository.
114
+ #
115
+ # @param [String] branch_name for reference
116
+ #
117
+ # @return [Branch] new branch
118
+ def branch_for(branch_name)
119
+ Branch.new(self, branch_name)
120
+ end
121
+
122
+ # Generates a list of known (checked-out) branches from the current git
123
+ # directory.
124
+ #
125
+ # @param [Hash] options for branches
126
+ # @option options [TrueClass|FalseClass] :all is true to include remote branches (default), else local only
127
+ #
128
+ # @return [Array] list of branches
129
+ def branches(options = {})
130
+ options = {
131
+ :all => true
132
+ }.merge(options)
133
+ git_args = ['branch']
134
+ git_args << '-a' if options[:all] # note older versions of git don't accept --all
135
+ branches = BranchCollection.new(self)
136
+ git_output(git_args).lines.each do |line|
137
+ # ignore the no-branch branch that git helpfully provides when current
138
+ # HEAD is a tag or otherwise not-a-branch.
139
+ unless line.strip == '* (no branch)'
140
+ branch = Branch.new(self, line)
141
+ branches << branch if branch
142
+ end
143
+ end
144
+ branches
145
+ end
146
+
147
+ # Factory method for a tag object referencing this repository.
148
+ #
149
+ # @param [String] tag_name for reference
150
+ #
151
+ # @return [Branch] new branch
152
+ def tag_for(tag_name)
153
+ Tag.new(self, tag_name)
154
+ end
155
+
156
+ # Generates a list of known (fetched) tags from the current git directory.
157
+ #
158
+ # @return [Array] list of tags
159
+ def tags
160
+ git_output('tag').lines.map { |line| Tag.new(self, line.strip) }
161
+ end
162
+
163
+ # Generates a list of commits using the given 'git log' arguments.
164
+ #
165
+ # @param [String] revision to log or nil
166
+ # @param [Hash] options for log
167
+ # @option options [Integer] :skip as lines of most recent history to skip (Default = include most recent)
168
+ # @option options [Integer] :tail as max history of log
169
+ # @option options [TrueClass|FalseClass] :no_merges as true to exclude merge commits
170
+ # @option options [TrueClass|FalseClass] :full_hashes as true show full hashes, false for (7-character) abbreviations
171
+ #
172
+ # @return [Array] list of commits
173
+ def log(revision, options = {})
174
+ options = {
175
+ :skip => nil,
176
+ :tail => 1_000,
177
+ :no_merges => false,
178
+ :full_hashes => false,
179
+ }.merge(options)
180
+ skip = options[:skip]
181
+ git_args = [
182
+ 'log',
183
+ "-n#{options[:tail]}",
184
+ "--format=\"%#{options[:full_hashes] ? 'H' : 'h'} %at %aE\"" # double-quotes are Windows friendly
185
+ ]
186
+ git_args << "--skip #{skip}" if skip
187
+ git_args << "--no-merges" if options[:no_merges]
188
+ git_args << revision if revision
189
+ git_output(git_args).lines.map { |line| Commit.new(self, line) }
190
+ end
191
+
192
+ # Cleans the current repository of untracked files.
193
+ #
194
+ # @param [Array] args for clean
195
+ #
196
+ # @return [TrueClass] always true
197
+ def clean(*args)
198
+ git_args = ['clean', args]
199
+ spit_output(git_args)
200
+ true
201
+ end
202
+
203
+ # Cleans everything and optionally cleans .gitignored files.
204
+ #
205
+ # @param [Hash] options for checkout
206
+ # @option options [TrueClass|FalseClass] :directories as true to clean untracked directories (but not untracked submodules)
207
+ # @option options [TrueClass|FalseClass] :gitignored as true to clean gitignored (untracked) files
208
+ # @option options [TrueClass|FalseClass] :submodules as true to clean untracked submodules (requires force)
209
+ #
210
+ # @return [TrueClass] always true
211
+ def clean_all(options = {})
212
+ options = {
213
+ :directories => false,
214
+ :gitignored => false,
215
+ :submodules => false,
216
+ }.merge(options)
217
+ git_args = ['-f'] # force is required or else -n only lists files.
218
+ git_args << '-f' if options[:submodules] # double-tap -f to kill untracked submodules
219
+ git_args << '-d' if options[:directories]
220
+ git_args << '-x' if options[:gitignored]
221
+ clean(git_args)
222
+ true
223
+ end
224
+
225
+ # Checkout.
226
+ #
227
+ # @param [String] revision for checkout
228
+ # @param [Hash] options for checkout
229
+ # @option options [TrueClass|FalseClass] :force as true to force checkout
230
+ #
231
+ # @return [TrueClass] always true
232
+ def checkout_to(revision, options = {})
233
+ options = {
234
+ :force => false
235
+ }.merge(options)
236
+ git_args = ['checkout', revision]
237
+ git_args << '--force' if options[:force]
238
+ vet_output(git_args)
239
+ true
240
+ end
241
+
242
+ # Performs a hard reset to the given revision, if given, or else the last
243
+ # checked-out SHA.
244
+ #
245
+ # @param [String] revision as target for hard reset or nil for hard reset to HEAD
246
+ #
247
+ # @return [TrueClass] always true
248
+ def hard_reset_to(revision)
249
+ git_args = ['reset', '--hard']
250
+ git_args << revision if revision
251
+ vet_output(git_args)
252
+ true
253
+ end
254
+
255
+ # Queries the recursive list of submodule paths for the current workspace.
256
+ #
257
+ # @param [Hash] options for submodules
258
+ # @option options [TrueClass|FalseClass] :recursive as true to recursively get submodule paths
259
+ #
260
+ # @return [Array] list of submodule paths or empty
261
+ def submodule_paths(options = {})
262
+ options = {
263
+ :recursive => false
264
+ }.merge(options)
265
+ git_args = ['submodule', 'status']
266
+ git_args << '--recursive' if options[:recursive]
267
+ git_output(git_args).lines.map do |line|
268
+ data = line.chomp
269
+ if matched = SUBMODULE_STATUS_REGEX.match(data)
270
+ matched[3]
271
+ else
272
+ raise GitError,
273
+ "Unexpected output from submodule status: #{data.inspect}"
274
+ end
275
+ end
276
+ end
277
+
278
+ # Updates submodules for the current workspace.
279
+ #
280
+ # @param [Hash] options for submodules
281
+ # @option options [TrueClass|FalseClass] :recursive as true to recursively update submodules
282
+ #
283
+ # @return [TrueClass] always true
284
+ def update_submodules(options = {})
285
+ options = {
286
+ :recursive => false
287
+ }.merge(options)
288
+ git_args = ['submodule', 'update', '--init']
289
+ git_args << '--recursive' if options[:recursive]
290
+ spit_output(git_args)
291
+ true
292
+ end
293
+
294
+ # Determines the SHA referenced by the given revision. Raises on failure.
295
+ #
296
+ # @param [String] revision or nil for current SHA
297
+ #
298
+ # @return [String] SHA for revision
299
+ def sha_for(revision)
300
+ # note that 'git show-ref' produces easier-to-parse output but it matches
301
+ # both local and remote branch to a simple branch name whereas 'git show'
302
+ # matches at-most-one and requires origin/ for remote branches.
303
+ git_args = ['show', revision].compact
304
+ result = nil
305
+ git_output(git_args).lines.each do |line|
306
+ if matched = COMMIT_SHA1_REGEX.match(line.strip)
307
+ result = matched[1]
308
+ break
309
+ end
310
+ end
311
+ unless result
312
+ raise GitError, 'Unable to locate commit in show output.'
313
+ end
314
+ result
315
+ end
316
+
317
+ # Executes and returns the output for a git command. Raises on failure.
318
+ #
319
+ # @param [String|Array] args to execute
320
+ #
321
+ # @return [String] output
322
+ def git_output(*args)
323
+ inner_execute(:output_for, args)
324
+ end
325
+
326
+ # Prints the output for a git command. Raises on failure.
327
+ #
328
+ # @param [String|Array] args to execute
329
+ #
330
+ # @return [TrueClass] always true
331
+ def spit_output(*args)
332
+ inner_execute(:execute, args)
333
+ end
334
+
335
+ # msysgit on Windows exits zero even when checkout|reset|fetch fails so we
336
+ # need to scan the output for error or fatal messages. it does no harm to do
337
+ # the same on Linux even though the exit code works properly there.
338
+ #
339
+ # @param [String|Array] args to execute
340
+ #
341
+ # @return [TrueClass] always true
342
+ def vet_output(*args)
343
+ last_output = git_output(*args).strip
344
+ logger.info(last_output) unless last_output.empty?
345
+ if last_output.downcase =~ /^(error|fatal):/
346
+ raise GitError, "Git exited zero but an error was detected in output."
347
+ end
348
+ true
349
+ end
350
+
351
+ private
352
+
353
+ # git defaults to working in the current directory but is sensitive to
354
+ # GIT_ env vars. we prefer the working directory so ensure any GIT_ that
355
+ # override the working directory are cleared.
356
+ CLEAR_GIT_ENV_VARS = ['GIT_DIR', 'GIT_INDEX_FILE', 'GIT_WORK_TREE'].freeze
357
+
358
+ def inner_execute(shell_method, git_args)
359
+ shell.send(
360
+ shell_method,
361
+ ['git', git_args].flatten.join(' '),
362
+ :logger => logger,
363
+ :directory => @repo_dir,
364
+ :clear_env_vars => CLEAR_GIT_ENV_VARS)
365
+ end
366
+
367
+ end # Repository
368
+ end # RightGit
@@ -0,0 +1,82 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_git/git'
25
+
26
+ module RightGit::Git
27
+
28
+ # A tag in a Git repository.
29
+ class Tag
30
+ attr_reader :repo, :name
31
+
32
+ class TagError < GitError; end
33
+
34
+ # @param [Repository] repo hosting tag
35
+ # @param [String] name of tag
36
+ def initialize(repo, name)
37
+ # TEAL FIX: only invalid characters seem to be whitespace and some file
38
+ # system special characters; need to locate a definitive schema for tag
39
+ # names.
40
+ if name.index(/\s|[:\\?*<>\|]/)
41
+ raise TagError, 'name is invalid'
42
+ end
43
+ @repo = repo
44
+ @name = name
45
+ end
46
+
47
+ # @return [String] stringized
48
+ def to_s
49
+ "#{self.class.name}: #{@name.inspect}"
50
+ end
51
+ alias inspect to_s
52
+
53
+ # @param [Tag] other
54
+ # @return [TrueClass|FalseClass] true if equivalent
55
+ def ==(other)
56
+ if other.kind_of?(self.class)
57
+ @name == other.name
58
+ else
59
+ false
60
+ end
61
+ end
62
+
63
+ # @param [Tag] other
64
+ # @return [Integer] comparison value
65
+ def <=>(other)
66
+ if other.kind_of?(self.class)
67
+ @name <=> other.name
68
+ else
69
+ raise ::ArgumentError, 'Wrong type'
70
+ end
71
+ end
72
+
73
+ # Deletes this tag.
74
+ #
75
+ # @return [TrueClass] always true
76
+ def delete
77
+ @repo.vet_output("tag -d #{@name}")
78
+ true
79
+ end
80
+
81
+ end # Tag
82
+ end # RightGit
@@ -0,0 +1,36 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_git'
25
+
26
+ module RightGit
27
+ module Git
28
+ class GitError < RightGitError; end
29
+
30
+ autoload :Branch, 'right_git/git/branch'
31
+ autoload :BranchCollection, 'right_git/git/branch_collection'
32
+ autoload :Commit, 'right_git/git/commit'
33
+ autoload :Repository, 'right_git/git/repository'
34
+ autoload :Tag, 'right_git/git/tag'
35
+ end
36
+ end
@@ -0,0 +1,191 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_git/shell'
25
+
26
+ # local
27
+ require 'logger'
28
+ require 'stringio'
29
+ require 'singleton'
30
+
31
+ module RightGit::Shell
32
+
33
+ # Default shell singleton implementation.
34
+ class Default
35
+ include ::RightGit::Shell::Interface
36
+ include ::Singleton
37
+
38
+ def self.respond_to?(*arguments)
39
+ instance.respond_to?(*arguments) || super
40
+ end
41
+
42
+ def self.method_missing(method_sym, *arguments, &block)
43
+ if instance.respond_to?(method_sym)
44
+ instance.send(method_sym, *arguments, &block)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ # Implements execute interface.
51
+ def execute(cmd, options = {})
52
+ options = {
53
+ :directory => nil,
54
+ :logger => nil,
55
+ :outstream => nil,
56
+ :raise_on_failure => true,
57
+ :set_env_vars => nil,
58
+ :clear_env_vars => nil,
59
+ }.merge(options)
60
+ logger = options[:logger] || default_logger
61
+ outstream = options[:outstream]
62
+
63
+ # build execution block.
64
+ exitstatus = nil
65
+ executioner = lambda do
66
+ logger.info("+ #{cmd}")
67
+ ::IO.popen("#{cmd} 2>&1", 'r') do |output|
68
+ output.sync = true
69
+ done = false
70
+ while !done
71
+ begin
72
+ data = output.readline
73
+ if outstream
74
+ outstream << data
75
+ else
76
+ logger.info(data.strip)
77
+ end
78
+ rescue ::EOFError
79
+ done = true
80
+ end
81
+ end
82
+ end
83
+ exitstatus = $?.exitstatus
84
+ if (!$?.success? && options[:raise_on_failure])
85
+ raise ShellError, "Execution failed with exitstatus #{exitstatus}"
86
+ end
87
+ end
88
+
89
+ # configure and invoke.
90
+ configure_executioner(executioner, options).call
91
+
92
+ return exitstatus
93
+ end
94
+
95
+ # Implements output_for interface.
96
+ def output_for(cmd, options = {})
97
+ output = StringIO.new
98
+ execute(cmd, options.merge(:outstream => output))
99
+ output.string
100
+ end
101
+
102
+ # Encapsulates the given executioner with child-process-modifying behavior
103
+ # based on options. Builds the executioner as a series of callbacks.
104
+ #
105
+ # @param [Proc] executioner to configure
106
+ # @param [Hash] options for execution
107
+ #
108
+ # @return [Proc] configured executioner
109
+ def configure_executioner(executioner, options)
110
+ # set specific environment variables, if requested.
111
+ sev = options[:set_env_vars]
112
+ if (sev && !sev.empty?)
113
+ executioner = lambda do |e|
114
+ lambda { set_env_vars(sev) { e.call } }
115
+ end.call(executioner)
116
+ end
117
+
118
+ # clear specific environment variables, if requested.
119
+ cev = options[:clear_env_vars]
120
+ if (cev && !cev.empty?)
121
+ executioner = lambda do |e|
122
+ lambda { clear_env_vars(cev) { e.call } }
123
+ end.call(executioner)
124
+ end
125
+
126
+ # working directory.
127
+ if directory = options[:directory]
128
+ executioner = lambda do |e, d|
129
+ lambda { ::Dir.chdir(d) { e.call } }
130
+ end.call(executioner, directory)
131
+ end
132
+ executioner
133
+ end
134
+
135
+ # Sets the given list of environment variables while
136
+ # executing the given block.
137
+ #
138
+ # === Parameters
139
+ # @param [Hash] variables to set
140
+ #
141
+ # === Yield
142
+ # @yield [] called with environment set
143
+ #
144
+ # === Return
145
+ # @return [TrueClass] always true
146
+ def set_env_vars(variables)
147
+ save_vars = {}
148
+ variables.each do |k, v|
149
+ k = k.to_s
150
+ save_vars[k] = ENV[k]
151
+ ENV[k] = v.nil? ? v : v.to_s
152
+ end
153
+ begin
154
+ yield
155
+ ensure
156
+ variables.each_key do |k|
157
+ k = k.to_s
158
+ ENV[k] = save_vars[k]
159
+ end
160
+ end
161
+ true
162
+ end
163
+
164
+ # Clears (set-to-nil) the given list of environment variables while
165
+ # executing the given block.
166
+ #
167
+ # @param [Array] names of variables to clear
168
+ #
169
+ # @yield [] called with environment cleared
170
+ #
171
+ # @return [TrueClass] always true
172
+ def clear_env_vars(names, &block)
173
+ save_vars = {}
174
+ names.each do |k|
175
+ k = k.to_s
176
+ save_vars[k] = ENV[k]
177
+ ENV[k] = nil
178
+ end
179
+ begin
180
+ yield
181
+ ensure
182
+ names.each do |k|
183
+ k = k.to_s
184
+ ENV[k] = save_vars[k]
185
+ end
186
+ end
187
+ true
188
+ end
189
+
190
+ end # Default
191
+ end # RightGit::Shell