gitdocs 0.5.0.pre6 → 0.5.0.pre7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +1 -0
  3. data/.haml-lint.yml +3 -0
  4. data/.jslint.yml +84 -0
  5. data/.rubocop.yml +13 -0
  6. data/CHANGELOG +11 -0
  7. data/README.md +6 -2
  8. data/Rakefile +22 -3
  9. data/gitdocs.gemspec +36 -29
  10. data/lib/gitdocs.rb +5 -2
  11. data/lib/gitdocs/cli.rb +31 -8
  12. data/lib/gitdocs/configuration.rb +95 -49
  13. data/lib/gitdocs/manager.rb +36 -28
  14. data/lib/gitdocs/migration/001_create_shares.rb +2 -0
  15. data/lib/gitdocs/migration/002_add_remote_branch.rb +2 -0
  16. data/lib/gitdocs/migration/003_create_configs.rb +2 -0
  17. data/lib/gitdocs/migration/004_add_index_for_path.rb +4 -0
  18. data/lib/gitdocs/migration/005_add_start_web_frontend.rb +2 -0
  19. data/lib/gitdocs/migration/006_add_web_port_to_config.rb +2 -0
  20. data/lib/gitdocs/migration/007_add_sync_type.rb +11 -0
  21. data/lib/gitdocs/notifier.rb +89 -6
  22. data/lib/gitdocs/public/img/file.png +0 -0
  23. data/lib/gitdocs/public/img/folder.png +0 -0
  24. data/lib/gitdocs/public/js/app.js +26 -11
  25. data/lib/gitdocs/public/js/edit.js +3 -3
  26. data/lib/gitdocs/public/js/settings.js +8 -5
  27. data/lib/gitdocs/public/js/util.js +21 -20
  28. data/lib/gitdocs/rendering.rb +14 -9
  29. data/lib/gitdocs/repository.rb +180 -216
  30. data/lib/gitdocs/repository/path.rb +166 -0
  31. data/lib/gitdocs/runner.rb +22 -65
  32. data/lib/gitdocs/search.rb +35 -0
  33. data/lib/gitdocs/server.rb +123 -86
  34. data/lib/gitdocs/version.rb +1 -1
  35. data/lib/gitdocs/views/_header.haml +6 -6
  36. data/lib/gitdocs/views/app.haml +17 -17
  37. data/lib/gitdocs/views/dir.haml +10 -10
  38. data/lib/gitdocs/views/edit.haml +8 -9
  39. data/lib/gitdocs/views/file.haml +1 -1
  40. data/lib/gitdocs/views/home.haml +4 -4
  41. data/lib/gitdocs/views/revisions.haml +6 -6
  42. data/lib/gitdocs/views/search.haml +6 -6
  43. data/lib/gitdocs/views/settings.haml +23 -16
  44. data/test/.rubocop.yml +13 -0
  45. data/test/integration/browse_test.rb +149 -0
  46. data/test/integration/full_sync_test.rb +3 -11
  47. data/test/integration/share_management_test.rb +59 -10
  48. data/test/integration/status_test.rb +2 -0
  49. data/test/integration/test_helper.rb +40 -7
  50. data/test/unit/configuration_test.rb +82 -0
  51. data/test/unit/notifier_test.rb +165 -0
  52. data/test/unit/repository_path_test.rb +368 -0
  53. data/test/{repository_test.rb → unit/repository_test.rb} +426 -245
  54. data/test/unit/runner_test.rb +122 -0
  55. data/test/unit/search_test.rb +52 -0
  56. data/test/{test_helper.rb → unit/test_helper.rb} +5 -0
  57. metadata +138 -41
  58. data/lib/gitdocs/docfile.rb +0 -23
  59. data/test/configuration_test.rb +0 -41
  60. data/test/notifier_test.rb +0 -68
  61. data/test/runner_test.rb +0 -123
@@ -0,0 +1,368 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require File.expand_path('../test_helper', __FILE__)
3
+
4
+ describe Gitdocs::Repository::Path do
5
+ let(:path) { Gitdocs::Repository::Path.new(repository, relative_path) }
6
+ let(:repository) { stub(root: local_repo_path) }
7
+ let(:local_repo_path) { 'tmp/unit/local' }
8
+
9
+ let(:relative_path) { 'directory/file' }
10
+
11
+ before do
12
+ FileUtils.rm_rf('tmp/unit')
13
+ end
14
+
15
+ describe '#write' do
16
+ subject { path.write('foobar', :message) }
17
+ before { repository.expects(:write_commit_message).with(:message) }
18
+
19
+ describe 'directory missing' do
20
+ before { subject }
21
+ it { local_file_content.must_equal "foobar\n" }
22
+ end
23
+
24
+ describe 'directory exists' do
25
+ before do
26
+ mkdir('directory')
27
+ subject
28
+ end
29
+ it { local_file_content.must_equal "foobar\n" }
30
+ end
31
+
32
+ describe 'file exists' do
33
+ before do
34
+ write('directory/file', 'deadbeef')
35
+ subject
36
+ end
37
+ it { local_file_content.must_equal "foobar\n" }
38
+ end
39
+ end
40
+
41
+ describe '#touch' do
42
+ subject { path.touch }
43
+
44
+ describe 'when directory does not exist' do
45
+ before { subject }
46
+ it { local_file_content.must_equal '' }
47
+ end
48
+
49
+ describe 'when directory already exists' do
50
+ before do
51
+ mkdir('directory')
52
+ subject
53
+ end
54
+ it { local_file_content.must_equal '' }
55
+ end
56
+
57
+ describe 'when file already exists' do
58
+ before do
59
+ write('directory/file', 'test')
60
+ subject
61
+ end
62
+ it { local_file_content.must_equal 'test' }
63
+ end
64
+ end
65
+
66
+ describe '#mkdir' do
67
+ subject { path.mkdir }
68
+
69
+ describe 'directory does not exist' do
70
+ before { subject }
71
+ it { File.directory?(File.join(local_repo_path, 'directory', 'file')) }
72
+ end
73
+
74
+ describe 'directory does exist' do
75
+ before do
76
+ mkdir('directory/file')
77
+ subject
78
+ end
79
+ it { File.directory?(File.join(local_repo_path, 'directory', 'file')) }
80
+ end
81
+
82
+ describe 'already exists as a file' do
83
+ before { write('directory/file', 'foobar') }
84
+ it { assert_raises(Errno::EEXIST) { subject } }
85
+ end
86
+ end
87
+
88
+ describe '#remove' do
89
+ subject { path.remove }
90
+
91
+ describe 'missing' do
92
+ it { subject.must_be_nil }
93
+ end
94
+
95
+ describe 'directory' do
96
+ before { mkdir('directory/file') }
97
+ it { subject.must_be_nil }
98
+ end
99
+
100
+ describe 'file' do
101
+ before do
102
+ write('directory/file', 'foobar')
103
+ subject
104
+ end
105
+ it { local_file_exist?.must_equal false }
106
+ end
107
+ end
108
+
109
+ describe '#text?' do
110
+ subject { path.text? }
111
+
112
+ describe 'missing' do
113
+ it { subject.must_equal false }
114
+ end
115
+
116
+ describe 'directory' do
117
+ before { mkdir('directory/file') }
118
+ it { subject.must_equal false }
119
+ end
120
+
121
+ describe 'not a text file' do
122
+ let(:relative_path) { 'directory/file.png' }
123
+ it { subject.must_equal false }
124
+ end
125
+
126
+ describe 'empty file' do
127
+ before { write('directory/file', '') }
128
+ it { subject.must_equal true }
129
+ end
130
+
131
+ describe 'text file' do
132
+ before { write('directory/file', 'foobar') }
133
+ it { subject.must_equal true }
134
+ end
135
+ end
136
+
137
+ describe '#meta' do
138
+ subject { path.meta }
139
+ before do
140
+ repository.stubs(:last_commit_for).with(relative_path).returns(commit)
141
+ end
142
+
143
+ describe 'when missing' do
144
+ let(:commit) { nil }
145
+ it { assert_raises(RuntimeError) { subject } }
146
+ end
147
+
148
+ describe 'on a 'do
149
+ let(:commit) { stub(author: { name: :name, time: :time }) }
150
+ before do
151
+ write('directory0/file0', '')
152
+ write('directory/file1', 'foo')
153
+ write('directory/file2', 'bar')
154
+ end
155
+
156
+ describe 'file size 0' do
157
+ let(:relative_path) { 'directory0/file0' }
158
+ it { subject[:author].must_equal :name }
159
+ it { subject[:size].must_equal(-1) }
160
+ it { subject[:modified].must_equal :time }
161
+ end
162
+
163
+ describe 'file non-zero size' do
164
+ let(:relative_path) { 'directory/file1' }
165
+ it { subject[:author].must_equal :name }
166
+ it { subject[:size].must_equal(3) }
167
+ it { subject[:modified].must_equal :time }
168
+ end
169
+
170
+ describe 'directory size 0' do
171
+ let(:relative_path) { 'directory0' }
172
+ it { subject[:author].must_equal :name }
173
+ it { subject[:size].must_equal(-1) }
174
+ it { subject[:modified].must_equal :time }
175
+ end
176
+
177
+ describe 'directory non-zero size' do
178
+ let(:relative_path) { 'directory' }
179
+ it { subject[:author].must_equal :name }
180
+ it { subject[:size].must_equal(6) }
181
+ it { subject[:modified].must_equal :time }
182
+ end
183
+ end
184
+ end
185
+
186
+ describe '#exist?' do
187
+ subject { path.exist? }
188
+
189
+ describe 'missing' do
190
+ it { subject.must_equal false }
191
+ end
192
+
193
+ describe 'directory' do
194
+ before { mkdir('directory/file') }
195
+ it { subject.must_equal true }
196
+ end
197
+
198
+ describe 'file' do
199
+ before { write('directory/file', 'foobar') }
200
+ it { subject.must_equal true }
201
+ end
202
+ end
203
+
204
+ describe '#directory?' do
205
+ subject { path.directory? }
206
+
207
+ describe 'missing' do
208
+ it { subject.must_equal false }
209
+ end
210
+
211
+ describe 'directory' do
212
+ before { mkdir('directory/file') }
213
+ it { subject.must_equal true }
214
+ end
215
+
216
+ describe 'file' do
217
+ before { write('directory/file', 'foobar') }
218
+ it { subject.must_equal false }
219
+ end
220
+ end
221
+
222
+ describe '#absolute_path' do
223
+ subject { path.absolute_path(ref) }
224
+
225
+ describe 'no revision' do
226
+ let(:ref) { nil }
227
+ it { subject.must_equal absolute_local_path }
228
+ end
229
+
230
+ describe 'with revision' do
231
+ let(:ref) { :ref }
232
+ before { repository.stubs(:blob_at).with(relative_path, :ref).returns(blob) }
233
+
234
+ describe 'no blob' do
235
+ let(:blob) { nil }
236
+ it { File.read(subject).must_equal "\n" }
237
+ end
238
+
239
+ describe 'has blob' do
240
+ let(:blob) { stub(text: 'beef') }
241
+ it { File.read(subject).must_equal "beef\n" }
242
+ end
243
+ end
244
+ end
245
+
246
+ describe '#readme_path' do
247
+ subject { path.readme_path }
248
+
249
+ describe 'no directory' do
250
+ it { subject.must_be_nil }
251
+ end
252
+
253
+ describe 'no README' do
254
+ before { mkdir('directory/file') }
255
+ it { subject.must_be_nil }
256
+ end
257
+
258
+ describe 'with README.md' do
259
+ before { write('directory/file/README.md', 'foobar') }
260
+ it { subject.must_equal absolute_local_path('README.md') }
261
+ end
262
+ end
263
+
264
+ describe '#file_listing' do
265
+ subject { path.file_listing }
266
+
267
+ describe 'missing' do
268
+ it { subject.must_be_nil }
269
+ end
270
+
271
+ describe 'file' do
272
+ before { write('directory/file', 'foobar') }
273
+ it { subject.must_be_nil }
274
+ end
275
+
276
+ describe 'directory' do
277
+ before do
278
+ write('directory/file/.hidden', 'beef')
279
+ mkdir('directory/file/dir1')
280
+ write('directory/file/file1', 'foo')
281
+ write('directory/file/file2', 'bar')
282
+
283
+ # Paths which should not be included
284
+ write('directory/file/.git', 'test')
285
+ write('directory/file/.gitignore', 'test')
286
+ write('directory/file/.gitmessage~', 'test')
287
+ end
288
+
289
+ it { subject.size.must_equal 4 }
290
+ it { subject.map(&:name).must_equal %w(dir1 file1 file2 .hidden) }
291
+ it { subject.map(&:is_directory).must_equal [true, false, false, false] }
292
+ end
293
+ end
294
+
295
+ describe '#content' do
296
+ subject { path.content }
297
+
298
+ describe 'missing' do
299
+ it { subject.must_be_nil }
300
+ end
301
+
302
+ describe 'directory' do
303
+ before { mkdir('directory/file') }
304
+ it { subject.must_be_nil }
305
+ end
306
+
307
+ describe 'file' do
308
+ before { write('directory/file', 'foobar') }
309
+ it { subject.must_equal 'foobar' }
310
+ end
311
+ end
312
+
313
+ describe '#revisions' do
314
+ subject { path.revisions }
315
+
316
+ before do
317
+ repository.stubs(:commits_for).returns([
318
+ stub(oid: '1234567890', message: "short1\nlong", author: { name: :name1, time: :time1 }),
319
+ stub(oid: '0987654321', message: "short2\nlong", author: { name: :name2, time: :time2 })
320
+ ])
321
+ end
322
+ it { subject.size.must_equal(2) }
323
+ it { subject[0].must_equal(commit: '1234567', subject: 'short1', author: :name1, date: :time1) }
324
+ it { subject[1].must_equal(commit: '0987654', subject: 'short2', author: :name2, date: :time2) }
325
+ end
326
+
327
+ describe '#revert' do
328
+ subject { path.revert('ref') }
329
+ before { repository.expects(:blob_at).with(relative_path, 'ref').returns(blob) }
330
+
331
+ describe 'blob missing' do
332
+ let(:blob) { nil }
333
+ it { subject.must_equal(nil) }
334
+ end
335
+
336
+ describe 'blob present' do
337
+ let(:blob) { stub(text: 'deadbeef') }
338
+ before { path.expects(:write).with('deadbeef', "Reverting '#{relative_path}' to ref") }
339
+ it { subject }
340
+ end
341
+ end
342
+
343
+ #############################################################################
344
+
345
+ private
346
+
347
+ def write(filename, content)
348
+ mkdir(File.dirname(filename))
349
+ File.write(File.join(local_repo_path, filename), content)
350
+ end
351
+
352
+ def mkdir(*path)
353
+ FileUtils.mkdir_p(File.join(local_repo_path, *path))
354
+ end
355
+
356
+ def local_file_exist?
357
+ File.exist?(File.join(local_repo_path, relative_path))
358
+ end
359
+
360
+ def local_file_content
361
+ return nil unless local_file_exist?
362
+ File.read(File.join(local_repo_path, relative_path))
363
+ end
364
+
365
+ def absolute_local_path(*path_elements)
366
+ File.join(File.absolute_path(local_repo_path), relative_path, *path_elements)
367
+ end
368
+ end
@@ -48,11 +48,13 @@ describe Gitdocs::Repository do
48
48
  end
49
49
 
50
50
  describe 'with a share that is a repository' do
51
- let(:path_or_share) { stub(
52
- path: local_repo_path,
53
- remote_name: 'remote',
54
- branch_name: 'branch'
55
- ) }
51
+ let(:path_or_share) do
52
+ stub(
53
+ path: local_repo_path,
54
+ remote_name: 'remote',
55
+ branch_name: 'branch'
56
+ )
57
+ end
56
58
  it { subject.must_be_kind_of Gitdocs::Repository }
57
59
  it { subject.valid?.must_equal true }
58
60
  it { subject.invalid_reason.must_be_nil }
@@ -80,59 +82,6 @@ describe Gitdocs::Repository do
80
82
  end
81
83
  end
82
84
 
83
- describe '.search' do
84
- subject { Gitdocs::Repository.search('term', repositories) }
85
-
86
- let(:repositories) { Array.new(4, stub(root: 'root')) }
87
- before do
88
- repositories[0].expects(:search).with('term').returns(:result1)
89
- repositories[1].expects(:search).with('term').returns(:result2)
90
- repositories[2].expects(:search).with('term').returns(:result3)
91
- repositories[3].expects(:search).with('term').returns([])
92
- end
93
-
94
- it do
95
- subject.must_equal({
96
- Gitdocs::Repository::RepoDescriptor.new('root', 1) => :result3,
97
- Gitdocs::Repository::RepoDescriptor.new('root', 2) => :result2,
98
- Gitdocs::Repository::RepoDescriptor.new('root', 3) => :result1
99
- })
100
- end
101
- end
102
-
103
- describe '#search' do
104
- subject { repository.search(term) }
105
-
106
- describe 'empty term' do
107
- let(:term) { '' }
108
- it { subject.must_equal [] }
109
- end
110
-
111
- describe 'nothing found' do
112
- let(:term) { 'foo' }
113
- before do
114
- write_and_commit('file1', 'bar', 'commit', author1)
115
- write_and_commit('file2', 'beef', 'commit', author1)
116
- end
117
- it { subject.must_equal [] }
118
- end
119
-
120
- describe 'term found' do
121
- let(:term) { 'foo' }
122
- before do
123
- write_and_commit('file1', 'foo', 'commit', author1)
124
- write_and_commit('file2', 'beef', 'commit', author1)
125
- write_and_commit('file3', 'foobar', 'commit', author1)
126
- end
127
- it do
128
- subject.must_equal([
129
- Gitdocs::Repository::SearchResult.new('file1', 'foo'),
130
- Gitdocs::Repository::SearchResult.new('file3', 'foobar')
131
- ])
132
- end
133
- end
134
- end
135
-
136
85
  describe '#root' do
137
86
  subject { repository.root }
138
87
 
@@ -185,158 +134,394 @@ describe Gitdocs::Repository do
185
134
  end
186
135
  end
187
136
 
188
- describe '#pull' do
189
- subject { repository.pull }
137
+ describe '#dirty?' do
138
+ subject { repository.dirty? }
190
139
 
191
- let(:path_or_share) { stub(
192
- path: local_repo_path,
193
- remote_name: 'origin',
194
- branch_name: 'master'
195
- ) }
140
+ let(:path_or_share) do
141
+ stub(
142
+ path: local_repo_path,
143
+ remote_name: 'origin',
144
+ branch_name: 'master'
145
+ )
146
+ end
196
147
 
197
148
  describe 'when invalid' do
198
149
  let(:path_or_share) { 'tmp/unit/missing' }
199
- it { subject.must_be_nil }
150
+ it { subject.must_equal false }
200
151
  end
201
152
 
202
- describe 'when there is no remote' do
203
- it { subject.must_equal :no_remote }
153
+ describe 'when no existing commits' do
154
+ describe 'and no new files' do
155
+ it { subject.must_equal false }
156
+ end
157
+
158
+ describe 'and new files' do
159
+ before { write('file1', 'foobar') }
160
+ it { subject.must_equal true }
161
+ end
162
+
163
+ describe 'and new empty directory' do
164
+ before { mkdir('directory') }
165
+ it { subject.must_equal true }
166
+ end
204
167
  end
205
168
 
206
- describe 'with a valid remote with no initial commits' do
207
- before { create_local_repo_with_remote }
169
+ describe 'when commits exist' do
170
+ before { write_and_commit('file1', 'foobar', 'initial commit', author1) }
208
171
 
209
- describe 'when there is an error fetching' do
210
- before do
211
- Grit::Repo.any_instance.stubs(:remote_fetch)
212
- .raises(Grit::Git::CommandFailed.new('', 1, 'fetch error output'))
213
- end
214
- it { subject.must_equal 'fetch error output' }
172
+ describe 'and no changes' do
173
+ it { subject.must_equal false }
215
174
  end
216
175
 
217
- describe 'when there are no local commits' do
218
- it { subject.must_equal :ok }
176
+ describe 'add empty directory' do
177
+ before { mkdir('directory') }
178
+ it { subject.must_equal false }
219
179
  end
220
180
 
221
- describe 'when there is a local commit' do
222
- before { write_and_commit('file1', 'beef', 'conflict commit', author1) }
223
- it { subject.must_equal :ok }
224
- it { subject ; commit_count(local_repo).must_equal 1 }
181
+ describe 'add file' do
182
+ before { write('file2', 'foobar') }
183
+ it { subject.must_equal true }
225
184
  end
226
185
 
227
- describe 'then new remote commits are fetched' do
228
- before do
229
- bare_commit(
230
- remote_repo,
231
- 'file2', 'deadbeef',
232
- 'second commit',
233
- 'author@example.com', 'A U Thor'
234
- )
235
- end
186
+ describe 'modify existing file' do
187
+ before { write('file1', 'deadbeef') }
188
+ it { subject.must_equal true }
189
+ end
236
190
 
237
- describe 'when there is an error merging' do
238
- before do
239
- Grit::Git.any_instance.stubs(:merge)
240
- .raises(Grit::Git::CommandFailed.new('', 1, 'merge error output'))
241
- end
242
- it { subject.must_equal 'merge error output' }
243
- end
191
+ describe 'delete file' do
192
+ before { rm_rf('file1') }
193
+ it { subject.must_equal true }
194
+ end
195
+ end
196
+ end
244
197
 
245
- describe ' and merged' do
246
- it { subject.must_equal :ok }
247
- it { subject ; local_file_exist?('file2').must_equal true }
248
- it { subject ; commit_count(local_repo).must_equal 1 }
249
- end
198
+ describe '#need_sync' do
199
+ subject { repository.need_sync? }
200
+
201
+ let(:path_or_share) do
202
+ stub(
203
+ path: local_repo_path,
204
+ remote_name: 'origin',
205
+ branch_name: 'master'
206
+ )
207
+ end
208
+
209
+ describe 'when invalid' do
210
+ let(:path_or_share) { 'tmp/unit/missing' }
211
+ it { subject.must_equal false }
212
+ end
213
+
214
+ describe 'when no remotes' do
215
+ it { subject.must_equal false }
216
+ end
217
+
218
+ describe 'when no remote commits' do
219
+ before { create_local_repo_with_remote }
220
+
221
+ describe 'no local commits' do
222
+ it { subject.must_equal false }
223
+ end
224
+
225
+ describe 'local commits' do
226
+ before { write_and_commit('file1', 'beef', 'conflict commit', author1) }
227
+ it { subject.must_equal true }
250
228
  end
251
229
  end
252
230
 
253
- describe 'with a valid remote with commits' do
231
+ describe 'when remote commits' do
254
232
  before { create_local_repo_with_remote_with_commit }
255
233
 
256
- describe 'when there is nothing to pull' do
257
- it { subject.must_equal :ok }
234
+ describe 'no local commits' do
235
+ it { subject.must_equal false }
236
+ end
237
+
238
+ describe 'new local commit' do
239
+ before { write_and_commit('file2', 'beef', 'conflict commit', author1) }
240
+ it { subject.must_equal true }
258
241
  end
259
242
 
260
- describe 'when there is a conflict' do
243
+ describe 'new remote commit' do
261
244
  before do
262
245
  bare_commit(
263
246
  remote_repo,
264
- 'file1', 'dead',
247
+ 'file3', 'dead',
265
248
  'second commit',
266
249
  'author@example.com', 'A U Thor'
267
250
  )
268
- write_and_commit('file1', 'beef', 'conflict commit', author1)
251
+ repository.fetch
269
252
  end
270
253
 
271
- it { subject.must_equal ['file1'] }
272
- it { subject ; commit_count(local_repo).must_equal 2 }
273
- it { subject ; local_file_count.must_equal 3 }
274
- it { subject ; local_file_content('file1 (f6ea049 original)').must_equal 'foobar' }
275
- it { subject ; local_file_content('file1 (18ed963)').must_equal 'beef' }
276
- it { subject ; local_file_content('file1 (7bfce5c)').must_equal 'dead' }
254
+ it { subject.must_equal true }
277
255
  end
278
256
 
279
- describe 'when there is a conflict, with additional files' do
257
+ describe 'new local and remote commit' do
280
258
  before do
281
259
  bare_commit(
282
260
  remote_repo,
283
- 'file1', 'dead',
284
- 'second commit',
285
- 'author@example.com', 'A U Thor'
286
- )
287
- bare_commit(
288
- remote_repo,
289
- 'file2', 'foo',
261
+ 'file3', 'dead',
290
262
  'second commit',
291
263
  'author@example.com', 'A U Thor'
292
264
  )
293
- write_and_commit('file1', 'beef', 'conflict commit', author1)
265
+ repository.fetch
266
+ write_and_commit('file4', 'beef', 'conflict commit', author1)
294
267
  end
295
268
 
296
- it { subject.must_equal ['file1'] }
297
- it { subject ; commit_count(local_repo).must_equal 2 }
298
- it { subject ; local_file_count.must_equal 3 }
299
- it { subject ; local_file_content('file1 (f6ea049 original)').must_equal 'foobar' }
300
- it { subject ; local_file_content('file1 (18ed963)').must_equal 'beef' }
301
- it { subject ; local_file_content('file2').must_equal 'foo' }
269
+ it { subject.must_equal true }
302
270
  end
271
+ end
272
+ end
303
273
 
304
- describe 'when there is a non-conflicted local commit' do
305
- before { write_and_commit('file1', 'beef', 'conflict commit', author1) }
306
- it { subject.must_equal :ok }
307
- it { subject ; commit_count(local_repo).must_equal 2 }
274
+ describe '#grep' do
275
+ subject { repository.grep('foo') { |file, context| @grep_result << "#{file} #{context}" } }
276
+
277
+ before { @grep_result = [] }
278
+
279
+ describe 'timeout' do
280
+ before do
281
+ Grit::Repo.any_instance.stubs(:remote_fetch)
282
+ .raises(Grit::Git::GitTimeout.new)
283
+ end
284
+ it { subject ; @grep_result.must_equal([]) }
285
+ it { subject.must_equal '' }
286
+ end
287
+
288
+ describe 'command failure' do
289
+ before do
290
+ Grit::Repo.any_instance.stubs(:remote_fetch)
291
+ .raises(Grit::Git::CommandFailed.new('', 1, 'grep error output'))
292
+ end
293
+ it { subject ; @grep_result.must_equal([]) }
294
+ it { subject.must_equal '' }
295
+ end
296
+
297
+ describe 'success' do
298
+ before do
299
+ write_and_commit('file1', 'foo', 'commit', author1)
300
+ write_and_commit('file2', 'beef', 'commit', author1)
301
+ write_and_commit('file3', 'foobar', 'commit', author1)
302
+ write_and_commit('file4', "foo\ndead\nbeef\nfoobar", 'commit', author1)
303
+ end
304
+ it { subject ; @grep_result.must_equal(['file1 foo', 'file3 foobar', 'file4 foo', 'file4 foobar']) }
305
+ it { subject.must_equal("file1:foo\nfile3:foobar\nfile4:foo\nfile4:foobar\n") }
306
+ end
307
+ end
308
+
309
+ describe '#fetch' do
310
+ subject { repository.fetch }
311
+
312
+ describe 'when invalid' do
313
+ let(:path_or_share) { 'tmp/unit/missing' }
314
+ it { subject.must_be_nil }
315
+ end
316
+
317
+ describe 'when no remote' do
318
+ it { subject.must_equal :no_remote }
319
+ end
320
+
321
+ describe 'with remote' do
322
+ before { create_local_repo_with_remote }
323
+
324
+ describe 'and times out' do
325
+ before do
326
+ Grit::Repo.any_instance.stubs(:remote_fetch)
327
+ .raises(Grit::Git::GitTimeout.new)
328
+ end
329
+ it { subject.must_equal "Fetch timed out for #{File.absolute_path(local_repo_path)}" }
330
+ end
331
+
332
+ describe 'and command fails' do
333
+ before do
334
+ Grit::Repo.any_instance.stubs(:remote_fetch)
335
+ .raises(Grit::Git::CommandFailed.new('', 1, 'fetch error output'))
336
+ end
337
+ it { subject.must_equal 'fetch error output' }
308
338
  end
309
339
 
310
- describe 'when new remote commits are pulled and merged' do
340
+ describe 'and success' do
311
341
  before do
312
342
  bare_commit(
313
343
  remote_repo,
314
- 'file2', 'deadbeef',
315
- 'second commit',
316
- 'author@example.com', 'A U Thor'
344
+ 'file1', 'deadbeef',
345
+ 'commit', 'author@example.com', 'A U Thor'
317
346
  )
318
347
  end
319
348
  it { subject.must_equal :ok }
320
- it { subject ; local_file_exist?('file2').must_equal true }
321
- it { subject ; commit_count(local_repo).must_equal 2 }
349
+
350
+ describe 'side effects' do
351
+ before { subject }
352
+ it { local_repo_remote_branch.tip.oid.wont_be_nil }
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ describe '#merge' do
359
+ subject { repository.merge }
360
+
361
+ let(:path_or_share) do
362
+ stub(
363
+ path: local_repo_path,
364
+ remote_name: 'origin',
365
+ branch_name: 'master'
366
+ )
367
+ end
368
+
369
+ describe 'when invalid' do
370
+ let(:path_or_share) { 'tmp/unit/missing' }
371
+ it { subject.must_be_nil }
372
+ end
373
+
374
+ describe 'when no remote' do
375
+ it { subject.must_equal :no_remote }
376
+ end
377
+
378
+ describe 'has remote but nothing to merge' do
379
+ before { create_local_repo_with_remote }
380
+ it { subject.must_equal :ok }
381
+ end
382
+
383
+ describe 'has remote and times out' do
384
+ before do
385
+ create_local_repo_with_remote
386
+ bare_commit(
387
+ remote_repo,
388
+ 'file1', 'deadbeef',
389
+ 'commit', 'author@example.com', 'A U Thor'
390
+ )
391
+ repository.fetch
392
+
393
+ Grit::Git.any_instance.stubs(:merge)
394
+ .raises(Grit::Git::GitTimeout.new)
395
+ end
396
+ it { subject.must_equal "Merge timed out for #{File.absolute_path(local_repo_path)}" }
397
+ end
398
+
399
+ describe 'and fails, but does not conflict' do
400
+ before do
401
+ create_local_repo_with_remote
402
+ bare_commit(
403
+ remote_repo,
404
+ 'file1', 'deadbeef',
405
+ 'commit', 'author@example.com', 'A U Thor'
406
+ )
407
+ repository.fetch
408
+
409
+ Grit::Git.any_instance.stubs(:merge)
410
+ .raises(Grit::Git::CommandFailed.new('', 1, 'merge error output'))
411
+ end
412
+ it { subject.must_equal 'merge error output' }
413
+ end
414
+
415
+ describe 'and there is a conflict' do
416
+ before do
417
+ create_local_repo_with_remote_with_commit
418
+ bare_commit(
419
+ remote_repo,
420
+ 'file1', 'dead',
421
+ 'second commit',
422
+ 'author@example.com', 'A U Thor'
423
+ )
424
+ write_and_commit('file1', 'beef', 'conflict commit', author1)
425
+ repository.fetch
426
+ end
427
+
428
+ it { subject.must_equal ['file1'] }
429
+
430
+ describe 'side effects' do
431
+ before { subject }
432
+ it { commit_count(local_repo).must_equal 2 }
433
+ it { local_file_count.must_equal 3 }
434
+ it { local_file_content('file1 (f6ea049 original)').must_equal 'foobar' }
435
+ it { local_file_content('file1 (18ed963)').must_equal 'beef' }
436
+ it { local_file_content('file1 (7bfce5c)').must_equal 'dead' }
437
+ end
438
+ end
439
+
440
+ describe 'and there is a conflict, with additional files' do
441
+ before do
442
+ create_local_repo_with_remote_with_commit
443
+ bare_commit(
444
+ remote_repo,
445
+ 'file1', 'dead',
446
+ 'second commit',
447
+ 'author@example.com', 'A U Thor'
448
+ )
449
+ bare_commit(
450
+ remote_repo,
451
+ 'file2', 'foo',
452
+ 'second commit',
453
+ 'author@example.com', 'A U Thor'
454
+ )
455
+ write_and_commit('file1', 'beef', 'conflict commit', author1)
456
+ repository.fetch
457
+ end
458
+
459
+ it { subject.must_equal ['file1'] }
460
+
461
+ describe 'side effects' do
462
+ before { subject }
463
+ it { commit_count(local_repo).must_equal 2 }
464
+ it { local_file_count.must_equal 3 }
465
+ it { local_file_content('file1 (f6ea049 original)').must_equal 'foobar' }
466
+ it { local_file_content('file1 (18ed963)').must_equal 'beef' }
467
+ it { local_file_content('file2').must_equal 'foo' }
468
+ end
469
+ end
470
+
471
+ describe 'and there are non-conflicted local commits' do
472
+ before do
473
+ create_local_repo_with_remote_with_commit
474
+ write_and_commit('file1', 'beef', 'conflict commit', author1)
475
+ repository.fetch
476
+ end
477
+ it { subject.must_equal :ok }
478
+
479
+ describe 'side effects' do
480
+ before { subject }
481
+ it { local_file_count.must_equal 1 }
482
+ it { commit_count(local_repo).must_equal 2 }
483
+ end
484
+ end
485
+
486
+ describe 'when new remote commits are merged' do
487
+ before do
488
+ create_local_repo_with_remote_with_commit
489
+ bare_commit(
490
+ remote_repo,
491
+ 'file2', 'deadbeef',
492
+ 'second commit',
493
+ 'author@example.com', 'A U Thor'
494
+ )
495
+ repository.fetch
496
+ end
497
+ it { subject.must_equal :ok }
498
+
499
+ describe 'side effects' do
500
+ before { subject }
501
+ it { local_file_exist?('file2').must_equal true }
502
+ it { commit_count(local_repo).must_equal 2 }
322
503
  end
323
504
  end
324
505
  end
325
506
 
326
507
  describe '#commit' do
327
- subject { repository.commit('message') }
508
+ subject { repository.commit }
328
509
 
329
- let(:path_or_share) { stub(
330
- path: local_repo_path,
331
- remote_name: 'origin',
332
- branch_name: 'master'
333
- ) }
510
+ let(:path_or_share) do
511
+ stub(
512
+ path: local_repo_path,
513
+ remote_name: 'origin',
514
+ branch_name: 'master'
515
+ )
516
+ end
334
517
 
335
518
  describe 'when invalid' do
336
519
  let(:path_or_share) { 'tmp/unit/missing' }
337
520
  it { subject.must_be_nil }
338
521
  end
339
522
 
523
+ # TODO: should test the paths which use the message file
524
+
340
525
  describe 'no previous commits' do
341
526
  describe 'nothing to commit' do
342
527
  it { subject.must_equal false }
@@ -348,10 +533,14 @@ describe Gitdocs::Repository do
348
533
  mkdir('directory')
349
534
  end
350
535
  it { subject.must_equal true }
351
- it { subject ; local_file_exist?('directory/.gitignore').must_equal true }
352
- it { subject ; commit_count(local_repo).must_equal 1 }
353
- it { subject ; head_commit(local_repo).message.must_equal "message\n" }
354
- it { subject ; local_repo_clean?.must_equal true }
536
+
537
+ describe 'side effects' do
538
+ before { subject }
539
+ it { local_file_exist?('directory/.gitignore').must_equal true }
540
+ it { commit_count(local_repo).must_equal 1 }
541
+ it { head_commit(local_repo).message.must_equal "Auto-commit from gitdocs\n" }
542
+ it { local_repo_clean?.must_equal true }
543
+ end
355
544
  end
356
545
  end
357
546
 
@@ -368,15 +557,19 @@ describe Gitdocs::Repository do
368
557
  describe 'changes to commit' do
369
558
  before do
370
559
  write('file1', 'foobar')
371
- FileUtils.rm_rf(File.join(local_repo_path, 'file2'))
560
+ rm_rf('file2')
372
561
  write('file3', 'foobar')
373
562
  mkdir('directory')
374
563
  end
375
564
  it { subject.must_equal true }
376
- it { subject ; local_file_exist?('directory/.gitignore').must_equal true }
377
- it { subject ; commit_count(local_repo).must_equal 3 }
378
- it { subject ; head_commit(local_repo).message.must_equal "message\n" }
379
- it { subject ; local_repo_clean?.must_equal true }
565
+
566
+ describe 'side effects' do
567
+ before { subject }
568
+ it { local_file_exist?('directory/.gitignore').must_equal true }
569
+ it { commit_count(local_repo).must_equal 3 }
570
+ it { head_commit(local_repo).message.must_equal "Auto-commit from gitdocs\n" }
571
+ it { local_repo_clean?.must_equal true }
572
+ end
380
573
  end
381
574
  end
382
575
  end
@@ -384,11 +577,13 @@ describe Gitdocs::Repository do
384
577
  describe '#push' do
385
578
  subject { repository.push }
386
579
 
387
- let(:path_or_share) { stub(
388
- path: local_repo_path,
389
- remote_name: 'origin',
390
- branch_name: 'master'
391
- ) }
580
+ let(:path_or_share) do
581
+ stub(
582
+ path: local_repo_path,
583
+ remote_name: 'origin',
584
+ branch_name: 'master'
585
+ )
586
+ end
392
587
 
393
588
  describe 'when invalid' do
394
589
  let(:path_or_share) { 'tmp/unit/missing' }
@@ -404,7 +599,11 @@ describe Gitdocs::Repository do
404
599
 
405
600
  describe 'and no local commits' do
406
601
  it { subject.must_equal :nothing }
407
- it { subject ; commit_count(remote_repo).must_equal 0 }
602
+
603
+ describe 'side effects' do
604
+ before { subject }
605
+ it { commit_count(remote_repo).must_equal 0 }
606
+ end
408
607
  end
409
608
 
410
609
  describe 'and a local commit' do
@@ -421,7 +620,11 @@ describe Gitdocs::Repository do
421
620
 
422
621
  describe 'and the push succeeds' do
423
622
  it { subject.must_equal :ok }
424
- it { subject ; commit_count(remote_repo).must_equal 1 }
623
+
624
+ describe 'side effects' do
625
+ before { subject }
626
+ it { commit_count(remote_repo).must_equal 1 }
627
+ end
425
628
  end
426
629
  end
427
630
  end
@@ -431,7 +634,11 @@ describe Gitdocs::Repository do
431
634
 
432
635
  describe 'and no local commits' do
433
636
  it { subject.must_equal :nothing }
434
- it { subject ; commit_count(remote_repo).must_equal 1 }
637
+
638
+ describe 'side effects' do
639
+ before { subject }
640
+ it { commit_count(remote_repo).must_equal 1 }
641
+ end
435
642
  end
436
643
 
437
644
  describe 'and a local commit' do
@@ -449,13 +656,21 @@ describe Gitdocs::Repository do
449
656
  describe 'and the push conflicts' do
450
657
  before { bare_commit(remote_repo, 'file2', 'dead', 'commit', 'A U Thor', 'author@example.com') }
451
658
 
452
- it { subject ; commit_count(remote_repo).must_equal 2 }
453
659
  it { subject.must_equal :conflict }
660
+
661
+ describe 'side effects' do
662
+ before { subject }
663
+ it { commit_count(remote_repo).must_equal 2 }
664
+ end
454
665
  end
455
666
 
456
667
  describe 'and the push succeeds' do
457
668
  it { subject.must_equal :ok }
458
- it { subject ; commit_count(remote_repo).must_equal 2 }
669
+
670
+ describe 'side effects' do
671
+ before { subject }
672
+ it { commit_count(remote_repo).must_equal 2 }
673
+ end
459
674
  end
460
675
  end
461
676
  end
@@ -479,12 +694,12 @@ describe Gitdocs::Repository do
479
694
 
480
695
  describe 'all' do
481
696
  let(:last_oid) { nil }
482
- it { subject.must_equal({ author1 => 3, author2 => 1 }) }
697
+ it { subject.must_equal(author1 => 3, author2 => 1) }
483
698
  end
484
699
 
485
700
  describe 'some' do
486
701
  let(:last_oid) { @intermediate_oid }
487
- it { subject.must_equal({ author1 => 2, author2 => 1 }) }
702
+ it { subject.must_equal(author1 => 2, author2 => 1) }
488
703
  end
489
704
 
490
705
  describe 'missing oid' do
@@ -494,110 +709,67 @@ describe Gitdocs::Repository do
494
709
  end
495
710
  end
496
711
 
497
- describe '#file_meta' do
498
- subject { repository.file_meta(file_name) }
712
+ describe '#write_commit_message' do
713
+ subject { repository.write_commit_message(commit_message) }
714
+ before { subject }
499
715
 
500
- before do
501
- write_and_commit('directory0/file0', '', 'initial commit', author1)
502
- write_and_commit('directory/file1', 'foo', 'commit1', author1)
503
- write_and_commit('directory/file2', 'bar', 'commit2', author2)
504
- write_and_commit('directory/file2', 'beef', 'commit3', author2)
505
- end
506
-
507
- describe 'on a missing file' do
508
- let(:file_name) { 'missing_file' }
509
- it { assert_raises(RuntimeError) { subject } }
716
+ describe 'with missing message' do
717
+ let(:commit_message) { nil }
718
+ it { local_file_exist?('.gitmessage~').must_equal(false) }
510
719
  end
511
720
 
512
- describe 'on a file' do
513
- describe 'of size zero' do
514
- let(:file_name) { 'directory0/file0' }
515
- it { subject[:author].must_equal 'Art T. Fish' }
516
- it { subject[:size].must_equal -1 }
517
- it { subject[:modified].wont_be_nil }
518
- end
519
-
520
- describe 'of non-zero size' do
521
- let(:file_name) { 'directory/file1' }
522
- it { subject[:author].must_equal 'Art T. Fish' }
523
- it { subject[:size].must_equal 3 }
524
- it { subject[:modified].wont_be_nil }
525
- end
721
+ describe 'with empty message' do
722
+ let(:commit_message) { '' }
723
+ it { local_file_exist?('.gitmessage~').must_equal(false) }
526
724
  end
527
725
 
528
- describe 'on a directory' do
529
- describe 'of size zero' do
530
- let(:file_name) { 'directory0' }
531
- it { subject[:author].must_equal 'Art T. Fish' }
532
- it { subject[:size].must_equal -1 }
533
- it { subject[:modified].wont_be_nil }
534
- end
535
-
536
- describe 'of non-zero size' do
537
- let(:file_name) { 'directory' }
538
- it { subject[:author].must_equal 'A U Thor' }
539
- it { subject[:size].must_equal 7 }
540
- it { subject[:modified].wont_be_nil }
541
- end
726
+ describe 'with valid message' do
727
+ let(:commit_message) { 'foobar' }
728
+ it { local_file_content('.gitmessage~').must_equal('foobar') }
542
729
  end
543
730
  end
544
731
 
545
- describe '#file_revisions' do
546
- subject { repository.file_revisions('directory') }
732
+ describe '#commits_for' do
733
+ subject { repository.commits_for('directory/file', 2) }
547
734
 
548
735
  before do
549
736
  write_and_commit('directory0/file0', '', 'initial commit', author1)
550
- @commit1 = write_and_commit('directory/file1', 'foo', 'commit1', author1)
551
- @commit2 = write_and_commit('directory/file2', 'bar', 'commit2', author2)
552
- @commit3 = write_and_commit('directory/file2', 'beef', 'commit3', author2)
737
+ write_and_commit('directory/file', 'foo', 'commit1', author1)
738
+ @commit2 = write_and_commit('directory/file', 'bar', 'commit2', author2)
739
+ @commit3 = write_and_commit('directory/file', 'beef', 'commit3', author2)
553
740
  end
554
741
 
555
- it { subject.length.must_equal 3 }
556
- it { subject.map { |x| x[:author] }.must_equal ['A U Thor', 'A U Thor', 'Art T. Fish'] }
557
- it { subject.map { |x| x[:commit] }.must_equal [@commit3[0, 7], @commit2[0, 7], @commit1[0, 7]] }
558
- it { subject.map { |x| x[:subject] }.must_equal ['commit3', 'commit2', 'commit1'] }
742
+ it { subject.map(&:oid).must_equal([@commit3, @commit2]) }
559
743
  end
560
744
 
561
- describe '#file_revision_at' do
562
- subject { repository.file_revision_at('directory/file2', @commit) }
745
+ describe '#last_commit_for' do
746
+ subject { repository.last_commit_for('directory/file') }
563
747
 
564
748
  before do
565
- write_and_commit('directory0/file0', '', 'initial commit', author1)
566
- write_and_commit('directory/file1', 'foo', 'commit1', author1)
567
- write_and_commit('directory/file2', 'bar', 'commit2', author2)
568
- @commit = write_and_commit('directory/file2', 'beef', 'commit3', author2)
749
+ write_and_commit('directory/file', 'foo', 'commit1', author1)
750
+ write_and_commit('directory/file', 'bar', 'commit2', author2)
751
+ @commit3 = write_and_commit('directory/file', 'beef', 'commit3', author2)
569
752
  end
570
753
 
571
- it { subject.must_equal '/tmp/file2' }
572
- it { File.read(subject).must_equal "beef\n" }
754
+ it { subject.oid.must_equal(@commit3) }
573
755
  end
574
756
 
575
- describe '#file_revert' do
576
- subject { repository.file_revert('directory/file2', ref) }
577
-
578
- let(:file_name) { File.join(local_repo_path, 'directory', 'file2') }
757
+ describe '#blob_at' do
758
+ subject { repository.blob_at('directory/file', @commit) }
579
759
 
580
760
  before do
581
- @commit0 = write_and_commit('directory0/file0', '', 'initial commit', author1)
582
- write_and_commit('directory/file1', 'foo', 'commit1', author1)
583
- @commit2 = write_and_commit('directory/file2', 'bar', 'commit2', author2)
584
- write_and_commit('directory/file2', 'beef', 'commit3', author2)
585
- end
586
-
587
- describe 'file does not include the revision' do
588
- let(:ref) { @commit0 }
589
- it { subject ; local_file_content('directory', 'file2').must_equal 'beef' }
761
+ write_and_commit('directory/file', 'foo', 'commit1', author1)
762
+ @commit = write_and_commit('directory/file', 'bar', 'commit2', author2)
763
+ write_and_commit('directory/file', 'beef', 'commit3', author2)
590
764
  end
591
765
 
592
- describe 'file does include the revision' do
593
- let(:ref) { @commit2 }
594
- it { subject ; local_file_content('directory', 'file2').must_equal "bar\n" }
595
- end
766
+ it { subject.text.must_equal('bar') }
596
767
  end
597
768
 
598
769
  ##############################################################################
599
770
 
600
771
  private
772
+
601
773
  def create_local_repo_with_remote
602
774
  FileUtils.rm_rf(local_repo_path)
603
775
  repo = Rugged::Repository.clone_at(remote_repo.path, local_repo_path)
@@ -627,6 +799,10 @@ describe Gitdocs::Repository do
627
799
  File.write(File.join(local_repo_path, filename), content)
628
800
  end
629
801
 
802
+ def rm_rf(filename)
803
+ FileUtils.rm_rf(File.join(local_repo_path, filename))
804
+ end
805
+
630
806
  def write_and_commit(filename, content, commit_msg, author)
631
807
  mkdir(File.dirname(filename))
632
808
  File.write(File.join(local_repo_path, filename), content)
@@ -634,7 +810,7 @@ describe Gitdocs::Repository do
634
810
  `cd #{local_repo_path} ; git rev-parse HEAD`.strip
635
811
  end
636
812
 
637
- def bare_commit(repo, filename, content, message, email, name)
813
+ def bare_commit(repo, filename, content, message, email, name) # rubocop:disable ParameterLists
638
814
  index = Rugged::Index.new
639
815
  index.add(
640
816
  path: filename,
@@ -642,14 +818,15 @@ describe Gitdocs::Repository do
642
818
  mode: 0100644
643
819
  )
644
820
 
645
- Rugged::Commit.create(remote_repo, {
821
+ Rugged::Commit.create(
822
+ remote_repo,
646
823
  tree: index.write_tree(repo),
647
824
  author: { email: email, name: name, time: Time.now },
648
825
  committer: { email: email, name: name, time: Time.now },
649
826
  message: message,
650
- parents: repo.empty? ? [] : [ repo.head.target ].compact,
827
+ parents: repo.empty? ? [] : [repo.head.target].compact,
651
828
  update_ref: 'HEAD'
652
- })
829
+ )
653
830
  end
654
831
 
655
832
  def commit_count(repo)
@@ -680,6 +857,10 @@ describe Gitdocs::Repository do
680
857
  files.count
681
858
  end
682
859
 
860
+ def local_repo_remote_branch
861
+ Rugged::Branch.lookup(local_repo, 'origin/master', :remote)
862
+ end
863
+
683
864
  def local_repo_clean?
684
865
  local_repo.diff_workdir(local_repo.head.target, include_untracked: true).deltas.empty?
685
866
  end