libdolt 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- libdolt (0.12.0)
4
+ libdolt (0.13.0)
5
5
  em_pessimistic (~> 0.1)
6
6
  em_rugged (~> 0.3)
7
7
  eventmachine (~> 1.0)
@@ -25,13 +25,16 @@ GEM
25
25
  eventmachine (1.0.0)
26
26
  github-markup (0.7.4)
27
27
  htmlentities (4.3.1)
28
- json (1.7.5)
28
+ json (1.7.6)
29
29
  makeup (0.3.0)
30
30
  github-markup (~> 0.7)
31
31
  htmlentities (~> 4.3)
32
32
  pygments.rb (~> 0.2)
33
+ metaclass (0.0.1)
33
34
  mime-types (1.19)
34
35
  minitest (2.12.1)
36
+ mocha (0.13.1)
37
+ metaclass (~> 0.0.1)
35
38
  posix-spawn (0.3.6)
36
39
  pygments.rb (0.3.2)
37
40
  posix-spawn (~> 0.3.6)
@@ -53,6 +56,7 @@ DEPENDENCIES
53
56
  em-minitest-spec (~> 1.1)
54
57
  libdolt!
55
58
  minitest (~> 2.0)
59
+ mocha
56
60
  rake (~> 0.9)
57
61
  redcarpet (= 2.2.0)
58
62
  tiltout (~> 1.4)
@@ -0,0 +1,107 @@
1
+ # encoding: utf-8
2
+ #--
3
+ # Copyright (C) 2013 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 "when"
19
+ require "eventmachine"
20
+ require "em_pessimistic"
21
+ require "fileutils"
22
+
23
+ module Dolt
24
+ module Git
25
+ class Archiver
26
+ def initialize(work_dir, cache_dir)
27
+ # A hash of currently processing archiving jobs. It contains tuples of
28
+ # "#{repo.id}-#{oid}-#{format}" => promises representing the eventual
29
+ # completion of archiving tasks. When an archiving task is completed,
30
+ # its promise is removed from the hash. Previously generated tarballs
31
+ # can be found on disk.
32
+ @processing = {}
33
+ @work_dir = work_dir
34
+ @cache_dir = cache_dir
35
+ end
36
+
37
+ # Returns a promise that resolves with the filename of the generated
38
+ # tarball/zip file.
39
+ #
40
+ # repo - A repository object
41
+ # oid - A valid commit oid
42
+ # format - A symbol. If it is not :zip, tar.gz is assumed.
43
+ def archive(repo, oid, format = :tgz)
44
+ filename = cache_path(repo, oid, format)
45
+ return When.resolve(filename) if File.exists?(filename)
46
+ pending = pending_process(repo, oid, format)
47
+ return pending if pending
48
+ start_process(repo, oid, format)
49
+ end
50
+
51
+ private
52
+ def process_id(repo, oid, format)
53
+ "#{repo.id}-#{oid}-#{ext(format)}"
54
+ end
55
+
56
+ def pending_process(repo, oid, format)
57
+ @processing[process_id(repo, oid, format)]
58
+ end
59
+
60
+ def start_process(repo, oid, format)
61
+ @processing[process_id(repo, oid, format)] = When.defer do |d|
62
+ p = EMPessimistic::DeferrableChildProcess.open(cmd(repo, oid, format))
63
+
64
+ p.callback do |output, status|
65
+ filename = cache_path(repo, oid, format)
66
+ FileUtils.mv(work_path(repo, oid, format), filename)
67
+ d.resolve(filename)
68
+ end
69
+
70
+ p.errback do |output, status|
71
+ d.reject(Exception.new(output))
72
+ end
73
+ end
74
+ end
75
+
76
+ def cmd(repository, oid, format)
77
+ path_segment = repository.path_segment.gsub(/\//, "-")
78
+ cmd = "sh -c 'git --git-dir #{repository.full_repository_path} archive "
79
+ cmd += "--prefix='#{u(path_segment)}/' --format="
80
+ wpath = u(work_path(repository, oid, format))
81
+ cmd + (format.to_s == "zip" ? "zip #{u(oid)} > #{wpath}" : "tar #{u(oid)} | gzip -m > #{wpath}") + "'"
82
+ end
83
+
84
+ def cache_path(repository, oid, format)
85
+ File.join(@cache_dir, basename(repository, oid, format))
86
+ end
87
+
88
+ def work_path(repository, oid, format)
89
+ File.join(@work_dir, basename(repository, oid, format))
90
+ end
91
+
92
+ def basename(repository, oid, format)
93
+ path_segment = repository.path_segment.gsub(/\//, "-")
94
+ "#{path_segment}-#{oid}.#{ext(format)}"
95
+ end
96
+
97
+ def ext(format)
98
+ format.to_s == "zip" ? "zip" : "tar.gz"
99
+ end
100
+
101
+ # Unquote a string by stripping off any single or double quotes
102
+ def u(string)
103
+ string.gsub("'", '').gsub('"', '')
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,7 +1,6 @@
1
-
2
1
  # encoding: utf-8
3
2
  #--
4
- # Copyright (C) 2012 Gitorious AS
3
+ # Copyright (C) 2012-2013 Gitorious AS
5
4
  #
6
5
  # This program is free software: you can redistribute it and/or modify
7
6
  # it under the terms of the GNU Affero General Public License as published by
@@ -24,8 +23,9 @@ class Time; def to_json(*args); "\"#{iso8601}\""; end; end
24
23
 
25
24
  module Dolt
26
25
  class RepoActions
27
- def initialize(repo_resolver)
26
+ def initialize(repo_resolver, archiver = nil)
28
27
  @repo_resolver = repo_resolver
28
+ @archiver = archiver
29
29
  end
30
30
 
31
31
  def blob(repo, ref, path, &block)
@@ -71,6 +71,13 @@ module Dolt
71
71
  repo_action(repo, ref, path, :tree, :tree_history, ref, path, count, &block)
72
72
  end
73
73
 
74
+ def archive(repo, ref, format, &block)
75
+ repository = resolve_repository(repo)
76
+ d = @archiver.archive(repository, ref, format)
77
+ d.callback { |filename| block.call(nil, filename) }
78
+ d.errback { |err| block.call(err, nil) }
79
+ end
80
+
74
81
  def repositories
75
82
  repo_resolver.all
76
83
  end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  #--
3
- # Copyright (C) 2012 Gitorious AS
3
+ # Copyright (C) 2012-2013 Gitorious AS
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
6
6
  # it under the terms of the GNU Affero General Public License as published by
@@ -17,5 +17,5 @@
17
17
  #++
18
18
 
19
19
  module Dolt
20
- VERSION = "0.13.0"
20
+ VERSION = "0.14.0"
21
21
  end
data/libdolt.gemspec CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency "rake", "~> 0.9"
29
29
  s.add_development_dependency "redcarpet", "2.2.0"
30
30
  s.add_development_dependency "tiltout", "~>1.4"
31
+ s.add_development_dependency "mocha"
31
32
 
32
33
  s.files = `git ls-files`.split("\n")
33
34
  s.test_files = `git ls-files -- {test}/*`.split("\n")
@@ -0,0 +1,182 @@
1
+ # encoding: utf-8
2
+ #--
3
+ # Copyright (C) 2013 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 "test_helper"
19
+ require "mocha/setup"
20
+ require "libdolt/git/archiver"
21
+
22
+ class StubRepository
23
+ attr_reader :id, :full_repository_path, :path_segment
24
+ def initialize(path_segment)
25
+ @@counter ||= 0
26
+ @id = (@@counter += 1)
27
+ @full_repository_path = "/repos/#{path_segment}.git"
28
+ @path_segment = path_segment
29
+ end
30
+ end
31
+
32
+ class StubProcessStatus
33
+ attr_reader :exitstatus
34
+ def initialize(code)
35
+ @exitstatus = code
36
+ end
37
+ end
38
+
39
+ describe Dolt::Git::Archiver do
40
+ include EM::MiniTest::Spec
41
+
42
+ describe "archive" do
43
+ before do
44
+ @archiver = Dolt::Git::Archiver.new("/work", "/cache")
45
+ end
46
+
47
+ it "resolves with existing cached file" do
48
+ File.stubs(:exists?).with("/cache/gts-mainline-master.tar.gz").returns(true)
49
+ repo = StubRepository.new("gts/mainline")
50
+
51
+ @archiver.archive(repo, "master", :tar).then do |filename|
52
+ assert_equal "/cache/gts-mainline-master.tar.gz", filename
53
+ done!
54
+ end
55
+ wait!
56
+ end
57
+
58
+ it "generates tarball" do
59
+ repo = StubRepository.new("gts/mainline")
60
+
61
+ cmd = "sh -c 'git --git-dir /repos/gts/mainline.git archive --prefix='gts-mainline/' " +
62
+ "--format=tar master | gzip -m > /work/gts-mainline-master.tar.gz'"
63
+ d = EM::DefaultDeferrable.new
64
+ EMPessimistic::DeferrableChildProcess.expects(:open).with(cmd).returns(d)
65
+
66
+ @archiver.archive(repo, "master", :tar)
67
+ end
68
+
69
+ it "uses gzip format from string" do
70
+ repo = StubRepository.new("gts/mainline")
71
+
72
+ cmd = "sh -c 'git --git-dir /repos/gts/mainline.git archive --prefix='gts-mainline/' " +
73
+ "--format=tar master | gzip -m > /work/gts-mainline-master.tar.gz'"
74
+ d = EM::DefaultDeferrable.new
75
+ EMPessimistic::DeferrableChildProcess.expects(:open).with(cmd).returns(d)
76
+
77
+ @archiver.archive(repo, "master", "tar")
78
+ end
79
+
80
+ it "uses zip format from string" do
81
+ repo = StubRepository.new("gts/mainline")
82
+
83
+ cmd = "sh -c 'git --git-dir /repos/gts/mainline.git archive --prefix='gts-mainline/' " +
84
+ "--format=zip master > /work/gts-mainline-master.zip'"
85
+ d = EM::DefaultDeferrable.new
86
+ EMPessimistic::DeferrableChildProcess.expects(:open).with(cmd).returns(d)
87
+
88
+ @archiver.archive(repo, "master", "zip")
89
+ end
90
+
91
+ it "moves tarball when successfully generated" do
92
+ FileUtils.expects(:mv).with("/work/gts-mainline-master.tar.gz",
93
+ "/cache/gts-mainline-master.tar.gz")
94
+ repo = StubRepository.new("gts/mainline")
95
+ d = EM::DefaultDeferrable.new
96
+ EMPessimistic::DeferrableChildProcess.expects(:open).returns(d)
97
+ d.succeed("", StubProcessStatus.new(0))
98
+
99
+ @archiver.archive(repo, "master", :tar)
100
+ end
101
+
102
+ it "does not move tarball when raising error" do
103
+ FileUtils.expects(:mv).with("/work/gts-mainline-master.tar.gz",
104
+ "/cache/gts-mainline-master.tar.gz").never
105
+ repo = StubRepository.new("gts/mainline")
106
+ d = EM::DefaultDeferrable.new
107
+ EMPessimistic::DeferrableChildProcess.expects(:open).returns(d)
108
+ d.fail("", StubProcessStatus.new(1))
109
+
110
+ @archiver.archive(repo, "master", :tar)
111
+ end
112
+
113
+ it "resolves promise with generated filename" do
114
+ FileUtils.stubs(:mv)
115
+ repo = StubRepository.new("gts/mainline")
116
+ d = EM::DefaultDeferrable.new
117
+ EMPessimistic::DeferrableChildProcess.expects(:open).returns(d)
118
+ d.succeed("", StubProcessStatus.new(0))
119
+
120
+ @archiver.archive(repo, "master", :tar).then do |filename|
121
+ assert_equal "/cache/gts-mainline-master.tar.gz", filename
122
+ done!
123
+ end
124
+ wait!
125
+ end
126
+
127
+ it "rejects promise when failing to archive" do
128
+ repo = StubRepository.new("gts/mainline")
129
+ d = EM::DefaultDeferrable.new
130
+ EMPessimistic::DeferrableChildProcess.expects(:open).returns(d)
131
+ d.fail("It done failed", StubProcessStatus.new(1))
132
+
133
+ @archiver.archive(repo, "master", :tar).errback do |err|
134
+ assert_match "It done failed", err.message
135
+ done!
136
+ end
137
+ wait!
138
+ end
139
+
140
+ it "does not spawn multiple identical processes" do
141
+ FileUtils.stubs(:mv)
142
+ repo = StubRepository.new("gts/mainline")
143
+ d = EM::DefaultDeferrable.new
144
+ EMPessimistic::DeferrableChildProcess.expects(:open).once.returns(d)
145
+ callbacks = 0
146
+ blk = lambda do |filename|
147
+ assert_equal "/cache/gts-mainline-master.tar.gz", filename
148
+ callbacks += 1
149
+ done! if callbacks == 2
150
+ end
151
+
152
+ @archiver.archive(repo, "master", :tar).then(&blk)
153
+ @archiver.archive(repo, "master", :tar).then(&blk)
154
+
155
+ wait!
156
+ d.succeed("", StubProcessStatus.new(0))
157
+ end
158
+
159
+ it "spawns new process when format is different" do
160
+ FileUtils.stubs(:mv)
161
+ repo = StubRepository.new("gts/mainline")
162
+ d = EM::DefaultDeferrable.new
163
+ EMPessimistic::DeferrableChildProcess.expects(:open).twice.returns(d)
164
+ callbacks = 0
165
+
166
+ @archiver.archive(repo, "master", :tar).then do |filename|
167
+ assert_equal "/cache/gts-mainline-master.tar.gz", filename
168
+ callbacks += 1
169
+ done! if callbacks == 2
170
+ end
171
+
172
+ @archiver.archive(repo, "master", :zip).then do |filename|
173
+ assert_equal "/cache/gts-mainline-master.zip", filename
174
+ callbacks += 1
175
+ done! if callbacks == 2
176
+ end
177
+
178
+ wait!
179
+ d.succeed("", StubProcessStatus.new(0))
180
+ end
181
+ end
182
+ end
@@ -21,6 +21,7 @@ require "libdolt/view"
21
21
  class Blob
22
22
  attr_reader :content
23
23
  def initialize(content); @content = content; end
24
+ def text(max_lines, encoding); content; end
24
25
  end
25
26
 
26
27
  describe "blob template" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libdolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-04 00:00:00.000000000 Z
12
+ date: 2013-01-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -235,6 +235,22 @@ dependencies:
235
235
  - - ~>
236
236
  - !ruby/object:Gem::Version
237
237
  version: '1.4'
238
+ - !ruby/object:Gem::Dependency
239
+ name: mocha
240
+ requirement: !ruby/object:Gem::Requirement
241
+ none: false
242
+ requirements:
243
+ - - ! '>='
244
+ - !ruby/object:Gem::Version
245
+ version: '0'
246
+ type: :development
247
+ prerelease: false
248
+ version_requirements: !ruby/object:Gem::Requirement
249
+ none: false
250
+ requirements:
251
+ - - ! '>='
252
+ - !ruby/object:Gem::Version
253
+ version: '0'
238
254
  description: Dolt API for serving git trees and syntax highlighted blobs
239
255
  email:
240
256
  - christian@gitorious.org
@@ -249,6 +265,7 @@ files:
249
265
  - Readme.md
250
266
  - lib/libdolt.rb
251
267
  - lib/libdolt/disk_repo_resolver.rb
268
+ - lib/libdolt/git/archiver.rb
252
269
  - lib/libdolt/git/blame.rb
253
270
  - lib/libdolt/git/commit.rb
254
271
  - lib/libdolt/git/repository.rb
@@ -274,6 +291,7 @@ files:
274
291
  - lib/libdolt/view/tree.rb
275
292
  - lib/libdolt/view/urls.rb
276
293
  - libdolt.gemspec
294
+ - test/libdolt/git/archiver_test.rb
277
295
  - test/libdolt/git/blame_test.rb
278
296
  - test/libdolt/git/commit_test.rb
279
297
  - test/libdolt/git/repository_test.rb