| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  | # redMine - project management software | 
					
						
							|  |  |  | # Copyright (C) 2006-2007  Jean-Philippe Lang | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  | # modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  | # as published by the Free Software Foundation; either version 2 | 
					
						
							|  |  |  | # of the License, or (at your option) any later version. | 
					
						
							|  |  |  | #  | 
					
						
							|  |  |  | # This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | # GNU General Public License for more details. | 
					
						
							|  |  |  | #  | 
					
						
							|  |  |  | # You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | # along with this program; if not, write to the Free Software | 
					
						
							|  |  |  | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require 'redmine/scm/adapters/mercurial_adapter' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Repository::Mercurial < Repository | 
					
						
							| 
									
										
										
										
											2011-01-02 06:05:54 +00:00
										 |  |  |   # sort changesets by revision number | 
					
						
							|  |  |  |   has_many :changesets, :order => "#{Changeset.table_name}.id DESC", :foreign_key => 'repository_id' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  |   attr_protected :root_url | 
					
						
							|  |  |  |   validates_presence_of :url | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-16 07:32:35 +00:00
										 |  |  |   FETCH_AT_ONCE = 100  # number of changesets to fetch at once | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-01 05:14:03 +00:00
										 |  |  |   ATTRIBUTE_KEY_NAMES = { | 
					
						
							|  |  |  |       "url"          => "Root directory", | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   def self.human_attribute_name(attribute_key_name) | 
					
						
							|  |  |  |     ATTRIBUTE_KEY_NAMES[attribute_key_name] || super | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-14 08:45:34 +00:00
										 |  |  |   def self.scm_adapter_class | 
					
						
							| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  |     Redmine::Scm::Adapters::MercurialAdapter | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2011-01-11 16:03:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  |   def self.scm_name | 
					
						
							|  |  |  |     'Mercurial' | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2011-01-11 16:03:01 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-16 00:50:11 +00:00
										 |  |  |   def supports_directory_revisions? | 
					
						
							|  |  |  |     true | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-28 12:09:32 +00:00
										 |  |  |   def repo_log_encoding | 
					
						
							|  |  |  |     'UTF-8' | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-11 16:03:01 +00:00
										 |  |  |   # Returns the readable identifier for the given mercurial changeset | 
					
						
							|  |  |  |   def self.format_changeset_identifier(changeset) | 
					
						
							| 
									
										
										
										
											2011-01-11 16:03:45 +00:00
										 |  |  |     "#{changeset.revision}:#{changeset.scmid}" | 
					
						
							| 
									
										
										
										
											2011-01-11 16:03:01 +00:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # Returns the identifier for the given Mercurial changeset | 
					
						
							|  |  |  |   def self.changeset_identifier(changeset) | 
					
						
							|  |  |  |     changeset.scmid | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-11 16:04:28 +00:00
										 |  |  |   def diff_format_revisions(cs, cs_to, sep=':') | 
					
						
							|  |  |  |     super(cs, cs_to, ' ') | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-08 06:24:27 +00:00
										 |  |  |   # Finds and returns a revision with a number or the beginning of a hash | 
					
						
							|  |  |  |   def find_changeset_by_name(name) | 
					
						
							| 
									
										
										
										
											2011-01-13 12:04:51 +00:00
										 |  |  |     return nil if name.nil? || name.empty? | 
					
						
							| 
									
										
										
										
											2011-01-08 06:24:27 +00:00
										 |  |  |     if /[^\d]/ =~ name or name.to_s.size > 8
 | 
					
						
							|  |  |  |       e = changesets.find(:first, :conditions => ['scmid = ?', name.to_s]) | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       e = changesets.find(:first, :conditions => ['revision = ?', name.to_s]) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     return e if e | 
					
						
							|  |  |  |     changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])  # last ditch | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-02 06:05:54 +00:00
										 |  |  |   # Returns the latest changesets for +path+; sorted by revision number | 
					
						
							| 
									
										
										
										
											2011-03-11 07:34:14 +00:00
										 |  |  |   # | 
					
						
							|  |  |  |   # Because :order => 'id DESC' is defined at 'has_many', | 
					
						
							|  |  |  |   # there is no need to set 'order'. | 
					
						
							|  |  |  |   # But, MySQL test fails. | 
					
						
							|  |  |  |   # Sqlite3 and PostgreSQL pass. | 
					
						
							|  |  |  |   # Is this MySQL bug? | 
					
						
							| 
									
										
										
										
											2011-01-02 06:05:54 +00:00
										 |  |  |   def latest_changesets(path, rev, limit=10) | 
					
						
							| 
									
										
										
										
											2011-03-14 10:36:34 +00:00
										 |  |  |     changesets.find(:all, :include => :user, | 
					
						
							|  |  |  |                     :conditions => latest_changesets_cond(path, rev, limit), | 
					
						
							|  |  |  |                     :limit => limit, :order => "#{Changeset.table_name}.id DESC") | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def latest_changesets_cond(path, rev, limit) | 
					
						
							|  |  |  |     cond, args = [], [] | 
					
						
							| 
									
										
										
										
											2011-03-14 13:41:01 +00:00
										 |  |  |     if scm.branchmap.member? rev | 
					
						
							|  |  |  |       # Mercurial named branch is *stable* in each revision. | 
					
						
							|  |  |  |       # So, named branch can be stored in database. | 
					
						
							|  |  |  |       # Mercurial provides *bookmark* which is equivalent with git branch. | 
					
						
							|  |  |  |       # But, bookmark is not implemented. | 
					
						
							|  |  |  |       cond << "#{Changeset.table_name}.scmid IN (?)" | 
					
						
							|  |  |  |       # Revisions in root directory and sub directory are not equal. | 
					
						
							|  |  |  |       # So, in order to get correct limit, we need to get all revisions. | 
					
						
							|  |  |  |       # But, it is very heavy. | 
					
						
							| 
									
										
										
										
											2011-03-16 00:50:53 +00:00
										 |  |  |       # Mercurial does not treat direcotry. | 
					
						
							|  |  |  |       # So, "hg log DIR" is very heavy. | 
					
						
							|  |  |  |       branch_limit = path.blank? ? limit : ( limit * 5 ) | 
					
						
							|  |  |  |       args << scm.nodes_in_branch(rev, :limit => branch_limit) | 
					
						
							| 
									
										
										
										
											2011-03-14 13:41:01 +00:00
										 |  |  |     elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil | 
					
						
							| 
									
										
										
										
											2011-03-14 10:36:34 +00:00
										 |  |  |       cond << "#{Changeset.table_name}.id <= ?" | 
					
						
							|  |  |  |       args << last.id | 
					
						
							| 
									
										
										
										
											2011-01-02 06:05:54 +00:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-03-14 10:36:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     unless path.blank? | 
					
						
							|  |  |  |       cond << "EXISTS (SELECT * FROM #{Change.table_name}
 | 
					
						
							|  |  |  |                  WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id | 
					
						
							|  |  |  |                  AND (#{Change.table_name}.path = ?  | 
					
						
							|  |  |  |                        OR #{Change.table_name}.path LIKE ? ESCAPE ?))" | 
					
						
							|  |  |  |       args << path.with_leading_slash | 
					
						
							|  |  |  |       args << "#{path.with_leading_slash.gsub(/[%_\\]/) { |s| "\\#{s}" }}/%" << '\\' | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     [cond.join(' AND '), *args] unless cond.empty? | 
					
						
							| 
									
										
										
										
											2011-01-02 06:05:54 +00:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2011-03-14 10:36:34 +00:00
										 |  |  |   private :latest_changesets_cond | 
					
						
							| 
									
										
										
										
											2011-01-02 06:05:54 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  |   def fetch_changesets | 
					
						
							| 
									
										
										
										
											2011-02-16 07:32:35 +00:00
										 |  |  |     scm_rev = scm.info.lastrev.revision.to_i | 
					
						
							|  |  |  |     db_rev = latest_changeset ? latest_changeset.revision.to_i : -1
 | 
					
						
							|  |  |  |     return unless db_rev < scm_rev  # already up-to-date | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger.debug "Fetching changesets for repository #{url}" if logger | 
					
						
							|  |  |  |     (db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| | 
					
						
							|  |  |  |       transaction do | 
					
						
							|  |  |  |         scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| | 
					
						
							|  |  |  |           cs = Changeset.create(:repository => self, | 
					
						
							|  |  |  |                                 :revision => re.revision, | 
					
						
							|  |  |  |                                 :scmid => re.scmid, | 
					
						
							|  |  |  |                                 :committer => re.author, | 
					
						
							|  |  |  |                                 :committed_on => re.time, | 
					
						
							|  |  |  |                                 :comments => re.message) | 
					
						
							|  |  |  |           re.paths.each { |e| cs.create_change(e) } | 
					
						
							| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2011-02-16 07:32:35 +00:00
										 |  |  |     self | 
					
						
							| 
									
										
										
										
											2007-06-12 20:12:05 +00:00
										 |  |  |   end | 
					
						
							|  |  |  | end |