gitdocs 0.5.0 → 0.6.0

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 (65) hide show
  1. checksums.yaml +6 -14
  2. data/.codeclimate.yml +26 -0
  3. data/.rubocop.yml +8 -2
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG +13 -0
  6. data/Gemfile +1 -1
  7. data/README.md +7 -6
  8. data/Rakefile +31 -5
  9. data/bin/gitdocs +1 -0
  10. data/config.ru +6 -4
  11. data/gitdocs.gemspec +22 -19
  12. data/lib/gitdocs.rb +54 -16
  13. data/lib/gitdocs/browser_app.rb +34 -41
  14. data/lib/gitdocs/cli.rb +41 -32
  15. data/lib/gitdocs/configuration.rb +40 -101
  16. data/lib/gitdocs/git_notifier.rb +111 -0
  17. data/lib/gitdocs/initializer.rb +83 -0
  18. data/lib/gitdocs/manager.rb +90 -60
  19. data/lib/gitdocs/migration/004_add_index_for_path.rb +1 -1
  20. data/lib/gitdocs/notifier.rb +70 -104
  21. data/lib/gitdocs/rendering_helper.rb +3 -0
  22. data/lib/gitdocs/repository.rb +324 -307
  23. data/lib/gitdocs/repository/committer.rb +77 -0
  24. data/lib/gitdocs/repository/path.rb +157 -140
  25. data/lib/gitdocs/search.rb +40 -25
  26. data/lib/gitdocs/settings_app.rb +5 -3
  27. data/lib/gitdocs/share.rb +64 -0
  28. data/lib/gitdocs/synchronizer.rb +40 -0
  29. data/lib/gitdocs/version.rb +1 -1
  30. data/lib/gitdocs/views/_header.haml +2 -2
  31. data/lib/gitdocs/views/dir.haml +3 -3
  32. data/lib/gitdocs/views/edit.haml +1 -1
  33. data/lib/gitdocs/views/file.haml +1 -1
  34. data/lib/gitdocs/views/home.haml +3 -3
  35. data/lib/gitdocs/views/layout.haml +13 -13
  36. data/lib/gitdocs/views/revisions.haml +3 -3
  37. data/lib/gitdocs/views/search.haml +1 -1
  38. data/lib/gitdocs/views/settings.haml +6 -6
  39. data/test/integration/cli/full_sync_test.rb +83 -0
  40. data/test/integration/cli/share_management_test.rb +29 -0
  41. data/test/integration/cli/status_test.rb +14 -0
  42. data/test/integration/test_helper.rb +185 -151
  43. data/test/integration/{browse_test.rb → web/browse_test.rb} +11 -29
  44. data/test/integration/web/share_management_test.rb +46 -0
  45. data/test/support/git_factory.rb +276 -0
  46. data/test/unit/browser_app_test.rb +346 -0
  47. data/test/unit/configuration_test.rb +8 -70
  48. data/test/unit/git_notifier_test.rb +116 -0
  49. data/test/unit/gitdocs_test.rb +90 -0
  50. data/test/unit/manager_test.rb +36 -0
  51. data/test/unit/notifier_test.rb +60 -124
  52. data/test/unit/repository_committer_test.rb +111 -0
  53. data/test/unit/repository_path_test.rb +92 -76
  54. data/test/unit/repository_test.rb +243 -356
  55. data/test/unit/search_test.rb +15 -0
  56. data/test/unit/settings_app_test.rb +80 -0
  57. data/test/unit/share_test.rb +97 -0
  58. data/test/unit/test_helper.rb +17 -3
  59. metadata +114 -108
  60. data/lib/gitdocs/runner.rb +0 -108
  61. data/lib/gitdocs/server.rb +0 -62
  62. data/test/integration/full_sync_test.rb +0 -66
  63. data/test/integration/share_management_test.rb +0 -95
  64. data/test/integration/status_test.rb +0 -21
  65. data/test/unit/runner_test.rb +0 -122
@@ -0,0 +1,46 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require File.expand_path('../../test_helper', __FILE__)
4
+
5
+ describe 'Manage which shares are being watched' do
6
+ it 'should update a share through the UI' do
7
+ gitdocs_add('local')
8
+ gitdocs_start
9
+ visit_and_click_link('Settings')
10
+
11
+ within('#settings') do
12
+ within('#share-0') do
13
+ fill_in('share[0][polling_interval]', with: '0.2')
14
+ select('Fetch only', from: 'share[0][sync_type]')
15
+ end
16
+ click_button('Save')
17
+ end
18
+
19
+ # Allow the asynchronous portion of the update finish before checking
20
+ # the result.
21
+ sleep(1)
22
+
23
+ click_link('Settings')
24
+ within('#settings') do
25
+ within('#share-0') do
26
+ page.must_have_field('share[0][polling_interval]', with: '0.2')
27
+ page.must_have_field('share[0][sync_type]', with: 'fetch')
28
+ end
29
+ end
30
+ end
31
+
32
+ describe 'remove a share' do
33
+ before { gitdocs_add('local') }
34
+
35
+ it do
36
+ gitdocs_start
37
+ visit_and_click_link('Settings')
38
+
39
+ within('#settings') do
40
+ within('#share-0') { click_link('Delete') }
41
+
42
+ page.must_have_css('.share', count: 0)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,276 @@
1
+ module GitFactory
2
+ class << self
3
+ attr_accessor :working_directory
4
+
5
+ def configure
6
+ yield self
7
+ end
8
+
9
+ # @return [Array<Hash>]
10
+ def users
11
+ [
12
+ { name: 'Art T. Fish', email: 'afish@example.com' },
13
+ { name: 'A U Thor', email: 'author@example.com' }
14
+ ]
15
+ end
16
+
17
+ # @return [Array<String>]
18
+ def authors
19
+ users.map { |x| "#{x[:name]} <#{x[:email]}>" }
20
+ end
21
+
22
+ # @param [#to_s] repo_name
23
+ #
24
+ # @return [String]
25
+ def expand_path(repo_name, *filename_path)
26
+ File.expand_path(
27
+ File.join(
28
+ working_directory, repo_name.to_s, *filename_path
29
+ )
30
+ )
31
+ end
32
+
33
+ # @param [#to_s] repo_name
34
+ #
35
+ # @return [Rugged::Repository]
36
+ def rugged_repository(repo_name)
37
+ Rugged::Repository.new(expand_path(repo_name))
38
+ end
39
+
40
+ # @param [#to_s] repo_name
41
+ #
42
+ # @return [String]
43
+ def init(repo_name)
44
+ repository_path = expand_path(repo_name)
45
+
46
+ FileUtils.rm_rf(repository_path)
47
+ FileUtils.mkdir_p(File.dirname(repository_path))
48
+
49
+ repo = Rugged::Repository.init_at(repository_path)
50
+ repo.config['user.email'] = users[0][:email]
51
+ repo.config['user.name'] = users[0][:name]
52
+
53
+ repository_path
54
+ end
55
+
56
+ # @param [#to_s] repo_name
57
+ #
58
+ # @return [String]
59
+ def init_bare(repo_name)
60
+ repository_path = expand_path(repo_name)
61
+
62
+ FileUtils.rm_rf(repository_path)
63
+ FileUtils.mkdir_p(File.dirname(repository_path))
64
+
65
+ repo = Rugged::Repository.init_at(repository_path, :bare)
66
+ repo.config['user.email'] = users[0][:email]
67
+ repo.config['user.name'] = users[0][:name]
68
+
69
+ repository_path
70
+ end
71
+
72
+ # @param [#to_s] bare_repo_name
73
+ # @param [#to_s] repo_name
74
+ #
75
+ # @return [String]
76
+ def clone(bare_repo_name, repo_name)
77
+ bare_repository_path = expand_path(bare_repo_name)
78
+ repository_path = expand_path(repo_name)
79
+
80
+ # assert bare_repository_path is a valid bare repository
81
+ FileUtils.rm_rf(repository_path)
82
+
83
+ repo = Rugged::Repository.clone_at(
84
+ bare_repository_path, repository_path
85
+ )
86
+ repo.config['user.email'] = users[0][:email]
87
+ repo.config['user.name'] = users[0][:name]
88
+
89
+ repository_path
90
+ end
91
+
92
+ # @param [#to_s] repo_name
93
+ # @param [String] directory_name
94
+ #
95
+ # @return [void]
96
+ def mkdir(repo_name, directory_name)
97
+ directory_path = expand_path(repo_name, directory_name)
98
+ FileUtils.mkdir_p(directory_path)
99
+ end
100
+
101
+ # @param [#to_s] repo_name
102
+ # @param [String] filename
103
+ # @param [String] content
104
+ #
105
+ # @return [void]
106
+ def write(repo_name, filename, content)
107
+ file_path = expand_path(repo_name, filename)
108
+ FileUtils.mkdir_p(File.dirname(file_path))
109
+ File.write(file_path, content)
110
+ end
111
+
112
+ # @param [#to_s] repo_name
113
+ # @param [String] filename
114
+ # @param [String] content
115
+ #
116
+ # @return [void]
117
+ def append(repo_name, filename, content)
118
+ file_path = expand_path(repo_name, filename)
119
+ FileUtils.mkdir_p(File.dirname(file_path))
120
+ File.open(file_path, 'a') { |f| f << content }
121
+ end
122
+
123
+ # @param [#to_s] repo_name
124
+ # @param [String] filename
125
+ #
126
+ # @return [void]
127
+ def rm(repo_name, filename)
128
+ file_path = expand_path(repo_name, filename)
129
+ FileUtils.rm_rf(file_path)
130
+ end
131
+
132
+ # @param [#to_s] repo_name
133
+ # @param [String] filename
134
+ # @param [String] content
135
+ # @param [String] author_id
136
+ #
137
+ # @return [void]
138
+ def commit(repo_name, filename, content, author_id = 0)
139
+ commit_message = 'commit'
140
+ write(repo_name, filename, content)
141
+
142
+ repository_path = expand_path(repo_name)
143
+ `cd #{repository_path} ; git add #{filename}; git commit -m '#{commit_message}' --author='#{authors[author_id]}'`
144
+ `cd #{repository_path} ; git rev-parse HEAD`.strip
145
+ end
146
+
147
+ # @overload bare_commit(repo_name, filename, content)
148
+ # @param [#to_s] repo_name
149
+ # @param [String] filename
150
+ # @param [String] content
151
+ #
152
+ # @overload bare_commit(repo_name, filename, content, author_id)
153
+ # @param [#to_s] repo_name
154
+ # @param [String] filename
155
+ # @param [String] content
156
+ # @param [String] author_id
157
+ #
158
+ # @return [void]
159
+ def bare_commit(repo_name, filename, content, author_id = 0)
160
+ commit_message = 'commit'
161
+ repo = rugged_repository(repo_name)
162
+
163
+ index = Rugged::Index.new
164
+ index.add(
165
+ path: filename,
166
+ oid: repo.write(content, :blob),
167
+ mode: 0100644
168
+ )
169
+
170
+ author_hash = {
171
+ email: users[author_id][:email],
172
+ name: users[author_id][:name],
173
+ time: Time.now
174
+ }
175
+
176
+ Rugged::Commit.create(
177
+ repo,
178
+ tree: index.write_tree(repo),
179
+ author: author_hash,
180
+ committer: author_hash,
181
+ message: commit_message,
182
+ parents: repo.empty? ? [] : [repo.head.target].compact,
183
+ update_ref: 'HEAD'
184
+ )
185
+ end
186
+ end
187
+ end
188
+
189
+ module GitInspector
190
+ class << self
191
+ # @param [#to_s] repo_name
192
+ #
193
+ # @return [Integer]
194
+ def commit_count(repo_name)
195
+ repo = GitFactory.rugged_repository(repo_name)
196
+ walker = Rugged::Walker.new(repo)
197
+ walker.push(repo.head.target)
198
+ walker.count
199
+ rescue Rugged::ReferenceError
200
+ # The repo does not have a head => no commits.
201
+ 0
202
+ end
203
+
204
+ # @param [to_s] repo_name
205
+ #
206
+ # @return [Boolean]
207
+ def clean?(repo_name)
208
+ repo = GitFactory.rugged_repository(repo_name)
209
+ repo.diff_workdir(
210
+ repo.head.target, include_untracked: true
211
+ ).deltas.empty?
212
+ rescue Rugged::Error
213
+ false
214
+ end
215
+
216
+ # @param [#to_s] repo_name
217
+ #
218
+ # @return [String]
219
+ def remote_oid(repo_name)
220
+ repo = GitFactory.rugged_repository(repo_name)
221
+ branch = repo.branches['origin/master']
222
+ return unless branch
223
+ branch.target_id
224
+ end
225
+
226
+ # @param [#to_s] repo_name
227
+ #
228
+ # @return [nil]
229
+ # @return [String]
230
+ def last_message(repo_name)
231
+ repo = GitFactory.rugged_repository(repo_name)
232
+ walker = Rugged::Walker.new(repo)
233
+ walker.push(repo.head.target)
234
+ commit = walker.first
235
+ return unless commit
236
+ commit.message
237
+ rescue Rugged::ReferenceError
238
+ # The repo does not have a head => no commits => no head commit.
239
+ nil
240
+ end
241
+
242
+ # NOTE: This method is ignoring hidden files.
243
+ # @param [#to_s] repo_name
244
+ #
245
+ # @return [Integer]
246
+ def file_count(repo_name)
247
+ repository_path = GitFactory.expand_path(repo_name)
248
+ files = Dir.chdir(repository_path) { Dir.glob('*') }
249
+ files.count
250
+ end
251
+
252
+ # @param [#to_s] repo_name
253
+ # @param [String] filename
254
+ #
255
+ # @return [String]
256
+ def file_exist?(repo_name, filename)
257
+ file_path = GitFactory.expand_path(repo_name, filename)
258
+ File.exist?(file_path)
259
+ end
260
+
261
+ # @param [#to_s] repo_name
262
+ # @param [String] filename
263
+ #
264
+ # @return [nil]
265
+ # @return [String]
266
+ def file_content(repo_name, filename)
267
+ return unless file_exist?(repo_name, filename)
268
+ file_path = GitFactory.expand_path(repo_name, filename)
269
+ File.read(file_path)
270
+ end
271
+ end
272
+ end
273
+
274
+ GitFactory.configure do |config|
275
+ config.working_directory = Dir.tmpdir
276
+ end
@@ -0,0 +1,346 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ ENV['RACK_ENV'] = 'test'
4
+ require File.expand_path('../test_helper', __FILE__)
5
+ require 'rack/test'
6
+
7
+ describe Gitdocs::BrowserApp do
8
+ include Rack::Test::Methods
9
+ def app
10
+ Gitdocs::BrowserApp
11
+ end
12
+
13
+ describe 'get /' do
14
+ describe 'with one share' do
15
+ before do
16
+ Gitdocs::Share.stubs(:all).returns([:one])
17
+ Gitdocs::Share.stubs(:first).returns(stub(id: :id))
18
+
19
+ get '/'
20
+ end
21
+ specify do
22
+ last_response.status.must_equal(302)
23
+ last_response.headers['Location'].must_equal('http://example.org/id/')
24
+ end
25
+ end
26
+
27
+ describe 'with multiple shares' do
28
+ before do
29
+ Gitdocs::Share.stubs(:all).returns(
30
+ [
31
+ stub(id: :id1, path: :path1),
32
+ stub(id: :id2, path: :path2)
33
+ ]
34
+ )
35
+
36
+ get '/'
37
+ end
38
+ specify do
39
+ last_response.status.must_equal(200)
40
+ last_response.body.must_include('Select a share to browse')
41
+ last_response.body.must_include('id1')
42
+ last_response.body.must_include('path1')
43
+ last_response.body.must_include('id2')
44
+ last_response.body.must_include('path2')
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'get /search' do
50
+ before do
51
+ Gitdocs::Search.stubs(:search).with('term').returns(results)
52
+
53
+ get '/search', q: 'term'
54
+ end
55
+
56
+ describe 'empty' do
57
+ let(:results) { {} }
58
+ specify do
59
+ last_response.status.must_equal(200)
60
+ last_response.body.must_include('Matches for &quot;term&quot;')
61
+ last_response.body.must_include('No results')
62
+ end
63
+ end
64
+
65
+ describe 'not empty' do
66
+ let(:results) do
67
+ descriptor1 = stub(name: 'repo1', index: 'index1')
68
+ search_result1 = stub(file: 'filename1', context: 'context1')
69
+ descriptor2 = stub(name: 'repo2', index: 'index2')
70
+ search_result2 = stub(file: 'filename2', context: 'context2')
71
+
72
+ {
73
+ descriptor1 => [search_result1],
74
+ descriptor2 => [search_result2]
75
+ }
76
+ end
77
+ specify do
78
+ last_response.status.must_equal(200)
79
+ last_response.body.must_include('Matches for &quot;term&quot;')
80
+ last_response.body.must_include('repo1')
81
+ last_response.body.must_include('/index1/filename1')
82
+ last_response.body.must_include('context1')
83
+ last_response.body.must_include('repo2')
84
+ last_response.body.must_include('/index2/filename2')
85
+ last_response.body.must_include('context2')
86
+ end
87
+ end
88
+ end
89
+
90
+ describe 'resource methods' do
91
+ let(:repository) { stub(root: 'root_path') }
92
+ let(:repository_path) { stub }
93
+ before do
94
+ Gitdocs::Share
95
+ .stubs(:find)
96
+ .with(1234)
97
+ .returns(share = stub)
98
+ Gitdocs::Repository
99
+ .stubs(:new)
100
+ .with(share)
101
+ .returns(repository)
102
+ Gitdocs::Repository::Path
103
+ .stubs(:new)
104
+ .with(repository, '/path1/path2')
105
+ .returns(repository_path)
106
+ end
107
+
108
+ describe 'get /:id' do
109
+ describe 'meta' do
110
+ before do
111
+ repository_path.stubs(:meta).returns(key: :value)
112
+
113
+ get '/1234/path1/path2', mode: 'meta'
114
+ end
115
+ specify do
116
+ last_response.status.must_equal(200)
117
+ last_response.content_type.must_equal('application/json')
118
+ last_response.body.must_equal('{"key":"value"}')
119
+ end
120
+ end
121
+
122
+ describe 'edit' do
123
+ before do
124
+ repository_path.stubs(text?: text, content: :content)
125
+
126
+ get '/1234/path1/path2', mode: 'edit'
127
+ end
128
+
129
+ describe 'not text' do
130
+ let(:text) { false }
131
+ specify { last_response.status.must_equal(404) }
132
+ end
133
+
134
+ describe 'text' do
135
+ let(:text) { true }
136
+ specify do
137
+ last_response.status.must_equal(200)
138
+ last_response.body.must_include('content')
139
+ end
140
+ end
141
+ end
142
+
143
+ describe 'revisions' do
144
+ before do
145
+ repository_path.stubs(revisions: revisions)
146
+
147
+ get '/1234/path1/path2', mode: 'revisions'
148
+ end
149
+
150
+ describe 'none' do
151
+ let(:revisions) { [] }
152
+ specify do
153
+ last_response.status.must_equal(200)
154
+ last_response.body.must_include('No revisions for this file could be found.')
155
+ end
156
+ end
157
+
158
+ describe 'some' do
159
+ let(:revisions) do
160
+ [
161
+ {
162
+ commit: 'DEADBEEF',
163
+ subject: 'I am a commit',
164
+ author: 'Author <author@example.com>',
165
+ date: Time.parse('2016-01-01')
166
+ }
167
+ ]
168
+ end
169
+ specify do
170
+ last_response.status.must_equal(200)
171
+ last_response.body.must_include('DEADBEEF')
172
+ last_response.body.must_include('?revision=DEADBEEF')
173
+ last_response.body.must_include('I am a commit')
174
+ last_response.body.must_include('Author <author@example.com>')
175
+ last_response.body.must_include(Time.parse('2016-01-01').iso8601)
176
+ end
177
+ end
178
+ end
179
+
180
+ describe 'raw' do
181
+ before do
182
+ repository_path.stubs(absolute_path: :absolute_path)
183
+ app.any_instance.stubs(:send_file).with(:absolute_path)
184
+
185
+ get '/1234/path1/path2', mode: 'raw'
186
+ end
187
+ specify { last_response.status.must_equal(200) }
188
+ end
189
+
190
+ describe 'simple show' do
191
+ describe 'directory' do
192
+ before do
193
+ repository_path.stubs(
194
+ directory?: directory,
195
+ readme_path: :readme_path,
196
+ file_listing: file_listing
197
+ )
198
+ app
199
+ .any_instance
200
+ .stubs(:file_content_render)
201
+ .with(:readme_path)
202
+ .returns(:readme_content)
203
+
204
+ get '/1234/path1/path2'
205
+ end
206
+
207
+ describe 'empty' do
208
+ let(:directory) { true }
209
+ let(:file_listing) { [] }
210
+ specify do
211
+ last_response.status.must_equal(200)
212
+ last_response.body.must_include('No files were found in this directory.')
213
+ last_response.body.must_include('readme_content')
214
+ end
215
+ end
216
+
217
+ describe 'non-empty' do
218
+ let(:directory) { true }
219
+ let(:file_listing) do
220
+ [
221
+ stub(name: 'filename', is_directory: false),
222
+ stub(name: 'directory', is_directory: true)
223
+ ]
224
+ end
225
+ specify do
226
+ last_response.status.must_equal(200)
227
+ last_response.body.must_include('/img/file.png')
228
+ last_response.body.must_include('filename')
229
+ last_response.body.must_include('/img/folder.png')
230
+ last_response.body.must_include('directory')
231
+ last_response.body.must_include('readme_content')
232
+ end
233
+ end
234
+ end
235
+
236
+ describe 'file' do
237
+ before do
238
+ repository_path.stubs(directory?: false)
239
+ repository_path
240
+ .stubs(:absolute_path)
241
+ .with('revision')
242
+ .returns(:revision_path)
243
+ app
244
+ .any_instance
245
+ .stubs(:file_content_render)
246
+ .with(:revision_path)
247
+ .returns(:content)
248
+
249
+ get '/1234/path1/path2', revision: 'revision'
250
+ end
251
+ specify do
252
+ last_response.status.must_equal(200)
253
+ last_response.body.must_include('content')
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ describe 'post /:id' do
260
+ before { repository_path.stubs(relative_path: 'path1/path2/new') }
261
+
262
+ describe 'upload' do
263
+ before do
264
+ repository_path.expects(:join).with(File.basename(__FILE__))
265
+ repository_path.expects(:mv).with(regexp_matches(/RackMultipart/))
266
+
267
+ post '/1234/path1/path2', file: Rack::Test::UploadedFile.new(__FILE__, 'text/plain')
268
+ end
269
+ specify do
270
+ last_response.status.must_equal(302)
271
+ last_response.headers['Location'].must_equal('http://example.org/1234/path1/path2/new')
272
+ end
273
+ end
274
+
275
+ describe 'empty file' do
276
+ before do
277
+ repository_path.expects(:join).with('new')
278
+ repository_path.expects(:touch)
279
+
280
+ post '/1234/path1/path2', filename: 'new', new_file: 'value'
281
+ end
282
+ specify do
283
+ last_response.status.must_equal(302)
284
+ last_response.headers['Location'].must_equal('http://example.org/1234/path1/path2/new?mode=edit')
285
+ end
286
+ end
287
+
288
+ describe 'directory' do
289
+ before do
290
+ repository_path.expects(:join).with('new')
291
+ repository_path.expects(:mkdir)
292
+
293
+ post '/1234/path1/path2', filename: 'new', new_directory: 'value'
294
+ end
295
+ specify do
296
+ last_response.status.must_equal(302)
297
+ last_response.headers['Location'].must_equal('http://example.org/1234/path1/path2/new')
298
+ end
299
+ end
300
+ end
301
+
302
+ describe 'put /:id' do
303
+ before { repository_path.stubs(relative_path: 'path1/path2') }
304
+
305
+ describe 'update and commit' do
306
+ before do
307
+ repository_path.expects(:write).with('data')
308
+ repository.expects(:write_commit_message).with('message')
309
+
310
+ put '/1234/path1/path2', data: 'data', message: 'message'
311
+ end
312
+ specify do
313
+ last_response.status.must_equal(302)
314
+ last_response.headers['Location'].must_equal('http://example.org/1234/path1/path2')
315
+ end
316
+ end
317
+
318
+ describe 'revert' do
319
+ before do
320
+ repository_path.expects(:revert).with('revision')
321
+ repository_path.stubs(relative_path: 'path1/path2')
322
+ repository.expects(:write_commit_message).with("Reverting 'path1/path2' to revision")
323
+
324
+ put '/1234/path1/path2', revision: 'revision'
325
+ end
326
+ specify do
327
+ last_response.status.must_equal(302)
328
+ last_response.headers['Location'].must_equal('http://example.org/1234/path1/path2')
329
+ end
330
+ end
331
+ end
332
+
333
+ describe 'delete /:id' do
334
+ before do
335
+ repository_path.expects(:remove)
336
+ repository_path.stubs(relative_dirname: 'path1')
337
+
338
+ delete '/1234/path1/path2'
339
+ end
340
+ specify do
341
+ last_response.status.must_equal(302)
342
+ last_response.headers['Location'].must_equal('http://example.org/1234/path1')
343
+ end
344
+ end
345
+ end
346
+ end