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.
- checksums.yaml +6 -14
- data/.codeclimate.yml +26 -0
- data/.rubocop.yml +8 -2
- data/.travis.yml +8 -0
- data/CHANGELOG +13 -0
- data/Gemfile +1 -1
- data/README.md +7 -6
- data/Rakefile +31 -5
- data/bin/gitdocs +1 -0
- data/config.ru +6 -4
- data/gitdocs.gemspec +22 -19
- data/lib/gitdocs.rb +54 -16
- data/lib/gitdocs/browser_app.rb +34 -41
- data/lib/gitdocs/cli.rb +41 -32
- data/lib/gitdocs/configuration.rb +40 -101
- data/lib/gitdocs/git_notifier.rb +111 -0
- data/lib/gitdocs/initializer.rb +83 -0
- data/lib/gitdocs/manager.rb +90 -60
- data/lib/gitdocs/migration/004_add_index_for_path.rb +1 -1
- data/lib/gitdocs/notifier.rb +70 -104
- data/lib/gitdocs/rendering_helper.rb +3 -0
- data/lib/gitdocs/repository.rb +324 -307
- data/lib/gitdocs/repository/committer.rb +77 -0
- data/lib/gitdocs/repository/path.rb +157 -140
- data/lib/gitdocs/search.rb +40 -25
- data/lib/gitdocs/settings_app.rb +5 -3
- data/lib/gitdocs/share.rb +64 -0
- data/lib/gitdocs/synchronizer.rb +40 -0
- data/lib/gitdocs/version.rb +1 -1
- data/lib/gitdocs/views/_header.haml +2 -2
- data/lib/gitdocs/views/dir.haml +3 -3
- data/lib/gitdocs/views/edit.haml +1 -1
- data/lib/gitdocs/views/file.haml +1 -1
- data/lib/gitdocs/views/home.haml +3 -3
- data/lib/gitdocs/views/layout.haml +13 -13
- data/lib/gitdocs/views/revisions.haml +3 -3
- data/lib/gitdocs/views/search.haml +1 -1
- data/lib/gitdocs/views/settings.haml +6 -6
- data/test/integration/cli/full_sync_test.rb +83 -0
- data/test/integration/cli/share_management_test.rb +29 -0
- data/test/integration/cli/status_test.rb +14 -0
- data/test/integration/test_helper.rb +185 -151
- data/test/integration/{browse_test.rb → web/browse_test.rb} +11 -29
- data/test/integration/web/share_management_test.rb +46 -0
- data/test/support/git_factory.rb +276 -0
- data/test/unit/browser_app_test.rb +346 -0
- data/test/unit/configuration_test.rb +8 -70
- data/test/unit/git_notifier_test.rb +116 -0
- data/test/unit/gitdocs_test.rb +90 -0
- data/test/unit/manager_test.rb +36 -0
- data/test/unit/notifier_test.rb +60 -124
- data/test/unit/repository_committer_test.rb +111 -0
- data/test/unit/repository_path_test.rb +92 -76
- data/test/unit/repository_test.rb +243 -356
- data/test/unit/search_test.rb +15 -0
- data/test/unit/settings_app_test.rb +80 -0
- data/test/unit/share_test.rb +97 -0
- data/test/unit/test_helper.rb +17 -3
- metadata +114 -108
- data/lib/gitdocs/runner.rb +0 -108
- data/lib/gitdocs/server.rb +0 -62
- data/test/integration/full_sync_test.rb +0 -66
- data/test/integration/share_management_test.rb +0 -95
- data/test/integration/status_test.rb +0 -21
- data/test/unit/runner_test.rb +0 -122
data/lib/gitdocs/cli.rb
CHANGED
@@ -13,20 +13,28 @@ module Gitdocs
|
|
13
13
|
end
|
14
14
|
|
15
15
|
desc 'start', 'Starts a daemonized gitdocs process'
|
16
|
-
method_option :
|
17
|
-
method_option :
|
18
|
-
method_option :
|
16
|
+
method_option :foreground, type: :boolean, aliases: '-fg'
|
17
|
+
method_option :verbose, type: :boolean, aliases: '-v'
|
18
|
+
method_option :port, type: :string, aliases: '-p'
|
19
|
+
method_option :pid, type: :string, aliases: '-P'
|
19
20
|
def start
|
20
21
|
unless stopped?
|
21
22
|
say 'Gitdocs is already running, please use restart', :red
|
22
23
|
return
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
Gitdocs::Initializer.verbose = options[:verbose]
|
27
|
+
|
28
|
+
if options[:foreground]
|
29
|
+
say 'Run in the foreground', :yellow
|
30
|
+
Gitdocs::Initializer.foreground = true
|
31
|
+
Manager.start(web_port)
|
28
32
|
else
|
29
|
-
|
33
|
+
# Clear the arguments so that they will not be processed by the
|
34
|
+
# Dante execution.
|
35
|
+
ARGV.clear
|
36
|
+
runner.execute { Manager.start(web_port) }
|
37
|
+
|
30
38
|
if running?
|
31
39
|
say 'Started gitdocs', :green
|
32
40
|
else
|
@@ -57,7 +65,7 @@ module Gitdocs
|
|
57
65
|
method_option :pid, type: :string, aliases: '-P'
|
58
66
|
desc 'add PATH', 'Adds a path to gitdocs'
|
59
67
|
def add(path)
|
60
|
-
|
68
|
+
Share.create_by_path!(normalize_path(path))
|
61
69
|
say "Added path #{path} to doc list"
|
62
70
|
restart if running?
|
63
71
|
end
|
@@ -65,21 +73,23 @@ module Gitdocs
|
|
65
73
|
method_option :pid, type: :string, aliases: '-P'
|
66
74
|
desc 'rm PATH', 'Removes a path from gitdocs'
|
67
75
|
def rm(path)
|
68
|
-
|
76
|
+
Share.remove_by_path(path)
|
69
77
|
say "Removed path #{path} from doc list"
|
70
78
|
restart if running?
|
71
79
|
end
|
72
80
|
|
81
|
+
method_option :pid, type: :string, aliases: '-P'
|
73
82
|
desc 'clear', 'Clears all paths from gitdocs'
|
74
83
|
def clear
|
75
|
-
|
84
|
+
Share.destroy_all
|
76
85
|
say 'Cleared paths from gitdocs'
|
86
|
+
restart if running?
|
77
87
|
end
|
78
88
|
|
79
89
|
method_option :pid, type: :string, aliases: '-P'
|
80
90
|
desc 'create PATH REMOTE', 'Creates a new gitdoc root based on an existing remote'
|
81
91
|
def create(path, remote)
|
82
|
-
|
92
|
+
Repository.clone(path, remote)
|
83
93
|
add(path)
|
84
94
|
say "Created #{path} path for gitdoc"
|
85
95
|
end
|
@@ -89,11 +99,11 @@ module Gitdocs
|
|
89
99
|
def status
|
90
100
|
say "GitDoc v#{VERSION}"
|
91
101
|
say "Running: #{running?}"
|
92
|
-
say "File System Watch Method: #{
|
102
|
+
say "File System Watch Method: #{Gitdocs::Manager.listen_method}"
|
93
103
|
say 'Watched repositories:'
|
94
104
|
tp.set(:max_width, 100)
|
95
105
|
status_display = lambda do |share|
|
96
|
-
repository =
|
106
|
+
repository = Repository.new(share)
|
97
107
|
|
98
108
|
status = ''
|
99
109
|
status += '*' if repository.dirty?
|
@@ -103,7 +113,7 @@ module Gitdocs
|
|
103
113
|
status
|
104
114
|
end
|
105
115
|
tp(
|
106
|
-
|
116
|
+
Share.all,
|
107
117
|
{ sync: { display_method: :sync_type } },
|
108
118
|
{ s: status_display },
|
109
119
|
:path
|
@@ -119,8 +129,6 @@ module Gitdocs
|
|
119
129
|
return
|
120
130
|
end
|
121
131
|
|
122
|
-
web_port = options[:port]
|
123
|
-
web_port ||= config.web_frontend_port
|
124
132
|
Launchy.open("http://localhost:#{web_port}/")
|
125
133
|
end
|
126
134
|
|
@@ -137,42 +145,43 @@ module Gitdocs
|
|
137
145
|
|
138
146
|
# Helpers for thor
|
139
147
|
no_tasks do
|
148
|
+
# @return [Dante::Runner]
|
140
149
|
def runner
|
141
150
|
Dante::Runner.new(
|
142
151
|
'gitdocs',
|
143
152
|
debug: false,
|
144
153
|
daemonize: true,
|
145
|
-
pid_path:
|
154
|
+
pid_path: pid_path,
|
155
|
+
log_path: Gitdocs.log_path
|
146
156
|
)
|
147
157
|
end
|
148
158
|
|
149
|
-
|
150
|
-
@config ||= Configuration.new
|
151
|
-
end
|
152
|
-
|
159
|
+
# @return [Boolean]
|
153
160
|
def running?
|
154
161
|
runner.daemon_running?
|
155
162
|
end
|
156
163
|
|
164
|
+
# @return [Boolean]
|
157
165
|
def stopped?
|
158
166
|
runner.daemon_stopped?
|
159
167
|
end
|
160
168
|
|
169
|
+
# @return [String]
|
161
170
|
def pid_path
|
162
171
|
options[:pid] || '/tmp/gitdocs.pid'
|
163
172
|
end
|
164
173
|
|
165
|
-
# @return [
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
174
|
+
# @return [Integer]
|
175
|
+
def web_port
|
176
|
+
result = options[:port]
|
177
|
+
result ||= Configuration.web_frontend_port
|
178
|
+
result.to_i
|
179
|
+
end
|
180
|
+
|
181
|
+
# @param [String] path
|
182
|
+
# @return [String]
|
183
|
+
def normalize_path(path)
|
184
|
+
File.expand_path(path, Dir.pwd)
|
176
185
|
end
|
177
186
|
end
|
178
187
|
end
|
@@ -1,112 +1,51 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
3
|
require 'active_record'
|
4
|
-
require 'grit'
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
class Config < ActiveRecord::Base
|
25
|
-
#attr_accessible :start_web_frontend, :web_frontend_port
|
26
|
-
end
|
27
|
-
|
28
|
-
# return [Boolean]
|
29
|
-
def start_web_frontend
|
30
|
-
global.start_web_frontend
|
31
|
-
end
|
32
|
-
|
33
|
-
# @return [Integer]
|
34
|
-
def web_frontend_port
|
35
|
-
global.web_frontend_port
|
36
|
-
end
|
37
|
-
|
38
|
-
# @param [String] path
|
39
|
-
# @param [Hash] opts
|
40
|
-
def add_path(path, opts = nil)
|
41
|
-
path = normalize_path(path)
|
42
|
-
path_opts = { path: path }
|
43
|
-
path_opts.merge!(opts) if opts
|
44
|
-
Share.new(path_opts).save!
|
45
|
-
end
|
46
|
-
|
47
|
-
# @param [Hash] new_config
|
48
|
-
# @option new_config [Hash] 'config'
|
49
|
-
# @option new_config [Array<Hash>] 'share'
|
50
|
-
def update_all(new_config)
|
51
|
-
global.update_attributes(new_config['config'])
|
52
|
-
new_config['share'].each do |index, share_config|
|
53
|
-
# Skip the share update if there is no path specified.
|
54
|
-
next unless share_config['path'] && !share_config['path'].empty?
|
55
|
-
|
56
|
-
# Split the remote_branch into remote and branch
|
57
|
-
remote_branch = share_config.delete('remote_branch')
|
58
|
-
if remote_branch
|
59
|
-
share_config['remote_name'], share_config['branch_name'] = remote_branch.split('/', 2)
|
60
|
-
end
|
61
|
-
shares[index.to_i].update_attributes(share_config)
|
5
|
+
# @!attribute path
|
6
|
+
# @return [String]
|
7
|
+
# @!attribute polling_interval
|
8
|
+
# @return [Double] defaults to 15.0
|
9
|
+
# @!attribute notification
|
10
|
+
# @return [Boolean] default to true
|
11
|
+
# @!attribute remote_name
|
12
|
+
# @return [String] default to 'origin'
|
13
|
+
# @!attribute remote_branch
|
14
|
+
# @return [String] default to 'master'
|
15
|
+
# @attribute sync_type
|
16
|
+
# @return ['full','fetch']
|
17
|
+
module Gitdocs
|
18
|
+
class Configuration
|
19
|
+
# @return [Boolean]
|
20
|
+
def self.start_web_frontend
|
21
|
+
Config.global.start_web_frontend
|
62
22
|
end
|
63
|
-
end
|
64
23
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
# @param [Integer] id of the share to remove
|
72
|
-
#
|
73
|
-
# @return [true] share was deleted
|
74
|
-
# @return [false] share does not exist
|
75
|
-
def remove_by_id(id)
|
76
|
-
Share.find(id).destroy
|
77
|
-
true
|
78
|
-
rescue ActiveRecord::RecordNotFound
|
79
|
-
false
|
80
|
-
end
|
81
|
-
|
82
|
-
def clear
|
83
|
-
Share.destroy_all
|
84
|
-
end
|
85
|
-
|
86
|
-
def shares
|
87
|
-
Share.all
|
88
|
-
end
|
89
|
-
|
90
|
-
##############################################################################
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
def global
|
95
|
-
fail if Config.all.size > 1
|
96
|
-
Config.create! if Config.all.empty?
|
97
|
-
Config.all.first
|
98
|
-
end
|
99
|
-
|
100
|
-
def normalize_path(path)
|
101
|
-
File.expand_path(path, Dir.pwd)
|
102
|
-
end
|
24
|
+
# @return [Integer]
|
25
|
+
def self.web_frontend_port
|
26
|
+
Config.global.web_frontend_port
|
27
|
+
end
|
103
28
|
|
104
|
-
|
105
|
-
|
106
|
-
|
29
|
+
# @param [Hash] new_config
|
30
|
+
def self.update(new_config)
|
31
|
+
Config.global.update_attributes(new_config)
|
32
|
+
end
|
107
33
|
|
108
|
-
|
109
|
-
|
34
|
+
# NOTE: This record has been kept as a subclass to avoid changing the
|
35
|
+
# database table. There are other ways to achieve this, but this seemed most
|
36
|
+
# clear for now. [2015-06-26 -- acant]
|
37
|
+
#
|
38
|
+
# @!attribute start_frontend_port
|
39
|
+
# @return [Boolean] defaults to true
|
40
|
+
# @!attribute web_frontend_port
|
41
|
+
# @return [Integer] defaults to 8888
|
42
|
+
class Config < ActiveRecord::Base
|
43
|
+
# @return [Gitdocs::Configuration::Config]
|
44
|
+
def self.global
|
45
|
+
fail if all.size > 1
|
46
|
+
create! if all.empty?
|
47
|
+
all.first
|
48
|
+
end
|
110
49
|
end
|
111
50
|
end
|
112
51
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Notifications about git specific operations
|
4
|
+
module Gitdocs
|
5
|
+
class GitNotifier
|
6
|
+
# @param [String] root
|
7
|
+
# @param [Boolean] show_notifications
|
8
|
+
def initialize(root, show_notifications)
|
9
|
+
@root = root
|
10
|
+
@show_notifications = show_notifications
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [nil, Symbol, Array<String>, Hash<String => Integer>, #to_s] result
|
14
|
+
#
|
15
|
+
# @return [void]
|
16
|
+
def for_merge(result)
|
17
|
+
return if result.nil?
|
18
|
+
return if result == :no_remote
|
19
|
+
return if result == :ok
|
20
|
+
return if result == {}
|
21
|
+
|
22
|
+
if result.is_a?(Array)
|
23
|
+
Notifier.warn(
|
24
|
+
'There were some conflicts',
|
25
|
+
result.map { |f| "* #{f}" }.join("\n"),
|
26
|
+
@show_notifications
|
27
|
+
)
|
28
|
+
elsif result.is_a?(Hash)
|
29
|
+
Notifier.info(
|
30
|
+
"Updated with #{change_to_s(result)}",
|
31
|
+
"In #{@root}:\n#{author_list(result)}",
|
32
|
+
@show_notifications
|
33
|
+
)
|
34
|
+
else
|
35
|
+
Notifier.error(
|
36
|
+
'There was a problem synchronizing this gitdoc',
|
37
|
+
"A problem occurred in #{@root}:\n#{result}",
|
38
|
+
@show_notifications
|
39
|
+
)
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [nil, Symbol, Hash<String => Integer>, #to_s] result of push
|
45
|
+
#
|
46
|
+
# @return [void]
|
47
|
+
def for_push(result)
|
48
|
+
return if result.nil?
|
49
|
+
return if result == :no_remote
|
50
|
+
return if result == :nothing
|
51
|
+
|
52
|
+
if result == :conflict
|
53
|
+
Notifier.warn(
|
54
|
+
"There was a conflict in #{@root}, retrying",
|
55
|
+
'',
|
56
|
+
@show_notifications
|
57
|
+
)
|
58
|
+
elsif result.is_a?(Hash)
|
59
|
+
Notifier.info(
|
60
|
+
"Pushed #{change_to_s(result)}",
|
61
|
+
"#{@root} has been pushed",
|
62
|
+
@show_notifications
|
63
|
+
)
|
64
|
+
else
|
65
|
+
Notifier.error(
|
66
|
+
"BAD Could not push changes in #{@root}",
|
67
|
+
result.to_s,
|
68
|
+
@show_notifications
|
69
|
+
)
|
70
|
+
end
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param [Exception] exception
|
75
|
+
#
|
76
|
+
# @return [void]
|
77
|
+
def on_error(exception)
|
78
|
+
Notifier.error(
|
79
|
+
"Unexpected error when fetching/pushing in #{@root}",
|
80
|
+
exception.to_s,
|
81
|
+
@show_notifications
|
82
|
+
)
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
############################################################################
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
# @param [Hash<String => Integer>] changes
|
91
|
+
# @return [String]
|
92
|
+
def author_list(changes)
|
93
|
+
changes
|
94
|
+
.map { |author, count| "* #{author} (#{change_to_s(count)})" }
|
95
|
+
.join("\n")
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Integer, Hash<String => Integer>] count_or_hash
|
99
|
+
# @return [String]
|
100
|
+
def change_to_s(count_or_hash)
|
101
|
+
count =
|
102
|
+
if count_or_hash.respond_to?(:values)
|
103
|
+
count_or_hash.values.reduce(:+)
|
104
|
+
else
|
105
|
+
count_or_hash
|
106
|
+
end
|
107
|
+
|
108
|
+
"#{count} change#{count == 1 ? '' : 's'}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
module Gitdocs
|
6
|
+
class Initializer
|
7
|
+
# @return [nil]
|
8
|
+
def self.initialize_all
|
9
|
+
initialize_database
|
10
|
+
initialize_old_paths
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [nil]
|
14
|
+
def self.initialize_database
|
15
|
+
FileUtils.mkdir_p(root_dirname)
|
16
|
+
ActiveRecord::Base.establish_connection(
|
17
|
+
adapter: 'sqlite3',
|
18
|
+
database: database
|
19
|
+
)
|
20
|
+
ActiveRecord::Migrator.migrate(
|
21
|
+
File.expand_path('../migration', __FILE__)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [nil]
|
26
|
+
def self.initialize_old_paths
|
27
|
+
old_path_dirname = File.expand_path('paths', root_dirname)
|
28
|
+
return unless File.exist?(old_path_dirname)
|
29
|
+
|
30
|
+
File.read(old_path_dirname).split("\n").each do |path|
|
31
|
+
begin
|
32
|
+
Share.create_by_path!(path)
|
33
|
+
rescue # rubocop:disable HandleExceptions
|
34
|
+
# Nothing to do, because we want the process to keep going.
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
def self.root_dirname
|
41
|
+
@root_dirname ||= File.expand_path('.gitdocs', ENV['HOME'])
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [nil, String] value
|
45
|
+
# @return [nil]
|
46
|
+
def self.root_dirname=(value)
|
47
|
+
return if value.nil?
|
48
|
+
@root_dirname = value
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String]
|
52
|
+
def self.database
|
53
|
+
@database ||= File.join(root_dirname, 'config.db')
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param [nil, String] value
|
57
|
+
# @return [nil]
|
58
|
+
def self.database=(value)
|
59
|
+
return if value.nil?
|
60
|
+
@database = value
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean]
|
64
|
+
def self.foreground
|
65
|
+
@foreground ||= false
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.foreground=(value)
|
69
|
+
return if value.nil?
|
70
|
+
@foreground = value
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Boolean]
|
74
|
+
def self.verbose
|
75
|
+
@verbose ||= false
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param [Boolean] value
|
79
|
+
def self.verbose=(value)
|
80
|
+
@verbose = !!value # rubocop:disable DoubleNegation
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|