gitdocs 0.5.0.pre6 → 0.5.0.pre7

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 (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