gitdocs 0.1.5 → 0.2.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.
- 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
|