Make repository.blob handle shas for blobs
[gitorious:libdolt.git] / lib / libdolt / repository_lookup.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
19 # Need consistent Time formatting in JSON
20 require "time"
21 class Time; def to_json(*args); "\"#{iso8601}\""; end; end
22
23 module Dolt
24   class RepositoryLookup
25     def initialize(repo_resolver, archiver = nil)
26       @repo_resolver = repo_resolver
27       @archiver = archiver
28     end
29
30     def blob(repo, ref, path)
31       repository = resolve_repository(repo)
32       blob = repository.rev_parse(ref)
33
34       if !blob.is_a?(Rugged::Blob)
35         blob = repository.rev_parse("#{ref}:#{path}")
36       end
37
38       tpl_data(repository, ref, path, {
39           :blob => blob,
40           :filemode => filemode(repository, ref, path)
41         })
42     end
43
44     def tree(repo, ref, path)
45       repository = resolve_repository(repo)
46       tpl_data(repository, ref, path, {
47           :tree => repository.tree(ref, path)
48         }).merge(:readme => readme(repo, ref, path))
49     end
50
51     def tree_entry(repo, ref, path)
52       repository = resolve_repository(repo)
53       result = repository.tree_entry(ref, path)
54       key = result.class.to_s.match(/Blob/) ? :blob : :tree
55       hash = tpl_data(repository, ref, path, { key => result, :type => key })
56       hash[:readme] = readme(repo, ref, path) if key == :tree
57       hash[:filemode] = filemode(repository, ref, path) if key == :blob
58       hash
59     end
60
61     def blame(repo, ref, path)
62       repository = resolve_repository(repo)
63       tpl_data(repository, ref, path, {
64           :blame => repository.blame(ref, path),
65           :filemode => filemode(repository, ref, path)
66         })
67     end
68
69     def history(repo, ref, path, count)
70       repository = resolve_repository(repo)
71       tpl_data(repository, ref, path, {
72           :commits => repository.log(ref, path, count)
73         })
74     end
75
76     def refs(repo)
77       repository = resolve_repository(repo)
78       names = repository.refs.map(&:name)
79       {
80         :tags => expand_refs(repository, names, :tags),
81         :heads => expand_refs(repository, names, :heads)
82       }.merge(repository.to_hash)
83     end
84
85     def tree_history(repo, ref, path, count)
86       repository = resolve_repository(repo)
87       tpl_data(repository, ref, path, {
88           :tree => repository.tree_history(ref, path, count)
89         })
90     end
91
92     def archive(repo, ref, format)
93       repository = resolve_repository(repo)
94       @archiver.archive(repository, ref, format)
95     end
96
97     def repositories
98       repo_resolver.all
99     end
100
101     def resolve_repository(repo)
102       ResolvedRepository.new(repo, repo_resolver.resolve(repo))
103     end
104
105     def rev_parse_oid(repo, ref)
106       resolve_repository(repo).rev_parse_oid(ref)
107     end
108
109     private
110     def repo_resolver; @repo_resolver; end
111
112     def tpl_data(repo, ref, path, locals = {})
113       { :path => path,
114         :ref => ref }.merge(repo.to_hash).merge(locals)
115     end
116
117     def expand_refs(repository, names, type)
118       names = names.map { |n| ForceUtf8::Encode.encode(n) }
119       names.select { |n| n =~ /#{type}/ }.map do |n|
120         [n.sub(/^refs\/#{type}\//, ""), repository.rev_parse_oid(n)]
121       end
122     end
123
124     def readme(repo_name, ref, path)
125       repository = resolve_repository(repo_name)
126       readmes = repository.readmes(ref, path)
127       readme = readmes.detect { |blob| Makeup::Markup.can_render?(blob[:name]) }
128       return unless readme
129       blob_path = File.join(*[path, readme[:name]].reject { |p| p == "" })
130       blob = repository.actual_blob(ref, blob_path)
131       return nil if blob.nil?
132       { :blob => blob, :path => blob_path }
133     end
134
135     def filemode(repo, ref, path)
136       file = File.basename(path)
137       refspec = "#{ref}:#{File.dirname(path).sub(/^\.$/, '')}"
138       entry = repo.rev_parse(refspec).find { |e| e[:name] == file }
139       entry.nil? ? nil : entry[:filemode].to_s(8)
140     rescue Rugged::InvalidError
141       "100644" # Pretty much a guess :/
142     end
143   end
144
145   class ResolvedRepository
146     def initialize(slug, repository)
147       @repository = repository
148       @data = { :repository_slug => slug }
149       @data[:repository_meta] = repository.meta if repository.respond_to?(:meta)
150     end
151
152     def to_hash
153       @data
154     end
155
156     def method_missing(method, *args, &block)
157       @repository.send(method, *args, &block)
158     end
159   end
160 end