Merge branch 'master' into 1971-show-image-thumbnails
[arvados.git] / services / api / app / models / commit.rb
1 class Commit < ActiveRecord::Base
2   require 'shellwords'
3
4   def self.git_check_ref_format(e)
5     if !e or e.empty? or e[0] == '-' or e[0] == '$'
6       # definitely not valid
7       false
8     else
9       `git check-ref-format --allow-onelevel #{e.shellescape}`
10       $?.success?
11     end
12   end
13
14   def self.find_commit_range(current_user, repository, minimum, maximum, exclude)
15     if (minimum and !git_check_ref_format(minimum)) or !git_check_ref_format(maximum)
16       logger.warn "find_commit_range called with invalid minimum or maximum: '#{minimum}', '#{maximum}'"
17       return nil
18     end
19
20     if minimum and minimum.empty?
21         minimum = nil
22     end
23
24     if !maximum
25       maximum = "HEAD"
26     end
27
28     # Get list of actual repository directories under management
29     on_disk_repos = repositories
30
31     # Get list of repository objects readable by user
32     readable = Repository.readable_by(current_user)
33
34     # filter repository objects on requested repository name
35     if repository
36       readable = readable.where(name: repository)
37     end
38
39     commits = []
40     readable.each do |r|
41       if on_disk_repos[r.name]
42         ENV['GIT_DIR'] = on_disk_repos[r.name][:git_dir]
43
44         # We've filtered for invalid characters, so we can pass the contents of
45         # minimum and maximum safely on the command line
46
47         # Get the commit hash for the upper bound
48         max_hash = nil
49         IO.foreach("|git rev-list --max-count=1 #{maximum.shellescape}") do |line|
50           max_hash = line.strip
51         end
52
53         # If not found or string is invalid, nothing else to do
54         next if !max_hash or !git_check_ref_format(max_hash)
55
56         resolved_exclude = nil
57         if exclude
58           resolved_exclude = []
59           exclude.each do |e|
60             if git_check_ref_format(e)
61               IO.foreach("|git rev-list --max-count=1 #{e.shellescape}") do |line|
62                 resolved_exclude.push(line.strip)
63               end
64             else
65               logger.warn "find_commit_range called with invalid exclude invalid characters: '#{exclude}'"
66               return nil
67             end
68           end
69         end
70
71         if minimum
72           # Get the commit hash for the lower bound
73           min_hash = nil
74           IO.foreach("|git rev-list --max-count=1 #{minimum.shellescape}") do |line|
75             min_hash = line.strip
76           end
77
78           # If not found or string is invalid, nothing else to do
79           next if !min_hash or !git_check_ref_format(min_hash)
80
81           # Now find all commits between them
82           IO.foreach("|git rev-list #{min_hash.shellescape}..#{max_hash.shellescape}") do |line|
83             hash = line.strip
84             commits.push(hash) if !resolved_exclude or !resolved_exclude.include? hash
85           end
86
87           commits.push(min_hash) if !resolved_exclude or !resolved_exclude.include? min_hash
88         else
89           commits.push(max_hash) if !resolved_exclude or !resolved_exclude.include? max_hash
90         end
91       end
92     end
93
94     if !commits or commits.empty?
95       nil
96     else
97       commits
98     end
99   end
100
101   # Import all commits from configured git directory into the commits
102   # database.
103
104   def self.import_all
105     repositories.each do |repo_name, repo|
106       stat = { true => 0, false => 0 }
107       ENV['GIT_DIR'] = repo[:git_dir]
108       IO.foreach("|git rev-list --format=oneline --all") do |line|
109         sha1, message = line.strip.split " ", 2
110         imported = false
111         Commit.find_or_create_by_repository_name_and_sha1_and_message(repo_name, sha1, message[0..254]) do
112           imported = true
113         end
114         stat[!!imported] += 1
115         if (stat[true] + stat[false]) % 100 == 0
116           if $stdout.tty? or ARGV[0] == '-v'
117             puts "#{$0} #{$$}: repo #{repo_name} add #{stat[true]} skip #{stat[false]}"
118           end
119         end
120       end
121       if $stdout.tty? or ARGV[0] == '-v'
122         puts "#{$0} #{$$}: repo #{repo_name} add #{stat[true]} skip #{stat[false]}"
123       end
124     end
125   end
126
127   def self.refresh_repositories
128     @repositories = nil
129   end
130
131   protected
132
133   def self.repositories
134     return @repositories if @repositories
135
136     @repositories = {}
137     @gitdirbase = Rails.configuration.git_repositories_dir
138     Dir.foreach @gitdirbase do |repo|
139       next if repo.match /^\./
140       git_dir = File.join(@gitdirbase,
141                           repo.match(/\.git$/) ? repo : File.join(repo, '.git'))
142       next if git_dir == Rails.configuration.git_internal_dir
143       repo_name = repo.sub(/\.git$/, '')
144       @repositories[repo_name] = {git_dir: git_dir}
145     end
146
147     @repositories
148   end
149 end