annotate app/models/repository.rb @ 8:0c83d98252d9 yuya

* Add custom repo prefix and proper auth realm, remove auth cache (seems like an unwise feature), pass DB handle around, various other bits of tidying
author Chris Cannam
date Thu, 12 Aug 2010 15:31:37 +0100
parents 7c48bad7d85d
children b859cc0c4fa1 102056ec2de9
rev   line source
Chris@0 1 # redMine - project management software
Chris@0 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
Chris@0 3 #
Chris@0 4 # This program is free software; you can redistribute it and/or
Chris@0 5 # modify it under the terms of the GNU General Public License
Chris@0 6 # as published by the Free Software Foundation; either version 2
Chris@0 7 # of the License, or (at your option) any later version.
Chris@0 8 #
Chris@0 9 # This program is distributed in the hope that it will be useful,
Chris@0 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
Chris@0 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Chris@0 12 # GNU General Public License for more details.
Chris@0 13 #
Chris@0 14 # You should have received a copy of the GNU General Public License
Chris@0 15 # along with this program; if not, write to the Free Software
Chris@0 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Chris@0 17
Chris@0 18 class Repository < ActiveRecord::Base
Chris@0 19 belongs_to :project
Chris@3 20 has_many :changesets, :order => "#{Changeset.table_name}.id DESC"
Chris@0 21 has_many :changes, :through => :changesets
Chris@0 22
Chris@0 23 # Raw SQL to delete changesets and changes in the database
Chris@0 24 # has_many :changesets, :dependent => :destroy is too slow for big repositories
Chris@0 25 before_destroy :clear_changesets
Chris@0 26
Chris@0 27 # Checks if the SCM is enabled when creating a repository
Chris@0 28 validate_on_create { |r| r.errors.add(:type, :invalid) unless Setting.enabled_scm.include?(r.class.name.demodulize) }
Chris@0 29
Chris@0 30 # Removes leading and trailing whitespace
Chris@0 31 def url=(arg)
Chris@0 32 write_attribute(:url, arg ? arg.to_s.strip : nil)
Chris@0 33 end
Chris@0 34
Chris@0 35 # Removes leading and trailing whitespace
Chris@0 36 def root_url=(arg)
Chris@0 37 write_attribute(:root_url, arg ? arg.to_s.strip : nil)
Chris@0 38 end
Chris@0 39
Chris@0 40 def scm
Chris@0 41 @scm ||= self.scm_adapter.new url, root_url, login, password
Chris@0 42 update_attribute(:root_url, @scm.root_url) if root_url.blank?
Chris@0 43 @scm
Chris@0 44 end
Chris@0 45
Chris@0 46 def scm_name
Chris@0 47 self.class.scm_name
Chris@0 48 end
Chris@0 49
Chris@0 50 def supports_cat?
Chris@0 51 scm.supports_cat?
Chris@0 52 end
Chris@0 53
Chris@0 54 def supports_annotate?
Chris@0 55 scm.supports_annotate?
Chris@0 56 end
Chris@0 57
Chris@0 58 def entry(path=nil, identifier=nil)
Chris@0 59 scm.entry(path, identifier)
Chris@0 60 end
Chris@0 61
Chris@0 62 def entries(path=nil, identifier=nil)
Chris@0 63 scm.entries(path, identifier)
Chris@0 64 end
Chris@0 65
Chris@0 66 def branches
Chris@0 67 scm.branches
Chris@0 68 end
Chris@0 69
Chris@0 70 def tags
Chris@0 71 scm.tags
Chris@0 72 end
Chris@0 73
Chris@0 74 def default_branch
Chris@0 75 scm.default_branch
Chris@0 76 end
Chris@0 77
Chris@0 78 def properties(path, identifier=nil)
Chris@0 79 scm.properties(path, identifier)
Chris@0 80 end
Chris@0 81
Chris@0 82 def cat(path, identifier=nil)
Chris@0 83 scm.cat(path, identifier)
Chris@0 84 end
Chris@0 85
Chris@0 86 def diff(path, rev, rev_to)
Chris@0 87 scm.diff(path, rev, rev_to)
Chris@0 88 end
Chris@0 89
Chris@0 90 # Returns a path relative to the url of the repository
Chris@0 91 def relative_path(path)
Chris@0 92 path
Chris@0 93 end
Chris@0 94
Chris@0 95 # Finds and returns a revision with a number or the beginning of a hash
Chris@0 96 def find_changeset_by_name(name)
Chris@3 97 # TODO: is this query efficient enough? can we write as single query?
Chris@3 98 e = changesets.find(:first, :conditions => ['revision = ? OR scmid = ?', name.to_s, name.to_s])
Chris@3 99 return e if e
Chris@3 100 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
Chris@0 101 end
Chris@0 102
Chris@0 103 def latest_changeset
Chris@0 104 @latest_changeset ||= changesets.find(:first)
Chris@0 105 end
Chris@0 106
Chris@0 107 # Returns the latest changesets for +path+
Chris@0 108 # Default behaviour is to search in cached changesets
Chris@0 109 def latest_changesets(path, rev, limit=10)
Chris@0 110 if path.blank?
Chris@0 111 changesets.find(:all, :include => :user,
Chris@0 112 :limit => limit)
Chris@0 113 else
Chris@0 114 changes.find(:all, :include => {:changeset => :user},
Chris@0 115 :conditions => ["path = ?", path.with_leading_slash],
Chris@3 116 :order => "#{Changeset.table_name}.id DESC",
Chris@0 117 :limit => limit).collect(&:changeset)
Chris@0 118 end
Chris@0 119 end
Chris@0 120
Chris@0 121 def scan_changesets_for_issue_ids
Chris@0 122 self.changesets.each(&:scan_comment_for_issue_ids)
Chris@0 123 end
Chris@0 124
Chris@0 125 # Returns an array of committers usernames and associated user_id
Chris@0 126 def committers
Chris@0 127 @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
Chris@0 128 end
Chris@0 129
Chris@0 130 # Maps committers username to a user ids
Chris@0 131 def committer_ids=(h)
Chris@0 132 if h.is_a?(Hash)
Chris@0 133 committers.each do |committer, user_id|
Chris@0 134 new_user_id = h[committer]
Chris@0 135 if new_user_id && (new_user_id.to_i != user_id.to_i)
Chris@0 136 new_user_id = (new_user_id.to_i > 0 ? new_user_id.to_i : nil)
Chris@0 137 Changeset.update_all("user_id = #{ new_user_id.nil? ? 'NULL' : new_user_id }", ["repository_id = ? AND committer = ?", id, committer])
Chris@0 138 end
Chris@0 139 end
Chris@0 140 @committers = nil
Chris@0 141 @found_committer_users = nil
Chris@0 142 true
Chris@0 143 else
Chris@0 144 false
Chris@0 145 end
Chris@0 146 end
Chris@0 147
Chris@0 148 # Returns the Redmine User corresponding to the given +committer+
Chris@0 149 # It will return nil if the committer is not yet mapped and if no User
Chris@0 150 # with the same username or email was found
Chris@0 151 def find_committer_user(committer)
Chris@0 152 unless committer.blank?
Chris@0 153 @found_committer_users ||= {}
Chris@0 154 return @found_committer_users[committer] if @found_committer_users.has_key?(committer)
Chris@0 155
Chris@0 156 user = nil
Chris@0 157 c = changesets.find(:first, :conditions => {:committer => committer}, :include => :user)
Chris@0 158 if c && c.user
Chris@0 159 user = c.user
Chris@0 160 elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/
Chris@0 161 username, email = $1.strip, $3
Chris@0 162 u = User.find_by_login(username)
Chris@0 163 u ||= User.find_by_mail(email) unless email.blank?
Chris@0 164 user = u
Chris@0 165 end
Chris@0 166 @found_committer_users[committer] = user
Chris@0 167 user
Chris@0 168 end
Chris@0 169 end
Chris@0 170
Chris@0 171 # Fetches new changesets for all repositories of active projects
Chris@0 172 # Can be called periodically by an external script
Chris@0 173 # eg. ruby script/runner "Repository.fetch_changesets"
Chris@0 174 def self.fetch_changesets
Chris@0 175 Project.active.has_module(:repository).find(:all, :include => :repository).each do |project|
Chris@0 176 if project.repository
Chris@3 177 begin
Chris@3 178 project.repository.fetch_changesets
Chris@3 179 rescue Redmine::Scm::Adapters::CommandFailed => e
Chris@3 180 logger.error "Repository: error during fetching changesets: #{e.message}"
Chris@3 181 end
Chris@0 182 end
Chris@0 183 end
Chris@0 184 end
Chris@0 185
Chris@0 186 # scan changeset comments to find related and fixed issues for all repositories
Chris@0 187 def self.scan_changesets_for_issue_ids
Chris@0 188 find(:all).each(&:scan_changesets_for_issue_ids)
Chris@0 189 end
Chris@0 190
Chris@0 191 def self.scm_name
Chris@0 192 'Abstract'
Chris@0 193 end
Chris@0 194
Chris@0 195 def self.available_scm
Chris@0 196 subclasses.collect {|klass| [klass.scm_name, klass.name]}
Chris@0 197 end
Chris@0 198
Chris@0 199 def self.factory(klass_name, *args)
Chris@0 200 klass = "Repository::#{klass_name}".constantize
Chris@0 201 klass.new(*args)
Chris@0 202 rescue
Chris@0 203 nil
Chris@0 204 end
Chris@0 205
Chris@0 206 private
Chris@0 207
Chris@0 208 def before_save
Chris@0 209 # Strips url and root_url
Chris@0 210 url.strip!
Chris@0 211 root_url.strip!
Chris@0 212 true
Chris@0 213 end
Chris@0 214
Chris@0 215 def clear_changesets
Chris@0 216 cs, ch, ci = Changeset.table_name, Change.table_name, "#{table_name_prefix}changesets_issues#{table_name_suffix}"
Chris@0 217 connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
Chris@0 218 connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})")
Chris@0 219 connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}")
Chris@0 220 end
Chris@0 221 end