libdolt 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/Gemfile.lock +56 -0
- data/Rakefile +10 -0
- data/Readme.md +99 -0
- data/lib/libdolt/async/when.rb +128 -0
- data/lib/libdolt/disk_repo_resolver.rb +39 -0
- data/lib/libdolt/git/blame.rb +112 -0
- data/lib/libdolt/git/commit.rb +73 -0
- data/lib/libdolt/git/repository.rb +139 -0
- data/lib/libdolt/git/submodule.rb +35 -0
- data/lib/libdolt/git/tree.rb +42 -0
- data/lib/libdolt/repo_actions.rb +95 -0
- data/lib/libdolt/version.rb +21 -0
- data/lib/libdolt/view/binary_blob_embedder.rb +41 -0
- data/lib/libdolt/view/blame.rb +57 -0
- data/lib/libdolt/view/blob.rb +97 -0
- data/lib/libdolt/view/breadcrumb.rb +47 -0
- data/lib/libdolt/view/commit.rb +27 -0
- data/lib/libdolt/view/gravatar.rb +29 -0
- data/lib/libdolt/view/markup.rb +44 -0
- data/lib/libdolt/view/multi_repository.rb +27 -0
- data/lib/libdolt/view/object.rb +44 -0
- data/lib/libdolt/view/single_repository.rb +27 -0
- data/lib/libdolt/view/smart_blob_renderer.rb +33 -0
- data/lib/libdolt/view/syntax_highlight.rb +40 -0
- data/lib/libdolt/view/tab_width.rb +30 -0
- data/lib/libdolt/view/tree.rb +100 -0
- data/lib/libdolt/view.rb +23 -0
- data/lib/libdolt.rb +29 -0
- data/libdolt.gemspec +35 -0
- data/test/libdolt/async/when_test.rb +112 -0
- data/test/libdolt/git/blame_test.rb +128 -0
- data/test/libdolt/git/commit_test.rb +89 -0
- data/test/libdolt/git/repository_test.rb +186 -0
- data/test/libdolt/repo_actions_test.rb +236 -0
- data/test/libdolt/templates/blame_test.rb +54 -0
- data/test/libdolt/templates/blob_test.rb +116 -0
- data/test/libdolt/templates/commits_test.rb +59 -0
- data/test/libdolt/templates/raw_test.rb +39 -0
- data/test/libdolt/templates/refs_test.rb +36 -0
- data/test/libdolt/templates/tree_history_test.rb +91 -0
- data/test/libdolt/templates/tree_test.rb +63 -0
- data/test/libdolt/view/binary_blob_embedder_test.rb +49 -0
- data/test/libdolt/view/blame_test.rb +122 -0
- data/test/libdolt/view/blob_test.rb +116 -0
- data/test/libdolt/view/breadcrumb_test.rb +46 -0
- data/test/libdolt/view/commit_test.rb +31 -0
- data/test/libdolt/view/gravatar_test.rb +30 -0
- data/test/libdolt/view/markup_test.rb +70 -0
- data/test/libdolt/view/multi_repository_test.rb +35 -0
- data/test/libdolt/view/object_test.rb +83 -0
- data/test/libdolt/view/single_repository_test.rb +30 -0
- data/test/libdolt/view/smart_blob_renderer_test.rb +38 -0
- data/test/libdolt/view/syntax_highlight_test.rb +80 -0
- data/test/libdolt/view/tab_width_test.rb +40 -0
- data/test/libdolt/view/tree_test.rb +196 -0
- data/test/test_helper.rb +50 -0
- data/views/500.erb +22 -0
- data/views/blame.erb +42 -0
- data/views/blob.erb +31 -0
- data/views/commits.erb +26 -0
- data/views/index.erb +25 -0
- data/views/layout.erb +45 -0
- data/views/raw.erb +19 -0
- data/views/refs.erb +22 -0
- data/views/tree.erb +50 -0
- data/views/tree_history.erb +19 -0
- metadata +326 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
libdolt (0.6.0)
|
5
|
+
em_pessimistic (~> 0.1)
|
6
|
+
em_rugged (~> 0.1.2)
|
7
|
+
eventmachine (~> 1.0)
|
8
|
+
htmlentities (~> 4.3)
|
9
|
+
json (~> 1.7)
|
10
|
+
makeup (~> 0)
|
11
|
+
mime-types (~> 1.19)
|
12
|
+
tzinfo (~> 0.3)
|
13
|
+
|
14
|
+
GEM
|
15
|
+
remote: http://rubygems.org/
|
16
|
+
specs:
|
17
|
+
em-minitest-spec (1.1.1)
|
18
|
+
eventmachine
|
19
|
+
em_pessimistic (0.1.2)
|
20
|
+
eventmachine (~> 1.0)
|
21
|
+
em_rugged (0.1.3)
|
22
|
+
eventmachine (~> 1.0)
|
23
|
+
rugged (= 0.17.0.b6)
|
24
|
+
eventmachine (1.0.0)
|
25
|
+
github-markup (0.7.4)
|
26
|
+
htmlentities (4.3.1)
|
27
|
+
json (1.7.5)
|
28
|
+
makeup (0.1.1)
|
29
|
+
github-markup (~> 0.7)
|
30
|
+
htmlentities (~> 4.3)
|
31
|
+
pygments.rb (~> 0.2)
|
32
|
+
mime-types (1.19)
|
33
|
+
minitest (2.12.1)
|
34
|
+
posix-spawn (0.3.6)
|
35
|
+
pygments.rb (0.3.1)
|
36
|
+
posix-spawn (~> 0.3.6)
|
37
|
+
yajl-ruby (~> 1.1.0)
|
38
|
+
rake (0.9.2.2)
|
39
|
+
redcarpet (2.1.1)
|
40
|
+
rugged (0.17.0.b6)
|
41
|
+
tilt (1.3.3)
|
42
|
+
tiltout (1.0.0)
|
43
|
+
tilt (~> 1.3)
|
44
|
+
tzinfo (0.3.33)
|
45
|
+
yajl-ruby (1.1.0)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
em-minitest-spec (~> 1.1)
|
52
|
+
libdolt!
|
53
|
+
minitest (~> 2.0)
|
54
|
+
rake (~> 0.9)
|
55
|
+
redcarpet
|
56
|
+
tiltout (~> 1)
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Dolt - The Git project browser
|
2
|
+
|
3
|
+
Dolt is a stand-alone Git repository browser. It can be used to explore repos in
|
4
|
+
your browser of choice and features syntax highlighting with
|
5
|
+
[Pygments](http://pygments.org/),
|
6
|
+
[Markdown](http://daringfireball.net/projects/markdown/)/[org-mode](http://orgmode.org/)/[+++](https://github.com/github/markup/)
|
7
|
+
rendering, commit log and blame.
|
8
|
+
|
9
|
+
Dolt is also a library, designed to render Git trees, blobs, commit log and
|
10
|
+
blame. It can render said views with or without a layout, or you can provide
|
11
|
+
your own templates (through [Tilt](https://github.com/rtomayko/tilt/)). You can
|
12
|
+
also provide your own rendering implementation to render other formats than
|
13
|
+
templates outputting HTML.
|
14
|
+
|
15
|
+
Dolt is the implementation of the next generation repo browser to be used in the
|
16
|
+
[Gitorious](http://gitorious.org) software.
|
17
|
+
|
18
|
+
## Installing Dolt
|
19
|
+
|
20
|
+
To install `dolt` you need Ruby, RubyGems and Python development files. The
|
21
|
+
Python development files are required to support Pygments syntax highlighting.
|
22
|
+
|
23
|
+
Note: Dolt uses [libgit2](http://libgit2.github.com) and its Ruby bindings,
|
24
|
+
[Rugged](http://github.com/libgit2/rugged) through
|
25
|
+
[em-rugged](http://gitorious.org/gitorious/em-rugged) for Git access where
|
26
|
+
feasible. Currently, ``EMRugged`` relies on a version of `Rugged` that is not
|
27
|
+
yet released, so you have to build it yourself.
|
28
|
+
[See em-rugged instructions](http://github.com/cjohansen/em-rugged).
|
29
|
+
|
30
|
+
### Systems using apt (Debian/Ubuntu, others)
|
31
|
+
|
32
|
+
# 1) Install Ruby (skip if you already have Ruby installed)
|
33
|
+
sudo apt-get install ruby
|
34
|
+
|
35
|
+
# 2) Install Python development files
|
36
|
+
sudo apt-get install python-dev
|
37
|
+
|
38
|
+
# 3) Install dolt. This may or may not require the use of sudo, depending on
|
39
|
+
# how you installed Ruby. This step assumes that you already built and
|
40
|
+
# installed em-rugged as explained above.
|
41
|
+
sudo gem install dolt
|
42
|
+
|
43
|
+
### Systems using yum (Fedora/CentOS/RedHat, others)
|
44
|
+
|
45
|
+
# 1) Install Ruby (skip if you already have Ruby installed)
|
46
|
+
sudo yum install ruby
|
47
|
+
|
48
|
+
# 2) Install Python development files
|
49
|
+
sudo yum install python-devel
|
50
|
+
|
51
|
+
# 3) Install dolt. This may or may not require the use of sudo, depending on
|
52
|
+
# how you installed Ruby. This step assumes that you already built and
|
53
|
+
# installed em-rugged as explained above.
|
54
|
+
sudo gem install dolt
|
55
|
+
|
56
|
+
# The Dolt CLI
|
57
|
+
|
58
|
+
The `dolt` library installs a CLI that can be used to quickly browse either a
|
59
|
+
single (typically the current) repository, or multiple repositories.
|
60
|
+
|
61
|
+
## Browsing a single repository
|
62
|
+
|
63
|
+
In a git repository, issue the following command:
|
64
|
+
|
65
|
+
$ dolt .
|
66
|
+
|
67
|
+
Then open a browser at [http://localhost:3000](http://localhost:3000). You will
|
68
|
+
be redirected to the root tree, and can browse the repository. To view trees and
|
69
|
+
blobs at specific refs, use the URL. A branch/tag selector will be added later.
|
70
|
+
|
71
|
+
## Browsing multiple repositories
|
72
|
+
|
73
|
+
The idea is that eventually, `dolt` should be able to serve up all Git
|
74
|
+
repositories managed by your Gitorious server. It does not yet do that, because
|
75
|
+
there currently is no "repository resolver" that understands the hashed paths
|
76
|
+
Gitorious uses.
|
77
|
+
|
78
|
+
Meanwhile, if you have a directory that contains multiple git repositories, you
|
79
|
+
can browse all of them through the same process by doing:
|
80
|
+
|
81
|
+
$ dolt /path/to/repos
|
82
|
+
|
83
|
+
Now [http://localhost:3000/repo](http://localhost:3000/repo) will allow you to
|
84
|
+
browse the `/path/repos/repo` repository. As `dolt` matures, there will be a
|
85
|
+
listing of all repositories and more.
|
86
|
+
|
87
|
+
## Markup rendering
|
88
|
+
|
89
|
+
Dolt uses the [``GitHub::Markup``](https://github.com/github/markup/) library to
|
90
|
+
render certain markup formats as HTML. Dolt does not have a hard dependency on
|
91
|
+
any of the required gems to actually render markups, so see the
|
92
|
+
[``GitHub::Markup`` docs](https://github.com/github/markup/) for information on
|
93
|
+
what and how to install support for various languages.
|
94
|
+
|
95
|
+
# License
|
96
|
+
|
97
|
+
Dolt is free software licensed under the
|
98
|
+
[GNU Affero General Public License (AGPL)](http://www.gnu.org/licenses/agpl-3.0.html).
|
99
|
+
Dolt is developed as part of the Gitorious project.
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
|
3
|
+
module When
|
4
|
+
class Promise
|
5
|
+
def initialize(deferred = EM::DefaultDeferrable.new)
|
6
|
+
@deferred = deferred
|
7
|
+
end
|
8
|
+
|
9
|
+
def callback(&block)
|
10
|
+
@deferred.callback(&block)
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def errback(&block)
|
15
|
+
@deferred.errback(&block)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Resolver
|
21
|
+
def initialize(deferred = EM::DefaultDeferrable.new)
|
22
|
+
@deferred = deferred
|
23
|
+
@resolved = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve(*args)
|
27
|
+
mark_resolved
|
28
|
+
@deferred.succeed(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reject(*args)
|
32
|
+
mark_resolved
|
33
|
+
@deferred.fail(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def resolved?
|
37
|
+
@resolved
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def mark_resolved
|
42
|
+
raise StandardError.new("Already resolved") if @resolved
|
43
|
+
@resolved = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Deferred
|
48
|
+
attr_reader :resolver, :promise
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
deferred = EM::DefaultDeferrable.new
|
52
|
+
@resolver = Resolver.new(deferred)
|
53
|
+
@promise = Promise.new(deferred)
|
54
|
+
end
|
55
|
+
|
56
|
+
def resolve(*args)
|
57
|
+
@resolver.resolve(*args)
|
58
|
+
end
|
59
|
+
|
60
|
+
def reject(*args)
|
61
|
+
@resolver.reject(*args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def callback(&block)
|
65
|
+
@promise.callback(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def errback(&block)
|
69
|
+
@promise.errback(&block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def resolved?
|
73
|
+
@resolver.resolved?
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.resolved(value)
|
77
|
+
d = self.new
|
78
|
+
d.resolve(value)
|
79
|
+
d
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.rejected(value)
|
83
|
+
d = self.new
|
84
|
+
d.reject(value)
|
85
|
+
d
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.deferred(val)
|
90
|
+
return val if val.respond_to?(:callback) && val.respond_to?(:errback)
|
91
|
+
Deferred.resolved(val).promise
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.all(promises)
|
95
|
+
raise(ArgumentError, "expected enumerable promises") if !promises.is_a?(Enumerable)
|
96
|
+
resolved = 0
|
97
|
+
results = []
|
98
|
+
d = Deferred.new
|
99
|
+
|
100
|
+
attempt_resolution = lambda do |err, res|
|
101
|
+
break if d.resolved?
|
102
|
+
if err.nil?
|
103
|
+
d.resolve(res) if promises.length == resolved
|
104
|
+
else
|
105
|
+
d.reject(err)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
wait_for_all(promises) do |err, result, index|
|
110
|
+
resolved += 1
|
111
|
+
results[index] = result
|
112
|
+
attempt_resolution.call(err, results)
|
113
|
+
end
|
114
|
+
|
115
|
+
attempt_resolution.call(nil, results) if promises.length == 0
|
116
|
+
d.promise
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
def self.wait_for_all(promises, &block)
|
121
|
+
promises.each_with_index do |p, i|
|
122
|
+
p.callback do |result|
|
123
|
+
block.call(nil, result, i)
|
124
|
+
end
|
125
|
+
p.errback { |e| block.call(e, nil, i) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2012 Gitorious AS
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#++
|
18
|
+
require "libdolt/git/repository"
|
19
|
+
|
20
|
+
module Dolt
|
21
|
+
class DiskRepoResolver
|
22
|
+
def initialize(root)
|
23
|
+
@root = root
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve(repo)
|
27
|
+
Dolt::Git::Repository.new(File.join(root, repo))
|
28
|
+
end
|
29
|
+
|
30
|
+
def all
|
31
|
+
(Dir.entries(root).select do |e|
|
32
|
+
File.exists?(File.join(root, e, ".git"))
|
33
|
+
end).sort
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def root; @root; end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2012 Gitorious AS
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#++
|
18
|
+
require "tzinfo"
|
19
|
+
|
20
|
+
module Dolt
|
21
|
+
module Git
|
22
|
+
class Blame
|
23
|
+
attr_reader :chunks
|
24
|
+
|
25
|
+
def initialize(chunks)
|
26
|
+
@chunks = chunks
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_porcelain(output)
|
30
|
+
self.new(Dolt::Git::Blame::PorcelainParser.new(output).parse)
|
31
|
+
end
|
32
|
+
|
33
|
+
class PorcelainParser
|
34
|
+
def initialize(output)
|
35
|
+
@output = output
|
36
|
+
@commits = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse
|
40
|
+
lines = @output.split("\n")
|
41
|
+
chunks = []
|
42
|
+
|
43
|
+
while lines.length > 0
|
44
|
+
chunk = extract_header(lines)
|
45
|
+
affected_lines = extract_lines(lines, chunk[:num_lines])
|
46
|
+
|
47
|
+
if chunks.last && chunk[:oid] == chunks.last[:oid]
|
48
|
+
chunks.last[:lines].concat(affected_lines)
|
49
|
+
else
|
50
|
+
chunk[:lines] = affected_lines
|
51
|
+
chunks << chunk
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
chunks
|
56
|
+
end
|
57
|
+
|
58
|
+
def is_header?(line)
|
59
|
+
line =~ /^[0-9a-f]{40} \d+ \d+ \d+$/
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_header(lines)
|
63
|
+
header = lines.shift
|
64
|
+
pieces = header.scan(/^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/).first
|
65
|
+
header = { :oid => pieces.first, :num_lines => pieces[3].to_i }
|
66
|
+
|
67
|
+
if lines.first =~ /^author/
|
68
|
+
header[:author] = extract_hash(lines, :author)
|
69
|
+
header[:committer] = extract_hash(lines, :committer)
|
70
|
+
header[:summary] = extract(lines, "summary")
|
71
|
+
throwaway = lines.shift until throwaway =~ /^filename/
|
72
|
+
@commits[header[:oid]] = header
|
73
|
+
else
|
74
|
+
header[:author] = @commits[header[:oid]][:author]
|
75
|
+
header[:committer] = @commits[header[:oid]][:committer]
|
76
|
+
header[:summary] = @commits[header[:oid]][:summary]
|
77
|
+
end
|
78
|
+
|
79
|
+
header
|
80
|
+
end
|
81
|
+
|
82
|
+
def extract_lines(lines, num)
|
83
|
+
extracted = []
|
84
|
+
|
85
|
+
num.times do
|
86
|
+
if extracted.length > 0
|
87
|
+
line = lines.shift # Header for next line
|
88
|
+
end
|
89
|
+
|
90
|
+
content = lines.shift # Actual content
|
91
|
+
extracted.push(content[1..content.length]) # 8 spaces padding
|
92
|
+
end
|
93
|
+
|
94
|
+
extracted
|
95
|
+
end
|
96
|
+
|
97
|
+
def extract_hash(lines, type)
|
98
|
+
{
|
99
|
+
:name => extract(lines, "#{type}"),
|
100
|
+
:mail => extract(lines, "#{type}-mail").gsub(/[<>]/, ""),
|
101
|
+
:time => (Time.at(extract(lines, "#{type}-time").to_i) +
|
102
|
+
Time.zone_offset(extract(lines, "#{type}-tz")))
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract(lines, thing)
|
107
|
+
lines.shift.split("#{thing} ")[1]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2012 Gitorious AS
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#++
|
18
|
+
require "time"
|
19
|
+
|
20
|
+
module Dolt
|
21
|
+
module Git
|
22
|
+
class Commit
|
23
|
+
def self.parse_log(log)
|
24
|
+
commits = []
|
25
|
+
lines = log.split("\n")
|
26
|
+
commits << extract_commit(lines) while lines.length > 0
|
27
|
+
commits
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.extract_commit(lines)
|
31
|
+
commit = { :oid => lines.shift.split(" ")[1] }
|
32
|
+
while (line = lines.shift) != ""
|
33
|
+
pieces = line.split(": ")
|
34
|
+
extract_property(commit, pieces[0], pieces[1])
|
35
|
+
end
|
36
|
+
|
37
|
+
commit[:summary] = extract_commit_summary(lines)
|
38
|
+
commit[:message] = extract_commit_message(lines)
|
39
|
+
commit
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.extract_property(hash, name, value)
|
43
|
+
key = name.downcase.to_sym
|
44
|
+
|
45
|
+
case key
|
46
|
+
when :author
|
47
|
+
pieces = value.match(/(.*)\s<(.*)>/)
|
48
|
+
value = { :name => pieces[1], :email => pieces[2] }
|
49
|
+
when :date
|
50
|
+
value = Time.parse(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
hash[key] = value
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.extract_commit_summary(lines)
|
57
|
+
summary = lines.shift
|
58
|
+
lines.shift if lines.first == ""
|
59
|
+
summary.sub(/^ /, "")
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.extract_commit_message(lines)
|
63
|
+
message = ""
|
64
|
+
|
65
|
+
while !lines.first.nil? && lines.first !~ /^commit [a-z0-9]{40}$/
|
66
|
+
message << lines.shift
|
67
|
+
end
|
68
|
+
|
69
|
+
message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2012 Gitorious AS
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#++
|
18
|
+
require "em_rugged/repository"
|
19
|
+
require "em_pessimistic/deferrable_child_process"
|
20
|
+
require "em/deferrable"
|
21
|
+
require "libdolt/git/blame"
|
22
|
+
require "libdolt/git/commit"
|
23
|
+
require "libdolt/git/submodule"
|
24
|
+
require "libdolt/git/tree"
|
25
|
+
require "libdolt/async/when"
|
26
|
+
|
27
|
+
module Dolt
|
28
|
+
module Git
|
29
|
+
class Repository < EMRugged::Repository
|
30
|
+
def submodules(ref)
|
31
|
+
d = EventMachine::DefaultDeferrable.new
|
32
|
+
gm = rev_parse("#{ref}:.gitmodules")
|
33
|
+
gm.callback do |config|
|
34
|
+
d.succeed(Dolt::Git::Submodule.parse_config(config.content))
|
35
|
+
end
|
36
|
+
# Fails if .gitmodules cannot be found, which means no submodules
|
37
|
+
gm.errback { |err| d.succeed([]) }
|
38
|
+
d
|
39
|
+
end
|
40
|
+
|
41
|
+
def tree(ref, path)
|
42
|
+
d = EventMachine::DefaultDeferrable.new
|
43
|
+
rp = rev_parse("#{ref}:#{path}")
|
44
|
+
rp.callback do |tree|
|
45
|
+
break d.fail(StandardError.new("Not a tree")) unless tree.is_a?(Rugged::Tree)
|
46
|
+
break d.succeed(tree) if !tree.find { |e| e[:type].nil? }
|
47
|
+
annotate_submodules(ref, path, d, tree)
|
48
|
+
end
|
49
|
+
rp.errback { |err| d.fail(err) }
|
50
|
+
d
|
51
|
+
end
|
52
|
+
|
53
|
+
def blame(ref, path)
|
54
|
+
deferred_method("blame -l -t -p #{ref} #{path}") do |output, s|
|
55
|
+
Dolt::Git::Blame.parse_porcelain(output)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def log(ref, path, limit)
|
60
|
+
entry_history(ref, path, limit)
|
61
|
+
end
|
62
|
+
|
63
|
+
def tree_history(ref, path, limit = 1)
|
64
|
+
d = EventMachine::DefaultDeferrable.new
|
65
|
+
rp = rev_parse("#{ref}:#{path}")
|
66
|
+
rp.errback { |err| d.fail(err) }
|
67
|
+
rp.callback do |tree|
|
68
|
+
if tree.class != Rugged::Tree
|
69
|
+
message = "#{ref}:#{path} is not a tree (#{tree.class.to_s})"
|
70
|
+
break d.fail(Exception.new(message))
|
71
|
+
end
|
72
|
+
|
73
|
+
building = build_history(path || "./", ref, tree, limit)
|
74
|
+
building.callback { |history| d.succeed(history) }
|
75
|
+
building.errback { |err| d.fail(err) }
|
76
|
+
end
|
77
|
+
d
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
def entry_history(ref, entry, limit)
|
82
|
+
deferred_method("log -n #{limit} #{ref} -- #{entry}") do |out, s|
|
83
|
+
Dolt::Git::Commit.parse_log(out)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def build_history(path, ref, entries, limit)
|
88
|
+
d = EventMachine::DefaultDeferrable.new
|
89
|
+
resolve = lambda { |p| path == "" ? p : File.join(path, p) }
|
90
|
+
progress = When.all(entries.map do |e|
|
91
|
+
entry_history(ref, resolve.call(e[:name]), limit)
|
92
|
+
end)
|
93
|
+
progress.errback { |e| d.fail(e) }
|
94
|
+
progress.callback do |history|
|
95
|
+
d.succeed(entries.map { |e| e.merge({ :history => history.shift }) })
|
96
|
+
end
|
97
|
+
d
|
98
|
+
end
|
99
|
+
|
100
|
+
def annotate_submodules(ref, path, deferrable, tree)
|
101
|
+
submodules(ref).callback do |submodules|
|
102
|
+
entries = tree.entries.map do |entry|
|
103
|
+
if entry[:type].nil?
|
104
|
+
mod = path == "" ? entry[:name] : File.join(path, entry[:name])
|
105
|
+
meta = submodules.find { |s| s[:path] == mod }
|
106
|
+
if meta
|
107
|
+
entry[:type] = :submodule
|
108
|
+
entry[:url] = meta[:url]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
entry
|
112
|
+
end
|
113
|
+
|
114
|
+
deferrable.succeed(Dolt::Git::Tree.new(tree.oid, entries))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def deferred_method(cmd, &block)
|
119
|
+
d = EventMachine::DefaultDeferrable.new
|
120
|
+
cmd = git(cmd)
|
121
|
+
p = EMPessimistic::DeferrableChildProcess.open(cmd)
|
122
|
+
|
123
|
+
p.callback do |output, status|
|
124
|
+
d.succeed(block.call(output, status))
|
125
|
+
end
|
126
|
+
|
127
|
+
p.errback do |stderr, status|
|
128
|
+
d.fail(stderr)
|
129
|
+
end
|
130
|
+
|
131
|
+
d
|
132
|
+
end
|
133
|
+
|
134
|
+
def git(cmd)
|
135
|
+
"git --git-dir #{subject.path} #{cmd}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#--
|
3
|
+
# Copyright (C) 2012 Gitorious AS
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Affero General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU Affero General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Dolt
|
20
|
+
module Git
|
21
|
+
class Submodule
|
22
|
+
def self.parse_config(config)
|
23
|
+
config.split("\n").inject([]) do |modules, line|
|
24
|
+
if line =~ /\[submodule ".*"\]/
|
25
|
+
modules << {}
|
26
|
+
else
|
27
|
+
_, key, val = *line.match(/\s([^\s]+) = ([^\s]+)/)
|
28
|
+
modules.last[key.to_sym] = val
|
29
|
+
end
|
30
|
+
modules
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|