grack 0.0.2 → 0.1.0.pre1

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +7 -0
  3. data/.yardopts +1 -0
  4. data/LICENSE +22 -0
  5. data/NEWS.md +19 -0
  6. data/README.md +211 -0
  7. data/Rakefile +222 -0
  8. data/lib/git_adapter.rb +1 -0
  9. data/lib/grack.rb +1 -0
  10. data/lib/grack/app.rb +482 -0
  11. data/lib/grack/compatible_git_adapter.rb +99 -0
  12. data/lib/grack/file_streamer.rb +41 -0
  13. data/lib/grack/git_adapter.rb +142 -0
  14. data/lib/grack/io_streamer.rb +45 -0
  15. data/tests/app_test.rb +534 -0
  16. data/tests/compatible_git_adapter_test.rb +137 -0
  17. data/tests/example/_git/COMMIT_EDITMSG +1 -0
  18. data/tests/example/_git/HEAD +1 -0
  19. data/tests/example/_git/config +6 -0
  20. data/tests/example/_git/description +1 -0
  21. data/tests/example/_git/hooks/applypatch-msg.sample +15 -0
  22. data/tests/example/_git/hooks/commit-msg.sample +24 -0
  23. data/tests/example/_git/hooks/post-commit.sample +8 -0
  24. data/tests/example/_git/hooks/post-receive.sample +15 -0
  25. data/tests/example/_git/hooks/post-update.sample +8 -0
  26. data/tests/example/_git/hooks/pre-applypatch.sample +14 -0
  27. data/tests/example/_git/hooks/pre-commit.sample +50 -0
  28. data/tests/example/_git/hooks/pre-rebase.sample +169 -0
  29. data/tests/example/_git/hooks/prepare-commit-msg.sample +36 -0
  30. data/tests/example/_git/hooks/update.sample +128 -0
  31. data/tests/example/_git/index +0 -0
  32. data/tests/example/_git/info/exclude +6 -0
  33. data/tests/example/_git/info/refs +1 -0
  34. data/tests/example/_git/logs/HEAD +1 -0
  35. data/tests/example/_git/logs/refs/heads/master +1 -0
  36. data/tests/example/_git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90 +0 -0
  37. data/tests/example/_git/objects/cb/067e06bdf6e34d4abebf6cf2de85d65a52c65e +0 -0
  38. data/tests/example/_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a +0 -0
  39. data/tests/example/_git/objects/info/packs +2 -0
  40. data/tests/example/_git/objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.idx +0 -0
  41. data/tests/example/_git/objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.pack +0 -0
  42. data/tests/example/_git/refs/heads/master +1 -0
  43. data/tests/file_streamer_test.rb +37 -0
  44. data/tests/git_adapter_test.rb +104 -0
  45. data/tests/io_streamer_test.rb +36 -0
  46. data/tests/test_helper.rb +36 -0
  47. metadata +292 -19
  48. data/lib/git_http.rb +0 -304
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cb383037da24ee5963414d2d7dee6cb496b602fe
4
+ data.tar.gz: df32f8f77576e5602c9d6e77970083b30fd69a9c
5
+ SHA512:
6
+ metadata.gz: df02a597393545859f15d6c94136c4c61e9c291943ecc96d7271a62b21fa3d0bd45383a13b874db89c4bb0009fc2e333aac25c159f72052fc5f20a4bdbae779a
7
+ data.tar.gz: 711850b57b041acd41e2fa8809fb643549ac640ad1d358ccea47a97ebd41763b869ed39c8054f2da1e3f9a791b1d2bfbd2d524e4df4981967c8bcee144b7fb61
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - 2.1.1
6
+ - 2.2.2
7
+ script: bundle exec rake test
@@ -0,0 +1 @@
1
+ --protected --private --main README.md lib/**/*.rb - NEWS.md LICENSE
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2015 Scott Chacon <schacon@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NEWS.md ADDED
@@ -0,0 +1,19 @@
1
+ # News and Notifications by Version
2
+
3
+ This file lists noteworthy changes which may affect users of this project. More
4
+ detailed information is available in the rest of the documentation.
5
+
6
+ **NOTE:** Date stamps in the following entries are in YYYY/MM/DD format.
7
+
8
+ ## v0.1.0.pre1
9
+
10
+ * Moved projects to [grackorg/grack](https://github.com/grackorg/grack)
11
+ * Test release for major rewrite. See https://github.com/grackorg/grack/pull/3.
12
+
13
+ ## v0.0.2 (2012/10/10)
14
+
15
+ * Fix the file list in the gem.
16
+
17
+ ## v0.0.1 (2012/10/10)
18
+
19
+ * Birthday
@@ -0,0 +1,211 @@
1
+ [![Gem Version](https://badge.fury.io/rb/grack.svg)](http://badge.fury.io/rb/grack)
2
+ [![Build Status](https://travis-ci.org/grackorg/grack.svg?branch=master)](https://travis-ci.org/grackorg/grack)
3
+ [![Dependency Status](https://gemnasium.com/grackorg/grack.svg)](https://gemnasium.com/grackorg/grack)
4
+
5
+ # Grack - Ruby/Rack Git Smart HTTP Server Handler
6
+
7
+ This project aims to replace the builtin git-http-backend CGI handler
8
+ distributed with C Git with a Rack application.
9
+
10
+ ## Links
11
+
12
+ * Homepage :: https://github.com/grackorg/grack
13
+ * Source :: https://github.com/grackorg/grack.git
14
+
15
+ ## Description
16
+
17
+ This project aims to replace the builtin git-http-backend CGI handler
18
+ distributed with C Git with a Rack application. By default, Grack uses calls to
19
+ git on the system to implement Smart HTTP. Since the git-http-backend is really
20
+ just a simple wrapper for the upload-pack and receive-pack processes with the
21
+ '--stateless-rpc' option, this does not actually re-implement very much.
22
+ However, it is possible to use a different backend by specifying a different
23
+ Adapter.
24
+
25
+ The default git-http-backend only runs as a CGI script, and specifically is
26
+ only targeted for Apache 2.x usage (it requires PATH_INFO to be set and
27
+ specifically formatted). So, instead of trying to get it to work with other
28
+ CGI capable webservers (Lighttpd, etc), we can get it running on nearly every
29
+ major and minor webserver out there by making it Rack capable. Rack
30
+ applications can run with the following handlers:
31
+
32
+ * CGI
33
+ * FCGI
34
+ * Mongrel (and EventedMongrel and SwiftipliedMongrel)
35
+ * WEBrick
36
+ * SCGI
37
+ * LiteSpeed
38
+ * Thin
39
+
40
+ These web servers include Rack handlers in their distributions:
41
+
42
+ * Ebb
43
+ * Fuzed
44
+ * Phusion Passenger (which is mod_rack for Apache and for nginx)
45
+ * Unicorn
46
+
47
+ With [Warbler](http://caldersphere.rubyforge.org/warbler/classes/Warbler.html),
48
+ and JRuby, we can also generate a WAR file that can be deployed in any Java web
49
+ application server (Tomcat, Glassfish, Websphere, JBoss, etc).
50
+
51
+ By default, Grack uses calls to git on the system to implement Smart HTTP.
52
+ Since the git-http-backend is really just a simple wrapper for the upload-pack
53
+ and receive-pack processes with the '--stateless-rpc' option, this does not
54
+ actually re-implement very much. However, it is possible to use a different
55
+ backend by specifying a different Adapter. See below for a list.
56
+
57
+ Note that while it is technically possible to host non-bare repositories with
58
+ this gem, it is discouraged. The only somewhat safe option is to serve such a
59
+ repository as read-only since there is a greater risk of arbitrary filesystem
60
+ traversal when a checkout tree must be traversed to reach the repository
61
+ administrative area (`.git` directory). Additionally, any recent version of Git
62
+ prevents pushes into non-bare repositories by default since pushing into the
63
+ currently checked out branch can effectively "break" the checkout tree.
64
+
65
+ ### Git Adapters
66
+
67
+ Grack makes calls to the git binary through the GitAdapter abstraction class.
68
+ Grack can be made to use a different backend by specifying a call-able object,
69
+ such as a lambda, in Grack's configuration that provides new adapter instances
70
+ per request. For example:
71
+
72
+ ```ruby
73
+ Grack::App.new(:git_adapter_factory => ->{ MyAdapter.new })
74
+ ```
75
+
76
+ Alternative adapters available:
77
+ * [rjgit_grack](http://github.com/dometto/rjgit_grack) lets Grack use the
78
+ [RJGit](http://github.com/repotag/rjgit) gem to implement Smart HTTP in pure
79
+ Jruby. (Currently requires use of backward compatibility support via
80
+ Grack::CompatibleGitAdapter)
81
+
82
+ ### Developing Adapters
83
+
84
+ Adapters are abstraction classes that handle the actual implementation of the
85
+ Smart HTTP protocol (advertising refs, uploading and receiving packfiles). Such
86
+ abstraction classes must have the following methods:
87
+
88
+ ```ruby
89
+ MyAdapter.repository_path=(repository_path)
90
+ MyAdapter.exist?
91
+ MyAdapter.handle_pack(kind, io_in, io_out, opts = {})
92
+ MyAdapter.file(path)
93
+ MyAdapter.update_server_info
94
+ MyAdapter.allow_push?
95
+ MyAdapter.allow_pull?
96
+ ```
97
+
98
+ See `Grack::GitAdapter` for more detailed documentation and an example
99
+ implementation.
100
+
101
+ ## Features
102
+
103
+ * Supports Git Smart HTTP protocol.
104
+ * Supports Git Basic HTTP protocol.
105
+ * Limits push/pull access globally and per-repository.
106
+ * Thread safe operation.
107
+
108
+ ## Known Bugs/Limitations
109
+
110
+ * Will likely block fully evented web servers when using the stock Git adapter.
111
+
112
+ ## Synopsis
113
+
114
+ In `config.ru`:
115
+
116
+ ```ruby
117
+ require 'grack/app'
118
+ require 'grack/git_adapter'
119
+
120
+ config = {
121
+ :root => '/path/to/bare/repositories',
122
+ :allow_push => true,
123
+ :allow_pull => true,
124
+ :git_adapter_factory => ->{ GitAdapter.new }
125
+ }
126
+
127
+ run Grack::App.new(config)
128
+ ```
129
+
130
+ Then run:
131
+
132
+ ```sh
133
+ $ bundle exec rackup --host localhost --port 8080 config.ru
134
+ $ git clone http://localhost:8080/your-repository.git
135
+ ```
136
+
137
+ ## Runtime Requirements
138
+
139
+ * Ruby >=1.9.3
140
+ * Git >=1.7 (if using the included Git adapter)
141
+ * rack (>= 0)
142
+
143
+ ## Development Requirements
144
+
145
+ * All runtime requirements
146
+ * rake (>= 10.1.1, ~> 10.1)
147
+ * rack-test (>= 0.6.3, ~> 0.6)
148
+ * minitest (>= 5.8.0, ~> 5.8)
149
+ * mocha (>= 1.1.0, ~> 1.1)
150
+ * simplecov (>= 0.10.0, ~> 0.10)
151
+ * yard (>= 0.8.7.3, ~> 0.8.7)
152
+ * redcarpet (>= 3.1.0, ~> 3.1)
153
+ * github-markup (>= 1.0.2, ~> 1.0)
154
+ * pry (~> 0)
155
+
156
+ ## Contributing
157
+
158
+ Contributions for bug fixes, documentation, extensions, tests, etc. are
159
+ encouraged.
160
+
161
+ 1. Clone the repository.
162
+ 2. Fix a bug or add a feature.
163
+ 3. Add tests for the fix or feature.
164
+ 4. Make a pull request.
165
+
166
+ ## Development
167
+
168
+ After checking out the source, run:
169
+
170
+ ```sh
171
+ $ bundle install
172
+ $ bundle exec rake test yard
173
+ ```
174
+
175
+ This will install all dependencies, run the tests/specs, and generate the
176
+ documentation.
177
+
178
+ ## Authors
179
+
180
+ Thanks to all contributors. Without your help this project would not exist.
181
+
182
+ * Scott Chacon :: schacon@gmail.com
183
+ * Dawa Ometto :: dawa.ometto@phil.uu.nl
184
+ * Jeremy Bopp :: jeremy@bopp.net
185
+
186
+ ## License
187
+
188
+ ```
189
+ (The MIT License)
190
+
191
+ Copyright (c) 2015 Scott Chacon <schacon@gmail.com>
192
+
193
+ Permission is hereby granted, free of charge, to any person obtaining
194
+ a copy of this software and associated documentation files (the
195
+ 'Software'), to deal in the Software without restriction, including
196
+ without limitation the rights to use, copy, modify, merge, publish,
197
+ distribute, sublicense, and/or sell copies of the Software, and to
198
+ permit persons to whom the Software is furnished to do so, subject to
199
+ the following conditions:
200
+
201
+ The above copyright notice and this permission notice shall be
202
+ included in all copies or substantial portions of the Software.
203
+
204
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
205
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
206
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
207
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
208
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
209
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
210
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
211
+ ```
@@ -0,0 +1,222 @@
1
+ # encoding: UTF-8
2
+ # -*- ruby -*-
3
+
4
+ require 'erb'
5
+ require 'rake/testtask'
6
+ require 'rubygems/package_task'
7
+ require 'rake/clean'
8
+ require 'yard'
9
+
10
+ # Load the gemspec file for this project.
11
+ GEMSPEC = Dir['*.gemspec'].first
12
+ SPEC = eval(File.read(GEMSPEC), nil, GEMSPEC)
13
+
14
+ # A dynamically generated list of files that should match the manifest (the
15
+ # combined contents of SPEC.files and SPEC.test_files). The idea is for this
16
+ # list to contain all project files except for those that have been explicitly
17
+ # excluded. This list will be compared with the manifest from the SPEC in order
18
+ # to help catch the addition or removal of files to or from the project that
19
+ # have not been accounted for either by an exclusion here or an inclusion in the
20
+ # SPEC manifest.
21
+ #
22
+ # NOTE:
23
+ # It is critical that the manifest is *not* automatically generated via globbing
24
+ # and the like; otherwise, this will yield a simple comparison between
25
+ # redundantly generated lists of files that probably will not protect the
26
+ # project from the unintentional inclusion or exclusion of files in the
27
+ # distribution.
28
+ PKG_FILES = FileList.new(Dir.glob('**/*', File::FNM_DOTMATCH)) do |files|
29
+ # Exclude anything that doesn't exist as well as directories.
30
+ files.exclude {|file| ! File.exist?(file) || File.directory?(file)}
31
+ # Exclude Git administrative files.
32
+ files.exclude('.git/**/*', '**/.gitignore', '**/.gitmodule', '**/.gitkeep')
33
+ # Exclude editor swap/temporary files.
34
+ files.exclude('**/.*.sw?', '**/.sw?')
35
+ # Exclude the gemspec file.
36
+ files.exclude(GEMSPEC)
37
+ # Exclude the README template file.
38
+ files.exclude('README.md.erb')
39
+ # Exclude resources for bundler.
40
+ files.exclude('Gemfile', 'Gemfile.lock')
41
+ files.exclude(%r{^.bundle([/\\]|$)})
42
+ files.exclude(%r{^vendor/bundle([/\\]|$)})
43
+ # Exclude generated content, except for the README file.
44
+ files.exclude(%r{^(pkg|doc|coverage|.yardoc)([/\\]|$)})
45
+ # Exclude examples.
46
+ files.exclude('examples/**/*')
47
+ # Exclude Rubinius compiled Ruby files.
48
+ files.exclude('**/*.rbc')
49
+ end
50
+
51
+ # Make sure that :clean and :clobber will not whack the repository files.
52
+ CLEAN.exclude('.git/**')
53
+ # Vim swap files are fair game for clean up.
54
+ CLEAN.include('**/.*.sw?')
55
+
56
+ # Returns the value of the VERSION environment variable as a Gem::Version object
57
+ # assuming it is set and a valid Gem version string. Otherwise, raises an
58
+ # exception.
59
+ def get_version_argument
60
+ version = ENV['VERSION']
61
+ if version.to_s.empty?
62
+ raise "No version specified: Add VERSION=X.Y.Z to the command line"
63
+ end
64
+ begin
65
+ Gem::Version.create(version.dup)
66
+ rescue ArgumentError
67
+ raise "Invalid version specified in `VERSION=#{version}'"
68
+ end
69
+ end
70
+
71
+ # Performs an in place, per line edit of the file indicated by _path_ by calling
72
+ # the sub method on each line and passing _pattern_, _replacement_, and _b_ as
73
+ # arguments.
74
+ def file_sub(path, pattern, replacement = nil, &b)
75
+ tmp_path = "#{path}.tmp"
76
+ File.open(path) do |infile|
77
+ File.open(tmp_path, 'w') do |outfile|
78
+ infile.each do |line|
79
+ outfile.write(line.sub(pattern, replacement, &b))
80
+ end
81
+ end
82
+ end
83
+ File.rename(tmp_path, path)
84
+ end
85
+
86
+ # Updates the version string in the gemspec file to the string in _version_.
87
+ def set_version(version)
88
+ file_sub(GEMSPEC, /(\.version\s*=\s*).*/, "\\1'#{version}'")
89
+ end
90
+
91
+ # Returns a string that is line wrapped at word boundaries, where each line is
92
+ # no longer than _line_width_ characters.
93
+ #
94
+ # This is mostly lifted directly from ActionView::Helpers::TextHelper.
95
+ def word_wrap(text, line_width = 80)
96
+ text.split("\n").collect do |line|
97
+ line.length > line_width ?
98
+ line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip :
99
+ line
100
+ end * "\n"
101
+ end
102
+
103
+ desc 'Alias for build:gem'
104
+ task :build => 'build:gem'
105
+
106
+ # Build related tasks.
107
+ namespace :build do
108
+ # Create the gem and package tasks.
109
+ Gem::PackageTask.new(SPEC).define
110
+
111
+ # Ensure that the manifest is consulted when building the gem. Any
112
+ # generated/compiled files should be available at that time.
113
+ task :gem => :check_manifest
114
+
115
+ desc 'Verify the manifest'
116
+ task :check_manifest do
117
+ manifest_files = (SPEC.files + SPEC.test_files).sort.uniq
118
+ pkg_files = PKG_FILES.sort.uniq
119
+ if manifest_files != pkg_files then
120
+ common_files = manifest_files & pkg_files
121
+ manifest_files -= common_files
122
+ pkg_files -= common_files
123
+ message = ["The manifest does not match the automatic file list."]
124
+ unless manifest_files.empty? then
125
+ message << " Extraneous files:\n " + manifest_files.join("\n ")
126
+ end
127
+ unless pkg_files.empty?
128
+ message << " Missing files:\n " + pkg_files.join("\n ")
129
+ end
130
+ raise message.join("\n")
131
+ end
132
+ end
133
+
134
+ # Creates the README.md file from its template and other sources.
135
+ file 'README.md' => ['README.md.erb', 'LICENSE', GEMSPEC] do
136
+ spec = SPEC
137
+ File.open('README.md', 'w') do |readme|
138
+ readme.write(
139
+ ERB.new(File.read('README.md.erb'), nil, '-').result(binding)
140
+ )
141
+ end
142
+ end
143
+ end
144
+
145
+ # Ensure that the clobber task also clobbers package files.
146
+ task :clobber => 'build:clobber_package'
147
+
148
+ # Create the documentation task.
149
+ YARD::Rake::YardocTask.new
150
+ # Ensure that the README file is (re)generated first.
151
+ task :yard => 'README.md'
152
+
153
+ # Gem related tasks.
154
+ namespace :gem do
155
+ desc 'Alias for build:gem'
156
+ task :build => 'build:gem'
157
+
158
+ desc 'Publish the gemfile'
159
+ task :publish => ['version:check', :test, 'repo:tag', :build] do
160
+ sh "gem push pkg/#{SPEC.name}-#{SPEC.version}*.gem"
161
+ end
162
+ end
163
+
164
+ Rake::TestTask.new do |t|
165
+ t.pattern = 'tests/**/*_test.rb'
166
+ end
167
+
168
+ # Version string management tasks.
169
+ namespace :version do
170
+ desc 'Set the version for the project to a specified version'
171
+ task :set do
172
+ set_version(get_version_argument)
173
+ end
174
+
175
+ desc 'Set the version for the project back to 0.0.0'
176
+ task :reset do
177
+ set_version('0.0.0')
178
+ end
179
+
180
+ desc 'Check that all version strings are correctly set'
181
+ task :check => ['version:check:spec', 'version:check:news']
182
+
183
+ namespace :check do
184
+ desc 'Check that the version in the gemspec is correctly set'
185
+ task :spec do
186
+ version = get_version_argument
187
+ if version != SPEC.version
188
+ raise "The given version `#{version}' does not match the gemspec version `#{SPEC.version}'"
189
+ end
190
+ end
191
+
192
+ desc 'Check that the NEWS.md file mentions the version'
193
+ task :news do
194
+ version = get_version_argument
195
+ begin
196
+ File.open('NEWS.md') do |news|
197
+ unless news.each_line.any? {|l| l =~ /^## v#{Regexp.escape(version.to_s)} /}
198
+ raise "The NEWS.md file does not mention version `#{version}'"
199
+ end
200
+ end
201
+ rescue Errno::ENOENT
202
+ raise 'No NEWS.md file found'
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ # Repository and workspace management tasks.
209
+ namespace :repo do
210
+ desc 'Tag the current HEAD with the version string'
211
+ task :tag => :check_workspace do
212
+ version = get_version_argument
213
+ sh "git tag -s -m 'Release v#{version}' v#{version}"
214
+ end
215
+
216
+ desc 'Ensure the workspace is fully committed and clean'
217
+ task :check_workspace => ['README.md'] do
218
+ unless `git status --untracked-files=all --porcelain`.empty?
219
+ raise 'Workspace has been modified. Commit pending changes and try again.'
220
+ end
221
+ end
222
+ end