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
@@ -0,0 +1,77 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
module Gitdocs
|
3
|
+
class Repository
|
4
|
+
class Committer
|
5
|
+
# @raise if the repository is not valid for commits
|
6
|
+
def initialize(root_dirname)
|
7
|
+
@root_dirname = root_dirname
|
8
|
+
@rugged = Rugged::Repository.new(root_dirname)
|
9
|
+
@grit = Grit::Repo.new(root_dirname)
|
10
|
+
@commit_message_path = File.expand_path('.gitmessage~', root_dirname)
|
11
|
+
rescue Rugged::OSError
|
12
|
+
raise(Gitdocs::Repository::InvalidError, 'No directory')
|
13
|
+
rescue Rugged::RepositoryError
|
14
|
+
raise(Gitdocs::Repository::InvalidError, 'Not a repository')
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Boolean]
|
18
|
+
def commit
|
19
|
+
# Do this first to allow the message file to be deleted, if it exists.
|
20
|
+
message = read_and_delete_commit_message_file
|
21
|
+
|
22
|
+
mark_empty_directories
|
23
|
+
|
24
|
+
# FIXME: Consider a more appropriate location for the dirty check.
|
25
|
+
return false unless Gitdocs::Repository.new(@root_dirname).dirty?
|
26
|
+
Gitdocs.log_debug("Repo #{@root_dirname} is dirty")
|
27
|
+
|
28
|
+
# Commit any changes in the working directory.
|
29
|
+
Dir.chdir(@root_dirname) do
|
30
|
+
@rugged.index.add_all
|
31
|
+
@rugged.index.update_all
|
32
|
+
end
|
33
|
+
@rugged.index.write
|
34
|
+
Gitdocs.log_debug("Index to be committed #{@rugged.index}")
|
35
|
+
|
36
|
+
commit_result = @grit.commit_index(message)
|
37
|
+
Gitdocs.log_debug("Commit result: <#{commit_result.inspect}>")
|
38
|
+
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param [String] message
|
43
|
+
# @return [void]
|
44
|
+
def write_commit_message(message)
|
45
|
+
return unless message
|
46
|
+
return if message.empty?
|
47
|
+
|
48
|
+
File.open(@commit_message_path, 'w') { |f| f.print(message) }
|
49
|
+
end
|
50
|
+
|
51
|
+
##########################################################################
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def mark_empty_directories
|
56
|
+
Find.find(@root_dirname).each do |path|
|
57
|
+
Find.prune if File.basename(path) == '.git'
|
58
|
+
if File.directory?(path) && Dir.entries(path).count == 2
|
59
|
+
FileUtils.touch(File.join(path, '.gitignore'))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [String] either the message in the file, or the regular
|
65
|
+
# automatic commit message.
|
66
|
+
def read_and_delete_commit_message_file
|
67
|
+
return(
|
68
|
+
'Auto-commit from gitdocs'
|
69
|
+
) unless File.exist?(@commit_message_path)
|
70
|
+
|
71
|
+
message = File.read(@commit_message_path)
|
72
|
+
File.delete(@commit_message_path)
|
73
|
+
message
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -2,170 +2,187 @@
|
|
2
2
|
|
3
3
|
# Class for executing File and Git operations on a specific path in the
|
4
4
|
# repository.
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
5
|
+
module Gitdocs
|
6
|
+
class Repository
|
7
|
+
class Path
|
8
|
+
attr_reader :relative_path
|
9
|
+
|
10
|
+
# @param [Gitdocs::Repository] repository
|
11
|
+
# @param [String] relative_path
|
12
|
+
def initialize(repository, relative_path)
|
13
|
+
@repository = repository
|
14
|
+
@relative_path = relative_path.gsub(%r{^/}, '')
|
15
|
+
@absolute_path = File.join(
|
16
|
+
File.absolute_path(@repository.root), @relative_path
|
17
|
+
)
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
# @return [String]
|
21
|
+
def relative_dirname
|
22
|
+
result = File.dirname(@relative_path)
|
23
|
+
return '' if result == '.'
|
24
|
+
result
|
25
|
+
end
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def write(content, commit_message)
|
28
|
-
FileUtils.mkdir_p(File.dirname(@absolute_path))
|
29
|
-
File.open(@absolute_path, 'w') { |f| f.puts(content) }
|
27
|
+
def join(path_fragment)
|
28
|
+
@relative_path = File.join(@relative_path, path_fragment)
|
29
|
+
@absolute_path = File.join(@absolute_path, path_fragment)
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
# Write the content to the path and create any necessary directories.
|
33
|
+
#
|
34
|
+
# @param [String] content
|
35
|
+
# @return [void]
|
36
|
+
def write(content)
|
37
|
+
FileUtils.mkdir_p(File.dirname(@absolute_path))
|
38
|
+
File.open(@absolute_path, 'w') { |f| f.puts(content) }
|
39
|
+
end
|
33
40
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
41
|
+
# Touch and path and create any necessary directories.
|
42
|
+
def touch
|
43
|
+
FileUtils.mkdir_p(File.dirname(@absolute_path))
|
44
|
+
FileUtils.touch(@absolute_path)
|
45
|
+
end
|
39
46
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
# Create the path as a directory.
|
48
|
+
def mkdir
|
49
|
+
FileUtils.mkdir_p(@absolute_path)
|
50
|
+
end
|
44
51
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
# Move file to the repository path
|
53
|
+
# @param [String] filename
|
54
|
+
def mv(filename)
|
55
|
+
FileUtils.mkdir_p(File.dirname(@absolute_path))
|
56
|
+
FileUtils.mv(filename, @absolute_path)
|
57
|
+
end
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
59
|
+
# Remove the path, but only if it is a file.
|
60
|
+
def remove
|
61
|
+
return nil unless File.file?(@absolute_path)
|
62
|
+
FileUtils.rm(@absolute_path)
|
63
|
+
end
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# @raise [RuntimeError] if the file is not found in any commits
|
65
|
-
#
|
66
|
-
# @return [Hash<:author => String, :size => Integer, modified => Time>]
|
67
|
-
def meta
|
68
|
-
commit = @repository.last_commit_for(@relative_path)
|
69
|
-
|
70
|
-
# FIXME: This should actually just return an empty hash
|
71
|
-
fail("File #{@relative_path} not found") unless commit
|
72
|
-
|
73
|
-
{
|
74
|
-
author: commit.author[:name],
|
75
|
-
size: total_size,
|
76
|
-
modified: commit.author[:time]
|
77
|
-
}
|
78
|
-
end
|
65
|
+
# @return [Boolean]
|
66
|
+
def text?
|
67
|
+
return false unless File.file?(@absolute_path)
|
68
|
+
mime_type = File.mime_type?(File.open(@absolute_path))
|
69
|
+
!!(mime_type =~ %r{text/|x-empty}) # rubocop:disable DoubleNegation
|
70
|
+
end
|
79
71
|
|
80
|
-
|
81
|
-
|
82
|
-
|
72
|
+
# Returns file meta data based on relative file path
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# meta
|
76
|
+
# => { :author => "Nick", :size => 1000, :modified => ... }
|
77
|
+
#
|
78
|
+
# @raise [RuntimeError] if the file is not found in any commits
|
79
|
+
#
|
80
|
+
# @return [Hash<:author => String, :size => Integer, modified => Time>]
|
81
|
+
def meta
|
82
|
+
commit = @repository.last_commit_for(@relative_path)
|
83
|
+
|
84
|
+
# FIXME: This should actually just return an empty hash
|
85
|
+
fail("File #{@relative_path} not found") unless commit
|
86
|
+
|
87
|
+
{
|
88
|
+
author: commit.author[:name],
|
89
|
+
size: total_size,
|
90
|
+
modified: commit.author[:time]
|
91
|
+
}
|
92
|
+
end
|
83
93
|
|
84
|
-
|
85
|
-
|
86
|
-
|
94
|
+
def exist?
|
95
|
+
File.exist?(@absolute_path)
|
96
|
+
end
|
87
97
|
|
88
|
-
|
89
|
-
|
98
|
+
def directory?
|
99
|
+
File.directory?(@absolute_path)
|
100
|
+
end
|
90
101
|
|
91
|
-
|
92
|
-
|
93
|
-
tmp_path = File.expand_path(File.basename(@relative_path), Dir.tmpdir)
|
94
|
-
File.open(tmp_path, 'w') { |f| f.puts content }
|
95
|
-
tmp_path
|
96
|
-
end
|
102
|
+
def absolute_path(ref = nil)
|
103
|
+
return @absolute_path unless ref
|
97
104
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
105
|
+
blob = @repository.blob_at(@relative_path, ref)
|
106
|
+
content = blob ? blob.text : ''
|
107
|
+
tmp_path = File.expand_path(File.basename(@relative_path), Dir.tmpdir)
|
108
|
+
File.open(tmp_path, 'w') { |f| f.puts content }
|
109
|
+
tmp_path
|
110
|
+
end
|
102
111
|
|
103
|
-
|
112
|
+
def readme_path
|
113
|
+
return nil unless directory?
|
114
|
+
Dir.glob(File.join(@absolute_path, 'README.{md}')).first
|
115
|
+
end
|
104
116
|
|
105
|
-
|
106
|
-
# * excluding any git related directories
|
107
|
-
# * sorted by filename, ignoring any leading '.'s
|
108
|
-
def file_listing
|
109
|
-
return nil unless directory?
|
117
|
+
DirEntry = Struct.new(:name, :is_directory)
|
110
118
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
119
|
+
# @return [Array<DirEntry>] entries in the directory
|
120
|
+
# * excluding any git related directories
|
121
|
+
# * sorted by filename, ignoring any leading '.'s
|
122
|
+
def file_listing
|
123
|
+
return nil unless directory?
|
116
124
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
125
|
+
Dir
|
126
|
+
.glob(File.join(@absolute_path, '{*,.*}'))
|
127
|
+
.reject { |x| x.match(%r{/\.(\.|git|gitignore|gitmessage~)?$}) }
|
128
|
+
.sort_by { |x| File.basename(x).sub(/^\./, '') }
|
129
|
+
.map { |x| DirEntry.new(File.basename(x), File.directory?(x)) }
|
130
|
+
end
|
121
131
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
# @return [Array<Hash>]
|
127
|
-
def revisions
|
128
|
-
@repository.commits_for(@relative_path, 100).map do |commit|
|
129
|
-
{
|
130
|
-
commit: commit.oid[0, 7],
|
131
|
-
subject: commit.message.split("\n")[0],
|
132
|
-
author: commit.author[:name],
|
133
|
-
date: commit.author[:time]
|
134
|
-
}
|
135
|
-
end
|
136
|
-
end
|
132
|
+
def content
|
133
|
+
return nil unless File.file?(@absolute_path)
|
134
|
+
File.read(@absolute_path)
|
135
|
+
end
|
137
136
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
137
|
+
# Returns the revisions available for a particular file
|
138
|
+
#
|
139
|
+
# @param [String] file
|
140
|
+
#
|
141
|
+
# @return [Array<Hash>]
|
142
|
+
def revisions
|
143
|
+
@repository.commits_for(@relative_path, 100).map do |commit|
|
144
|
+
{
|
145
|
+
commit: commit.oid[0, 7],
|
146
|
+
subject: commit.message.split("\n")[0],
|
147
|
+
author: commit.author[:name],
|
148
|
+
date: commit.author[:time]
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
143
152
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
153
|
+
# Revert file to the specified ref
|
154
|
+
#
|
155
|
+
# @param [String] ref
|
156
|
+
def revert(ref)
|
157
|
+
return unless ref
|
149
158
|
|
150
|
-
|
151
|
-
|
159
|
+
blob = @repository.blob_at(@relative_path, ref)
|
160
|
+
# Silently fail if the file/ref do not existing in the repository.
|
161
|
+
# Which is consistent with the original behaviour.
|
162
|
+
# TODO: should consider throwing an exception on this condition
|
163
|
+
return unless blob
|
164
|
+
|
165
|
+
write(blob.text)
|
166
|
+
end
|
152
167
|
|
153
|
-
|
168
|
+
##########################################################################
|
154
169
|
|
155
|
-
|
170
|
+
private
|
156
171
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
172
|
+
def total_size
|
173
|
+
result =
|
174
|
+
if File.directory?(@absolute_path)
|
175
|
+
Dir[File.join(@absolute_path, '**', '*')].reduce(0) do |size, filename|
|
176
|
+
File.symlink?(filename) ? size : size + File.size(filename)
|
177
|
+
end
|
178
|
+
else
|
179
|
+
File.symlink?(@absolute_path) ? 0 : File.size(@absolute_path)
|
180
|
+
end
|
166
181
|
|
167
|
-
|
168
|
-
|
169
|
-
|
182
|
+
# HACK: A value of 0 breaks the table sort for some reason
|
183
|
+
return -1 if result == 0
|
184
|
+
result
|
185
|
+
end
|
186
|
+
end
|
170
187
|
end
|
171
188
|
end
|
data/lib/gitdocs/search.rb
CHANGED
@@ -1,35 +1,50 @@
|
|
1
|
-
|
2
|
-
RepoDescriptor = Struct.new(:name, :index)
|
3
|
-
SearchResult = Struct.new(:file, :context)
|
1
|
+
# -*- encoding : utf-8 -*-
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module Gitdocs
|
4
|
+
class Search
|
5
|
+
RepoDescriptor = Struct.new(:name, :index)
|
6
|
+
SearchResult = Struct.new(:file, :context)
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
# @param [String] term
|
9
|
+
# @return (see #search)
|
10
|
+
def self.search(term)
|
11
|
+
new(Share.all.map { |x| Repository.new(x) }).search(term)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Array<Gitdocs::Repository>] repositories
|
15
|
+
def initialize(repositories)
|
16
|
+
@repositories = repositories
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [String] term
|
20
|
+
# @return [Hash<RepoDescriptor, Array<SearchResult>>]
|
21
|
+
def search(term)
|
22
|
+
results = {}
|
23
|
+
@repositories.each_with_index do |repository, index|
|
24
|
+
descriptor = RepoDescriptor.new(repository.root, index)
|
25
|
+
results[descriptor] = search_repository(repository, term)
|
26
|
+
end
|
27
|
+
results.delete_if { |_key, value| value.empty? }
|
15
28
|
end
|
16
|
-
results.delete_if { |_key, value| value.empty? }
|
17
|
-
end
|
18
29
|
|
19
|
-
|
30
|
+
private
|
20
31
|
|
21
|
-
|
22
|
-
|
32
|
+
# @param [Repository] repository
|
33
|
+
# @param [String] term
|
34
|
+
# @return [Array<SearchResult>]
|
35
|
+
def search_repository(repository, term)
|
36
|
+
return [] if term.nil? || term.empty?
|
23
37
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
results = []
|
39
|
+
repository.grep(term) do |file, context|
|
40
|
+
result = results.find { |s| s.file == file }
|
41
|
+
if result
|
42
|
+
result.context += ' ... ' + context
|
43
|
+
else
|
44
|
+
results << SearchResult.new(file, context)
|
45
|
+
end
|
31
46
|
end
|
47
|
+
results
|
32
48
|
end
|
33
|
-
results
|
34
49
|
end
|
35
50
|
end
|