right_git 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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