Better naming
[gitorious:ktdreyers-dolt.git] / lib / dolt / sinatra / controller_actions.rb
1 # encoding: utf-8
2 #--
3 #   Copyright (C) 2012-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 "json"
19 require "time"
20 require "cgi"
21
22 module Dolt
23   module Sinatra
24     module ControllerActions
25       def redirect(url, status = 302)
26         response.status = status
27         response["Location"] = url
28         body ""
29       end
30
31       def render_error(error, repo, ref, data = {})
32         if error.class.to_s == "Rugged::ReferenceError" && ref == "HEAD"
33           return body(renderer.render("empty", {
34                 :repository => repo,
35                 :ref => ref
36               }.merge(data)))
37         end
38         template = error.class.to_s == "Rugged::IndexerError" ? :"404" : :"500"
39         add_headers(response)
40         body(renderer.render(template, {
41                                :error => error,
42                                :repository_slug => repo,
43                                :ref => ref
44                              }.merge(data)))
45       rescue Exception => err
46         err_backtrace = err.backtrace.map { |s| "<li>#{s}</li>" }
47         error_backtrace = error.backtrace.map { |s| "<li>#{s}</li>" }
48
49         body(<<-HTML)
50         <h1>Fatal Dolt Error</h1>
51         <p>
52           Dolt encountered an exception, and additionally
53           triggered another exception trying to render the error.
54         </p>
55         <p>Tried to render the #{template} template with the following data:</p>
56         <dl>
57           <dt>Repository</dt>
58           <dd>#{repo}</dd>
59           <dt>Ref</dt>
60           <dd>#{ref}</dd>
61         </dl>
62         <h2>Error: #{err.class} #{err.message}</h2>
63         <ul>#{err_backtrace.join()}</ul>
64         <h2>Original error: #{error.class} #{error.message}</h2>
65         <ul>#{error_backtrace.join()}</ul>
66         HTML
67       end
68
69       def raw(repo, ref, path, custom_data = {})
70         if oid = lookup_ref_oid(repo, ref)
71           redirect(raw_url(repo, oid, path), 307) and return
72         end
73
74         blob(repo, ref, path, custom_data, {
75                :template => :raw,
76                :content_type => "text/plain",
77                :template_options => { :layout => nil }
78              })
79       end
80
81       def blob(repo, ref, path, custom_data = {}, options = { :template => :blob })
82         if oid = lookup_ref_oid(repo, ref)
83           redirect(blob_url(repo, oid, path), 307) and return
84         end
85
86         data = (custom_data || {}).merge(lookup.blob(repo, u(ref), path))
87         blob = data[:blob]
88         return redirect(tree_url(repo, ref, path)) if blob.class.to_s !~ /\bBlob/
89         add_headers(response, options.merge(:ref => ref))
90         tpl_options = options[:template_options] || {}
91         body(renderer.render(options[:template], data, tpl_options))
92       end
93
94       def tree(repo, ref, path, custom_data = {})
95         if oid = lookup_ref_oid(repo, ref)
96           redirect(tree_url(repo, oid, path), 307) and return
97         end
98
99         data = (custom_data || {}).merge(lookup.tree(repo, u(ref), path))
100         tree = data[:tree]
101         return redirect(blob_url(repo, ref, path)) if tree.class.to_s !~ /\bTree/
102         add_headers(response, :ref => ref)
103         body(renderer.render(:tree, data))
104       end
105
106       def tree_entry(repo, ref, path, custom_data = {})
107         if oid = lookup_ref_oid(repo, ref)
108           redirect(tree_entry_url(repo, oid, path), 307) and return
109         end
110
111         data = (custom_data || {}).merge(lookup.tree_entry(repo, u(ref), path))
112         add_headers(response, :ref => ref)
113         body(renderer.render(data.key?(:tree) ? :tree : :blob, data))
114       end
115
116       def blame(repo, ref, path, custom_data = {})
117         if oid = lookup_ref_oid(repo, ref)
118           redirect(blame_url(repo, oid, path), 307) and return
119         end
120
121         data = (custom_data || {}).merge(lookup.blame(repo, u(ref), path))
122         add_headers(response, :ref => ref)
123         body(renderer.render(:blame, data))
124       end
125
126       def history(repo, ref, path, count, custom_data = {})
127         if oid = lookup_ref_oid(repo, ref)
128           redirect(history_url(repo, oid, path), 307) and return
129         end
130
131         data = (custom_data || {}).merge(lookup.history(repo, u(ref), path, count))
132         add_headers(response, :ref => ref)
133         body(renderer.render(:commits, data))
134       end
135
136       def refs(repo, custom_data = {})
137         data = (custom_data || {}).merge(lookup.refs(repo))
138         add_headers(response, :content_type => "application/json")
139         body(renderer.render(:refs, data, :layout => nil))
140       end
141
142       def tree_history(repo, ref, path, count = 1, custom_data = {})
143         if oid = lookup_ref_oid(repo, ref)
144           redirect(tree_history_url(repo, oid, path), 307) and return
145         end
146
147         data = (custom_data || {}).merge(lookup.tree_history(repo, u(ref), path, count))
148         add_headers(response, :content_type => "application/json", :ref => ref)
149         body(renderer.render(:tree_history, data, :layout => nil))
150       end
151
152       def resolve_repository(repo)
153         @cache ||= {}
154         @cache[repo] ||= lookup.resolve_repository(repo)
155       end
156
157       def lookup_ref_oid(repo, ref)
158         return if !respond_to?(:redirect_refs?) || !redirect_refs? || ref.length == 40
159         lookup.rev_parse_oid(repo, ref)
160       end
161
162       private
163       def u(str)
164         # Temporarily swap the + out with a magic byte, so
165         # filenames/branches with +'s won't get unescaped to a space
166         CGI.unescape(str.gsub("+", "\001")).gsub("\001", '+')
167       end
168
169       def add_headers(response, headers = {})
170         default_ct = "text/html; charset=utf-8"
171         response["Content-Type"] = headers[:content_type] || default_ct
172         response["X-UA-Compatible"] = "IE=edge"
173
174         if headers[:ref] && headers[:ref].length == 40
175           response["Cache-Control"] = "max-age=315360000, public"
176           year = 60*60*24*365
177           response["Expires"] = (Time.now + year).httpdate
178         end
179       end
180     end
181   end
182 end