From 3a9b0988c7515371531e47f9eef9f8e60ce352aa Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Wed, 12 Mar 2008 20:28:49 +0000 Subject: [PATCH] Merged Git support branch (r1200 to r1226). git-svn-id: http://redmine.rubyforge.org/svn/trunk@1236 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/repositories_controller.rb | 4 +- app/helpers/application_helper.rb | 7 +- app/helpers/repositories_helper.rb | 8 + app/models/changeset.rb | 11 +- app/models/repository.rb | 4 +- app/models/repository/bazaar.rb | 2 +- app/models/repository/cvs.rb | 40 ++- app/models/repository/darcs.rb | 9 +- app/models/repository/git.rb | 70 +++++ app/models/repository/subversion.rb | 2 +- .../repositories/_dir_list_content.rhtml | 2 +- app/views/repositories/_revisions.rhtml | 2 +- app/views/repositories/annotate.rhtml | 2 +- app/views/repositories/diff.rhtml | 10 +- app/views/repositories/revision.rhtml | 2 +- ...91_change_changesets_revision_to_string.rb | 9 + ..._change_changes_from_revision_to_string.rb | 9 + doc/RUNNING_TESTS | 16 ++ lib/redmine.rb | 2 +- lib/redmine/scm/adapters/darcs_adapter.rb | 8 +- lib/redmine/scm/adapters/git_adapter.rb | 261 ++++++++++++++++++ .../repositories/darcs_repository.tar.gz | Bin 0 -> 8075 bytes .../repositories/git_repository.tar.gz | Bin 0 -> 12445 bytes .../repositories_darcs_controller_test.rb | 94 +++++++ .../repositories_git_controller_test.rb | 123 +++++++++ test/unit/repository_cvs_test.rb | 6 +- test/unit/repository_darcs_test.rb | 55 ++++ test/unit/repository_git_test.rb | 56 ++++ 28 files changed, 762 insertions(+), 52 deletions(-) create mode 100644 app/models/repository/git.rb create mode 100644 db/migrate/091_change_changesets_revision_to_string.rb create mode 100644 db/migrate/092_change_changes_from_revision_to_string.rb create mode 100644 lib/redmine/scm/adapters/git_adapter.rb create mode 100644 test/fixtures/repositories/darcs_repository.tar.gz create mode 100644 test/fixtures/repositories/git_repository.tar.gz create mode 100644 test/functional/repositories_darcs_controller_test.rb create mode 100644 test/functional/repositories_git_controller_test.rb create mode 100644 test/unit/repository_darcs_test.rb create mode 100644 test/unit/repository_git_test.rb 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 0000000000000000000000000000000000000000..ba4d8d04c466eab54f26fa7fffd2927a44b15fb1 GIT binary patch literal 8075 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 Z3=9km3=9km42-`w{{;gOQ;h(i006{M#a;ja literal 0 HcmV?d00001 diff --git a/test/fixtures/repositories/git_repository.tar.gz b/test/fixtures/repositories/git_repository.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..84de88aa7d08d0f58ba6f07bcc4b876fcaab23a3 GIT binary patch literal 12445 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 b(4aws1`QfCXwaZR^LPFaIec>z0LTCUkCs 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