From: Jean-Philippe Lang Date: Wed, 12 Mar 2008 20:28:49 +0000 (+0000) Subject: Merged Git support branch (r1200 to r1226). X-Git-Tag: 0.7.0-RC1~68 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=3a9b0988c7515371531e47f9eef9f8e60ce352aa;p=redmine.git Merged Git support branch (r1200 to r1226). git-svn-id: http://redmine.rubyforge.org/svn/trunk@1236 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 13d3eaa32..bce5f66a9 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -134,7 +134,7 @@ class RepositoriesController < ApplicationController end def diff - @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1) + @rev_to = params[:rev_to] @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline' @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type) @@ -185,7 +185,7 @@ private render_404 and return false unless @repository @path = params[:path].join('/') unless params[:path].nil? @path ||= '' - @rev = params[:rev].to_i if params[:rev] + @rev = params[:rev] rescue ActiveRecord::RecordNotFound render_404 end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index f21b43a23..be0b808d2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -270,6 +270,7 @@ module ApplicationHelper # #52 -> Link to issue #52 # Changesets: # r52 -> Link to revision 52 + # commit:a85130f -> Link to scmid starting with a85130f # Documents: # document#17 -> Link to document with id 17 # document:Greetings -> Link to the document with title "Greetings" @@ -280,7 +281,7 @@ module ApplicationHelper # version:"1.0 beta 2" -> Link to version named "1.0 beta 2" # Attachments: # attachment:file.zip -> Link to the attachment of the current object named file.zip - text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m| + text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m| leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8 link = nil if esc.nil? @@ -325,6 +326,10 @@ module ApplicationHelper link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version}, :class => 'version' end + when 'commit' + if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) + link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project.id, :rev => changeset.revision}, :class => 'changeset', :title => truncate(changeset.comments, 100) + end when 'attachment' if attachments && attachment = attachments.detect {|a| a.filename == name } link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment}, diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index d7d7f4349..31daf1bd8 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -25,6 +25,10 @@ module RepositoriesHelper type ? CodeRay.scan(content, type).html : h(content) end + def format_revision(txt) + txt.to_s[0,8] + end + def to_utf8(str) return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip) @@ -76,6 +80,10 @@ module RepositoriesHelper content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?))) 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?))) + end + def cvs_field_tags(form, repository) content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) + content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?)) diff --git a/app/models/changeset.rb b/app/models/changeset.rb index dbe06935d..ce9ea28ca 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -32,7 +32,6 @@ class Changeset < ActiveRecord::Base :date_column => 'committed_on' validates_presence_of :repository_id, :revision, :committed_on, :commit_date - validates_numericality_of :revision, :only_integer => true validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true @@ -89,7 +88,11 @@ class Changeset < ActiveRecord::Base # don't change the status is the issue is closed next if issue.status.is_closed? user = committer_user || User.anonymous - journal = issue.init_journal(user, l(:text_status_changed_by_changeset, "r#{self.revision}")) + csettext = "r#{self.revision}" + if self.scmid && (! (csettext =~ /^r[0-9]+$/)) + csettext = "commit:\"#{self.scmid}\"" + end + journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext)) issue.status = fix_status issue.done_ratio = done_ratio if done_ratio issue.save @@ -114,11 +117,11 @@ class Changeset < ActiveRecord::Base # Returns the previous changeset def previous - @previous ||= Changeset.find(:first, :conditions => ['revision < ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision DESC') + @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC') end # Returns the next changeset def next - @next ||= Changeset.find(:first, :conditions => ['revision > ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision ASC') + @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC') end end diff --git a/app/models/repository.rb b/app/models/repository.rb index be31ac2e5..229c8dae4 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, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC" + has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" has_many :changes, :through => :changesets def scm @@ -51,7 +51,7 @@ class Repository < ActiveRecord::Base path = "/#{path}" unless path.starts_with?('/') Change.find(:all, :include => :changeset, :conditions => ["repository_id = ? AND path = ?", id, path], - :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset) + :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset) end def latest_changeset diff --git a/app/models/repository/bazaar.rb b/app/models/repository/bazaar.rb index 6e387f957..1b75066c2 100644 --- a/app/models/repository/bazaar.rb +++ b/app/models/repository/bazaar.rb @@ -51,7 +51,7 @@ class Repository::Bazaar < Repository scm_info = scm.info if scm_info # latest revision found in database - db_revision = latest_changeset ? latest_changeset.revision : 0 + db_revision = latest_changeset ? latest_changeset.revision.to_i : 0 # latest revision in the repository scm_revision = scm_info.lastrev.identifier.to_i if db_revision < scm_revision diff --git a/app/models/repository/cvs.rb b/app/models/repository/cvs.rb index 16d906316..a78b60806 100644 --- a/app/models/repository/cvs.rb +++ b/app/models/repository/cvs.rb @@ -82,9 +82,6 @@ class Repository::Cvs < Repository end def fetch_changesets - #not the preferred way with CVS. maybe we should introduce always a cron-job for this - last_commit = changesets.maximum(:committed_on) - # some nifty bits to introduce a commit-id with cvs # natively cvs doesn't provide any kind of changesets, there is only a revision per file. # we now take a guess using the author, the commitlog and the commit-date. @@ -94,8 +91,10 @@ class Repository::Cvs < Repository # we use a small delta here, to merge all changes belonging to _one_ changeset time_delta=10.seconds + fetch_since = latest_changeset ? latest_changeset.committed_on : nil transaction do - scm.revisions('', last_commit, nil, :with_paths => true) do |revision| + tmp_rev_num = 1 + scm.revisions('', fetch_since, nil, :with_paths => true) do |revision| # only add the change to the database, if it doen't exists. the cvs log # is not exclusive at all. unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision]) @@ -107,18 +106,16 @@ class Repository::Cvs < Repository }) # create a new changeset.... - unless cs - # we use a negative changeset-number here (just for inserting) + unless cs + # we use a temporaray revision number here (just for inserting) # later on, we calculate a continous positive number - next_rev = changesets.minimum(:revision) - next_rev = 0 if next_rev.nil? or next_rev > 0 - next_rev = next_rev - 1 - - cs=Changeset.create(:repository => self, - :revision => next_rev, - :committer => revision.author, - :committed_on => revision.time, - :comments => revision.message) + latest = changesets.find(:first, :order => 'id DESC') + cs = Changeset.create(:repository => self, + :revision => "_#{tmp_rev_num}", + :committer => revision.author, + :committed_on => revision.time, + :comments => revision.message) + tmp_rev_num += 1 end #convert CVS-File-States to internal Action-abbrevations @@ -139,12 +136,13 @@ class Repository::Cvs < Repository end end - next_rev = [changesets.maximum(:revision) || 0, 0].max - changesets.find(:all, :conditions=>["revision < 0"], :order=>"committed_on ASC").each() do |changeset| - next_rev = next_rev + 1 - changeset.revision = next_rev - changeset.save! + # Renumber new changesets in chronological order + c = changesets.find(:first, :order => 'committed_on DESC, id DESC', :conditions => "revision NOT LIKE '_%'") + next_rev = c.nil? ? 1 : (c.revision.to_i + 1) + changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset| + changeset.update_attribute :revision, next_rev + next_rev += 1 end - end + end # transaction end end diff --git a/app/models/repository/darcs.rb b/app/models/repository/darcs.rb index 48cc246fb..cc608d370 100644 --- a/app/models/repository/darcs.rb +++ b/app/models/repository/darcs.rb @@ -47,18 +47,19 @@ class Repository::Darcs < Repository def diff(path, rev, rev_to, type) patch_from = changesets.find_by_revision(rev) + return nil if patch_from.nil? patch_to = changesets.find_by_revision(rev_to) if rev_to if path.blank? path = patch_from.changes.collect{|change| change.path}.join(' ') end - scm.diff(path, patch_from.scmid, patch_to.scmid, type) + patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil, type) : nil end def fetch_changesets scm_info = scm.info if scm_info db_last_id = latest_changeset ? latest_changeset.scmid : nil - next_rev = latest_changeset ? latest_changeset.revision + 1 : 1 + next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1 # latest revision in the repository scm_revision = scm_info.lastrev.scmid unless changesets.find_by_scmid(scm_revision) @@ -71,9 +72,7 @@ class Repository::Darcs < Repository :committer => revision.author, :committed_on => revision.time, :comments => revision.message) - - next if changeset.new_record? - + revision.paths.each do |change| Change.create(:changeset => changeset, :action => change[:action], diff --git a/app/models/repository/git.rb b/app/models/repository/git.rb new file mode 100644 index 000000000..7213588ac --- /dev/null +++ b/app/models/repository/git.rb @@ -0,0 +1,70 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# Copyright (C) 2007 Patrick Aljord patcito@ŋmail.com +# 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/git_adapter' + +class Repository::Git < Repository + attr_protected :root_url + validates_presence_of :url + + def scm_adapter + Redmine::Scm::Adapters::GitAdapter + end + + def self.scm_name + 'Git' + end + + def changesets_for_path(path) + Change.find(:all, :include => :changeset, + :conditions => ["repository_id = ? AND path = ?", id, path], + :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset) + end + + def fetch_changesets + scm_info = scm.info + if scm_info + # latest revision found in database + db_revision = latest_changeset ? latest_changeset.revision : nil + # latest revision in the repository + scm_revision = scm_info.lastrev.scmid + + unless changesets.find_by_scmid(scm_revision) + + revisions = scm.revisions('', db_revision, nil) + transaction do + revisions.reverse_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]) + end + end + end + end + end + end +end diff --git a/app/models/repository/subversion.rb b/app/models/repository/subversion.rb index a0485608d..0c2239c43 100644 --- a/app/models/repository/subversion.rb +++ b/app/models/repository/subversion.rb @@ -39,7 +39,7 @@ class Repository::Subversion < Repository scm_info = scm.info if scm_info # latest revision found in database - db_revision = latest_changeset ? latest_changeset.revision : 0 + db_revision = latest_changeset ? latest_changeset.revision.to_i : 0 # latest revision in the repository scm_revision = scm_info.lastrev.identifier.to_i if db_revision < scm_revision diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml index a7b83e817..3564e52ab 100644 --- a/app/views/repositories/_dir_list_content.rhtml +++ b/app/views/repositories/_dir_list_content.rhtml @@ -23,7 +23,7 @@ else end %> <%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %> -<%= link_to(entry.lastrev.name, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> +<%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> <%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %> <%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %> <% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %> diff --git a/app/views/repositories/_revisions.rhtml b/app/views/repositories/_revisions.rhtml index 52992bb89..1bcf0208c 100644 --- a/app/views/repositories/_revisions.rhtml +++ b/app/views/repositories/_revisions.rhtml @@ -13,7 +13,7 @@ <% line_num = 1 %> <% revisions.each do |changeset| %> -<%= link_to changeset.revision, :action => 'revision', :id => project, :rev => changeset.revision %> +<%= link_to format_revision(changeset.revision), :action => 'revision', :id => project, :rev => changeset.revision %> <%= radio_button_tag('rev', changeset.revision, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %> <%= radio_button_tag('rev_to', changeset.revision, (line_num==2), :id => "cbto-#{line_num}", :onclick => "if ($('cb-#{line_num}').checked==true) {$('cb-#{line_num-1}').checked=true;}") if show_diff && (line_num > 1) %> <%= format_time(changeset.committed_on) %> diff --git a/app/views/repositories/annotate.rhtml b/app/views/repositories/annotate.rhtml index 28d99a393..b5669ef76 100644 --- a/app/views/repositories/annotate.rhtml +++ b/app/views/repositories/annotate.rhtml @@ -11,7 +11,7 @@ <%= line_num %> - <%= (revision.identifier ? link_to(revision.identifier, :action => 'revision', :id => @project, :rev => revision.identifier) : revision.revision) if revision %> + <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %> <%= h(revision.author.to_s.split('<').first) if revision %>
<%= line %>
diff --git a/app/views/repositories/diff.rhtml b/app/views/repositories/diff.rhtml index 88c5f17a0..eaef1abf5 100644 --- a/app/views/repositories/diff.rhtml +++ b/app/views/repositories/diff.rhtml @@ -1,4 +1,4 @@ -

<%= l(:label_revision) %> <%= @rev %>: <%= @path.gsub(/^.*\//, '') %>

+

<%= l(:label_revision) %> <%= format_revision(@rev) %> <%= @path.gsub(/^.*\//, '') %>

<% form_tag({ :controller => 'repositories', :action => 'diff'}, :method => 'get') do %> @@ -23,8 +23,8 @@ - @<%= @rev %> - @<%= @rev_to %> + @<%= format_revision @rev %> + @<%= format_revision @rev_to %> @@ -56,8 +56,8 @@ - @<%= @rev %> - @<%= @rev_to %> + @<%= format_revision @rev %> + @<%= format_revision @rev_to %> diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml index d60c0b0b7..5a7ef1fd5 100644 --- a/app/views/repositories/revision.rhtml +++ b/app/views/repositories/revision.rhtml @@ -19,7 +19,7 @@ <% end %> -

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

+

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

<% if @changeset.scmid %>ID: <%= @changeset.scmid %>
<% end %> <%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %>

diff --git a/db/migrate/091_change_changesets_revision_to_string.rb b/db/migrate/091_change_changesets_revision_to_string.rb new file mode 100644 index 000000000..e621a3909 --- /dev/null +++ b/db/migrate/091_change_changesets_revision_to_string.rb @@ -0,0 +1,9 @@ +class ChangeChangesetsRevisionToString < ActiveRecord::Migration + def self.up + change_column :changesets, :revision, :string, :null => false + end + + def self.down + change_column :changesets, :revision, :integer, :null => false + end +end diff --git a/db/migrate/092_change_changes_from_revision_to_string.rb b/db/migrate/092_change_changes_from_revision_to_string.rb new file mode 100644 index 000000000..b298a3f45 --- /dev/null +++ b/db/migrate/092_change_changes_from_revision_to_string.rb @@ -0,0 +1,9 @@ +class ChangeChangesFromRevisionToString < ActiveRecord::Migration + def self.up + change_column :changes, :from_revision, :string + end + + def self.down + change_column :changes, :from_revision, :integer + end +end diff --git a/doc/RUNNING_TESTS b/doc/RUNNING_TESTS index fde24413b..7a5e2b992 100644 --- a/doc/RUNNING_TESTS +++ b/doc/RUNNING_TESTS @@ -19,3 +19,19 @@ gunzip < test/fixtures/repositories/bazaar_repository.tar.gz | tar -xv -C tmp/te Mercurial --------- gunzip < test/fixtures/repositories/mercurial_repository.tar.gz | tar -xv -C tmp/test + +Git +--- +gunzip < test/fixtures/repositories/git_repository.tar.gz | tar -xv -C tmp/test + + +Running Tests +============= + +Run + + rake --tasks | grep test + +to see available tests. + +RAILS_ENV=test rake test will run tests. diff --git a/lib/redmine.rb b/lib/redmine.rb index e76d77e9e..4c5cbdaae 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -10,7 +10,7 @@ rescue LoadError # RMagick is not available end -REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar ) +REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git ) # Permissions Redmine::AccessControl.map do |map| diff --git a/lib/redmine/scm/adapters/darcs_adapter.rb b/lib/redmine/scm/adapters/darcs_adapter.rb index 2955b26dc..cd8610121 100644 --- a/lib/redmine/scm/adapters/darcs_adapter.rb +++ b/lib/redmine/scm/adapters/darcs_adapter.rb @@ -102,8 +102,12 @@ module Redmine def diff(path, identifier_from, identifier_to=nil, type="inline") path = '*' if path.blank? cmd = "#{DARCS_BIN} diff --repodir #{@url}" - cmd << " --to-match \"hash #{identifier_from}\"" - cmd << " --from-match \"hash #{identifier_to}\"" if identifier_to + if identifier_to.nil? + cmd << " --match \"hash #{identifier_from}\"" + else + cmd << " --to-match \"hash #{identifier_from}\"" + cmd << " --from-match \"hash #{identifier_to}\"" + end cmd << " -u #{path}" diff = [] shellout(cmd) do |io| diff --git a/lib/redmine/scm/adapters/git_adapter.rb b/lib/redmine/scm/adapters/git_adapter.rb new file mode 100644 index 000000000..b6b1b858d --- /dev/null +++ b/lib/redmine/scm/adapters/git_adapter.rb @@ -0,0 +1,261 @@ +# 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/abstract_adapter' + +module Redmine + module Scm + module Adapters + class GitAdapter < AbstractAdapter + + # Git executable name + GIT_BIN = "git" + + # Get the revision of a particuliar file + def get_rev (rev,path) + cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?) + cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if + rev=='latest' or rev.nil? + rev=[] + i=0 + shellout(cmd) do |io| + files=[] + changeset = {} + parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files + + io.each_line do |line| + if line =~ /^commit ([0-9a-f]{40})$/ + key = "commit" + value = $1 + if (parsing_descr == 1 || parsing_descr == 2) + parsing_descr = 0 + rev = Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + changeset = {} + files = [] + end + changeset[:commit] = $1 + elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ + key = $1 + value = $2 + if key == "Author" + changeset[:author] = value + elsif key == "Date" + changeset[:date] = value + end + elsif (parsing_descr == 0) && line.chomp.to_s == "" + parsing_descr = 1 + changeset[:description] = "" + elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/ + parsing_descr = 2 + fileaction = $1 + filepath = $2 + files << {:action => fileaction, :path => filepath} + elsif (parsing_descr == 1) && line.chomp.to_s == "" + parsing_descr = 2 + elsif (parsing_descr == 1) + changeset[:description] << line + end + end + rev = Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + + end + + get_rev('latest',path) if rev == [] + + return nil if $? && $?.exitstatus != 0 + return rev + end + + + def info + root_url = target('') + info = Info.new({:root_url => target(''), + :lastrev => revisions(root_url,nil,nil,{:limit => 1}).first + }) + info + rescue Errno::ENOENT => e + return nil + end + + def entries(path=nil, identifier=nil) + path ||= '' + entries = Entries.new + cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l " + cmd << shell_quote("HEAD:" + path) if identifier.nil? + cmd << shell_quote(identifier + ":" + path) if identifier + shellout(cmd) do |io| + io.each_line do |line| + e = line.chomp.to_s + if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/ + type = $1 + sha = $2 + size = $3 + name = $4 + entries << Entry.new({:name => name, + :path => (path.empty? ? name : "#{path}/#{name}"), + :kind => ((type == "tree") ? 'dir' : 'file'), + :size => ((type == "tree") ? nil : size), + :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}")) + + }) unless entries.detect{|entry| entry.name == name} + end + end + end + return nil if $? && $?.exitstatus != 0 + entries.sort_by_name + end + + def entry(path=nil, identifier=nil) + path ||= '' + search_path = path.split('/')[0..-2].join('/') + entry_name = path.split('/').last + e = entries(search_path, identifier) + e ? e.detect{|entry| entry.name == entry_name} : nil + end + + def revisions(path, identifier_from, identifier_to, options={}) + revisions = Revisions.new + cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw " + cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit] + cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from + cmd << " #{shell_quote identifier_to} " if identifier_to + #cmd << " HEAD " if !identifier_to + shellout(cmd) do |io| + files=[] + changeset = {} + parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files + revno = 1 + + io.each_line do |line| + if line =~ /^commit ([0-9a-f]{40})$/ + key = "commit" + value = $1 + if (parsing_descr == 1 || parsing_descr == 2) + parsing_descr = 0 + revisions << Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) + changeset = {} + files = [] + revno = revno + 1 + end + changeset[:commit] = $1 + elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ + key = $1 + value = $2 + if key == "Author" + changeset[:author] = value + elsif key == "Date" + changeset[:date] = value + end + elsif (parsing_descr == 0) && line.chomp.to_s == "" + parsing_descr = 1 + changeset[:description] = "" + elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/ + parsing_descr = 2 + fileaction = $1 + filepath = $2 + files << {:action => fileaction, :path => filepath} + elsif (parsing_descr == 1) && line.chomp.to_s == "" + parsing_descr = 2 + elsif (parsing_descr == 1) + changeset[:description] << line[4..-1] + end + end + + revisions << Revision.new({:identifier => changeset[:commit], + :scmid => changeset[:commit], + :author => changeset[:author], + :time => Time.parse(changeset[:date]), + :message => changeset[:description], + :paths => files + }) if changeset[:commit] + + end + + return nil if $? && $?.exitstatus != 0 + revisions + end + + def diff(path, identifier_from, identifier_to=nil, type="inline") + path ||= '' + if !identifier_to + identifier_to = nil + end + + cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil? + cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil? + cmd << " -- #{shell_quote path}" unless path.empty? + diff = [] + shellout(cmd) do |io| + io.each_line do |line| + diff << line + end + end + return nil if $? && $?.exitstatus != 0 + DiffTableList.new diff, type + end + + def annotate(path, identifier=nil) + identifier = 'HEAD' if identifier.blank? + cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}" + blame = Annotate.new + shellout(cmd) do |io| + io.each_line do |line| + next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)$/ + blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip)) + end + end + return nil if $? && $?.exitstatus != 0 + blame + end + + def cat(path, identifier=nil) + if identifier.nil? + identifier = 'HEAD' + end + cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}" + cat = nil + shellout(cmd) do |io| + io.binmode + cat = io.read + end + return nil if $? && $?.exitstatus != 0 + cat + end + end + end + end + +end + diff --git a/test/fixtures/repositories/darcs_repository.tar.gz b/test/fixtures/repositories/darcs_repository.tar.gz new file mode 100644 index 000000000..ba4d8d04c Binary files /dev/null and b/test/fixtures/repositories/darcs_repository.tar.gz differ diff --git a/test/fixtures/repositories/git_repository.tar.gz b/test/fixtures/repositories/git_repository.tar.gz new file mode 100644 index 000000000..84de88aa7 Binary files /dev/null and b/test/fixtures/repositories/git_repository.tar.gz differ diff --git a/test/functional/repositories_darcs_controller_test.rb b/test/functional/repositories_darcs_controller_test.rb new file mode 100644 index 000000000..fc77b8747 --- /dev/null +++ b/test/functional/repositories_darcs_controller_test.rb @@ -0,0 +1,94 @@ +# redMine - project management software +# Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper' +require 'repositories_controller' + +# Re-raise errors caught by the controller. +class RepositoriesController; def rescue_action(e) raise e end; end + +class RepositoriesDarcsControllerTest < Test::Unit::TestCase + fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules + + # No '..' in the repository path + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository' + + def setup + @controller = RepositoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + Repository::Darcs.create(:project => Project.find(3), :url => REPOSITORY_PATH) + end + + if File.directory?(REPOSITORY_PATH) + def test_show + get :show, :id => 3 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_not_nil assigns(:changesets) + end + + def test_browse_root + get :browse, :id => 3 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + 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_browse_directory + get :browse, :id => 3, :path => ['images'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal 2, assigns(:entries).size + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry + assert_equal 'file', entry.kind + assert_equal 'images/edit.png', entry.path + end + + def test_changes + get :changes, :id => 3, :path => ['images', 'edit.png'] + assert_response :success + assert_template 'changes' + assert_tag :tag => 'h2', :content => 'edit.png' + end + + def test_diff + Project.find(3).repository.fetch_changesets + # Full diff of changeset 5 + get :diff, :id => 3, :rev => 5 + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => /22/, + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + end + else + puts "Darcs test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff --git a/test/functional/repositories_git_controller_test.rb b/test/functional/repositories_git_controller_test.rb new file mode 100644 index 000000000..fec0bbaa0 --- /dev/null +++ b/test/functional/repositories_git_controller_test.rb @@ -0,0 +1,123 @@ +# 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 File.dirname(__FILE__) + '/../test_helper' +require 'repositories_controller' + +# Re-raise errors caught by the controller. +class RepositoriesController; def rescue_action(e) raise e end; end + +class RepositoriesGitControllerTest < Test::Unit::TestCase + fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules + + # No '..' in the repository path + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository' + REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/ + + def setup + @controller = RepositoriesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + Repository::Git.create(:project => Project.find(3), :url => REPOSITORY_PATH) + end + + if File.directory?(REPOSITORY_PATH) + def test_show + get :show, :id => 3 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:entries) + assert_not_nil assigns(:changesets) + end + + def test_browse_root + get :browse, :id => 3 + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal 3, assigns(:entries).size + 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_browse_directory + get :browse, :id => 3, :path => ['images'] + assert_response :success + assert_template 'browse' + assert_not_nil assigns(:entries) + assert_equal 2, assigns(:entries).size + entry = assigns(:entries).detect {|e| e.name == 'edit.png'} + assert_not_nil entry + assert_equal 'file', entry.kind + assert_equal 'images/edit.png', entry.path + end + + def test_changes + get :changes, :id => 3, :path => ['images', 'edit.png'] + assert_response :success + assert_template 'changes' + assert_tag :tag => 'h2', :content => 'edit.png' + end + + def test_entry_show + get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'] + assert_response :success + assert_template 'entry' + # Line 19 + assert_tag :tag => 'th', + :content => /10/, + :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 + # File content + assert @response.body.include?('WITHOUT ANY WARRANTY') + end + + def test_diff + # Full diff of changeset 2f9c0091 + get :diff, :id => 3, :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7' + assert_response :success + assert_template 'diff' + # Line 22 removed + assert_tag :tag => 'th', + :content => /22/, + :sibling => { :tag => 'td', + :attributes => { :class => /diff_out/ }, + :content => /def remove/ } + end + + def test_annotate + get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb'] + assert_response :success + assert_template 'annotate' + # Line 23, changeset 2f9c0091 + assert_tag :tag => 'th', :content => /23/, + :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } }, + :sibling => { :tag => 'td', :content => /jsmith/ }, + :sibling => { :tag => 'td', :content => /watcher =/ } + end + else + puts "Git test repository NOT FOUND. Skipping functional tests !!!" + def test_fake; assert true end + end +end diff --git a/test/unit/repository_cvs_test.rb b/test/unit/repository_cvs_test.rb index 3f6db06eb..b14d9d964 100644 --- a/test/unit/repository_cvs_test.rb +++ b/test/unit/repository_cvs_test.rb @@ -40,13 +40,13 @@ class RepositoryCvsTest < Test::Unit::TestCase assert_equal 5, @repository.changesets.count assert_equal 14, @repository.changes.count - assert_equal 'Two files changed', @repository.changesets.find_by_revision(3).comments + assert_not_nil @repository.changesets.find_by_comments('Two files changed') end def test_fetch_changesets_incremental @repository.fetch_changesets - # Remove changesets with revision > 2 - @repository.changesets.find(:all, :conditions => 'revision > 2').each(&:destroy) + # Remove the 3 latest changesets + @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy) @repository.reload assert_equal 2, @repository.changesets.count diff --git a/test/unit/repository_darcs_test.rb b/test/unit/repository_darcs_test.rb new file mode 100644 index 000000000..1228976f1 --- /dev/null +++ b/test/unit/repository_darcs_test.rb @@ -0,0 +1,55 @@ +# redMine - project management software +# Copyright (C) 2006-2008 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 File.dirname(__FILE__) + '/../test_helper' + +class RepositoryDarcsTest < Test::Unit::TestCase + fixtures :projects + + # No '..' in the repository path + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository' + + def setup + @project = Project.find(1) + assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH) + end + + if File.directory?(REPOSITORY_PATH) + def test_fetch_changesets_from_scratch + @repository.fetch_changesets + @repository.reload + + assert_equal 6, @repository.changesets.count + assert_equal 13, @repository.changes.count + assert_equal "Initial commit.", @repository.changesets.find_by_revision(1).comments + end + + def test_fetch_changesets_incremental + @repository.fetch_changesets + # Remove changesets with revision > 3 + @repository.changesets.find(:all, :conditions => 'revision > 3').each(&:destroy) + @repository.reload + assert_equal 3, @repository.changesets.count + + @repository.fetch_changesets + assert_equal 6, @repository.changesets.count + end + else + puts "Darcs test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end diff --git a/test/unit/repository_git_test.rb b/test/unit/repository_git_test.rb new file mode 100644 index 000000000..c7bd84a6e --- /dev/null +++ b/test/unit/repository_git_test.rb @@ -0,0 +1,56 @@ +# 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 File.dirname(__FILE__) + '/../test_helper' + +class RepositoryGitTest < Test::Unit::TestCase + fixtures :projects + + # No '..' in the repository path + REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository' + REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/ + + def setup + @project = Project.find(1) + assert @repository = Repository::Git.create(:project => @project, :url => REPOSITORY_PATH) + end + + 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(:first, :order => 'id ASC').comments + end + + def test_fetch_changesets_incremental + @repository.fetch_changesets + # Remove the 3 latest changesets + @repository.changesets.find(:all, :order => 'id DESC', :limit => 3).each(&:destroy) + @repository.reload + assert_equal 3, @repository.changesets.count + + @repository.fetch_changesets + assert_equal 6, @repository.changesets.count + end + else + puts "Git test repository NOT FOUND. Skipping unit tests !!!" + def test_fake; assert true end + end +end