gitdocs 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,108 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- module Gitdocs
4
- class Runner
5
- def self.start_all(shares)
6
- runners = shares.map { |share| Runner.new(share) }
7
- runners.each(&:run)
8
- runners
9
- end
10
-
11
- def initialize(share)
12
- @share = share
13
- @polling_interval = share.polling_interval
14
- @notifier = Gitdocs::Notifier.new(@share.notification)
15
- @repository = Gitdocs::Repository.new(share)
16
- end
17
-
18
- def root
19
- @repository.root
20
- end
21
-
22
- def run
23
- return false unless @repository.valid?
24
-
25
- @last_synced_revision = @repository.current_oid
26
-
27
- mutex = Mutex.new
28
-
29
- @notifier.info('Running gitdocs!', "Running gitdocs in '#{root}'")
30
-
31
- # Pull changes from remote repository
32
- syncer = proc do
33
- EM.defer(proc do
34
- mutex.synchronize { sync_changes }
35
- end, proc do
36
- EM.add_timer(@polling_interval) do
37
- syncer.call
38
- end
39
- end)
40
- end
41
- syncer.call
42
- # Listen for changes in local repository
43
-
44
- EM.defer(proc do
45
- listener = Guard::Listener.select_and_init(
46
- root, watch_all_modifications: true
47
- )
48
- listener.on_change do |directories|
49
- directories.uniq!
50
- directories.delete_if { |d| d =~ /\/\.git/ }
51
- unless directories.empty?
52
- EM.next_tick do
53
- EM.defer(proc do
54
- mutex.synchronize { sync_changes }
55
- end, proc {})
56
- end
57
- end
58
- end
59
- listener.start
60
- end, proc { EM.stop_reactor })
61
- end
62
-
63
- def clear_state
64
- @state = nil
65
- end
66
-
67
- def sync_changes
68
- # Commit #################################################################
69
- @repository.commit if @share.sync_type == 'full'
70
-
71
- # Fetch ##################################################################
72
- fetch_result = @repository.fetch
73
- return unless fetch_result == :ok
74
- return if @share.sync_type == 'fetch'
75
-
76
- # Merge ##################################################################
77
- merge_result = @repository.merge
78
- merge_result = latest_author_count if merge_result == :ok
79
- @notifier.merge_notification(merge_result, root)
80
- return if merge_result.is_a?(String)
81
-
82
- # Push ###################################################################
83
- result = @repository.push
84
- result = latest_author_count if result == :ok
85
- @notifier.push_notification(result, root)
86
- rescue => e
87
- # Rescue any standard exceptions which come from the push related
88
- # commands. This will prevent problems on a single share from killing
89
- # the entire daemon.
90
- @notifier.error("Unexpected error syncing changes in #{root}", "#{e}")
91
- end
92
-
93
- ############################################################################
94
-
95
- private
96
-
97
- # Update the author count for the last synced changes, and then update the
98
- # last synced revision id.
99
- #
100
- # @return [Hash<String,Int>]
101
- def latest_author_count
102
- last_oid = @last_synced_revision
103
- @last_synced_revision = @repository.current_oid
104
-
105
- @repository.author_count(last_oid)
106
- end
107
- end
108
- end
@@ -1,62 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- require 'thin'
4
- require 'gitdocs/browser_app'
5
- require 'gitdocs/settings_app'
6
-
7
- module Gitdocs
8
- class Server
9
- def initialize(manager, port = 8888, repositories)
10
- @manager = manager
11
- @port = port.to_i
12
- @repositories = repositories
13
- @search = Gitdocs::Search.new(repositories)
14
- end
15
-
16
- def self.start_and_wait(manager, override_port, repositories)
17
- return false unless manager.start_web_frontend
18
-
19
- web_port = override_port || manager.web_frontend_port
20
- server = Server.new(manager, web_port, repositories)
21
- server.start
22
- server.wait_for_start
23
- true
24
- end
25
-
26
- def start
27
- Gitdocs::SettingsApp.set :manager, @manager
28
- Gitdocs::BrowserApp.set :repositories, @repositories
29
-
30
- Thin::Logging.debug = @manager.debug
31
- Thin::Server.start('127.0.0.1', @port) do
32
- use Rack::Static,
33
- urls: %w(/css /js /img /doc),
34
- root: File.expand_path('../public', __FILE__)
35
- use Rack::MethodOverride
36
-
37
- map('/settings') { run Gitdocs::SettingsApp }
38
- map('/') { run Gitdocs::BrowserApp }
39
- end
40
- end
41
-
42
- def wait_for_start
43
- wait_for_web_server = proc do
44
- i = 0
45
- begin
46
- TCPSocket.open('127.0.0.1', @port).close
47
- @manager.log('Web server running!')
48
- rescue Errno::ECONNREFUSED
49
- sleep 0.2
50
- i += 1
51
- if i <= 20
52
- @manager.log('Retrying web server loop...')
53
- retry
54
- else
55
- @manager.log('Web server failed to start')
56
- end
57
- end
58
- end
59
- EM.defer(wait_for_web_server)
60
- end
61
- end
62
- end
@@ -1,66 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- require File.expand_path('../test_helper', __FILE__)
4
-
5
- describe 'fully synchronizing repositories' do
6
- before do
7
- git_clone_and_gitdocs_add(git_init_remote, 'clone1', 'clone2', 'clone3')
8
- start_daemon
9
- end
10
-
11
- it 'should sync new files' do
12
- write_file('clone1/newfile', 'testing')
13
- wait_for_clean_workdir('clone1')
14
-
15
- wait_for_exact_file_content('clone1/newfile', 'testing')
16
- wait_for_exact_file_content('clone2/newfile', 'testing')
17
- wait_for_exact_file_content('clone3/newfile', 'testing')
18
- end
19
-
20
- it 'should sync changes to an existing file' do
21
- write_file('clone1/file', 'testing')
22
- wait_for_clean_workdir('clone1')
23
-
24
- wait_for_exact_file_content('clone3/file', 'testing')
25
- append_to_file('clone3/file', "\nfoobar")
26
- wait_for_clean_workdir('clone3')
27
-
28
- wait_for_exact_file_content('clone1/file', "testing\nfoobar")
29
- wait_for_exact_file_content('clone2/file', "testing\nfoobar")
30
- wait_for_exact_file_content('clone3/file', "testing\nfoobar")
31
- end
32
-
33
- it 'should sync empty directories' do
34
- in_current_dir { _mkdir('clone1/empty_dir') }
35
- wait_for_clean_workdir('clone1')
36
-
37
- wait_for_directory('clone1/empty_dir')
38
- wait_for_directory('clone2/empty_dir')
39
- wait_for_directory('clone3/empty_dir')
40
- end
41
-
42
- it 'should mark unresolvable conflicts' do
43
- write_file('clone1/file', 'testing')
44
- wait_for_clean_workdir('clone1')
45
-
46
- append_to_file('clone2/file', 'foobar')
47
- append_to_file('clone3/file', 'deadbeef')
48
- wait_for_clean_workdir('clone2')
49
- wait_for_clean_workdir('clone3')
50
-
51
- # HACK: Leaving in the sleep and standard checks.
52
- # Trying to wait for the conflicts to be resolved does not seem to
53
- # be working consistently when run on TravisCI. Hopefully this will.
54
- sleep(6)
55
- in_current_dir do
56
- # Remember expected file counts include '.', '..', and '.git'
57
- assert_includes(5..6, Dir.entries('clone1').count)
58
- assert_includes(5..6, Dir.entries('clone2').count)
59
- assert_includes(5..6, Dir.entries('clone3').count)
60
- end
61
- # TODO: Want to convert to these methods in the future
62
- # wait_for_conflict_markers('clone1/file')
63
- # wait_for_conflict_markers('clone2/file')
64
- # wait_for_conflict_markers('clone3/file')
65
- end
66
- end
@@ -1,95 +0,0 @@
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 add a local repository' do
7
- git_init_local
8
- gitdocs_add
9
- gitdocs_status
10
- assert_gitdocs_status_contains(abs_current_dir('local'))
11
- end
12
-
13
- it 'should add a remote repository' do
14
- git_init_remote
15
- abs_remote_path = abs_current_dir('remote')
16
- cmd = "gitdocs create local #{abs_remote_path} --pid=gitdocs.pid"
17
- run_simple(cmd, true, 15)
18
- assert_success(true)
19
- assert_partial_output('Added path local to doc list', output_from(cmd))
20
- end
21
-
22
- it 'should update a share through the UI' do
23
- git_init_local
24
- gitdocs_add
25
- start_daemon
26
- visit('http://localhost:7777/')
27
- click_link('Settings')
28
-
29
- within('#settings') do
30
- within('#share-0') do
31
- fill_in('share[0][polling_interval]', with: '0.2')
32
- select('Fetch only', from: 'share[0][sync_type]')
33
- end
34
- click_button('Save')
35
- end
36
-
37
- # Allow the asynchronous portion of the update finish before checking
38
- # the result.
39
- sleep(1)
40
-
41
- click_link('Settings')
42
- within('#settings') do
43
- within('#share-0') do
44
- page.must_have_field('share[0][polling_interval]', with: '0.2')
45
- page.must_have_field('share[0][sync_type]', with: 'fetch')
46
- end
47
- end
48
- end
49
-
50
- describe 'remove a share' do
51
- before do
52
- git_init_local
53
- gitdocs_add
54
- end
55
-
56
- it 'through CLI' do
57
- cmd = 'gitdocs rm local --pid=gitdocs.pid'
58
- run_simple(cmd, true, 15)
59
- assert_success(true)
60
- assert_partial_output('Removed path local from doc list', output_from(cmd))
61
-
62
- gitdocs_status
63
- assert_gitdocs_status_not_contain(abs_current_dir('local'))
64
- end
65
-
66
- it 'through UI' do
67
- start_daemon
68
- visit('http://localhost:7777/')
69
- click_link('Settings')
70
-
71
- within('#settings') do
72
- within('#share-0') { click_link('Delete') }
73
-
74
- page.must_have_css('.share', count: 0)
75
- end
76
- end
77
- end
78
-
79
- it 'should clear all existing shares' do
80
- %w(local1 local2 local3).each do |path|
81
- git_init_local(path)
82
- gitdocs_add(path)
83
- end
84
-
85
- cmd = 'gitdocs clear --pid=gitdocs.pid'
86
- run_simple(cmd, true, 15)
87
- assert_success(true)
88
- assert_partial_output('Cleared paths from gitdocs', output_from(cmd))
89
-
90
- gitdocs_status
91
- assert_gitdocs_status_not_contain(abs_current_dir('local1'))
92
- assert_gitdocs_status_not_contain(abs_current_dir('local2'))
93
- assert_gitdocs_status_not_contain(abs_current_dir('local3'))
94
- end
95
- end
@@ -1,21 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- require File.expand_path('../test_helper', __FILE__)
4
-
5
- describe 'CLI with display daemon and share status' do
6
- it 'should display information about the daemon' do
7
- gitdocs_status
8
- assert_gitdocs_status_contains(Gitdocs::VERSION)
9
- assert_gitdocs_status_contains('Running: false')
10
- end
11
-
12
- it 'should display information about the shares' do
13
- git_clone_and_gitdocs_add(git_init_remote, 'clone1', 'clone2', 'clone3')
14
-
15
- gitdocs_status
16
-
17
- assert_gitdocs_status_contains(abs_current_dir('clone1'))
18
- assert_gitdocs_status_contains(abs_current_dir('clone2'))
19
- assert_gitdocs_status_contains(abs_current_dir('clone3'))
20
- end
21
- end
@@ -1,122 +0,0 @@
1
- # -*- encoding : utf-8 -*-
2
-
3
- require File.expand_path('../test_helper', __FILE__)
4
-
5
- describe 'gitdocs runner' do
6
- let(:runner) { Gitdocs::Runner.new(share) }
7
-
8
- let(:share) { stub(polling_interval: 1, notification: true) }
9
- let(:notifier) { stub }
10
- let(:repository) { stub(root: 'root_path') }
11
- before do
12
- Gitdocs::Notifier.stubs(:new).with(true).returns(notifier)
13
- Gitdocs::Repository.stubs(:new).with(share).returns(repository)
14
- end
15
-
16
- describe '#root' do
17
- subject { runner.root }
18
- it { subject.must_equal 'root_path' }
19
- end
20
-
21
- describe '#sync_changes' do
22
- subject { runner.sync_changes }
23
-
24
- describe 'fetch sync' do
25
- before do
26
- share.stubs(:sync_type).returns('fetch')
27
- repository.expects(:fetch).returns(fetch_result)
28
- end
29
-
30
- describe('fetch failure') { let(:fetch_result) { :not_ok } ; it { subject } }
31
- describe('fetch success') { let(:fetch_result) { :ok } ; it { subject } }
32
- end
33
-
34
- describe 'full sync' do
35
- before do
36
- share.stubs(:sync_type).returns('full')
37
- repository.expects(:commit)
38
- repository.expects(:fetch).returns(fetch_result)
39
- end
40
-
41
- describe 'fetch failure' do
42
- let(:fetch_result) { :not_ok }
43
- it { subject }
44
- end
45
-
46
- describe 'when merge error' do
47
- let(:fetch_result) { :ok }
48
- before do
49
- repository.expects(:merge).returns('error')
50
- notifier.expects(:merge_notification).with('error', 'root_path')
51
- end
52
- it { subject }
53
- end
54
-
55
- describe 'when merge not_ok' do
56
- let(:fetch_result) { :ok }
57
- before do
58
- repository.expects(:merge).returns(:not_ok)
59
- notifier.expects(:merge_notification).with(:not_ok, 'root_path')
60
- repository.expects(:push).returns(push_result)
61
- end
62
-
63
- describe 'and push is not_ok' do
64
- let(:push_result) { :not_ok }
65
- before { notifier.expects(:push_notification).with(:not_ok, 'root_path') }
66
- it { subject }
67
- end
68
-
69
- describe 'and push is ok' do
70
- let(:push_result) { :ok }
71
- before do
72
- runner.instance_variable_set(:@last_synced_revision, :oid)
73
- repository.stubs(:current_oid).returns(:next_oid)
74
- changes = { 'Alice' => 1, 'Bob' => 2 }
75
- repository.stubs(:author_count).with(:oid).returns(changes)
76
- notifier.expects(:push_notification).with(changes, 'root_path')
77
-
78
- subject
79
- end
80
- it { runner.instance_variable_get(:@last_synced_revision).must_equal :next_oid }
81
- end
82
- end
83
-
84
- describe 'merge ok' do
85
- let(:fetch_result) { :ok }
86
-
87
- before do
88
- repository.stubs(:current_oid).returns(:merge_oid, :push_oid)
89
-
90
- repository.expects(:merge).returns(:ok)
91
- runner.instance_variable_set(:@last_synced_revision, :oid)
92
- changes = { 'Alice' => 1, 'Bob' => 3 }
93
- repository.stubs(:author_count).with(:oid).returns(changes)
94
- notifier.expects(:merge_notification).with(changes, 'root_path')
95
- repository.expects(:push).returns(push_result)
96
- end
97
-
98
- describe 'and push is not_ok' do
99
- let(:push_result) { :not_ok }
100
- before do
101
- notifier.expects(:push_notification).with(:not_ok, 'root_path')
102
-
103
- subject
104
- end
105
- it { runner.instance_variable_get(:@last_synced_revision).must_equal :merge_oid }
106
- end
107
-
108
- describe 'and push is ok' do
109
- let(:push_result) { :ok }
110
- before do
111
- changes = { 'Charlie' => 5, 'Dan' => 7 }
112
- repository.stubs(:author_count).with(:merge_oid).returns(changes)
113
- notifier.expects(:push_notification).with(changes, 'root_path')
114
-
115
- subject
116
- end
117
- it { runner.instance_variable_get(:@last_synced_revision).must_equal :push_oid }
118
- end
119
- end
120
- end
121
- end
122
- end