gitdocs 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +57 -16
- data/gitdocs.gemspec +1 -0
- data/lib/gitdocs.rb +25 -9
- data/lib/gitdocs/cli.rb +0 -6
- data/lib/gitdocs/public/css/app.css +46 -0
- data/lib/gitdocs/public/css/reset.css +59 -0
- data/lib/gitdocs/runner.rb +53 -36
- data/lib/gitdocs/server.rb +15 -16
- data/lib/gitdocs/version.rb +1 -1
- data/lib/gitdocs/views/app.haml +9 -0
- data/lib/gitdocs/views/dir.haml +20 -0
- data/lib/gitdocs/views/file.haml +18 -0
- data/lib/gitdocs/views/home.haml +6 -0
- data/test/runner_test.rb +13 -1
- data/test/test_helper.rb +23 -18
- metadata +27 -10
data/README.md
CHANGED
@@ -1,12 +1,47 @@
|
|
1
1
|
# Gitdocs
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
Open-source dropbox alternative powered by git. Collaborate on files and tasks without any extra hassle.
|
4
|
+
gitdocs will automatically keep everyone's repos in sync by pushing and pulling changes.
|
5
|
+
This allows any git repo to be used as a collaborative task list, file share, or wiki for a team.
|
6
|
+
Supports a web front-end allowing each repo to be accessed through your browser.
|
7
|
+
|
8
|
+
**Note:** Right now, gitdocs only supports Mac OSX using fsevent. Linux and windows support are coming very soon, so check back here again.
|
9
|
+
|
10
|
+
## Why?
|
11
|
+
|
12
|
+
Why should you use gitdocs for your file and doc sharing needs?
|
13
|
+
|
14
|
+
* **Open** - gitdocs is entirely open-source under the MIT license
|
15
|
+
* **Simple** - gitdocs is the simplest thing that works in both setup and usage
|
16
|
+
* **Secure** - gitdocs leverages git (and existing providers like github) to store your data safely.
|
17
|
+
* **Versatile** - share task lists, code snippets, images, files or just use it as a wiki (with our web front-end)
|
18
|
+
* **Portable** - access your files anywhere you can use git (with upcoming cross-platform support)
|
19
|
+
|
20
|
+
The best part is that giving this a try is quick and easy.
|
21
|
+
|
22
|
+
## Quick Start
|
23
|
+
|
24
|
+
Gitdocs monitors any number of directories for changes and keeps them automatically synced. You can either add
|
25
|
+
existing git directories to be watched or have gitdocs pull down a repository for you.
|
26
|
+
|
27
|
+
There are plenty of great git hosting providers to safely store your data and you can trust the data is stored securely.
|
28
|
+
If you want a private repo to use with gitdocs, we recommend you check out [BitBucket](https://bitbucket.org/) which
|
29
|
+
provides free private git repos after registration.
|
30
|
+
|
31
|
+
To get started with gitdocs and a secure private bitbucket repo:
|
32
|
+
|
33
|
+
- `gem install gitdocs`
|
34
|
+
- `gitdocs start`
|
35
|
+
- Login to [BitBucket](https://bitbucket.org/) and add a new private repo named 'docs'
|
36
|
+
- Setup your SSH Key under [Account](https://bitbucket.org/account/) for ssh access
|
37
|
+
- `gitdocs create ~/Documents/gitdocs git@bitbucket.org:username/docs.git`
|
38
|
+
|
39
|
+
There you go! Now just start adding and editing files within the directory and they will be automatically
|
40
|
+
synchronized across all gitdocs-enabled clients.
|
6
41
|
|
7
42
|
## Installation
|
8
43
|
|
9
|
-
Install
|
44
|
+
Requires ruby and rubygems. Install as a gem:
|
10
45
|
|
11
46
|
```
|
12
47
|
gem install gitdocs
|
@@ -22,8 +57,7 @@ to enable Growl support (other platforms coming soon).
|
|
22
57
|
|
23
58
|
## Usage
|
24
59
|
|
25
|
-
|
26
|
-
existing git directories for monitoring or have gitdocs pull down a repository to monitor.
|
60
|
+
### Monitoring Repos
|
27
61
|
|
28
62
|
You can add existing folders to watch:
|
29
63
|
|
@@ -44,6 +78,8 @@ gitdocs rm my/path/to/watch
|
|
44
78
|
gitdocs clear
|
45
79
|
```
|
46
80
|
|
81
|
+
### Starting Gitdocs
|
82
|
+
|
47
83
|
You need to start gitdocs in order for the monitoring to work:
|
48
84
|
|
49
85
|
```
|
@@ -56,29 +92,33 @@ If the start command fails, you can run again with a debug flag:
|
|
56
92
|
gitdocs start -D
|
57
93
|
```
|
58
94
|
|
59
|
-
|
95
|
+
Once gitdocs has been started and is monitoring the correct directories, simply start editing or adding files to your
|
96
|
+
designated git repos and changes will be automatically pushed. Gitdocs can be easily stopped or restarted:
|
60
97
|
|
61
98
|
```
|
62
99
|
gitdocs stop
|
63
100
|
gitdocs restart
|
64
101
|
```
|
65
102
|
|
103
|
+
### Exploring Gitdocs
|
104
|
+
|
66
105
|
For an overview of gitdocs current status, run:
|
67
106
|
|
68
107
|
```
|
69
108
|
gitdocs status
|
70
109
|
```
|
71
110
|
|
72
|
-
|
73
|
-
designated git repos. Changes will be automatically pushed and pulled to your local repos.
|
111
|
+
To explore the repos in your browser, simply visit `http://localhost:8888` for access to all your docs within the browser.
|
74
112
|
|
75
|
-
|
113
|
+
### Conflict Resolution
|
76
114
|
|
77
|
-
|
78
|
-
|
79
|
-
|
115
|
+
Proper conflict resolution is an important part of any good doc and file collaboration tool.
|
116
|
+
In most cases, git does a good job of handling file merges for you. Still, what about cases where the conflict cannot be
|
117
|
+
resolved automatically?
|
80
118
|
|
81
|
-
|
119
|
+
Don't worrry, gitdocs makes this easy. In the event of a conflict, **all the different versions
|
120
|
+
of a document are stored** in the repo tagged with the **git sha** for the commit for each variation. The members
|
121
|
+
of the repo can then compare all versions and resolve the conflict.
|
82
122
|
|
83
123
|
## Planned Features
|
84
124
|
|
@@ -87,7 +127,6 @@ Gitdocs is a young project but we have big plans for it including:
|
|
87
127
|
- A web front-end UI for file uploading and editing of files (with rich text editor and syntax highlighting)
|
88
128
|
- Local-area peer-to-peer syncing, avoid 'polling' in cases where we can using a messaging protocol.
|
89
129
|
- Click-to-share instant access granting file access to users using a local tunnel or other means.
|
90
|
-
- Better conflict-resolution behavior on updates (maintain both versions of a file)
|
91
130
|
- Support for linux and windows platforms (coming soon), and maybe android and iOS as well?
|
92
131
|
|
93
132
|
## Prior Projects
|
@@ -96,9 +135,11 @@ Gitdocs is a fresh project that we spiked on in a few days time. Our primary goa
|
|
96
135
|
but provide the features that makes dropbox great. If you are interested in other Dropbox alternatives, be sure to checkout our notes below:
|
97
136
|
|
98
137
|
* [SparkleShare](http://sparkleshare.org/) is an open source, self-hosted Dropbox alternative. Nice project and a great alternative but has a lot of dependencies,
|
99
|
-
|
138
|
+
and lacks some of the features we have planned for gitdocs in the near future. More mature project, so be sure to take a look.
|
100
139
|
* [DVCS-Autosync](http://mayrhofer.eu.org/dvcs-autosync) is a project to create an open source replacement for Dropbox based on distributed version control systems.
|
101
140
|
Very similar project but again we have features planned that are out of scope (local tunnel file sharing, complete web ui for browsing, uploading and editing).
|
102
141
|
* [Lipsync](https://github.com/philcryer/lipsync) is another similar project. We haven't looked at this too closely, but thought we would mention it in this list.
|
142
|
+
* [bitpocket](https://github.com/sickill/bitpocket) is a project that uses rsync to synchronize data. Interesting concept, but
|
143
|
+
lacks revision history, author tracking, etc and we have features planned that are out of scope for this project
|
103
144
|
|
104
145
|
If any other open-source dropbox alternatives are available, we would love to hear about them so let us know!
|
data/gitdocs.gemspec
CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_dependency 'dante', '~> 0.0.4'
|
27
27
|
s.add_dependency 'growl', '~> 1.0.3'
|
28
28
|
s.add_dependency 'yajl-ruby'
|
29
|
+
s.add_dependency 'haml'
|
29
30
|
|
30
31
|
s.add_development_dependency 'minitest', "~> 2.6.1"
|
31
32
|
s.add_development_dependency 'rake'
|
data/lib/gitdocs.rb
CHANGED
@@ -9,21 +9,37 @@ require 'growl'
|
|
9
9
|
require 'yajl'
|
10
10
|
require 'dante'
|
11
11
|
|
12
|
+
|
12
13
|
module Gitdocs
|
13
|
-
|
14
|
+
|
15
|
+
DEBUG = ENV['DEBUG']
|
16
|
+
|
17
|
+
def self.run(config_root = nil, debug = DEBUG)
|
14
18
|
loop do
|
15
|
-
|
19
|
+
config = Configuration.new(config_root)
|
16
20
|
puts "Gitdocs v#{VERSION}" if debug
|
17
|
-
puts "Using configuration root: '#{
|
18
|
-
puts "Watch paths: #{
|
19
|
-
|
20
|
-
|
21
|
+
puts "Using configuration root: '#{config.config_root}'" if debug
|
22
|
+
puts "Watch paths: #{config.paths.join(", ")}" if debug
|
23
|
+
# Start the repo watchers
|
24
|
+
runners = []
|
25
|
+
threads = config.paths.map do |path|
|
26
|
+
t = Thread.new(runners) { |r|
|
27
|
+
runner = Runner.new(path)
|
28
|
+
r << runner
|
29
|
+
runner.run
|
30
|
+
}
|
21
31
|
t.abort_on_exception = true
|
22
32
|
t
|
23
33
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
34
|
+
sleep 1
|
35
|
+
unless defined?(pid) && pid
|
36
|
+
# Start the web front-end
|
37
|
+
pid = fork { Server.new(*runners).start }
|
38
|
+
at_exit { Process.kill("KILL", pid) rescue nil }
|
39
|
+
end
|
40
|
+
puts "Watch threads: #{threads.map { |t| "Thread status: '#{t.status}', running: #{t.alive?}" }}" if debug
|
41
|
+
puts "Joined #{threads.size} watch threads...running" if debug
|
42
|
+
threads.each(&:join)
|
27
43
|
sleep(60)
|
28
44
|
end
|
29
45
|
end
|
data/lib/gitdocs/cli.rb
CHANGED
@@ -75,12 +75,6 @@ module Gitdocs
|
|
75
75
|
say self.config.paths.map { |p| " - #{p}" }.join("\n")
|
76
76
|
end
|
77
77
|
|
78
|
-
desc "serve", "Serves web frontend for files"
|
79
|
-
def serve
|
80
|
-
puts "Serving docs..."
|
81
|
-
Gitdocs::Server.new(*self.config.paths.map{|p| Gitdocs::Runner.new(p)}).start
|
82
|
-
end
|
83
|
-
|
84
78
|
desc "config", "Configuration options for gitdocs"
|
85
79
|
def config
|
86
80
|
# TODO make this work
|
@@ -0,0 +1,46 @@
|
|
1
|
+
body {
|
2
|
+
margin-left: 20%;
|
3
|
+
margin-right: 20%;
|
4
|
+
font-family: Arial, Verdana;
|
5
|
+
}
|
6
|
+
|
7
|
+
body h1, h2 {
|
8
|
+
margin: 0;
|
9
|
+
padding-bottom: 0.8em;
|
10
|
+
font-weight: bold;
|
11
|
+
}
|
12
|
+
body h1 { font-size: 2em; }
|
13
|
+
body h2 { font-size: 1.5em; margin-top: 0.2em; }
|
14
|
+
|
15
|
+
body .container {
|
16
|
+
min-width: 500px;
|
17
|
+
background: #FCEA97;
|
18
|
+
border-style: solid;
|
19
|
+
border-width: 5px;
|
20
|
+
border-color: #FFC003;
|
21
|
+
border-radius: 15px;
|
22
|
+
padding: 2em;
|
23
|
+
margin-top: 1.2em;
|
24
|
+
}
|
25
|
+
|
26
|
+
body a { text-decoration: none; color: blue; }
|
27
|
+
body a:hover { text-decoration: underline; }
|
28
|
+
|
29
|
+
body .contents {
|
30
|
+
overflow: auto;
|
31
|
+
background-color: #F8F8F8;
|
32
|
+
border: 3px solid #EFEFEF;
|
33
|
+
padding: 1.2em;
|
34
|
+
margin: 0;
|
35
|
+
font-family: ;
|
36
|
+
}
|
37
|
+
|
38
|
+
body .contents h1 {
|
39
|
+
font-size: 2em;
|
40
|
+
padding-bottom: 0.8em;
|
41
|
+
}
|
42
|
+
|
43
|
+
body .contents h2 {
|
44
|
+
font-size: 1.4em;
|
45
|
+
padding-bottom: 0.6em;
|
46
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
/*
|
2
|
+
Copyright (c) 2010, Yahoo! Inc. All rights reserved.
|
3
|
+
Code licensed under the BSD License:
|
4
|
+
http://developer.yahoo.com/yui/license.html
|
5
|
+
version: 3.1.1
|
6
|
+
build: 47
|
7
|
+
*/
|
8
|
+
html {
|
9
|
+
color: #000;
|
10
|
+
background: #FFF; }
|
11
|
+
|
12
|
+
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td {
|
13
|
+
margin: 0;
|
14
|
+
padding: 0; }
|
15
|
+
|
16
|
+
table {
|
17
|
+
border-collapse: collapse;
|
18
|
+
border-spacing: 0; }
|
19
|
+
|
20
|
+
fieldset, img {
|
21
|
+
border: 0; }
|
22
|
+
|
23
|
+
address, caption, cite, code, dfn, em, strong, th, var {
|
24
|
+
font-style: normal;
|
25
|
+
font-weight: normal; }
|
26
|
+
|
27
|
+
li {
|
28
|
+
list-style: none; }
|
29
|
+
|
30
|
+
caption, th {
|
31
|
+
text-align: left; }
|
32
|
+
|
33
|
+
h1, h2, h3, h4, h5, h6 {
|
34
|
+
font-size: 100%;
|
35
|
+
font-weight: normal; }
|
36
|
+
|
37
|
+
q:before, q:after {
|
38
|
+
content: ''; }
|
39
|
+
|
40
|
+
abbr, acronym {
|
41
|
+
border: 0;
|
42
|
+
font-variant: normal; }
|
43
|
+
|
44
|
+
sup {
|
45
|
+
vertical-align: text-top; }
|
46
|
+
|
47
|
+
sub {
|
48
|
+
vertical-align: text-bottom; }
|
49
|
+
|
50
|
+
input, textarea, select {
|
51
|
+
font-family: inherit;
|
52
|
+
font-size: inherit;
|
53
|
+
font-weight: inherit; }
|
54
|
+
|
55
|
+
input, textarea, select {
|
56
|
+
*font-size: 100%; }
|
57
|
+
|
58
|
+
legend {
|
59
|
+
color: #000; }
|
data/lib/gitdocs/runner.rb
CHANGED
@@ -7,70 +7,88 @@ module Gitdocs
|
|
7
7
|
out, status = sh_with_code "which growlnotify"
|
8
8
|
@use_growl = opts && opts.key?(:growl) ? opts[:growl] : status.success?
|
9
9
|
@polling_interval = opts && opts[:polling_interval] || 15
|
10
|
-
@icon = File.expand_path("
|
10
|
+
@icon = File.expand_path("../../img/icon.png", __FILE__)
|
11
11
|
end
|
12
12
|
|
13
13
|
def run
|
14
14
|
info("Running gitdocs!", "Running gitdocs in `#{@root}'")
|
15
|
-
@current_remote = sh_string("git config branch.`git branch | grep '^\*' | sed -e 's/\* //'`.remote",
|
16
|
-
@current_branch = sh_string("git branch | grep '^\*' | sed -e 's/\* //'",
|
17
|
-
@current_revision =
|
15
|
+
@current_remote = sh_string("git config branch.`git branch | grep '^\*' | sed -e 's/\* //'`.remote", 'origin')
|
16
|
+
@current_branch = sh_string("git branch | grep '^\*' | sed -e 's/\* //'", 'master')
|
17
|
+
@current_revision = sh_string("git rev-parse HEAD")
|
18
|
+
|
18
19
|
mutex = Mutex.new
|
20
|
+
# Pull changes from remote repository
|
19
21
|
Thread.new do
|
20
22
|
loop do
|
21
|
-
mutex.synchronize
|
22
|
-
begin
|
23
|
-
out, status = sh_with_code("git fetch --all && git merge #{@current_remote}/#{@current_branch}")
|
24
|
-
if status.success?
|
25
|
-
changes = get_latest_changes
|
26
|
-
unless changes.empty?
|
27
|
-
info("Updated with #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been updated")
|
28
|
-
end
|
29
|
-
else
|
30
|
-
warn("Error attempting to pull", out)
|
31
|
-
end
|
32
|
-
push_changes
|
33
|
-
rescue Exception
|
34
|
-
error("There was an error", $!.message) rescue nil
|
35
|
-
end
|
36
|
-
end
|
23
|
+
mutex.synchronize { sync_changes }
|
37
24
|
sleep @polling_interval
|
38
25
|
end
|
39
|
-
end
|
26
|
+
end.abort_on_exception = true
|
27
|
+
|
28
|
+
# Listen for changes in local repository
|
40
29
|
listener = FSEvent.new
|
41
30
|
listener.watch(@root) do |directories|
|
42
31
|
directories.uniq!
|
43
32
|
directories.delete_if {|d| d =~ /\/\.git/}
|
44
33
|
unless directories.empty?
|
45
|
-
mutex.synchronize
|
46
|
-
push_changes
|
47
|
-
end
|
34
|
+
mutex.synchronize { push_changes }
|
48
35
|
end
|
49
36
|
end
|
50
37
|
at_exit { listener.stop }
|
51
38
|
listener.run
|
52
39
|
end
|
53
40
|
|
41
|
+
def sync_changes
|
42
|
+
out, status = sh_with_code("git fetch --all && git merge #{@current_remote}/#{@current_branch}")
|
43
|
+
if status.success?
|
44
|
+
changes = get_latest_changes
|
45
|
+
unless changes.empty?
|
46
|
+
author_list = changes.inject(Hash.new{|h, k| h[k] = 0}) {|h, c| h[c['author']] += 1; h}.to_a.sort{|a,b| b[1] <=> a[1]}.map{|(name, count)| "* #{name} (#{count} change#{count == 1 ? '' : 's'})"}.join("\n")
|
47
|
+
info("Updated with #{changes.size} change#{changes.size == 1 ? '' : 's'}", "In `#{@root}':\n#{author_list}")
|
48
|
+
end
|
49
|
+
push_changes
|
50
|
+
elsif out[/CONFLICT/]
|
51
|
+
conflicted_files = sh("git ls-files -u --full-name -z").split("\0").
|
52
|
+
inject(Hash.new{|h, k| h[k] = []}) {|h, line|
|
53
|
+
parts = line.split(/\t/)
|
54
|
+
h[parts.last] << parts.first.split(/ /)
|
55
|
+
h
|
56
|
+
}
|
57
|
+
warn("There were some conflicts", "#{conflicted_files.keys.map{|f| "* #{f}"}.join("\n")}")
|
58
|
+
conflicted_files.each do |conflict, ids|
|
59
|
+
conflict_start, conflict_end = conflict.scan(/(.*?)(|\.[^\.]+)$/).first
|
60
|
+
puts "solving #{conflict} with #{ids.inspect}"
|
61
|
+
ids.each do |(mode, sha, id)|
|
62
|
+
author = " original" if id == "1"
|
63
|
+
system("cd #{@root} && git show :#{id}:#{conflict} > '#{conflict_start} (#{sha[0..6]}#{author})#{conflict_end}'")
|
64
|
+
end
|
65
|
+
system("cd #{@root} && git rm #{conflict}") or raise
|
66
|
+
end
|
67
|
+
push_changes
|
68
|
+
elsif sh_string("git remote").nil? # no remote to pull from
|
69
|
+
# Do nothing, no remote repo yet
|
70
|
+
else
|
71
|
+
error("There was a problem synchronizing this gitdoc", "A problem occurred in #{@root}:\n#{out}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
54
75
|
def push_changes
|
55
76
|
sh 'find . -type d -regex ``./[^.].*'' -empty -exec touch \'{}/.gitignore\' \;'
|
56
77
|
sh 'git add .'
|
57
78
|
# TODO make this message nicer
|
58
79
|
sh "git commit -a -m'Auto-commit from gitdocs'" unless sh("git status -s").strip.empty?
|
59
|
-
if @current_revision.nil?
|
80
|
+
if @current_revision.nil? || sh('git status')[/branch is ahead/]
|
60
81
|
out, code = sh_with_code("git push #{@current_remote} #{@current_branch}")
|
61
82
|
if code.success?
|
62
83
|
changes = get_latest_changes
|
63
84
|
info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
|
85
|
+
elsif @current_revision.nil?
|
86
|
+
# ignorable
|
87
|
+
elsif out[/\[rejected\]/]
|
88
|
+
warn("There was a conflict in #{@root}, retrying", "")
|
64
89
|
else
|
65
|
-
error("Could not push changes", out)
|
66
|
-
|
67
|
-
elsif sh('git status')[/branch is ahead/]
|
68
|
-
out, code = sh_with_code("git push")
|
69
|
-
if code.success?
|
70
|
-
changes = get_latest_changes
|
71
|
-
info("Pushed #{changes.size} change#{changes.size == 1 ? '' : 's'}", "`#{@root}' has been pushed")
|
72
|
-
else
|
73
|
-
error("Could not push changes", out)
|
90
|
+
error("BAD Could not push changes in #{@root}", out)
|
91
|
+
exit
|
74
92
|
end
|
75
93
|
end
|
76
94
|
end
|
@@ -115,11 +133,10 @@ module Gitdocs
|
|
115
133
|
else
|
116
134
|
Kernel.warn("#{title}: #{msg}")
|
117
135
|
end
|
118
|
-
raise
|
119
136
|
end
|
120
137
|
|
121
138
|
# sh_string("git config branch.`git branch | grep '^\*' | sed -e 's/\* //'`.remote", "origin")
|
122
|
-
def sh_string(cmd, default)
|
139
|
+
def sh_string(cmd, default=nil)
|
123
140
|
val = sh(cmd).strip rescue nil
|
124
141
|
(val.nil? || val.empty?) ? default : val
|
125
142
|
end
|
data/lib/gitdocs/server.rb
CHANGED
@@ -10,17 +10,10 @@ module Gitdocs
|
|
10
10
|
def start(port = 8888)
|
11
11
|
gds = @gitdocs
|
12
12
|
Thin::Server.start('127.0.0.1', port) do
|
13
|
+
use Rack::Static, :urls => ['/css', '/img', '/doc'], :root => File.expand_path("../public", __FILE__)
|
13
14
|
run Renee {
|
14
15
|
if request.path_info == '/'
|
15
|
-
|
16
|
-
<html><body>
|
17
|
-
<table>
|
18
|
-
<% gds.each_with_index do |gd, idx| %>
|
19
|
-
<tr><a href="/<%=idx%>"><%=gd.root%></a></tr>
|
20
|
-
<% end %>
|
21
|
-
</table>
|
22
|
-
</body></html>
|
23
|
-
EOT
|
16
|
+
render! "home", :layout => 'app', :locals => {:gds => gds}
|
24
17
|
else
|
25
18
|
var :int do |idx|
|
26
19
|
gd = gds[idx]
|
@@ -28,17 +21,23 @@ module Gitdocs
|
|
28
21
|
expanded_path = File.expand_path(".#{request.path_info}", gd.root)
|
29
22
|
halt 400 unless expanded_path[/^#{Regexp.quote(gd.root)}/]
|
30
23
|
halt 404 unless File.exist?(expanded_path)
|
24
|
+
parent = File.dirname(request.path_info)
|
25
|
+
parent = '' if parent == '/'
|
26
|
+
parent = nil if parent == '.'
|
27
|
+
locals = {:idx => idx, :parent => parent, :root => gd.root, :file_path => expanded_path}
|
31
28
|
if File.directory?(expanded_path)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
contents = Dir[File.join(gd.root, request.path_info, '*')]
|
30
|
+
render! "dir", :layout => 'app', :locals => locals.merge(:contents => contents)
|
31
|
+
elsif request.params['mode'] != 'raw' && `file -I #{expanded_path}`.strip.match(%r{text/}) # render file
|
32
|
+
contents = Tilt.new(expanded_path).render rescue "<pre>#{File.read(expanded_path)}</pre>"
|
33
|
+
render! "file", :layout => 'app', :locals => locals.merge(:contents => contents)
|
34
|
+
else # other file
|
35
|
+
run! Rack::File.new(gd.root)
|
39
36
|
end
|
40
37
|
end
|
41
38
|
end
|
39
|
+
}.setup {
|
40
|
+
views_path File.expand_path("../views", __FILE__)
|
42
41
|
}
|
43
42
|
end
|
44
43
|
end
|
data/lib/gitdocs/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
%h1
|
2
|
+
=root
|
3
|
+
|
4
|
+
- if parent
|
5
|
+
%a{ :href => parent.empty? ? "/#{idx}" : "/#{idx}#{parent}", :class => "parent" }
|
6
|
+
↪
|
7
|
+
= parent.empty? ? '/' : parent
|
8
|
+
- else
|
9
|
+
%a{ :href => "/", :class => "parent" }
|
10
|
+
↪ Back to selection
|
11
|
+
|
12
|
+
%h2
|
13
|
+
=request.path_info.empty? ? '/' : request.path_info
|
14
|
+
|
15
|
+
%table
|
16
|
+
-contents.each_with_index do |f, i|
|
17
|
+
%tr
|
18
|
+
%td
|
19
|
+
%a{ :href => "/#{idx}#{request.path_info}/#{File.basename(f)}" }
|
20
|
+
="#{request.path_info}/#{File.basename(f)}"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
%h1
|
2
|
+
= root
|
3
|
+
|
4
|
+
- if parent
|
5
|
+
%a{ :href => parent.empty? ? "/#{idx}" : "/#{idx}#{parent}", :class => "parent" }
|
6
|
+
↪
|
7
|
+
= parent.empty? ? '/' : parent
|
8
|
+
- else
|
9
|
+
%a{ :href => "/", :class => "parent" }
|
10
|
+
↪ Back to selection
|
11
|
+
|
12
|
+
%h2
|
13
|
+
= request.path_info.empty? ? '/' : request.path_info
|
14
|
+
%a{ :href => "?mode=raw" }
|
15
|
+
(raw)
|
16
|
+
|
17
|
+
.contents
|
18
|
+
= preserve contents
|
data/test/runner_test.rb
CHANGED
@@ -4,10 +4,22 @@ describe "gitdocs runner" do
|
|
4
4
|
it "should clone files" do
|
5
5
|
with_clones(3) do |clone1, clone2, clone3|
|
6
6
|
File.open(File.join(clone1, "test"), 'w') { |f| f << "testing" }
|
7
|
-
sleep
|
7
|
+
sleep 3
|
8
8
|
assert_equal "testing", File.read(File.join(clone1, "test"))
|
9
9
|
assert_equal "testing", File.read(File.join(clone2, "test"))
|
10
10
|
assert_equal "testing", File.read(File.join(clone3, "test"))
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
14
|
+
it "should resolve conflicts files" do
|
15
|
+
with_clones(3) do |clone1, clone2, clone3|
|
16
|
+
File.open(File.join(clone1, "test.txt"), 'w') { |f| f << "testing" }
|
17
|
+
sleep 3
|
18
|
+
File.open(File.join(clone1, "test.txt"), 'w') { |f| f << "testing\n1" }
|
19
|
+
File.open(File.join(clone2, "test.txt"), 'w') { |f| f << "testing\n2" }
|
20
|
+
sleep 3
|
21
|
+
assert_includes 2..3, Dir[File.join(clone2, "*.txt")].to_a.size
|
22
|
+
assert_includes 2..3, Dir[File.join(clone3, "*.txt")].to_a.size
|
23
|
+
end
|
24
|
+
end
|
13
25
|
end
|
data/test/test_helper.rb
CHANGED
@@ -15,20 +15,23 @@ module Kernel
|
|
15
15
|
# Redirect standard out, standard error and the buffered logger for sprinkle to StringIO
|
16
16
|
# capture_stdout { any_commands; you_want } => "all output from the commands"
|
17
17
|
def capture_out
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
18
|
+
yield and return if ENV['DEBUG']
|
19
|
+
begin
|
20
|
+
old_out, old_err = STDOUT.dup, STDERR.dup
|
21
|
+
stdout_read, stdout_write = IO.pipe
|
22
|
+
stderr_read, stderr_write = IO.pipe
|
23
|
+
$stdout.reopen(stdout_write)
|
24
|
+
$stderr.reopen(stderr_write)
|
25
|
+
yield
|
26
|
+
stdout_write.close
|
27
|
+
stderr_write.close
|
28
|
+
out = stdout_read.rewind && stdout_read.read rescue nil
|
29
|
+
err = stderr_read.rewind && stderr_read.read rescue nil
|
30
|
+
[out, err]
|
31
|
+
ensure
|
32
|
+
$stdout.reopen(old_out)
|
33
|
+
$stderr.reopen(old_err)
|
34
|
+
end
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
@@ -43,10 +46,12 @@ class MiniTest::Spec
|
|
43
46
|
"/tmp/gitdocs/#{c}"
|
44
47
|
end
|
45
48
|
pids = sub_paths.map { |path| fork do
|
46
|
-
|
47
|
-
|
49
|
+
unless ENV['DEBUG']
|
50
|
+
STDOUT.reopen(File.open("/dev/null", 'w'))
|
51
|
+
STDERR.reopen(File.open("/dev/null", 'w'))
|
52
|
+
end
|
48
53
|
begin
|
49
|
-
Gitdocs::Runner.new(path, :growl => false, :polling_interval => 0.
|
54
|
+
Gitdocs::Runner.new(path, :growl => false, :polling_interval => 0.5).run
|
50
55
|
rescue
|
51
56
|
puts "RATHER BAD ~~~~~"
|
52
57
|
puts $!.message
|
@@ -60,6 +65,6 @@ class MiniTest::Spec
|
|
60
65
|
pids.each { |pid| Process.kill("INT", pid) rescue nil }
|
61
66
|
end
|
62
67
|
ensure
|
63
|
-
FileUtils.rm_rf("/tmp/gitdocs")
|
68
|
+
FileUtils.rm_rf("/tmp/gitdocs") unless ENV['DEBUG']
|
64
69
|
end
|
65
70
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: gitdocs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Josh Hull
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2011-12-
|
14
|
+
date: 2011-12-02 00:00:00 -08:00
|
15
15
|
default_executable:
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
@@ -103,49 +103,60 @@ dependencies:
|
|
103
103
|
type: :runtime
|
104
104
|
version_requirements: *id008
|
105
105
|
- !ruby/object:Gem::Dependency
|
106
|
-
name:
|
106
|
+
name: haml
|
107
107
|
prerelease: false
|
108
108
|
requirement: &id009 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: "0"
|
114
|
+
type: :runtime
|
115
|
+
version_requirements: *id009
|
116
|
+
- !ruby/object:Gem::Dependency
|
117
|
+
name: minitest
|
118
|
+
prerelease: false
|
119
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
109
120
|
none: false
|
110
121
|
requirements:
|
111
122
|
- - ~>
|
112
123
|
- !ruby/object:Gem::Version
|
113
124
|
version: 2.6.1
|
114
125
|
type: :development
|
115
|
-
version_requirements: *
|
126
|
+
version_requirements: *id010
|
116
127
|
- !ruby/object:Gem::Dependency
|
117
128
|
name: rake
|
118
129
|
prerelease: false
|
119
|
-
requirement: &
|
130
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
120
131
|
none: false
|
121
132
|
requirements:
|
122
133
|
- - ">="
|
123
134
|
- !ruby/object:Gem::Version
|
124
135
|
version: "0"
|
125
136
|
type: :development
|
126
|
-
version_requirements: *
|
137
|
+
version_requirements: *id011
|
127
138
|
- !ruby/object:Gem::Dependency
|
128
139
|
name: mocha
|
129
140
|
prerelease: false
|
130
|
-
requirement: &
|
141
|
+
requirement: &id012 !ruby/object:Gem::Requirement
|
131
142
|
none: false
|
132
143
|
requirements:
|
133
144
|
- - ">="
|
134
145
|
- !ruby/object:Gem::Version
|
135
146
|
version: "0"
|
136
147
|
type: :development
|
137
|
-
version_requirements: *
|
148
|
+
version_requirements: *id012
|
138
149
|
- !ruby/object:Gem::Dependency
|
139
150
|
name: fakeweb
|
140
151
|
prerelease: false
|
141
|
-
requirement: &
|
152
|
+
requirement: &id013 !ruby/object:Gem::Requirement
|
142
153
|
none: false
|
143
154
|
requirements:
|
144
155
|
- - ">="
|
145
156
|
- !ruby/object:Gem::Version
|
146
157
|
version: "0"
|
147
158
|
type: :development
|
148
|
-
version_requirements: *
|
159
|
+
version_requirements: *id013
|
149
160
|
description: Open-source Dropbox using Ruby and Git.
|
150
161
|
email:
|
151
162
|
- joshbuddy@gmail.com
|
@@ -167,9 +178,15 @@ files:
|
|
167
178
|
- lib/gitdocs.rb
|
168
179
|
- lib/gitdocs/cli.rb
|
169
180
|
- lib/gitdocs/configuration.rb
|
181
|
+
- lib/gitdocs/public/css/app.css
|
182
|
+
- lib/gitdocs/public/css/reset.css
|
170
183
|
- lib/gitdocs/runner.rb
|
171
184
|
- lib/gitdocs/server.rb
|
172
185
|
- lib/gitdocs/version.rb
|
186
|
+
- lib/gitdocs/views/app.haml
|
187
|
+
- lib/gitdocs/views/dir.haml
|
188
|
+
- lib/gitdocs/views/file.haml
|
189
|
+
- lib/gitdocs/views/home.haml
|
173
190
|
- lib/img/icon.png
|
174
191
|
- test/configuration_test.rb
|
175
192
|
- test/runner_test.rb
|