diff --git a/.gitignore b/.gitignore index 3b868d2..6597f08 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ /tmp/sockets/* /tmp/test/* /vendor/rails + +/.hg/ +/.hgignore +/.hgtags diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 0e0f94c..373c9f4 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -158,13 +158,27 @@ module RepositoriesHelper def darcs_field_tags(form, repository) content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?))) end - + def mercurial_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) + content_tag('p', form.text_field( + :url, + :label => 'Root directory', + :size => 60, + :required => true, + ## Mercurial repository is removable. + # :disabled => (repository && !repository.root_url.blank?) + :disabled => false + )) end def git_field_tags(form, repository) - content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) + content_tag('p', form.text_field( + :url, + :label => 'Path to .git directory', + :size => 60, + :required => true, + :disabled => (repository && !repository.root_url.blank?) + )) end def cvs_field_tags(form, repository) diff --git a/app/models/repository.rb b/app/models/repository.rb index dee705c..45e690f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -17,7 +17,7 @@ class Repository < ActiveRecord::Base belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" + has_many :changesets, :order => "#{Changeset.table_name}.scm_order DESC, #{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changes, :through => :changesets # Raw SQL to delete changesets and changes in the database @@ -96,7 +96,7 @@ class Repository < ActiveRecord::Base def find_changeset_by_name(name) changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%'])) end - + def latest_changeset @latest_changeset ||= changesets.find(:first) end @@ -105,17 +105,18 @@ class Repository < ActiveRecord::Base # Default behaviour is to search in cached changesets def latest_changesets(path, rev, limit=10) if path.blank? + # this is defined at "has_many" + # :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", changesets.find(:all, :include => :user, - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", :limit => limit) else changes.find(:all, :include => {:changeset => :user}, :conditions => ["path = ?", path.with_leading_slash], - :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", + :order => "#{Changeset.table_name}.scm_order DESC, #{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", :limit => limit).collect(&:changeset) end end - + def scan_changesets_for_issue_ids self.changesets.each(&:scan_comment_for_issue_ids) end diff --git a/app/models/repository/mercurial.rb b/app/models/repository/mercurial.rb index 18cbc94..ca0b27f 100644 --- a/app/models/repository/mercurial.rb +++ b/app/models/repository/mercurial.rb @@ -18,77 +18,309 @@ require 'redmine/scm/adapters/mercurial_adapter' class Repository::Mercurial < Repository - attr_protected :root_url + attr_protected :root_url validates_presence_of :url + @@limit_check_strip = 100 + @@num_convert_redmine_0_9 = 20 + def scm_adapter Redmine::Scm::Adapters::MercurialAdapter end - + def self.scm_name 'Mercurial' end - + + def branches + scm.branches + end + + def tags + scm.tags + end + def entries(path=nil, identifier=nil) - entries=scm.entries(path, identifier) + entries=scm.entries(path, identifier,:include_file_revs => true ) if entries entries.each do |entry| - next unless entry.is_file? - # Set the filesize unless browsing a specific revision - if identifier.nil? - full_path = File.join(root_url, entry.path) - entry.size = File.stat(full_path).size if File.file?(full_path) - end - # Search the DB for the entry's last change - change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") - if change - entry.lastrev.identifier = change.changeset.revision - entry.lastrev.name = change.changeset.revision - entry.lastrev.author = change.changeset.committer - entry.lastrev.revision = change.revision + if entry && entry.lastrev && entry.lastrev.identifier + rev = changesets.find( + :first, + :conditions => [ "revision = ?" , entry.lastrev.identifier ] + ) + next if rev + rev = changesets.find( + :first, + :conditions => ["scmid LIKE ?", entry.lastrev.identifier + '%'] + ) + entry.lastrev.identifier = rev.revision if rev end end end entries end + # TODO: + # This logic fails in following case. + # + # Before + # + # /-C + # A-------B + # + # After + # + # /-D + # A-------B + # + # This is very very rare case. + # + # For this case, we need to store HEAD info on DB? + # http://www.redmine.org/issues/4773#note-11 + # def fetch_changesets scm_info = scm.info - if scm_info - # latest revision found in database - db_revision = latest_changeset ? latest_changeset.revision.to_i : -1 - # latest revision in the repository - latest_revision = scm_info.lastrev - return if latest_revision.nil? - scm_revision = latest_revision.identifier.to_i - if db_revision < scm_revision - logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? - identifier_from = db_revision + 1 - while (identifier_from <= scm_revision) - # loads changesets by batches of 100 - identifier_to = [identifier_from + 99, scm_revision].min - revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true) - transaction do - revisions.each do |revision| - changeset = Changeset.create(:repository => self, - :revision => revision.identifier, - :scmid => revision.scmid, - :committer => revision.author, - :committed_on => revision.time, - :comments => revision.message) - - revision.paths.each do |change| - Change.create(:changeset => changeset, - :action => change[:action], - :path => change[:path], - :from_path => change[:from_path], - :from_revision => change[:from_revision]) + # Backout Redmine 0.9.x + # return unless scm_info or scm_info.lastrev.nil? + return unless scm_info + + transaction do + Changeset.update_all( + "scm_order = -1" , + ["repository_id = ? AND scm_order is null", id] + ) + end + + identifier_from = 0 + + transaction do + tip_on_db = changesets.find(:first, :order => 'scm_order DESC') + tip_on_db = convert_changeset(tip_on_db) if ( tip_on_db && ( tip_on_db.scm_order == -1 ) ) + tip_revno_on_db = -1 + hg_revno_dbtip = -1 + if tip_on_db + tip_revno_on_db = tip_on_db.scm_order + revs = scm.revisions( nil, tip_on_db.scmid, tip_on_db.scmid, + :lite => true) + hg_revno_dbtip = revs.first.scm_order if revs && revs.first + end + + # Redmine cannot check changeset.count == scm.num_revisions + # because Redmine ver.0.9.x has stripped revision on DB + # and 'revision' is uniq. + if ( tip_revno_on_db == hg_revno_dbtip ) + identifier_from = tip_revno_on_db + 1 + else + # At Redmine SVN r3394, git check history editing for only one week. + # Mercurial has revision number, + # so Redmine can check from big revision number. + # And strip revision in middle of history on shared repository + # is very rare case. + ver09x_changeset_first = + changesets.find(:first,:conditions =>[ "scm_order = -1" ] ) + if ( ver09x_changeset_first ) + convert_changesets(@@num_convert_redmine_0_9) + end + converted_cs = nil + flag_loop_break = false + convert_limit = @@limit_check_strip + changesets.find( + :all, + :order => 'scm_order DESC', + :limit => convert_limit).each do |cs| + prev_no = cs.scm_order + converted_cs = convert_changeset(cs) + if ( converted_cs && converted_cs.scm_order ) + if ( converted_cs.scm_order == prev_no ) + changesets.find(:all,:conditions => + [ "scm_order = ? AND scmid != ?" , cs.scm_order , cs.scmid ] + ).each do |cs1| + convert_changeset(cs1) + end + flag_loop_break = true + break + end + end + end + if ( flag_loop_break ) + identifier_from = converted_cs.scm_order + 1 + else + identifier_from = [scm.num_revisions - convert_limit , 0].max + end + end + end + + scm_revision = scm.num_revisions - 1 + # Reffered from Subversion logic. + while (identifier_from <= scm_revision) + transaction do + identifier_to = [identifier_from + 19, scm_revision].min + revisions = scm.revisions( + nil, identifier_from, identifier_to ) + if revisions + revisions.each do |rev| + dups = changesets.find( + :all, + :conditions =>["scmid = ? or scmid = ? " , + rev.scmid , rev.identifier] + ) + unless dups.empty? + dups.each do |cs1| + ## There is no way to store + ## Redmine 0.9.x original revision number. + cs1.scmid = rev.scmid + cs1.scm_order = rev.scm_order.to_i + cs1.save + end + next end + rev.save(self) end - end unless revisions.nil? + end identifier_from = identifier_to + 1 end + end + end + + # TODO: + # 1. Mercurial has Named branches. + # http://mercurial.selenic.com/wiki/NamedBranches + # a) + # Mercurial has --only-branch option. + # But, this is show this branch only. + # TortoiseHg 0.9 Repository Explorer is different. + # How does TortoiseHg handle branches? + # b) + # If no changeset on this named branch, + # no revisons show on repository tab. + # c) + # Mercurial version 1.2 introduced the ability to close a branch. + # http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches + # + # 2. + # If Setting.autofetch_changesets is off, + # no revisons show on repository tab. + # + def latest_changesets(path, rev, limit=10) + branch = nil + if branches.index(rev) + branch = rev + end + revisions = scm.revisions( + path, rev, 0, :limit => limit, + :lite => true , :branch => branch + ) + + return [] if revisions.nil? or revisions.empty? + + # Redmine 0.9.x changeset scmid is short id. + changesets.find( + :all, + :conditions => [ + "scmid IN (?) or scmid IN (?)", + # revisions.map!{|c| c.scmid} , + revisions.map{|c| c.scmid} , + revisions.map{|c| c.identifier} + ] + ) + end + + def diff(path, rev, rev_to) + from_scmid = rev + if ( rev =~ /^\d*$/ ) + from_rev = find_changeset_by_name(rev) + from_scmid = from_rev.scmid if from_rev + end + return nil if from_scmid.nil? + to_scmid = rev_to + if ( rev_to =~ /^\d*$/ ) + to_rev = find_changeset_by_name(rev_to) + to_scmid = to_rev.scmid if to_rev + end + scm.diff(path, from_scmid, to_scmid) + end + + # Redmine 0.9.x has revision with revision number. + # TODO: + # In case of strip, revision number is renumberd, + # this logic fails? + # We need to test with fixtures? + # And tag and branch can use '\d'??? + def find_changeset_by_name(name) + if name + changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["scmid LIKE ?", name + '%'])) + else + nil + end + end + + # This method is not used now. + # But, I plan to use in rake task. + def convert_changesets_all + changesets.find_each( + :conditions => [ "scm_order = -1 or scm_order is null" ], + :batch_size => 100) do |cs| + convert_changeset(cs) + end + end + + def convert_changesets(limit=10) + changesets.find( + :all, + :conditions =>[ "scm_order = -1 or scm_order is null" ], + :limit => limit).each do |cs| + convert_changeset(cs) + end + end + + # Mercurial revision number is sequential from 0. + # And Mercurial has multipile heads. + # If one head in middle of history was stripped, + # revision number was renumbered. + # In this case, Redmine 0.9.x had duplicate scmid on DB. + # + # Because revision is uniq on table, convert fails. + # And "rNN" is written in Wiki, issue message, etc. + def convert_changeset(cs) + ret = cs + revs = scm.revisions(nil, cs.scmid, cs.scmid, :lite => true) + rev = nil + rev = revs.first if revs + if rev + ret = convert_changeset_with_revision(cs, rev) + else + cs.delete + ret = nil + end + ret + end + + def convert_changeset_with_revision(cs, rev) + cs.scm_order = rev.scm_order.to_i + if false + dup_first1 = changesets.find( + :first, + :conditions =>["revision = ?" , rev.identifier ] + ) + if dup_first1.nil? + then + ## There is no way to store + ## Redmine 0.9.x original revision number. + # cs.revision = rev.identifier if cs.revision != rev.identifier end end + dup_first = changesets.find( + :first, + :conditions =>["scmid = ?" , rev.scmid ] + ) + if dup_first.nil? + then + cs.scmid = rev.scmid + end + + cs.save + ret = cs + ret end end diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml index 92597df..35bfa9c 100644 --- a/app/views/repositories/revision.rhtml +++ b/app/views/repositories/revision.rhtml @@ -21,7 +21,9 @@

<%= l(:label_revision) %> <%= format_revision(@changeset.revision) %>

-

<% if @changeset.scmid %>ID: <%= @changeset.scmid %>
<% end %> +

+<% if @changeset.scmid %>ID: <%= @changeset.scmid %>
<% end %> +<% if @changeset.scm_order %>SCM Order: <%= @changeset.scm_order %>
<% end %> <%= authoring(@changeset.committed_on, @changeset.author) %>

<%= textilizable @changeset.comments %> diff --git a/db/migrate/20100222000000_add_scm_order_to_changesets.rb b/db/migrate/20100222000000_add_scm_order_to_changesets.rb new file mode 100644 index 0000000..00f6f92 --- /dev/null +++ b/db/migrate/20100222000000_add_scm_order_to_changesets.rb @@ -0,0 +1,12 @@ + +class AddScmOrderToChangesets < ActiveRecord::Migration + def self.up + add_column :changesets, :scm_order, :integer, :null => true + add_index :changesets, :scm_order + end + + def self.down + remove_index :changesets, :scm_order + remove_column :changesets, :scm_order + end +end diff --git a/lib/redmine/scm/adapters/abstract_adapter.rb b/lib/redmine/scm/adapters/abstract_adapter.rb index a3ca61e..32b8b36 100644 --- a/lib/redmine/scm/adapters/abstract_adapter.rb +++ b/lib/redmine/scm/adapters/abstract_adapter.rb @@ -269,9 +269,10 @@ module Redmine }.last end end - + + class Revision - attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch + attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch, :scm_order def initialize(attributes={}) self.identifier = attributes[:identifier] @@ -283,6 +284,7 @@ module Redmine self.paths = attributes[:paths] self.revision = attributes[:revision] self.branch = attributes[:branch] + self.scm_order = attributes[:scm_order] end def save(repo) @@ -293,8 +295,9 @@ module Redmine :scmid => scmid, :committer => author, :committed_on => time, - :comments => message) - + :comments => message , + :scm_order => scm_order + ) if changeset.save paths.each do |file| Change.create( @@ -306,7 +309,7 @@ module Redmine end end end - + class Annotate attr_reader :lines, :revisions diff --git a/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5-lite.tmpl b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5-lite.tmpl new file mode 100644 index 0000000..9686357 --- /dev/null +++ b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5-lite.tmpl @@ -0,0 +1,8 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n{desc|escape}\n{tags}\n\n' + +tag = '{tag|escape}\n' +header='\n\n\n' +# footer="" \ No newline at end of file diff --git a/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl index b3029e6..98218a1 100644 --- a/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl +++ b/lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl @@ -1,7 +1,7 @@ changeset = 'This template must be used with --debug option\n' changeset_quiet = 'This template must be used with --debug option\n' changeset_verbose = 'This template must be used with --debug option\n' -changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{files}{file_adds}{file_dels}{file_copies}\n{desc|escape}\n{tags}\n\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{files}{file_adds}{file_dels}{file_copies}\n{desc|escape}\n{tags}\n\n' file = '{file|escape}\n' file_add = '{file_add|escape}\n' diff --git a/lib/redmine/scm/adapters/mercurial/hg-template-1.0-lite.tmpl b/lib/redmine/scm/adapters/mercurial/hg-template-1.0-lite.tmpl new file mode 100644 index 0000000..5da81e8 --- /dev/null +++ b/lib/redmine/scm/adapters/mercurial/hg-template-1.0-lite.tmpl @@ -0,0 +1,8 @@ +changeset = 'This template must be used with --debug option\n' +changeset_quiet = 'This template must be used with --debug option\n' +changeset_verbose = 'This template must be used with --debug option\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{desc|escape}\n{tags}\n\n' + +tag = '{tag|escape}\n' +header='\n\n\n' +# footer="" diff --git a/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl b/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl index 3eef850..d90594b 100644 --- a/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl +++ b/lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl @@ -1,7 +1,7 @@ changeset = 'This template must be used with --debug option\n' changeset_quiet = 'This template must be used with --debug option\n' changeset_verbose = 'This template must be used with --debug option\n' -changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{file_mods}{file_adds}{file_dels}{file_copies}\n{desc|escape}\n{tags}\n\n' +changeset_debug = '\n{author|escape}\n{date|isodate}\n\n{file_mods}{file_adds}{file_dels}{file_copies}\n{desc|escape}\n{tags}\n\n' file_mod = '{file_mod|escape}\n' file_add = '{file_add|escape}\n' diff --git a/lib/redmine/scm/adapters/mercurial_adapter.rb b/lib/redmine/scm/adapters/mercurial_adapter.rb index 79e2d13..30697d0 100644 --- a/lib/redmine/scm/adapters/mercurial_adapter.rb +++ b/lib/redmine/scm/adapters/mercurial_adapter.rb @@ -16,24 +16,30 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'redmine/scm/adapters/abstract_adapter' +require 'rexml/document' module Redmine module Scm - module Adapters + module Adapters class MercurialAdapter < AbstractAdapter - # Mercurial executable name HG_BIN = "hg" TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" TEMPLATE_NAME = "hg-template" TEMPLATE_EXTENSION = "tmpl" - + class << self + @@limit_include_file_revs = 20 + @@has_size_ext = true + def client_version - @@client_version ||= (hgversion || []) + @@client_version ||= (hgversion || []) end - - def hgversion + + # TODO: + # Mercurial version 1.2 introduced the ability to close a branch. + # http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches + def hgversion # The hg version is expressed either as a # release number (eg 0.9.5 or 1.0) or as a revision # id composed of 12 hexa characters. @@ -42,41 +48,129 @@ module Redmine theversion.split(".").collect(&:to_i) end end - + def hgversion_from_command_line %x{#{HG_BIN} --version}.match(/\(version (.*)\)/)[1] end - + def template_path @@template_path ||= template_path_for(client_version) end - - def template_path_for(version) + + def lite_template_path + @@lite_template_path ||= template_path_for(client_version,'lite') + end + + def template_path_for(version,style=nil) if ((version <=> [0,9,5]) > 0) || version.empty? ver = "1.0" else ver = "0.9.5" end - "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" + if style + tmpl = "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}-#{style}.#{TEMPLATE_EXTENSION}" + else + tmpl = "#{TEMPLATES_DIR}/#{TEMPLATE_NAME}-#{ver}.#{TEMPLATE_EXTENSION}" + end + tmpl + end + end + + # Mercurial default branch is "default". + # But, Mercurial has multipile heads. + def default_branch + @default_branch ||= 'tip' + end + + def branches + @branches ||= get_branches + end + + # TODO: + # Mercurial version 1.2 introduced the ability to close a branch. + # http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches + def get_branches + branches = [] + cmd = "#{HG_BIN} -R #{target('')} branches" + shellout(cmd) do |io| + io.each_line do |line| + branches << line.chomp.match('^([^:]+[^\s]+)[\s]+[\d]+:.*$')[1] + end end + branches + end + + def tags + @tags ||= get_tags end - + + def get_tags + tags = [] + cmd = "#{HG_BIN} -R #{target('')} tags -v" + shellout(cmd) do |io| + io.each_line do |line| + strs = line.chomp.match('^([^:]+[^\s]+)[\s]+[\d]+:(.*)$') + if strs[2] !~ /[\s]+local/ + tags << strs[1] + end + end + end + tags + end + + def tip + @tip ||= get_tip + end + + def get_tip + tip = nil + cmd = "#{HG_BIN} -R #{target('')} id -i -r tip" + shellout(cmd) do |io| + tip = io.gets.chomp + end + return nil if $? && $?.exitstatus != 0 + tip + end + def info cmd = "#{HG_BIN} -R #{target('')} root" root_url = nil shellout(cmd) do |io| - root_url = io.gets + root_url = io.gets.chomp end return nil if $? && $?.exitstatus != 0 - info = Info.new({:root_url => root_url.chomp, - :lastrev => revisions(nil,nil,nil,{:limit => 1}).last - }) + info = Info.new( + { + :root_url => root_url.chomp, + # :lastrev => revisions(nil,nil,nil,{:limit => 1}).last + }) info rescue CommandFailed return nil end - - def entries(path=nil, identifier=nil) + + def lastrev(path=nil, identifier=nil) + lastrev = revisions(path,identifier,0,:limit => 1, :lite => true) + return nil if lastrev.nil? or lastrev.empty? + lastrev.last + end + + def num_revisions + num = 0 + cmd = "#{HG_BIN} -R #{target('')} log -r tip --template=#{shell_quote('{rev}\n')}" + shellout(cmd) do |io| + line = io.gets + if line.nil? + num = 0 + else + num = line.chomp.to_i + 1 + end + break + end + num + end + + def entries(path=nil, identifier=nil, options={}) path ||= '' entries = Entries.new cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" @@ -89,34 +183,86 @@ module Redmine if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') e ||= line e = e.chomp.split(%r{[\/\\]}) - entries << Entry.new({:name => e.first, - :path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), - :kind => (e.size > 1 ? 'dir' : 'file'), - :lastrev => Revision.new - }) unless e.empty? || entries.detect{|entry| entry.name == e.first} + unless e.empty? || + entries.detect{|entry| entry.name == e.first} + kind = (e.size > 1 ? 'dir' : 'file') + ent_path = (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}") + lastrev = nil + s = nil + entries << Entry.new( + { + :name => e.first , + :path => ent_path , + :kind => kind , + :size => s , + :lastrev => lastrev + } + ) + end end end end return nil if $? && $?.exitstatus != 0 + + file_cnt = 0 + entries.each do |ent| + if ( ent.kind == 'file' ) + file_cnt += 1 + end + end + entries.each do |ent| + if ( ent.kind == 'file' ) + if options[:include_file_revs] && file_cnt < @@limit_include_file_revs + # Following process is very heavy. + ent.lastrev = lastrev(ent.path,identifier) + s = nil + s = size(path,identifier) if @@has_size_ext + if s.nil? and (identifier.to_s == default_branch or identifier.to_s == 'tip') + full_path = info.root_url + '/' + ent.path + ent.size = File.stat(full_path).size if File.file?(full_path) + end + else + ent.lastrev = Revision.new + end + else + # "hg log -l1 DIR" is VERY VERY HEAVY!! + ent.lastrev = Revision.new + end + end + entries.sort_by_name end - + # Fetch the revisions by using a template file that # makes Mercurial produce a xml output. - def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) + # + # TODO: + # Mercurial version 1.2 introduced the ability to close a branch. + # http://mercurial.selenic.com/wiki/PruningDeadBranches#Closing_branches + def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) revisions = Revisions.new - cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" + cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} --cwd #{target('')} log" + if options[:lite] + cmd << " --style #{shell_quote self.class.lite_template_path}" + else + cmd << " -C --style #{shell_quote self.class.template_path}" + end if identifier_from && identifier_to - cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" + cmd << " -r #{shell_quote(identifier_from.to_s)}:#{shell_quote(identifier_to.to_s)}" elsif identifier_from - cmd << " -r #{identifier_from.to_i}:" + cmd << " -r #{shell_quote(identifier_from.to_s)}:" + elsif identifier_to + cmd << " -r :#{shell_quote(identifier_to.to_s)}" end cmd << " --limit #{options[:limit].to_i}" if options[:limit] + cmd << " --only-branch #{options[:branch]}" if options[:branch] cmd << " #{path}" if path shellout(cmd) do |io| begin # HG doesn't close the XML Document... - doc = REXML::Document.new(io.read << "") + output = io.read + return nil if output.empty? + doc = REXML::Document.new(output << "") doc.elements.each("log/logentry") do |logentry| paths = [] copies = logentry.get_elements('paths/path-copied') @@ -124,7 +270,7 @@ module Redmine # Detect if the added file is a copy if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } from_path = c.attributes['copyfrom-path'] - from_rev = logentry.attributes['revision'] + from_rev = logentry.attributes['shortnode'] end paths << {:action => path.attributes['action'], :path => "/#{path.text}", @@ -132,15 +278,18 @@ module Redmine :from_revision => from_rev ? from_rev : nil } end - paths.sort! { |x,y| x[:path] <=> y[:path] } - - revisions << Revision.new({:identifier => logentry.attributes['revision'], - :scmid => logentry.attributes['node'], - :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), - :time => Time.parse(logentry.elements['date'].text).localtime, - :message => logentry.elements['msg'].text, - :paths => paths - }) + paths.sort! { |x,y| x[:path] <=> y[:path] } unless paths.empty? + revisions << Revision.new( + { + :identifier => logentry.attributes['shortnode'], + :scmid => logentry.attributes['node'], + :author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), + :time => Time.parse(logentry.elements['date'].text).localtime, + :message => logentry.elements['msg'].text, + :paths => paths, + :scm_order => logentry.attributes['revision'].to_i , + } + ) end rescue logger.debug($!) @@ -149,15 +298,14 @@ module Redmine return nil if $? && $?.exitstatus != 0 revisions end - + def diff(path, identifier_from, identifier_to=nil) path ||= '' if identifier_to - identifier_to = identifier_to.to_i + cmd = "#{HG_BIN} -R #{target('')} diff -r #{shell_quote(identifier_to.to_s)} -r #{shell_quote(identifier_from.to_s)} --nodates" else - identifier_to = identifier_from.to_i - 1 + cmd = "#{HG_BIN} -R #{target('')} diff -c #{identifier_from} --nodates" end - cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" cmd << " -I #{target(path)}" unless path.empty? diff = [] shellout(cmd) do |io| @@ -168,11 +316,11 @@ module Redmine return nil if $? && $?.exitstatus != 0 diff end - + def cat(path, identifier=nil) cmd = "#{HG_BIN} -R #{target('')} cat" - cmd << " -r " + (identifier ? identifier.to_s : "tip") - cmd << " #{target(path)}" + cmd << " -r " + shell_quote((identifier ? identifier.to_s : "tip")) + cmd << " #{target(path)}" unless path.empty? cat = nil shellout(cmd) do |io| io.binmode @@ -181,19 +329,38 @@ module Redmine return nil if $? && $?.exitstatus != 0 cat end - + + def size(path, identifier=nil) + # return nil + + cmd = "#{HG_BIN} --cwd #{target('')} size" + cmd << " -r " + shell_quote((identifier ? identifier.to_s : "tip")) + cmd << " #{path}" unless path.empty? + size = nil + shellout(cmd) do |io| + size = io.read + end + return nil if $? && $?.exitstatus != 0 + size.to_i + end + + # TODO: + # hg annotate behavior small changes at Ver.1.5. + # http://mercurial.selenic.com/wiki/UpgradeNotes#A1.5:_Small_behavior_changes + # hg annotate now follows copies and renames by default, + # use --no-follow for old behavior. def annotate(path, identifier=nil) path ||= '' + identifier = 'tip' if identifier.blank? cmd = "#{HG_BIN} -R #{target('')}" - cmd << " annotate -n -u" - cmd << " -r " + (identifier ? identifier.to_s : "tip") - cmd << " -r #{identifier.to_i}" if identifier - cmd << " #{target(path)}" + cmd << " annotate -c -u" + cmd << " -r #{shell_quote(identifier.to_s)}" + cmd << " #{target(path)}" unless path.empty? blame = Annotate.new shellout(cmd) do |io| io.each_line do |line| - next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} - blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip)) + next unless line =~ %r{^([^:]+)\s(\w+):(.*)$} + blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_s, :author => $1.strip)) end end return nil if $? && $?.exitstatus != 0 diff --git a/lib/tasks/testing.rake b/lib/tasks/testing.rake index 2028bd1..d4f84d2 100644 --- a/lib/tasks/testing.rake +++ b/lib/tasks/testing.rake @@ -26,11 +26,23 @@ namespace :test do system "svnadmin create #{repo_path}" system "gunzip < test/fixtures/repositories/subversion_repository.dump.gz | svnadmin load #{repo_path}" end - - (supported_scms - [:subversion]).each do |scm| + + desc "Creates a test mercurial repository" + task :mercurial => :create_dir do + repo_path = "tmp/test/mercurial_repository" + system "hg init #{repo_path}" + system "cp -f test/fixtures/repositories/mercurial/hgrc #{repo_path}/.hg/hgrc " + system "hg -R #{repo_path} pull test/fixtures/repositories/mercurial/default.r0.bundle" + system "hg -R #{repo_path} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle" + system "hg -R #{repo_path} pull test/fixtures/repositories/mercurial/branch00.r1.bundle" + + end + + (supported_scms - [:subversion, :mercurial]).each do |scm| desc "Creates a test #{scm} repository" task scm => :create_dir do - system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" + # system "gunzip < test/fixtures/repositories/#{scm}_repository.tar.gz | tar -xv -C tmp/test" + system "tar -xvz -C tmp/test -f test/fixtures/repositories/#{scm}_repository.tar.gz" end end diff --git a/test/fixtures/repositories/mercurial/branch00.r1.bundle b/test/fixtures/repositories/mercurial/branch00.r1.bundle new file mode 100644 index 0000000000000000000000000000000000000000..2fd5ab0eefe96b8f69f70eb7b6a35332affa2d63 GIT binary patch literal 772 zcmV+f1N;0)M=>x$T4*^jL0KkKSqU6Lc>n+l|Ns9{ajM13|Mh>O5(z*5|KYy62`(Z9 z83+^)b>P7XQN=(4T}Kl^29zQ^D9nff>IRxL(@hUkO#{>o0ib9A000dDG%|jpYKSlZ zOaKJIFaQ7m089d4A%FlQ0006bM8N@1Q$P&>000^q00000000dD00K!ypif7nYBfAe z2@gnVpbb4k$p8Q}Gy#xkdQCI{(?9^5Lmmh{o?Ft=^far6gxwd*S{Q0XO2%A8dRWJ)08|df>pyo96Lh5B2N6F z%$cXwm<1_uK#4Y+Lq|L>0-$kP+h9llkwILvDJW8YBq|mtxtcfIoBPzPmV{!8V5-~Z zY5am2!t96lTXZgTxdWvKfEz?Ry5Uk~=Cy(xHByqISt9dS7962u2$0uG3XrnFKy?xn zRu*q(hC8}IO$DBGkx>w7mJ@b?2kEf72^6se`f@&1^?7UNm}rnQYE!{XS9FCP-HUMA z+p@0ar1D^*Ar_T6wyW5J`?79Wq_#m2sH-a?x>SW(CKhA~c-5~EWu-3KkuW|OR#+&r z1*9kHx04vB)qvWnaSGTiK~XHlHGr6zEj{Kdg_#j`SBX>=8B^G@@)W{&q)P~(pu%zV z)(ljk7N{mS!r?1*M+wuCYY#%=(?=%fDhbkHkYl+YN>)keEvPa~WpO)v45E4-((e%Q zm<0IKKvkTeCIDb6t5?&U1W+XW9ckZSX0t)z-x$T4*^jL0KkKStYgZ+W-I-|NsC0yPN#~|NsC0|Nj5~|Nnn~jBnE^ zDTo;t_5QDgan#TS&q>8~U2N@DbwCtwG(>?<(N9xqdTJX})EG^vjX?Dtpm{@0siu!4 z^&X?iG#;SPXam$_9-~iFL)2&hXa<8LLFxggiaku5C^SfEF)*j-RQxJ5(=8>$ z0iYTU00000000000000015gObq+ufz$>}l%5NYWcfHX7>003wJGynk50gwOy00000 z0E0$`fEo=n0g%u%(?*69Kr(0z01XUAO)_8z#KZt-8e}pIBS2`$pa1|QlSr6Mrc9b9 z(^Jx5qX^SRjT!-yMw%K7o}d5#14e)}WB@b(XaE2J00D&Mh>&;$EhpVxsKPmrpk)lj z^L1i1SVCR8AhyVG6e1hrCUpoBP|t97(D(R1 zOp?L5HdGJ>InvkfNIXPU*}E5GMBN4OG9W`_=BZa}2DPLV<0=`B9IpCucYKCNxJn=k zp(q_{NkmEu1)XAsutbupyLa&xh(3 z_Z=X}837>320&cs=5!c_R`6{mqbmJ0cv;)Mm3e5SH+4wnnZvEeZS5B=++p5Cck+O3 z!4oMQ0un;Gu98*GBRwN|jX^(=gAzXDz4JB#<|VHRvN^!9eS+Go_Gxh&$|! z)3%u~#`=SZMFjxM7Omi!zFaW>!5BTc*B_((Kwa{4YHhIak5`diU*xwbmtS;_)tu5S!~R^ z8b>P-drXA)#c63u+##d@qDtgw3*`)!GN^_a;=d9v8u<}%#rZZ(&@pIBH^wAGQsmTN z4&hAul@M2!Tq{%v5?~ClD+;&v&1vR6CI*H$7?5s4+%K%-EM!!Noq`3_ti!MZ z66*7|AXitk7|?6XbiBrw@M}r@mX_01iRJ{cbv)wL&eU5rj^1-@3um5@N`eWeQWrE<=xZUNJ3_>b_3Z8Tk8jv%Y)O{Z z4pkJtADrg0BvjUO7j36NO4jtuj3tWFRV7Z@MNE5C_g<(X17^X%OL?+$L*wuX?1cC z+tPc8cbd>`c9aE#xbcL{u3ikhfYTwS3DT1t%x#BOsE3l17L$P)rEyzbV)ClP?8A= zqbrM-5EWGPrJPcfI=ov=TW<+*1@WieKj1o%l}*rh^s_KWbCU_Ni2iQGuR1PFr+xKP_L1N!y0g} zgc};rl|eHrN;i$D%PsKJ891jU#I6ZXNXEwg=d7zFd{oKkUiX_>z#Ah0UAiz2MtZ42 zCPv>)p<0C6$8yXVm|=v>)j&!NjK&tFK*qyDu%fIpRf2^vm6Y?cH|j?m%xI3%7quAW zLbqwqN{dcJQbc2CrNfe48zu^lG^09F0=r;kZRb+VLbS9>Zq8yA4trP&Dv_9KY)(MX zEayYO4n&pIMB+jglCD9}2YY{|p7MydodAKYFhLI}@d?3LSaA%@M=6!S%_M52HBc%< zg;WFBoVsx8&8%bKo>fU`fMwVtfYXp9TpKYPgVxY%a+wqaG~(7PgPamz>_suIyKSyx z*}ZU}#Ziw#5pQBy9Pq{s;rVhqih`Y8KYMJg_%6(9lme%nr>f?@p&s&pDc_x}@3ND|ujZGi5~ BLH+;$ literal 0 HcmV?d00001 diff --git a/test/fixtures/repositories/mercurial/default.r1-r5.bundle b/test/fixtures/repositories/mercurial/default.r1-r5.bundle new file mode 100644 index 0000000000000000000000000000000000000000..7d661edabf599e701cc1c27b13219050e313a9bb GIT binary patch literal 3126 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 Qz%lkN + +[extensions] +# MQ extention needs for unit test of check history editing. +hgext.mq = + +# share = +# hgext.convert = +# hgext.graphlog = +# extdiff = +# hgext.hgk = +# hgext.bookmarks = +# rebase= +# hgext.purge= + +# hggit = +# svn = + diff --git a/test/fixtures/repositories/mercurial_repository.tar.gz b/test/fixtures/repositories/mercurial_repository.tar.gz deleted file mode 100644 index 1d8ad305732f7a0d7bfbcd52bbeec919da88e3f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7827 zcmV;E9&F(siwFp_o?=G;3vFd`V|8+AVQgP=WpHnEX>@OLc`kHeascdF2{_bS8=sjF zL$;8V4AHfOnPFzgHe`)b(@l|WFox{2*s?^(UP{WfMTDD_eJk1bN<_(0h$Op^E#GkI zR?ofH?S6f}=X*ZCdEV#Dod5a%&-aQ1dYOOhSIMI7*B z0g;5FP$&ohvJB`qYUpm+ue_XGM^&;Q}bZ|8p` zbUFTS`u~3(j`!aR;xo*K$PSs$nI@`RSiw=Of7H$v0PxDCl(}jqv2>J@2BV^=d{||* znp##RA@T*Kn#s}zi^mbgab}hzF?(D5>UAKUs;mYL#-IBC%6RxOyTI??{~;vc$j$xVMqnNL z|BzkaI`cnV0*3s?{wPZO&G~;r0Hl0=!!3#wtyM3rg4XxSfoIbpPGx4%PkGL>{ABAf z^Z3i#oudWn9)QcBZ0K$|?XVrt{<|68{@&cX^(zdTKHLgP)F=q>x4YXzdfjU;>S9N_ zDFwK$!cXEZ}*-kgRO?XMO-cG)0m>b6;f|bNVzuF%qce6TvUG)uO9#_dw;IuUT?8 zC^D8on5i4nizcZu$-EpErDA8PozfX_ettgQ@rW+Pi*Nd(r+>1W#82El^ThIe892Gr z9GVCg^E~ve1jq4_#ZRaR8u=FV8YNV3EKm2`ZkH2D(|E3ua;(D@2r5 z)C^kX2U->2GB-4RKAxA(?^^ZKvnc-R@JdHym;Kk=1OivM0Y>M+^2WZP9R84}TR+ll zsW*XTM**rv+basR;E~4aMa(fr9N-*x$gFBIyDpBG)Z9p%kWh8b6P-D+-}4pgB(vKC zZ_EfhMD-#j`UAg!&m;&Z@SC%FF#l}9UDxlqK5B3o7kv`i)I7CXD>#}|O)3oS!=L?-R@i80JKR4O%B zs5CA{d()rVDoj%6>fL$&T(?98*8u;~sW~yXC^gPI)5>jy!Tx@9WPXOA84e--`q~%F z;QOlWp~-qnuL5h>4nFj5^>FBiUpb!K##?sDNn7@@QMv|~!sQ{gYkCFRa-fca+6!5i zV@|XQF99w{3d$~b4pLpw!H*)BSCyt(E|(SWnr<2wIHWUwu{gFor$mK=)czv>YsE$D z>z5Vx?M|O(vo=W42Wwrc+6sdPwb9V7@tE7@7UdYs()GDKAx*=bsk>jr_bBU&UB6c65*xs@J8!zp zjmsgnjBt~`RcXJW*C_8}{xcj))AZN*1dp$u_#h-2z}$~MiY0w{yL0rmdTGWoC^P!$Giiq4 zVdqHJfN)8Dz@z?iY^-mn9NaMrBa8FS1zlC?95v@zO+!-i!M-`Fd?lFu*Mqut@*@K@M;kAehtl&Fp5O|`#q_ROwtemQ-K+}3{J zN$ZginQSdqHtqmMAhVy(mfMhOuv&DGOM3d!mV1RrkB@&0RCq9K%^2xXum_QNWVH1W&xc}Y2RZ>b5r@@qm;$~k9Fp2QpJ zlGQP=eua+?@m=R~W;i4k2bGqDO2W+G<`R@|<|YU%7A}p&!ErcK69fv1 zm4KS8qTo-QWsMF)vnPw8c{EEqykD=5j>M_=*j5KNLDt{gH>eLt;jd)Y0^bb}M7`8eM+SW}Dc)!B-^16=~`5LSJ; zV!)MP3^@M<=g(pI`8J=JSv(ey}ctJHQ?x$WH9nFH|kk+*I?lp8A)Upz5>dNZv5oyBW<5_e$OyQeA{=q$Hdt9_0$J@I#AOBx}m(3 zfxoEV-)IZwfIvEnz+;uv0D;I^7cg&RK`KLKK!Gdx7PufF60Ax?(+~Ee(NXo&*>?;4 zlE#mQ#{IDz5bZuB5L=u+^jue0x`ILK7jhoZSBhSiFK8!czAyUvcvEMg0$MSGq-LZ> z;}^2n;mNA2$VfXz=dk#sWNdMMCN@_3Nt^;j{f9*zJU8TNsxCtw8q-|Bqngg`rq|w4 zeJSvC&Vx`l2AbU$P8Bbq6h;s9nh!RMa0dplx@d_deY(EmWl1NWLf-}7)?S}`C=t_% zSNb0jla6P^Zocl=xg!?v$asu2Mbv#8-be*Vx6q*j{IB9W{D19C|GP5*>-ay#^(DWa z|KTu6l*Fe0ZwP+(|ApoEIM!;{{6FrU^{uSuJa+fkF9~4}JZ|lXPh`kI2}8r?U~t*_=?6xg9O^(?@($4xg?JoZjtQe_f6RY;R}{GPLFsv&Ivi^ea^d%I6b zu?DGDoMVefe2_kL+T&ds{?_rwOvtxr=Y_}37l^V1(PZsbT9#s)CJs-eN8APX`n7iaC?jcwDA1eb<@F0R%(&4SuaC`fyB^MuTnZ&@u}n5G?cyRYtU5t?Ah7qqvY zFG|In*Mjyv?E!BuG>bFHpN3CGl0{Ux*w%E{(k`DDBh+l^iH0@od4-QS&a;(DH!M{o zX<@_+7P7~R9uO_sV)i_6e?023l%F|KK6=Qr&1tgF$>>#l+`|M39=W(-HA5p$hv#`= zP6n|-5m(RO^)6bfh&6k~&A#A)?TWAuroM2uW~qC`bM}-E-aMsF^OBbEHMc!+52u-gql5xBcA#$JKY_n9|F0#6 z{%IfoVEs?|vAO==2z-zIKPLvi&J3=*9#}8`kAxz=&i}&^o9};Z6h6oQEjj?eR*tpm zwfG=pY@HrDjKz5xr4fsL1%2w>w8aI?-<1uFC9=dhIEN-op=s?=7&TuOXjS zy~oO4Oj}q2^+ulxJLu5cCsw3iC(yHS7O!#WP%}r3<~dpaj?PGFVt>Q_DNsbuu2edF z&7E0v*uj0-FEFYvp1gsdIb0V;mF=iXWU7W$BWCEg$`)yuGiSTBhjPy{nGT(6#$dUJfgOie%rueqSxW z;FpW92H6Ce3{}y#)FzMnkehxb=Hekk^uETbsc|y_ z?O=%|+cYMH>D-H{q2BTyyECsfUJR@>lA6W`2WWA6CIn}=-$(jRj-YsTBs9rQcK6WL5}#vl$ky!BMPo;7zYX2jXU6|^U$E-y8Ug_^uj>URpM z_Y-C|a;NTGR`~ss<|5Klf?)>?^G=54MGLuhjLwQW@%<_ryi^&EuE;Pd(+;T=&{+T{ zH0c#T4C$Up{>6Uxz4wK>?ddZk2uDp7TPY#>ox5ZuinQ|IR?$edL&%Y3YSVYbdupB< zFYxpY467|gWbKv$8pj4k9cFDlY9x}n9OEY z^EZw;{nK)eGb&T>3(v+)yKue`fBD>yUo*1p>GQ#7&YlvfTpvddr!`-3&EGwYn0I5| zHB;@nAnuSij#o4zb+?`-bn1^i*xwbRWpk=XIC(~qAC6(sUR0DhsTZOw_=ym%$70&z zUwW+NRiO>txa)DU>(UTC-gZGQOPpBuak0yNNx{)&XaJA1&rC^_6SzAx*YdhW(Qj@X z<&b+sZ_OVmI7(TSkF7czXw5SX`qSyqicOPr+Bv@prHc*a*x#i8+AB?sjl0TAN+vD= z8D2DbIRh_Y;{jAEsRBtVE?@zbq*QQ{i*#ybCAdvGpt90cCAE@z)c$n z&?2IM7VCyB^tsfQ7O`S^PouScaec8amG`W#xQF`un$FxLfK;VE`SAPRcmI!@aOcjP zIddj+&Y3%Brun7qo;Rb`UMu@@KI_547wM?$zLxlz3Kq-Wwql5uCFq$#_Kl#t&Gi-Y zg2+ufY6K_c3kNF_9?4H*Q$vGGRb|l!Wn1G*$~K7CjTCR1_d)slso(s)n$<6U_41mK z^u-zPFVqkT2^k9q%+%YBU%&aC9SMD-GjD0^jveoRX=iXyoofT|`P#$3cIM=r^QuOs zix)b@f1XSTqa{&0wuWTI3MH>)tm?C(Us*~~acTL;ii8g~Y^i*~FL8mx)ylT=qhBi6 zrOrP#mhExc1DiK2H7uTPOBl^*FbYMta*bA8=oPEvQ`!DARsORJzc5y`tsYo3$o<2Y zAJ(G%UEzh+Z1~8cWhyYO7EA-PB^kJs;y`PurNn7C<)Owjm{R*{AW1(EQ#v)iz?1zv zqi&zd{^JTMsl=bw{C7M5=L!6}%>VfUK4brT2krL14|KQtp0TCc?EisU(JG+bUh>*1 z$D4NC?qc?GS1EH7o41bhe#*pOjbEshF?@uH>!kn{p)Yy#3orWkb{YNf*22 z$E&b1mz-moAdid3tG2lOI@z4Iq5ns}toJ(PcH323*StmK6cqCFHbwF=(mp`a0~N=eFsz>ydg{GSmbv6;c#UFNLa*AxuXgR4sH;-1ICc7rX!`*vYvqI? z6}KOv8f*JmK8Ch0{aFC7y;i!&S);91$^_xL+`r}>U3n~RYv^0of(s7cUEG;=Zh3Oy z!2yT3F8%Hb_Ak%h+sE{N1n}`VIdW{k$LgHbi+u=zukNSXPq|+dCm#4nJpHnI%i3cT zKXHi&cO6uG;_4#?e0e)Ai;%=@U@`mu zdl`=$|IZrm*9A|t{>MZ6|M~s`CjPTm@TC6l7^KfuM|b+4&qL=w36S#V^SLPg*PqMv zW%Pe<;NAGR2)5Hm-|!GHQ;#kkGbK>kU*ZBz0x=Yp{% zZ2>P|UNqs17B_Qu$fD)XMf%SlPa6L=BmS&)bhrM` zr{n+p{Gk8GhgJaje||h>|9`LG5#!$lUG(_(@EL^Gv^b9${{oPKsj@j14FHEf&o|J| z*P;Pj2SguawQB%7hk-o)s+}7bu-Y|%(+zalh3w*&vi_g}`m&G)s4hsXuP$k7YSLF{ z>g)6LdcA?gGZaV-27|GHWh{?2)@K@x#+F2v7HvpNO=62759*56nvm9-=+@R&Q)+>! zJkQirW73zH^yMafjmc1GGL)DMeC$!yk}%?7jCXg0T+%_cJg20adF>(+T;=-nnV zdC!}h=L}Yo$-xck37y5_OWW4ik5D^DdcL=Kyz{r)GgsakKOpkjODjIRIj-NtJ9qt( zPqQ!f3I6Ksx}tIC;|DBR;D=)q_WKOEAGjmBG2ChVc&9~np?#^#CAL6t0nD zQk=tfilPva91THHxC%Z2@j5Dg2d`b|OSzw!K;^WS>~ zPwf95dIj6v{Lf$D-=Y6~1@!(;9&`S0uV4aH`Lb97s$Urh&jN(QY1I)@zE2c;H} za{4ABE*V7NDqI5%PJEU`K}bOiA;nc%nqv&sjuN%$(DF!hbct}pLz0yT5KcnE^t?6* z;LsMqI&7BB7g4ZQW2Na0NDc77D8#u2kZM%U6SAuV3TWlpi*iuON43AR0fpuaQBEhJ z$f$Hk6E-r3P$+C5j76N&?}t6M+!K3ll|Ap~*4A zL=c~x7(Y8Hi~~Rtj@Y85_;?uuIV7eep&Vs6g%JvEd&^T`*J>eq3K>YpvT@kOQk=*} z_8)o-YTcjgJ_V^tv$zOUCrJhYLJm|U-5rBL=*AyiPlJRaAFH-*dd2FXc64swX3bYSmXt6B_g z*NR{&5UQc{rF3;2>1-!pJuV-(OE4|&juIp|6dNv<6AJX33A7%xELMrjNZuqJ7y?H+y;*_c#^$e>Gx%b{qMWHv6g>S~k6B6d@?c(ia0XI%E(NAd2kuq@ zSy_{D++jPcVt|h+O%@K8bRm_YZ71Xrn5BmOhI@j%d^J==`2c-FcGU{#7mDdh-L2e0 z%GUqHuv0TVY>jJA_FQ7C3eraZ&|hpPT96cO6JlqfTe3fG47WvSRF+0%X<(L0fkOf` z8BT{ZXLwjymx(1hch85r1XgFskJ#y25h1fx6Kqq|(>gk{6YsDxX%=qd2uW1T=knXz lPJcXav%`P^0|pEjFkrxd0RsjM7%=EX{0^uzbfy4!001CPd#eBd diff --git a/test/functional/repositories_mercurial_controller_test.rb b/test/functional/repositories_mercurial_controller_test.rb index f2639ee..d17fed3 100644 --- a/test/functional/repositories_mercurial_controller_test.rb +++ b/test/functional/repositories_mercurial_controller_test.rb @@ -33,6 +33,14 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase @response = ActionController::TestResponse.new User.current = nil Repository::Mercurial.create(:project => Project.find(3), :url => REPOSITORY_PATH) + + %x{hg -R #{REPOSITORY_PATH} update null} + %x{hg -R #{REPOSITORY_PATH} strip 0} + %x{hg -R #{REPOSITORY_PATH} verify} + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r0.bundle} + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/branch00.r1.bundle} + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle} + end if File.directory?(REPOSITORY_PATH) @@ -92,7 +100,7 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase :attributes => { :class => /line-num/ }, :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ } end - + def test_entry_download get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw' assert_response :success @@ -100,6 +108,8 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase assert @response.body.include?('WITHOUT ANY WARRANTY') end + # This shows "default branch". + # git shows "master", but Mercurial shows "tip". def test_directory_entry get :entry, :id => 3, :path => ['sources'] assert_response :success @@ -107,10 +117,22 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase assert_not_nil assigns(:entry) assert_equal 'sources', assigns(:entry).name end - + + def test_browse_branch + get :show, :id => 3, :rev => 'branch00' + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_equal 4, assigns(:entries).size + assert assigns(:entries).detect {|e| e.name == 'branch00-dir' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'} + assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'} + end + def test_diff # Full diff of changeset 4 - get :diff, :id => 3, :rev => 4 + get :diff, :id => 3, :rev => 'def6d2f1254a' assert_response :success assert_template 'diff' # Line 22 removed @@ -120,7 +142,7 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase :attributes => { :class => /diff_out/ }, :content => /def remove/ } end - + def test_annotate get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb'] assert_response :success @@ -131,6 +153,7 @@ class RepositoriesMercurialControllerTest < ActionController::TestCase :sibling => { :tag => 'td', :content => /jsmith/ }, :sibling => { :tag => 'td', :content => /watcher =/ } end + else puts "Mercurial test repository NOT FOUND. Skipping functional tests !!!" def test_fake; assert true end diff --git a/test/unit/repository_mercurial_test.rb b/test/unit/repository_mercurial_test.rb index 6ce3d5f..9548285 100644 --- a/test/unit/repository_mercurial_test.rb +++ b/test/unit/repository_mercurial_test.rb @@ -26,48 +26,87 @@ class RepositoryMercurialTest < ActiveSupport::TestCase def setup @project = Project.find(1) assert @repository = Repository::Mercurial.create(:project => @project, :url => REPOSITORY_PATH) + + %x{hg -R #{REPOSITORY_PATH} update null} + %x{hg -R #{REPOSITORY_PATH} strip 0} + %x{hg -R #{REPOSITORY_PATH} verify} + + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r0.bundle} + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/branch00.r1.bundle} + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle} end - - if File.directory?(REPOSITORY_PATH) + + if File.directory?(REPOSITORY_PATH) + def test_fetch_changesets_from_scratch @repository.fetch_changesets @repository.reload - - assert_equal 6, @repository.changesets.count - assert_equal 11, @repository.changes.count - assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision('0').comments + + assert_equal 7, @repository.changesets.count + assert_equal 12, @repository.changes.count + assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find_by_revision('0885933ad4f6').comments end def test_fetch_changesets_incremental @repository.fetch_changesets - # Remove changesets with revision > 2 - @repository.changesets.find(:all).each {|c| c.destroy if c.revision.to_i > 2} + ## Mercurial can set commit date, + # @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy) + @repository.changesets.find(:all, :order => 'scm_order DESC', :limit => 3).each(&:destroy) + @repository.reload - assert_equal 3, @repository.changesets.count - + assert_equal 4, @repository.changesets.count + @repository.fetch_changesets - assert_equal 6, @repository.changesets.count + assert_equal 7, @repository.changesets.count end def test_entries - assert_equal 2, @repository.entries("sources", 2).size - assert_equal 1, @repository.entries("sources", 3).size + %x{hg -R #{REPOSITORY_PATH} up null} + assert_equal 2, @repository.entries("sources", '400bb8672109').size + assert_equal 1, @repository.entries("sources", 'b3a615152df8').size end def test_locate_on_outdated_repository # Change the working dir state - %x{hg -R #{REPOSITORY_PATH} up -r 0} - assert_equal 1, @repository.entries("images", 0).size + %x{hg -R #{REPOSITORY_PATH} up null} + assert_equal 1, @repository.entries("images", '0885933ad4f6').size assert_equal 2, @repository.entries("images").size - assert_equal 2, @repository.entries("images", 2).size + assert_equal 2, @repository.entries("images", '400bb8672109').size end def test_cat - assert @repository.scm.cat("sources/welcome_controller.rb", 2) + assert @repository.scm.cat("sources/welcome_controller.rb", '400bb8672109') assert_nil @repository.scm.cat("sources/welcome_controller.rb") end + def test_simple_strip + %x{hg -R #{REPOSITORY_PATH} up null} + %x{hg -R #{REPOSITORY_PATH} strip def6d2f1254a} + @repository.fetch_changesets + @repository.reload + assert_equal 5, @repository.changesets.count + + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/default.r1-r5.bundle} + @repository.fetch_changesets + @repository.reload + assert_equal 7, @repository.changesets.count + end + + def test_middle_rev_strip + %x{hg -R #{REPOSITORY_PATH} up null} + %x{hg -R #{REPOSITORY_PATH} strip 96aec45e5255} + @repository.fetch_changesets + @repository.reload + assert_equal 6, @repository.changesets.count + %x{hg -R #{REPOSITORY_PATH} pull test/fixtures/repositories/mercurial/branch00.r1.bundle} + @repository.fetch_changesets + @repository.reload + assert_equal 7, @repository.changesets.count + + + end + else puts "Mercurial test repository NOT FOUND. Skipping unit tests !!!" def test_fake; assert true end