diff --git a/git/app/controllers/repositories_controller.rb b/git/app/controllers/repositories_controller.rb
index 13d3eaa32..bce5f66a9 100644
--- a/git/app/controllers/repositories_controller.rb
+++ b/git/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/git/app/helpers/application_helper.rb b/git/app/helpers/application_helper.rb
index a6acafb5b..d500d57ac 100644
--- a/git/app/helpers/application_helper.rb
+++ b/git/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/git/app/helpers/repositories_helper.rb b/git/app/helpers/repositories_helper.rb
index d7d7f4349..b53a7c0bb 100644
--- a/git/app/helpers/repositories_helper.rb
+++ b/git/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 => 'Root 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/git/app/models/changeset.rb b/git/app/models/changeset.rb
index 6bd15b158..79b70237e 100644
--- a/git/app/models/changeset.rb
+++ b/git/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
@@ -110,11 +109,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/git/app/models/repository.rb b/git/app/models/repository.rb
index be31ac2e5..229c8dae4 100644
--- a/git/app/models/repository.rb
+++ b/git/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/git/app/models/repository/bazaar.rb b/git/app/models/repository/bazaar.rb
index 6e387f957..1b75066c2 100644
--- a/git/app/models/repository/bazaar.rb
+++ b/git/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/git/app/models/repository/cvs.rb b/git/app/models/repository/cvs.rb
index 16d906316..cae0eef75 100644
--- a/git/app/models/repository/cvs.rb
+++ b/git/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,9 @@ 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|
+ 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 +105,14 @@ class Repository::Cvs < Repository
})
# create a new changeset....
- unless cs
- # we use a negative changeset-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)
+ unless cs
+ latest = changesets.find(:first, :order => 'id DESC')
+ next_rev = latest ? latest.revision.to_i + 1 : '1'
+ cs = Changeset.create(:repository => self,
+ :revision => next_rev,
+ :committer => revision.author,
+ :committed_on => revision.time,
+ :comments => revision.message)
end
#convert CVS-File-States to internal Action-abbrevations
@@ -138,13 +132,6 @@ 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!
- end
end
end
end
diff --git a/git/app/models/repository/git.rb b/git/app/models/repository/git.rb
new file mode 100644
index 000000000..2a5505448
--- /dev/null
+++ b/git/app/models/repository/git.rb
@@ -0,0 +1,64 @@
+# 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 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/git/app/models/repository/subversion.rb b/git/app/models/repository/subversion.rb
index a0485608d..0c2239c43 100644
--- a/git/app/models/repository/subversion.rb
+++ b/git/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/git/app/views/repositories/_dir_list_content.rhtml b/git/app/views/repositories/_dir_list_content.rhtml
index 352575259..e69996346 100644
--- a/git/app/views/repositories/_dir_list_content.rhtml
+++ b/git/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) if entry.lastrev %> |
<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %>
diff --git a/git/app/views/repositories/_revisions.rhtml b/git/app/views/repositories/_revisions.rhtml
index ea722329f..752494520 100644
--- a/git/app/views/repositories/_revisions.rhtml
+++ b/git/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/git/app/views/repositories/annotate.rhtml b/git/app/views/repositories/annotate.rhtml
index 3aa884104..93141e34f 100644
--- a/git/app/views/repositories/annotate.rhtml
+++ b/git/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) if revision %> |
<%= line %> |
diff --git a/git/app/views/repositories/diff.rhtml b/git/app/views/repositories/diff.rhtml
index 88c5f17a0..eaef1abf5 100644
--- a/git/app/views/repositories/diff.rhtml
+++ b/git/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/git/app/views/repositories/revision.rhtml b/git/app/views/repositories/revision.rhtml
index 80cfdf2b7..ce371bb72 100644
--- a/git/app/views/repositories/revision.rhtml
+++ b/git/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 %>, <%= format_time(@changeset.committed_on) %>
diff --git a/git/db/migrate/090_change_changesets_revision_to_string.rb b/git/db/migrate/090_change_changesets_revision_to_string.rb
new file mode 100644
index 000000000..0c93cb21d
--- /dev/null
+++ b/git/db/migrate/090_change_changesets_revision_to_string.rb
@@ -0,0 +1,9 @@
+class ChangeChangesetsRevisionToString < ActiveRecord::Migration
+ def self.up
+ change_column :changesets, :revision, :string
+ end
+
+ def self.down
+ change_column :changesets, :revision, :integer
+ end
+end
diff --git a/git/db/migrate/091_change_changes_from_revision_to_string.rb b/git/db/migrate/091_change_changes_from_revision_to_string.rb
new file mode 100644
index 000000000..b298a3f45
--- /dev/null
+++ b/git/db/migrate/091_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/git/doc/RUNNING_TESTS b/git/doc/RUNNING_TESTS
index fde24413b..7a5e2b992 100644
--- a/git/doc/RUNNING_TESTS
+++ b/git/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/git/lib/redmine.rb b/git/lib/redmine.rb
index e76d77e9e..4c5cbdaae 100644
--- a/git/lib/redmine.rb
+++ b/git/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/git/lib/redmine/scm/adapters/git_adapter.rb b/git/lib/redmine/scm/adapters/git_adapter.rb
new file mode 100644
index 000000000..bd77cab94
--- /dev/null
+++ b/git/lib/redmine/scm/adapters/git_adapter.rb
@@ -0,0 +1,263 @@
+# 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="cd #{target('')} && git show #{rev} -- #{path}" if rev!='latest'
+ cmd="cd #{target('')} && git log -1 master -- #{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
+ line_feeds = 0
+
+ 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,nil).first
+ })
+ info
+ rescue Errno::ENOENT => e
+ return nil
+ end
+
+ def entries(path=nil, identifier=nil)
+ path ||= ''
+ entries = Entries.new
+ cmd = "cd #{target('')} && #{GIT_BIN} ls-tree -l HEAD:#{path}" if identifier.nil?
+ cmd = "cd #{target('')} && #{GIT_BIN} ls-tree -l #{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 = "cd #{target('')} && #{GIT_BIN} log --raw "
+ cmd << " #{identifier_from}.. " if identifier_from
+ cmd << " #{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
+ line_feeds = 0
+ 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 = identifier_to
+ else
+ identifier_to = nil
+ end
+
+ cmd = "cd #{target('')} && #{GIT_BIN} show #{identifier_from}" if identifier_to.nil?
+ cmd = "cd #{target('')} && #{GIT_BIN} diff #{identifier_to} #{identifier_from}" if !identifier_to.nil?
+ cmd << " -- #{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 = "cd #{target('')} && #{GIT_BIN} blame -l #{identifier} -- #{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 = "cd #{target('')} && #{GIT_BIN} show #{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/git/test/functional/repositories_cvs_controller_test.rb b/git/test/functional/repositories_cvs_controller_test.rb
index 059823707..e15fc0b17 100644
--- a/git/test/functional/repositories_cvs_controller_test.rb
+++ b/git/test/functional/repositories_cvs_controller_test.rb
@@ -93,7 +93,7 @@ class RepositoriesCvsControllerTest < Test::Unit::TestCase
def test_diff
Project.find(1).repository.fetch_changesets
- get :diff, :id => 1, :rev => 3, :type => 'inline'
+ get :diff, :id => 1, :rev => 1, :type => 'inline'
assert_response :success
assert_template 'diff'
assert_tag :tag => 'td', :attributes => { :class => 'line-code diff_out' },
diff --git a/git/test/functional/repositories_git_controller_test.rb b/git/test/functional/repositories_git_controller_test.rb
new file mode 100644
index 000000000..fec0bbaa0
--- /dev/null
+++ b/git/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/git/test/unit/repository_cvs_test.rb b/git/test/unit/repository_cvs_test.rb
index 3f6db06eb..b14d9d964 100644
--- a/git/test/unit/repository_cvs_test.rb
+++ b/git/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/git/test/unit/repository_git_test.rb b/git/test/unit/repository_git_test.rb
new file mode 100644
index 000000000..c7bd84a6e
--- /dev/null
+++ b/git/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