gitdocs 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|