gitdocs 0.5.0.pre6 → 0.5.0.pre7
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 +8 -8
- data/.gitignore +1 -0
- data/.haml-lint.yml +3 -0
- data/.jslint.yml +84 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG +11 -0
- data/README.md +6 -2
- data/Rakefile +22 -3
- data/gitdocs.gemspec +36 -29
- data/lib/gitdocs.rb +5 -2
- data/lib/gitdocs/cli.rb +31 -8
- data/lib/gitdocs/configuration.rb +95 -49
- data/lib/gitdocs/manager.rb +36 -28
- data/lib/gitdocs/migration/001_create_shares.rb +2 -0
- data/lib/gitdocs/migration/002_add_remote_branch.rb +2 -0
- data/lib/gitdocs/migration/003_create_configs.rb +2 -0
- data/lib/gitdocs/migration/004_add_index_for_path.rb +4 -0
- data/lib/gitdocs/migration/005_add_start_web_frontend.rb +2 -0
- data/lib/gitdocs/migration/006_add_web_port_to_config.rb +2 -0
- data/lib/gitdocs/migration/007_add_sync_type.rb +11 -0
- data/lib/gitdocs/notifier.rb +89 -6
- data/lib/gitdocs/public/img/file.png +0 -0
- data/lib/gitdocs/public/img/folder.png +0 -0
- data/lib/gitdocs/public/js/app.js +26 -11
- data/lib/gitdocs/public/js/edit.js +3 -3
- data/lib/gitdocs/public/js/settings.js +8 -5
- data/lib/gitdocs/public/js/util.js +21 -20
- data/lib/gitdocs/rendering.rb +14 -9
- data/lib/gitdocs/repository.rb +180 -216
- data/lib/gitdocs/repository/path.rb +166 -0
- data/lib/gitdocs/runner.rb +22 -65
- data/lib/gitdocs/search.rb +35 -0
- data/lib/gitdocs/server.rb +123 -86
- data/lib/gitdocs/version.rb +1 -1
- data/lib/gitdocs/views/_header.haml +6 -6
- data/lib/gitdocs/views/app.haml +17 -17
- data/lib/gitdocs/views/dir.haml +10 -10
- data/lib/gitdocs/views/edit.haml +8 -9
- data/lib/gitdocs/views/file.haml +1 -1
- data/lib/gitdocs/views/home.haml +4 -4
- data/lib/gitdocs/views/revisions.haml +6 -6
- data/lib/gitdocs/views/search.haml +6 -6
- data/lib/gitdocs/views/settings.haml +23 -16
- data/test/.rubocop.yml +13 -0
- data/test/integration/browse_test.rb +149 -0
- data/test/integration/full_sync_test.rb +3 -11
- data/test/integration/share_management_test.rb +59 -10
- data/test/integration/status_test.rb +2 -0
- data/test/integration/test_helper.rb +40 -7
- data/test/unit/configuration_test.rb +82 -0
- data/test/unit/notifier_test.rb +165 -0
- data/test/unit/repository_path_test.rb +368 -0
- data/test/{repository_test.rb → unit/repository_test.rb} +426 -245
- data/test/unit/runner_test.rb +122 -0
- data/test/unit/search_test.rb +52 -0
- data/test/{test_helper.rb → unit/test_helper.rb} +5 -0
- metadata +138 -41
- data/lib/gitdocs/docfile.rb +0 -23
- data/test/configuration_test.rb +0 -41
- data/test/notifier_test.rb +0 -68
- data/test/runner_test.rb +0 -123
@@ -0,0 +1,166 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Class for executing File and Git operations on a specific path in the
|
4
|
+
# repository.
|
5
|
+
class Gitdocs::Repository::Path
|
6
|
+
attr_reader :relative_path
|
7
|
+
|
8
|
+
# @param [Gitdocs::Repository] repository
|
9
|
+
# @param [String] relative_path
|
10
|
+
def initialize(repository, relative_path)
|
11
|
+
@repository = repository
|
12
|
+
@relative_path = relative_path.gsub(/^\//, '')
|
13
|
+
@absolute_path = File.join(
|
14
|
+
File.absolute_path(@repository.root), @relative_path
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Write the content to the path and create any necessary directories.
|
19
|
+
#
|
20
|
+
# @param [String] content
|
21
|
+
# @param [String] commit_message
|
22
|
+
def write(content, commit_message)
|
23
|
+
FileUtils.mkdir_p(File.dirname(@absolute_path))
|
24
|
+
File.open(@absolute_path, 'w') { |f| f.puts(content) }
|
25
|
+
|
26
|
+
@repository.write_commit_message(commit_message)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Touch and path and create any necessary directories.
|
30
|
+
def touch
|
31
|
+
FileUtils.mkdir_p(File.dirname(@absolute_path))
|
32
|
+
FileUtils.touch(@absolute_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create the path as a directory.
|
36
|
+
def mkdir
|
37
|
+
FileUtils.mkdir_p(@absolute_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Remove the path, but only if it is a file.
|
41
|
+
def remove
|
42
|
+
return nil unless File.file?(@absolute_path)
|
43
|
+
FileUtils.rm(@absolute_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Boolean]
|
47
|
+
def text?
|
48
|
+
return false unless File.file?(@absolute_path)
|
49
|
+
mime_type = File.mime_type?(File.open(@absolute_path))
|
50
|
+
!!(mime_type =~ /text\/|x-empty/) # rubocop:disable DoubleNegation
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns file meta data based on relative file path
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# meta
|
57
|
+
# => { :author => "Nick", :size => 1000, :modified => ... }
|
58
|
+
#
|
59
|
+
# @raise [RuntimeError] if the file is not found in any commits
|
60
|
+
#
|
61
|
+
# @return [Hash<:author => String, :size => Integer, modified => Time>]
|
62
|
+
def meta
|
63
|
+
commit = @repository.last_commit_for(@relative_path)
|
64
|
+
|
65
|
+
# FIXME: This should actually just return an empty hash
|
66
|
+
fail("File #{@relative_path} not found") unless commit
|
67
|
+
|
68
|
+
{
|
69
|
+
author: commit.author[:name],
|
70
|
+
size: total_size,
|
71
|
+
modified: commit.author[:time]
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def exist?
|
76
|
+
File.exist?(@absolute_path)
|
77
|
+
end
|
78
|
+
|
79
|
+
def directory?
|
80
|
+
File.directory?(@absolute_path)
|
81
|
+
end
|
82
|
+
|
83
|
+
def absolute_path(ref = nil)
|
84
|
+
return @absolute_path unless ref
|
85
|
+
|
86
|
+
blob = @repository.blob_at(@relative_path, ref)
|
87
|
+
content = blob ? blob.text : ''
|
88
|
+
tmp_path = File.expand_path(File.basename(@relative_path), Dir.tmpdir)
|
89
|
+
File.open(tmp_path, 'w') { |f| f.puts content }
|
90
|
+
tmp_path
|
91
|
+
end
|
92
|
+
|
93
|
+
def readme_path
|
94
|
+
return nil unless directory?
|
95
|
+
Dir.glob(File.join(@absolute_path, 'README.{md}')).first
|
96
|
+
end
|
97
|
+
|
98
|
+
DirEntry = Struct.new(:name, :is_directory)
|
99
|
+
|
100
|
+
# @return [Array<DirEntry>] entries in the directory
|
101
|
+
# * excluding any git related directories
|
102
|
+
# * sorted by filename, ignoring any leading '.'s
|
103
|
+
def file_listing
|
104
|
+
return nil unless directory?
|
105
|
+
|
106
|
+
Dir.glob(File.join(@absolute_path, '{*,.*}'))
|
107
|
+
.reject { |x| x.match(/\/\.(\.|git|gitignore|gitmessage~)?$/) }
|
108
|
+
.sort_by { |x| File.basename(x).sub(/^\./, '') }
|
109
|
+
.map { |x| DirEntry.new(File.basename(x), File.directory?(x)) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def content
|
113
|
+
return nil unless File.file?(@absolute_path)
|
114
|
+
File.read(@absolute_path)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the revisions available for a particular file
|
118
|
+
#
|
119
|
+
# @param [String] file
|
120
|
+
#
|
121
|
+
# @return [Array<Hash>]
|
122
|
+
def revisions
|
123
|
+
@repository.commits_for(@relative_path, 100).map do |commit|
|
124
|
+
{
|
125
|
+
commit: commit.oid[0, 7],
|
126
|
+
subject: commit.message.split("\n")[0],
|
127
|
+
author: commit.author[:name],
|
128
|
+
date: commit.author[:time]
|
129
|
+
}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Revert file to the specified ref
|
134
|
+
#
|
135
|
+
# @param [String] ref
|
136
|
+
def revert(ref)
|
137
|
+
return unless ref
|
138
|
+
|
139
|
+
blob = @repository.blob_at(@relative_path, ref)
|
140
|
+
# Silently fail if the file/ref do not existing in the repository.
|
141
|
+
# Which is consistent with the original behaviour.
|
142
|
+
# TODO: should consider throwing an exception on this condition
|
143
|
+
return unless blob
|
144
|
+
|
145
|
+
write(blob.text, "Reverting '#{@relative_path}' to #{ref}")
|
146
|
+
end
|
147
|
+
|
148
|
+
#############################################################################
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def total_size
|
153
|
+
result =
|
154
|
+
if File.directory?(@absolute_path)
|
155
|
+
Dir[File.join(@absolute_path, '**', '*')].reduce(0) do |size, filename|
|
156
|
+
File.symlink?(filename) ? size : size + File.size(filename)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
File.symlink?(@absolute_path) ? 0 : File.size(@absolute_path)
|
160
|
+
end
|
161
|
+
|
162
|
+
# HACK: A value of 0 breaks the table sort for some reason
|
163
|
+
return -1 if result == 0
|
164
|
+
result
|
165
|
+
end
|
166
|
+
end
|
data/lib/gitdocs/runner.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
1
3
|
module Gitdocs
|
2
4
|
class Runner
|
3
5
|
def self.start_all(shares)
|
@@ -40,14 +42,16 @@ module Gitdocs
|
|
40
42
|
# Listen for changes in local repository
|
41
43
|
|
42
44
|
EM.defer(proc do
|
43
|
-
listener = Guard::Listener.select_and_init(
|
45
|
+
listener = Guard::Listener.select_and_init(
|
46
|
+
root, watch_all_modifications: true
|
47
|
+
)
|
44
48
|
listener.on_change do |directories|
|
45
49
|
directories.uniq!
|
46
50
|
directories.delete_if { |d| d =~ /\/\.git/ }
|
47
51
|
unless directories.empty?
|
48
52
|
EM.next_tick do
|
49
53
|
EM.defer(proc do
|
50
|
-
mutex.synchronize {
|
54
|
+
mutex.synchronize { sync_changes }
|
51
55
|
end, proc {})
|
52
56
|
end
|
53
57
|
end
|
@@ -61,70 +65,33 @@ module Gitdocs
|
|
61
65
|
end
|
62
66
|
|
63
67
|
def sync_changes
|
64
|
-
|
65
|
-
|
66
|
-
return if result.nil? || result == :no_remote
|
67
|
-
|
68
|
-
if result.kind_of?(String)
|
69
|
-
@notifier.error(
|
70
|
-
'There was a problem synchronizing this gitdoc',
|
71
|
-
"A problem occurred in #{root}:\n#{result}"
|
72
|
-
)
|
73
|
-
return
|
74
|
-
end
|
68
|
+
# Commit #################################################################
|
69
|
+
@repository.commit if @share.sync_type == 'full'
|
75
70
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@notifier.info(
|
81
|
-
"Updated with #{change_count(author_change_count)}",
|
82
|
-
"In '#{root}':\n#{author_list}"
|
83
|
-
)
|
84
|
-
end
|
85
|
-
else
|
86
|
-
#assert result.kind_of?(Array)
|
87
|
-
@notifier.warn(
|
88
|
-
'There were some conflicts',
|
89
|
-
result.map { |f| "* #{f}" }.join("\n")
|
90
|
-
)
|
91
|
-
end
|
71
|
+
# Fetch ##################################################################
|
72
|
+
fetch_result = @repository.fetch
|
73
|
+
return unless fetch_result == :ok
|
74
|
+
return if @share.sync_type == 'fetch'
|
92
75
|
|
93
|
-
|
94
|
-
|
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)
|
95
81
|
|
96
|
-
|
97
|
-
message_file = File.expand_path('.gitmessage~', root)
|
98
|
-
if File.exist?(message_file)
|
99
|
-
message = File.read(message_file)
|
100
|
-
File.delete(message_file)
|
101
|
-
else
|
102
|
-
message = 'Auto-commit from gitdocs'
|
103
|
-
end
|
104
|
-
|
105
|
-
@repository.commit(message)
|
82
|
+
# Push ###################################################################
|
106
83
|
result = @repository.push
|
107
|
-
|
108
|
-
|
109
|
-
level, title, message = case result
|
110
|
-
when :ok then [:info, "Pushed #{change_count(latest_author_count)}", "'#{root}' has been pushed"]
|
111
|
-
when :conflict then [:warn, "There was a conflict in #{root}, retrying", '']
|
112
|
-
else
|
113
|
-
# assert result.kind_of?(String)
|
114
|
-
[:error, "BAD Could not push changes in #{root}", result]
|
115
|
-
# TODO: need to add a status on shares so that the push problem can be
|
116
|
-
# displayed.
|
117
|
-
end
|
118
|
-
@notifier.send(level, title, message)
|
84
|
+
result = latest_author_count if result == :ok
|
85
|
+
@notifier.push_notification(result, root)
|
119
86
|
rescue => e
|
120
87
|
# Rescue any standard exceptions which come from the push related
|
121
88
|
# commands. This will prevent problems on a single share from killing
|
122
89
|
# the entire daemon.
|
123
|
-
@notifier.error("Unexpected error
|
124
|
-
# TODO: get logging and/or put the error message into a status field in the database
|
90
|
+
@notifier.error("Unexpected error syncing changes in #{root}", "#{e}")
|
125
91
|
end
|
126
92
|
|
127
93
|
############################################################################
|
94
|
+
|
128
95
|
private
|
129
96
|
|
130
97
|
# Update the author count for the last synced changes, and then update the
|
@@ -137,15 +104,5 @@ module Gitdocs
|
|
137
104
|
|
138
105
|
@repository.author_count(last_oid)
|
139
106
|
end
|
140
|
-
|
141
|
-
def change_count(count_or_hash)
|
142
|
-
count = if count_or_hash.respond_to?(:values)
|
143
|
-
count_or_hash .values.reduce(:+)
|
144
|
-
else
|
145
|
-
count_or_hash
|
146
|
-
end
|
147
|
-
|
148
|
-
"#{count} change#{count == 1 ? '' : 's'}"
|
149
|
-
end
|
150
107
|
end
|
151
108
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Gitdocs::Search
|
2
|
+
RepoDescriptor = Struct.new(:name, :index)
|
3
|
+
SearchResult = Struct.new(:file, :context)
|
4
|
+
|
5
|
+
# @param [Array<Gitdocs::Repository>] repositories
|
6
|
+
def initialize(repositories)
|
7
|
+
@repositories = repositories
|
8
|
+
end
|
9
|
+
|
10
|
+
def search(term)
|
11
|
+
results = {}
|
12
|
+
@repositories.each_with_index do |repository, index|
|
13
|
+
descriptor = RepoDescriptor.new(repository.root, index)
|
14
|
+
results[descriptor] = search_repository(repository, term)
|
15
|
+
end
|
16
|
+
results.delete_if { |_key, value| value.empty? }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def search_repository(repository, term)
|
22
|
+
return [] if term.empty?
|
23
|
+
|
24
|
+
results = []
|
25
|
+
repository.grep(term) do |file, context|
|
26
|
+
result = results.find { |s| s.file == file }
|
27
|
+
if result
|
28
|
+
result.context += ' ... ' + context
|
29
|
+
else
|
30
|
+
results << SearchResult.new(file, context)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
results
|
34
|
+
end
|
35
|
+
end
|
data/lib/gitdocs/server.rb
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Disable style checks that are invalid for Renee
|
4
|
+
# rubocop:disable Blocks, MultilineBlockChain
|
5
|
+
#
|
6
|
+
# TODO: extract the WebApp into its own class but until then...
|
7
|
+
# rubocop:disable LineLength, ClassLength, CyclomaticComplexity, BlockNesting
|
8
|
+
|
1
9
|
require 'thin'
|
2
10
|
require 'renee'
|
3
11
|
require 'coderay'
|
@@ -12,6 +20,17 @@ module Gitdocs
|
|
12
20
|
@manager = manager
|
13
21
|
@port = port.to_i
|
14
22
|
@repositories = repositories
|
23
|
+
@search = Gitdocs::Search.new(repositories)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.start_and_wait(manager, override_port, repositories)
|
27
|
+
return false unless manager.start_web_frontend
|
28
|
+
|
29
|
+
web_port = override_port || manager.web_frontend_port
|
30
|
+
server = Server.new(manager, web_port, repositories)
|
31
|
+
server.start
|
32
|
+
server.wait_for_start
|
33
|
+
true
|
15
34
|
end
|
16
35
|
|
17
36
|
def start
|
@@ -19,134 +38,152 @@ module Gitdocs
|
|
19
38
|
manager = @manager
|
20
39
|
Thin::Logging.debug = @manager.debug
|
21
40
|
Thin::Server.start('127.0.0.1', @port) do
|
22
|
-
use Rack::Static, urls:
|
41
|
+
use Rack::Static, urls: %w(/css /js /img /doc), root: File.expand_path('../public', __FILE__)
|
23
42
|
use Rack::MethodOverride
|
24
43
|
run Renee {
|
25
44
|
if request.path_info == '/'
|
26
|
-
if manager.
|
45
|
+
if manager.shares.size == 1
|
27
46
|
redirect! '/0'
|
28
47
|
else
|
29
|
-
render!
|
48
|
+
render!(
|
49
|
+
'home',
|
50
|
+
layout: 'app',
|
51
|
+
locals: { shares: manager.shares, nav_state: 'home' }
|
52
|
+
)
|
30
53
|
end
|
31
54
|
else
|
32
55
|
path 'settings' do
|
33
|
-
get.render!
|
56
|
+
get.render!(
|
57
|
+
'settings',
|
58
|
+
layout: 'app',
|
59
|
+
locals: { conf: manager, nav_state: 'settings' }
|
60
|
+
)
|
34
61
|
post do
|
35
|
-
|
36
|
-
manager.config.global.update_attributes(request.POST['config'])
|
37
|
-
request.POST['share'].each do |idx, share|
|
38
|
-
if remote_branch = share.delete('remote_branch')
|
39
|
-
share['remote_name'], share['branch_name'] = remote_branch.split('/', 2)
|
40
|
-
end
|
41
|
-
# Update paths
|
42
|
-
if share['path'] && !share['path'].empty?
|
43
|
-
shares[Integer(idx)].update_attributes(share)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
EM.add_timer(0.1) { manager.restart }
|
62
|
+
manager.update_all(request.POST)
|
47
63
|
redirect! '/settings'
|
48
64
|
end
|
49
65
|
end
|
50
66
|
|
51
67
|
path('search').get do
|
52
|
-
render!
|
68
|
+
render!(
|
69
|
+
'search',
|
70
|
+
layout: 'app',
|
71
|
+
locals: { results: @search.search(request.GET['q']), nav_state: nil }
|
72
|
+
)
|
53
73
|
end
|
54
74
|
|
55
75
|
path('shares') do
|
56
|
-
post do
|
57
|
-
Configuration::Share.create
|
58
|
-
redirect! '/settings'
|
59
|
-
end
|
60
|
-
|
61
76
|
var(:int) do |id|
|
62
77
|
delete do
|
63
|
-
|
64
|
-
|
65
|
-
share.destroy
|
66
|
-
redirect! '/settings'
|
78
|
+
halt(404) unless manager.remove_by_id(id)
|
79
|
+
redirect!('/settings')
|
67
80
|
end
|
68
81
|
end
|
69
82
|
end
|
70
83
|
|
71
84
|
var :int do |idx|
|
72
|
-
|
85
|
+
halt(404) unless repositories[idx]
|
86
|
+
path = Gitdocs::Repository::Path.new(
|
87
|
+
repositories[idx], URI.unescape(request.path_info)
|
88
|
+
)
|
89
|
+
|
90
|
+
mode = request.params['mode']
|
91
|
+
default_locals = {
|
92
|
+
idx: idx,
|
93
|
+
root: repositories[idx].root,
|
94
|
+
nav_state: nil
|
95
|
+
}
|
73
96
|
|
74
|
-
halt 404 if repository.nil?
|
75
|
-
file_path = URI.unescape(request.path_info)
|
76
|
-
expanded_path = File.expand_path(".#{file_path}", repository.root)
|
77
|
-
message_file = File.expand_path('.gitmessage~', repository.root)
|
78
|
-
halt 400 unless expanded_path[/^#{Regexp.quote(repository.root)}/]
|
79
|
-
parent = File.dirname(file_path)
|
80
|
-
parent = '' if parent == '/'
|
81
|
-
parent = nil if parent == '.'
|
82
|
-
locals = { idx: idx, parent: parent, root: repository.root, file_path: expanded_path, nav_state: nil }
|
83
|
-
mime = File.mime_type?(File.open(expanded_path)) if File.file?(expanded_path)
|
84
|
-
mode = request.params['mode']
|
85
97
|
if mode == 'meta' # Meta
|
86
|
-
halt 200, { 'Content-Type' => 'application/json' }, [
|
98
|
+
halt 200, { 'Content-Type' => 'application/json' }, [path.meta.to_json]
|
87
99
|
elsif mode == 'save' # Saving
|
88
|
-
|
89
|
-
|
90
|
-
redirect! '/' + idx.to_s + file_path
|
100
|
+
path.write(request.params['data'], request.params['message'])
|
101
|
+
redirect!("/#{idx}/#{path.relative_path}")
|
91
102
|
elsif mode == 'upload' # Uploading
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
rendered_readme =
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
103
|
+
file = request.params['file']
|
104
|
+
halt 404 unless file
|
105
|
+
tempfile = file[:tempfile]
|
106
|
+
filename = file[:filename]
|
107
|
+
FileUtils.mv(tempfile.path, path.absolute_path)
|
108
|
+
redirect!("/#{idx}/#{path.relative_path}/#{filename}")
|
109
|
+
elsif !path.exist? && !request.params['dir'] # edit for non-existent file
|
110
|
+
path.touch
|
111
|
+
redirect!("/#{idx}/#{path.relative_path}?mode=edit")
|
112
|
+
elsif !path.exist? && request.params['dir'] # create directory
|
113
|
+
path.mkdir
|
114
|
+
redirect!("/#{idx}/#{path.relative_path}")
|
115
|
+
elsif path.directory? # list directory
|
116
|
+
rendered_readme =
|
117
|
+
if path.readme_path
|
118
|
+
<<-EOS.gusb(/^\s+/, '')
|
119
|
+
<h3>#{File.basename(path.readme_path)}</h3>
|
120
|
+
<div class="tilt">#{render(path.readme_path)}</div>
|
121
|
+
EOS
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
render!(
|
126
|
+
'dir',
|
127
|
+
layout: 'app',
|
128
|
+
locals: default_locals.merge(
|
129
|
+
contents: path.file_listing,
|
130
|
+
rendered_readme: rendered_readme
|
131
|
+
)
|
132
|
+
)
|
110
133
|
elsif mode == 'revisions' # list revisions
|
111
|
-
|
112
|
-
|
134
|
+
render!(
|
135
|
+
'revisions',
|
136
|
+
layout: 'app',
|
137
|
+
locals: default_locals.merge(revisions: path.revisions)
|
138
|
+
)
|
113
139
|
elsif mode == 'revert' # revert file
|
114
|
-
|
115
|
-
|
116
|
-
repository.file_revert(file_path, revision)
|
117
|
-
end
|
118
|
-
redirect! '/' + idx.to_s + file_path
|
140
|
+
path.revert(request.params['revision'])
|
141
|
+
redirect!("/#{idx}/#{path.relative_path}")
|
119
142
|
elsif mode == 'delete' # delete file
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
143
|
+
path.remove
|
144
|
+
parent = File.dirname(path.relative_path)
|
145
|
+
parent = '' if parent == '/'
|
146
|
+
parent = nil if parent == '.'
|
147
|
+
redirect!("/#{idx}#{parent}")
|
148
|
+
elsif mode == 'edit' && path.text? # edit file
|
149
|
+
render!(
|
150
|
+
'edit',
|
151
|
+
layout: 'app',
|
152
|
+
locals: default_locals.merge(contents: path.content)
|
153
|
+
)
|
125
154
|
elsif mode != 'raw' # render file
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
155
|
+
revision_path = path.absolute_path(request.params['revision'])
|
156
|
+
contents =
|
157
|
+
begin # attempting to render file
|
158
|
+
%(<div class="tilt">#{render(revision_path)}</div>)
|
159
|
+
rescue RuntimeError # not tilt supported
|
160
|
+
if path.text?
|
161
|
+
<<-EOS.gsub(/^\s+/, '')
|
162
|
+
<pre class="CodeRay">
|
163
|
+
#{CodeRay.scan_file(revision_path).encode(:html)}
|
164
|
+
</pre>
|
165
|
+
EOS
|
166
|
+
else
|
167
|
+
%(<embed class="inline-file" src="/#{idx}#{request.path_info}?mode=raw"></embed>)
|
168
|
+
end
|
135
169
|
end
|
136
|
-
|
137
|
-
|
170
|
+
render!(
|
171
|
+
'file',
|
172
|
+
layout: 'app',
|
173
|
+
locals: default_locals.merge(contents: contents)
|
174
|
+
)
|
138
175
|
else # other file
|
139
|
-
run! Rack::File.new(
|
176
|
+
run! Rack::File.new(repositories[idx].root)
|
140
177
|
end
|
141
178
|
end
|
142
179
|
end
|
143
180
|
}.setup {
|
144
|
-
views_path
|
181
|
+
views_path(File.expand_path('../views', __FILE__))
|
145
182
|
}
|
146
183
|
end
|
147
184
|
end
|
148
185
|
|
149
|
-
def
|
186
|
+
def wait_for_start
|
150
187
|
wait_for_web_server = proc do
|
151
188
|
i = 0
|
152
189
|
begin
|