gitdocs 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +6 -14
  2. data/.codeclimate.yml +26 -0
  3. data/.rubocop.yml +8 -2
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG +13 -0
  6. data/Gemfile +1 -1
  7. data/README.md +7 -6
  8. data/Rakefile +31 -5
  9. data/bin/gitdocs +1 -0
  10. data/config.ru +6 -4
  11. data/gitdocs.gemspec +22 -19
  12. data/lib/gitdocs.rb +54 -16
  13. data/lib/gitdocs/browser_app.rb +34 -41
  14. data/lib/gitdocs/cli.rb +41 -32
  15. data/lib/gitdocs/configuration.rb +40 -101
  16. data/lib/gitdocs/git_notifier.rb +111 -0
  17. data/lib/gitdocs/initializer.rb +83 -0
  18. data/lib/gitdocs/manager.rb +90 -60
  19. data/lib/gitdocs/migration/004_add_index_for_path.rb +1 -1
  20. data/lib/gitdocs/notifier.rb +70 -104
  21. data/lib/gitdocs/rendering_helper.rb +3 -0
  22. data/lib/gitdocs/repository.rb +324 -307
  23. data/lib/gitdocs/repository/committer.rb +77 -0
  24. data/lib/gitdocs/repository/path.rb +157 -140
  25. data/lib/gitdocs/search.rb +40 -25
  26. data/lib/gitdocs/settings_app.rb +5 -3
  27. data/lib/gitdocs/share.rb +64 -0
  28. data/lib/gitdocs/synchronizer.rb +40 -0
  29. data/lib/gitdocs/version.rb +1 -1
  30. data/lib/gitdocs/views/_header.haml +2 -2
  31. data/lib/gitdocs/views/dir.haml +3 -3
  32. data/lib/gitdocs/views/edit.haml +1 -1
  33. data/lib/gitdocs/views/file.haml +1 -1
  34. data/lib/gitdocs/views/home.haml +3 -3
  35. data/lib/gitdocs/views/layout.haml +13 -13
  36. data/lib/gitdocs/views/revisions.haml +3 -3
  37. data/lib/gitdocs/views/search.haml +1 -1
  38. data/lib/gitdocs/views/settings.haml +6 -6
  39. data/test/integration/cli/full_sync_test.rb +83 -0
  40. data/test/integration/cli/share_management_test.rb +29 -0
  41. data/test/integration/cli/status_test.rb +14 -0
  42. data/test/integration/test_helper.rb +185 -151
  43. data/test/integration/{browse_test.rb → web/browse_test.rb} +11 -29
  44. data/test/integration/web/share_management_test.rb +46 -0
  45. data/test/support/git_factory.rb +276 -0
  46. data/test/unit/browser_app_test.rb +346 -0
  47. data/test/unit/configuration_test.rb +8 -70
  48. data/test/unit/git_notifier_test.rb +116 -0
  49. data/test/unit/gitdocs_test.rb +90 -0
  50. data/test/unit/manager_test.rb +36 -0
  51. data/test/unit/notifier_test.rb +60 -124
  52. data/test/unit/repository_committer_test.rb +111 -0
  53. data/test/unit/repository_path_test.rb +92 -76
  54. data/test/unit/repository_test.rb +243 -356
  55. data/test/unit/search_test.rb +15 -0
  56. data/test/unit/settings_app_test.rb +80 -0
  57. data/test/unit/share_test.rb +97 -0
  58. data/test/unit/test_helper.rb +17 -3
  59. metadata +114 -108
  60. data/lib/gitdocs/runner.rb +0 -108
  61. data/lib/gitdocs/server.rb +0 -62
  62. data/test/integration/full_sync_test.rb +0 -66
  63. data/test/integration/share_management_test.rb +0 -95
  64. data/test/integration/status_test.rb +0 -21
  65. data/test/unit/runner_test.rb +0 -122
@@ -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