Project

General

Profile

« Previous | Next » 

Revision 1236

Merged Git support branch (r1200 to r1226).

View differences:

trunk/app/controllers/repositories_controller.rb
134 134
  end
135 135
  
136 136
  def diff
137
    @rev_to = params[:rev_to] ? params[:rev_to].to_i : (@rev - 1)
137
    @rev_to = params[:rev_to]
138 138
    @diff_type = params[:type] || User.current.pref[:diff_type] || 'inline'
139 139
    @diff_type = 'inline' unless %w(inline sbs).include?(@diff_type)
140 140
    
......
185 185
    render_404 and return false unless @repository
186 186
    @path = params[:path].join('/') unless params[:path].nil?
187 187
    @path ||= ''
188
    @rev = params[:rev].to_i if params[:rev]
188
    @rev = params[:rev]
189 189
  rescue ActiveRecord::RecordNotFound
190 190
    render_404
191 191
  end
trunk/app/helpers/application_helper.rb
270 270
    #     #52 -> Link to issue #52
271 271
    #   Changesets:
272 272
    #     r52 -> Link to revision 52
273
    #     commit:a85130f -> Link to scmid starting with a85130f
273 274
    #   Documents:
274 275
    #     document#17 -> Link to document with id 17
275 276
    #     document:Greetings -> Link to the document with title "Greetings"
......
280 281
    #     version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
281 282
    #   Attachments:
282 283
    #     attachment:file.zip -> Link to the attachment of the current object named file.zip
283
    text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
284
    text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
284 285
      leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
285 286
      link = nil
286 287
      if esc.nil?
......
325 326
              link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
326 327
                                              :class => 'version'
327 328
            end
329
          when 'commit'
330
            if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
331
              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)
332
            end
328 333
          when 'attachment'
329 334
            if attachments && attachment = attachments.detect {|a| a.filename == name }
330 335
              link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
trunk/app/helpers/repositories_helper.rb
25 25
    type ? CodeRay.scan(content, type).html : h(content)
26 26
  end
27 27
  
28
  def format_revision(txt)
29
    txt.to_s[0,8]
30
  end
31
  
28 32
  def to_utf8(str)
29 33
    return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
30 34
    @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
......
76 80
      content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
77 81
  end
78 82

  
83
  def git_field_tags(form, repository)
84
      content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
85
  end
86

  
79 87
  def cvs_field_tags(form, repository)
80 88
      content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
81 89
      content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
trunk/app/models/changeset.rb
32 32
                     :date_column => 'committed_on'
33 33
  
34 34
  validates_presence_of :repository_id, :revision, :committed_on, :commit_date
35
  validates_numericality_of :revision, :only_integer => true
36 35
  validates_uniqueness_of :revision, :scope => :repository_id
37 36
  validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
38 37
  
......
89 88
          # don't change the status is the issue is closed
90 89
          next if issue.status.is_closed?
91 90
          user = committer_user || User.anonymous
92
          journal = issue.init_journal(user, l(:text_status_changed_by_changeset, "r#{self.revision}"))
91
          csettext = "r#{self.revision}"
92
          if self.scmid && (! (csettext =~ /^r[0-9]+$/))
93
            csettext = "commit:\"#{self.scmid}\""
94
          end
95
          journal = issue.init_journal(user, l(:text_status_changed_by_changeset, csettext))
93 96
          issue.status = fix_status
94 97
          issue.done_ratio = done_ratio if done_ratio
95 98
          issue.save
......
114 117
  
115 118
  # Returns the previous changeset
116 119
  def previous
117
    @previous ||= Changeset.find(:first, :conditions => ['revision < ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision DESC')
120
    @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
118 121
  end
119 122

  
120 123
  # Returns the next changeset
121 124
  def next
122
    @next ||= Changeset.find(:first, :conditions => ['revision > ? AND repository_id = ?', self.revision, self.repository_id], :order => 'revision ASC')
125
    @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
123 126
  end
124 127
end
trunk/app/models/repository/bazaar.rb
51 51
    scm_info = scm.info
52 52
    if scm_info
53 53
      # latest revision found in database
54
      db_revision = latest_changeset ? latest_changeset.revision : 0
54
      db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
55 55
      # latest revision in the repository
56 56
      scm_revision = scm_info.lastrev.identifier.to_i
57 57
      if db_revision < scm_revision
trunk/app/models/repository/cvs.rb
82 82
  end
83 83
  
84 84
  def fetch_changesets
85
    #not the preferred way with CVS. maybe we should introduce always a cron-job for this
86
    last_commit = changesets.maximum(:committed_on)
87
    
88 85
    # some nifty bits to introduce a commit-id with cvs
89 86
    # natively cvs doesn't provide any kind of changesets, there is only a revision per file.
90 87
    # we now take a guess using the author, the commitlog and the commit-date.
......
94 91
    # we use a small delta here, to merge all changes belonging to _one_ changeset
95 92
    time_delta=10.seconds
96 93
    
94
    fetch_since = latest_changeset ? latest_changeset.committed_on : nil
97 95
    transaction do
98
      scm.revisions('', last_commit, nil, :with_paths => true) do |revision|
96
      tmp_rev_num = 1
97
      scm.revisions('', fetch_since, nil, :with_paths => true) do |revision|
99 98
        # only add the change to the database, if it doen't exists. the cvs log
100 99
        # is not exclusive at all. 
101 100
        unless changes.find_by_path_and_revision(scm.with_leading_slash(revision.paths[0][:path]), revision.paths[0][:revision])
......
107 106
          })
108 107
        
109 108
          # create a new changeset.... 
110
          unless cs 
111
            # we use a negative changeset-number here (just for inserting)
109
          unless cs
110
            # we use a temporaray revision number here (just for inserting)
112 111
            # later on, we calculate a continous positive number
113
            next_rev = changesets.minimum(:revision)            
114
            next_rev = 0 if next_rev.nil? or next_rev > 0 
115
            next_rev = next_rev - 1
116
            
117
            cs=Changeset.create(:repository => self,
118
            :revision => next_rev, 
119
            :committer => revision.author, 
120
            :committed_on => revision.time,
121
            :comments => revision.message)
112
            latest = changesets.find(:first, :order => 'id DESC')
113
            cs = Changeset.create(:repository => self,
114
                                  :revision => "_#{tmp_rev_num}", 
115
                                  :committer => revision.author, 
116
                                  :committed_on => revision.time,
117
                                  :comments => revision.message)
118
            tmp_rev_num += 1
122 119
          end
123 120
        
124 121
          #convert CVS-File-States to internal Action-abbrevations
......
139 136
        end
140 137
      end
141 138
      
142
      next_rev = [changesets.maximum(:revision) || 0, 0].max
143
      changesets.find(:all, :conditions=>["revision < 0"], :order=>"committed_on ASC").each() do |changeset|
144
        next_rev = next_rev + 1
145
        changeset.revision = next_rev
146
        changeset.save!
139
      # Renumber new changesets in chronological order
140
      c = changesets.find(:first, :order => 'committed_on DESC, id DESC', :conditions => "revision NOT LIKE '_%'")
141
      next_rev = c.nil? ? 1 : (c.revision.to_i + 1)
142
      changesets.find(:all, :order => 'committed_on ASC, id ASC', :conditions => "revision LIKE '_%'").each do |changeset|
143
        changeset.update_attribute :revision, next_rev
144
        next_rev += 1
147 145
      end
148
    end
146
    end # transaction
149 147
  end
150 148
end
trunk/app/models/repository/darcs.rb
47 47
  
48 48
  def diff(path, rev, rev_to, type)
49 49
    patch_from = changesets.find_by_revision(rev)
50
    return nil if patch_from.nil?
50 51
    patch_to = changesets.find_by_revision(rev_to) if rev_to
51 52
    if path.blank?
52 53
      path = patch_from.changes.collect{|change| change.path}.join(' ')
53 54
    end
54
    scm.diff(path, patch_from.scmid, patch_to.scmid, type)
55
    patch_from ? scm.diff(path, patch_from.scmid, patch_to ? patch_to.scmid : nil, type) : nil
55 56
  end
56 57
  
57 58
  def fetch_changesets
58 59
    scm_info = scm.info
59 60
    if scm_info
60 61
      db_last_id = latest_changeset ? latest_changeset.scmid : nil
61
      next_rev = latest_changeset ? latest_changeset.revision + 1 : 1      
62
      next_rev = latest_changeset ? latest_changeset.revision.to_i + 1 : 1      
62 63
      # latest revision in the repository
63 64
      scm_revision = scm_info.lastrev.scmid      
64 65
      unless changesets.find_by_scmid(scm_revision)
......
71 72
                                         :committer => revision.author, 
72 73
                                         :committed_on => revision.time,
73 74
                                         :comments => revision.message)
74
            
75
            next if changeset.new_record?
76
            
75
                                         
77 76
            revision.paths.each do |change|
78 77
              Change.create(:changeset => changeset,
79 78
                            :action => change[:action],
trunk/app/models/repository/git.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
# Copyright (C) 2007  Patrick Aljord patcito@ŋmail.com
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require 'redmine/scm/adapters/git_adapter'
19

  
20
class Repository::Git < Repository
21
  attr_protected :root_url
22
  validates_presence_of :url
23

  
24
  def scm_adapter
25
    Redmine::Scm::Adapters::GitAdapter
26
  end
27
  
28
  def self.scm_name
29
    'Git'
30
  end
31

  
32
  def changesets_for_path(path)
33
    Change.find(:all, :include => :changeset, 
34
                :conditions => ["repository_id = ? AND path = ?", id, path],
35
                :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
36
  end
37

  
38
  def fetch_changesets
39
    scm_info = scm.info
40
    if scm_info
41
      # latest revision found in database
42
      db_revision = latest_changeset ? latest_changeset.revision : nil
43
      # latest revision in the repository
44
      scm_revision = scm_info.lastrev.scmid
45

  
46
      unless changesets.find_by_scmid(scm_revision)
47

  
48
        revisions = scm.revisions('', db_revision, nil)
49
        transaction do
50
          revisions.reverse_each do |revision|
51
            changeset = Changeset.create(:repository => self,
52
                                         :revision => revision.identifier,
53
                                         :scmid => revision.scmid,
54
                                         :committer => revision.author, 
55
                                         :committed_on => revision.time,
56
                                         :comments => revision.message)
57
            
58
            revision.paths.each do |change|
59
              Change.create(:changeset => changeset,
60
                            :action => change[:action],
61
                            :path => change[:path],
62
                            :from_path => change[:from_path],
63
                            :from_revision => change[:from_revision])
64
            end
65
          end
66
        end
67
      end
68
    end
69
  end
70
end
0 71

  
trunk/app/models/repository/subversion.rb
39 39
    scm_info = scm.info
40 40
    if scm_info
41 41
      # latest revision found in database
42
      db_revision = latest_changeset ? latest_changeset.revision : 0
42
      db_revision = latest_changeset ? latest_changeset.revision.to_i : 0
43 43
      # latest revision in the repository
44 44
      scm_revision = scm_info.lastrev.identifier.to_i
45 45
      if db_revision < scm_revision
trunk/app/models/repository.rb
17 17

  
18 18
class Repository < ActiveRecord::Base
19 19
  belongs_to :project
20
  has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.revision DESC"
20
  has_many :changesets, :dependent => :destroy, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
21 21
  has_many :changes, :through => :changesets
22 22
    
23 23
  def scm
......
51 51
    path = "/#{path}" unless path.starts_with?('/')
52 52
    Change.find(:all, :include => :changeset, 
53 53
      :conditions => ["repository_id = ? AND path = ?", id, path],
54
      :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
54
      :order => "committed_on DESC, #{Changeset.table_name}.id DESC").collect(&:changeset)
55 55
  end
56 56
  
57 57
  def latest_changeset
trunk/app/views/repositories/_dir_list_content.rhtml
23 23
end %>
24 24
</td>
25 25
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
26
<td class="revision"><%= link_to(entry.lastrev.name, :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
26
<td class="revision"><%= link_to(format_revision(entry.lastrev.name), :action => 'revision', :id => @project, :rev => entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %></td>
27 27
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td>
28 28
<td class="author"><%=h(entry.lastrev.author.to_s.split('<').first) if entry.lastrev %></td>
29 29
<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %>
trunk/app/views/repositories/_revisions.rhtml
13 13
<% line_num = 1 %>
14 14
<% revisions.each do |changeset| %>
15 15
<tr class="changeset <%= cycle 'odd', 'even' %>">
16
<td class="id"><%= link_to changeset.revision, :action => 'revision', :id => project, :rev => changeset.revision %></td>
16
<td class="id"><%= link_to format_revision(changeset.revision), :action => 'revision', :id => project, :rev => changeset.revision %></td>
17 17
<td class="checkbox"><%= 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) %></td>
18 18
<td class="checkbox"><%= 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) %></td>
19 19
<td class="committed_on"><%= format_time(changeset.committed_on) %></td>
trunk/app/views/repositories/annotate.rhtml
11 11
    <tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>">
12 12
      <th class="line-num"><%= line_num %></th>
13 13
      <td class="revision">
14
      <%= (revision.identifier ? link_to(revision.identifier, :action => 'revision', :id => @project, :rev => revision.identifier) : revision.revision) if revision %></td>
14
      <%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
15 15
      <td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td>
16 16
      <td class="line-code"><pre><%= line %></pre></td>
17 17
    </tr>
trunk/app/views/repositories/diff.rhtml
1
<h2><%= l(:label_revision) %> <%= @rev %>: <%= @path.gsub(/^.*\//, '') %></h2>
1
<h2><%= l(:label_revision) %> <%= format_revision(@rev) %> <%= @path.gsub(/^.*\//, '') %></h2>
2 2

  
3 3
<!-- Choose view type -->
4 4
<% form_tag({ :controller => 'repositories', :action => 'diff'}, :method => 'get') do %>
......
23 23
          </th>
24 24
        </tr>
25 25
        <tr>
26
          <th colspan="2">@<%= @rev %></th>
27
          <th colspan="2">@<%= @rev_to %></th>
26
          <th colspan="2">@<%= format_revision @rev %></th>
27
          <th colspan="2">@<%= format_revision @rev_to %></th>
28 28
        </tr>
29 29
      </thead>
30 30
      <tbody>
......
56 56
          </th>
57 57
        </tr>
58 58
        <tr>
59
          <th>@<%= @rev %></th>
60
          <th>@<%= @rev_to %></th>
59
          <th>@<%= format_revision @rev %></th>
60
          <th>@<%= format_revision @rev_to %></th>
61 61
          <th></th>
62 62
        </tr>
63 63
      </thead>
trunk/app/views/repositories/revision.rhtml
19 19
  <% end %>
20 20
</div>
21 21

  
22
<h2><%= l(:label_revision) %> <%= @changeset.revision %></h2>
22
<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
23 23

  
24 24
<p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
25 25
<em><%= @changeset.committer.to_s.split('<').first %>, <%= format_time(@changeset.committed_on) %></em></p>
trunk/db/migrate/091_change_changesets_revision_to_string.rb
1
class ChangeChangesetsRevisionToString < ActiveRecord::Migration
2
  def self.up
3
    change_column :changesets, :revision, :string, :null => false
4
  end
5

  
6
  def self.down
7
    change_column :changesets, :revision, :integer, :null => false
8
  end
9
end
0 10

  
trunk/db/migrate/092_change_changes_from_revision_to_string.rb
1
class ChangeChangesFromRevisionToString < ActiveRecord::Migration
2
  def self.up
3
    change_column :changes, :from_revision, :string
4
  end
5

  
6
  def self.down
7
    change_column :changes, :from_revision, :integer
8
  end
9
end
0 10

  
trunk/doc/RUNNING_TESTS
19 19
Mercurial
20 20
---------
21 21
gunzip < test/fixtures/repositories/mercurial_repository.tar.gz | tar -xv -C tmp/test
22

  
23
Git
24
---
25
gunzip < test/fixtures/repositories/git_repository.tar.gz | tar -xv -C tmp/test
26

  
27

  
28
Running Tests
29
=============
30

  
31
Run 
32

  
33
  rake --tasks | grep test
34

  
35
to see available tests.
36

  
37
RAILS_ENV=test rake test will run tests.
trunk/lib/redmine/scm/adapters/darcs_adapter.rb
102 102
        def diff(path, identifier_from, identifier_to=nil, type="inline")
103 103
          path = '*' if path.blank?
104 104
          cmd = "#{DARCS_BIN} diff --repodir #{@url}"
105
          cmd << " --to-match \"hash #{identifier_from}\""
106
          cmd << " --from-match \"hash #{identifier_to}\"" if identifier_to
105
          if identifier_to.nil?
106
            cmd << " --match \"hash #{identifier_from}\""
107
          else
108
            cmd << " --to-match \"hash #{identifier_from}\""
109
            cmd << " --from-match \"hash #{identifier_to}\""
110
          end
107 111
          cmd << " -u #{path}"
108 112
          diff = []
109 113
          shellout(cmd) do |io|
trunk/lib/redmine/scm/adapters/git_adapter.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require 'redmine/scm/adapters/abstract_adapter'
19

  
20
module Redmine
21
  module Scm
22
    module Adapters    
23
      class GitAdapter < AbstractAdapter
24
        
25
        # Git executable name
26
        GIT_BIN = "git"
27

  
28
        # Get the revision of a particuliar file
29
        def get_rev (rev,path)
30
          cmd="git --git-dir #{target('')} show #{shell_quote rev} -- #{shell_quote path}" if rev!='latest' and (! rev.nil?)
31
          cmd="git --git-dir #{target('')} log -1 master -- #{shell_quote path}" if 
32
            rev=='latest' or rev.nil?
33
          rev=[]
34
          i=0
35
          shellout(cmd) do |io|
36
            files=[]
37
            changeset = {}
38
            parsing_descr = 0  #0: not parsing desc or files, 1: parsing desc, 2: parsing files
39

  
40
            io.each_line do |line|
41
              if line =~ /^commit ([0-9a-f]{40})$/
42
                key = "commit"
43
                value = $1
44
                if (parsing_descr == 1 || parsing_descr == 2)
45
                  parsing_descr = 0
46
                  rev = Revision.new({:identifier => changeset[:commit],
47
                                      :scmid => changeset[:commit],
48
                                      :author => changeset[:author],
49
                                      :time => Time.parse(changeset[:date]),
50
                                      :message => changeset[:description],
51
                                      :paths => files
52
                                     })
53
                  changeset = {}
54
                  files = []
55
                end
56
                changeset[:commit] = $1
57
              elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
58
                key = $1
59
                value = $2
60
                if key == "Author"
61
                  changeset[:author] = value
62
                elsif key == "Date"
63
                  changeset[:date] = value
64
                end
65
              elsif (parsing_descr == 0) && line.chomp.to_s == ""
66
                parsing_descr = 1
67
                changeset[:description] = ""
68
              elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
69
                parsing_descr = 2
70
                fileaction = $1
71
                filepath = $2
72
                files << {:action => fileaction, :path => filepath}
73
              elsif (parsing_descr == 1) && line.chomp.to_s == ""
74
                parsing_descr = 2
75
              elsif (parsing_descr == 1)
76
                changeset[:description] << line
77
              end
78
            end	
79
            rev = Revision.new({:identifier => changeset[:commit],
80
                                :scmid => changeset[:commit],
81
                                :author => changeset[:author],
82
                                :time => Time.parse(changeset[:date]),
83
                                :message => changeset[:description],
84
                                :paths => files
85
                               })
86

  
87
          end
88

  
89
          get_rev('latest',path) if rev == []
90

  
91
          return nil if $? && $?.exitstatus != 0
92
          return rev
93
        end
94

  
95

  
96
        def info
97
          root_url = target('')
98
          info = Info.new({:root_url => target(''),
99
                            :lastrev => revisions(root_url,nil,nil,{:limit => 1}).first
100
                          })
101
          info
102
        rescue Errno::ENOENT => e
103
          return nil
104
        end
105
        
106
        def entries(path=nil, identifier=nil)
107
          path ||= ''
108
          entries = Entries.new
109
          cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
110
          cmd << shell_quote("HEAD:" + path) if identifier.nil?
111
          cmd << shell_quote(identifier + ":" + path) if identifier
112
          shellout(cmd)  do |io|
113
            io.each_line do |line|
114
              e = line.chomp.to_s
115
              if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
116
                type = $1
117
                sha = $2
118
                size = $3
119
                name = $4
120
                entries << Entry.new({:name => name,
121
                                       :path => (path.empty? ? name : "#{path}/#{name}"),
122
                                       :kind => ((type == "tree") ? 'dir' : 'file'),
123
                                       :size => ((type == "tree") ? nil : size),
124
                                       :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}")) 
125
                                                                  
126
                                     }) unless entries.detect{|entry| entry.name == name}
127
              end
128
            end
129
          end
130
          return nil if $? && $?.exitstatus != 0
131
          entries.sort_by_name
132
        end
133
        
134
        def entry(path=nil, identifier=nil)
135
          path ||= ''
136
          search_path = path.split('/')[0..-2].join('/')
137
          entry_name = path.split('/').last
138
          e = entries(search_path, identifier)
139
          e ? e.detect{|entry| entry.name == entry_name} : nil
140
        end
141
        
142
        def revisions(path, identifier_from, identifier_to, options={})
143
          revisions = Revisions.new
144
          cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw "
145
          cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
146
          cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
147
          cmd << " #{shell_quote identifier_to} " if identifier_to
148
          #cmd << " HEAD " if !identifier_to
149
          shellout(cmd) do |io|
150
            files=[]
151
            changeset = {}
152
            parsing_descr = 0  #0: not parsing desc or files, 1: parsing desc, 2: parsing files
153
            revno = 1
154

  
155
            io.each_line do |line|
156
              if line =~ /^commit ([0-9a-f]{40})$/
157
                key = "commit"
158
                value = $1
159
                if (parsing_descr == 1 || parsing_descr == 2)
160
                  parsing_descr = 0
161
                  revisions << Revision.new({:identifier => changeset[:commit],
162
                                             :scmid => changeset[:commit],
163
                                             :author => changeset[:author],
164
                                             :time => Time.parse(changeset[:date]),
165
                                             :message => changeset[:description],
166
                                             :paths => files
167
                                            })
168
                  changeset = {}
169
                  files = []
170
                  revno = revno + 1
171
                end
172
                changeset[:commit] = $1
173
              elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
174
                key = $1
175
                value = $2
176
                if key == "Author"
177
                  changeset[:author] = value
178
                elsif key == "Date"
179
                  changeset[:date] = value
180
                end
181
              elsif (parsing_descr == 0) && line.chomp.to_s == ""
182
                parsing_descr = 1
183
                changeset[:description] = ""
184
              elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
185
                parsing_descr = 2
186
                fileaction = $1
187
                filepath = $2
188
                files << {:action => fileaction, :path => filepath}
189
              elsif (parsing_descr == 1) && line.chomp.to_s == ""
190
                parsing_descr = 2
191
              elsif (parsing_descr == 1)
192
                changeset[:description] << line[4..-1]
193
              end
194
            end	
195

  
196
            revisions << Revision.new({:identifier => changeset[:commit],
197
                                       :scmid => changeset[:commit],
198
                                       :author => changeset[:author],
199
                                       :time => Time.parse(changeset[:date]),
200
                                       :message => changeset[:description],
201
                                       :paths => files
202
                                      }) if changeset[:commit]
203

  
204
          end
205

  
206
          return nil if $? && $?.exitstatus != 0
207
          revisions
208
        end
209
        
210
        def diff(path, identifier_from, identifier_to=nil, type="inline")
211
          path ||= ''
212
          if !identifier_to
213
            identifier_to = nil
214
          end
215
          
216
          cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil?
217
          cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil?
218
          cmd << " -- #{shell_quote path}" unless path.empty?
219
          diff = []
220
          shellout(cmd) do |io|
221
            io.each_line do |line|
222
              diff << line
223
            end
224
          end
225
          return nil if $? && $?.exitstatus != 0
226
          DiffTableList.new diff, type
227
        end
228
        
229
        def annotate(path, identifier=nil)
230
          identifier = 'HEAD' if identifier.blank?
231
          cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}"
232
          blame = Annotate.new
233
          shellout(cmd) do |io|
234
            io.each_line do |line|
235
              next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)$/
236
              blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip))
237
            end
238
          end
239
          return nil if $? && $?.exitstatus != 0
240
          blame
241
        end
242
        
243
        def cat(path, identifier=nil)
244
          if identifier.nil?
245
            identifier = 'HEAD'
246
          end
247
          cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
248
          cat = nil
249
          shellout(cmd) do |io|
250
            io.binmode
251
            cat = io.read
252
          end
253
          return nil if $? && $?.exitstatus != 0
254
          cat
255
        end
256
      end
257
    end
258
  end
259

  
260
end
261

  
0 262

  
trunk/lib/redmine.rb
10 10
  # RMagick is not available
11 11
end
12 12

  
13
REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar )
13
REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git )
14 14

  
15 15
# Permissions
16 16
Redmine::AccessControl.map do |map|
trunk/test/functional/repositories_darcs_controller_test.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2008  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.dirname(__FILE__) + '/../test_helper'
19
require 'repositories_controller'
20

  
21
# Re-raise errors caught by the controller.
22
class RepositoriesController; def rescue_action(e) raise e end; end
23

  
24
class RepositoriesDarcsControllerTest < Test::Unit::TestCase
25
  fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
26

  
27
  # No '..' in the repository path
28
  REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository'
29

  
30
  def setup
31
    @controller = RepositoriesController.new
32
    @request    = ActionController::TestRequest.new
33
    @response   = ActionController::TestResponse.new
34
    User.current = nil
35
    Repository::Darcs.create(:project => Project.find(3), :url => REPOSITORY_PATH)
36
  end
37
  
38
  if File.directory?(REPOSITORY_PATH)
39
    def test_show
40
      get :show, :id => 3
41
      assert_response :success
42
      assert_template 'show'
43
      assert_not_nil assigns(:entries)
44
      assert_not_nil assigns(:changesets)
45
    end
46
    
47
    def test_browse_root
48
      get :browse, :id => 3
49
      assert_response :success
50
      assert_template 'browse'
51
      assert_not_nil assigns(:entries)
52
      assert_equal 3, assigns(:entries).size
53
      assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
54
      assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
55
      assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
56
    end
57
    
58
    def test_browse_directory
59
      get :browse, :id => 3, :path => ['images']
60
      assert_response :success
61
      assert_template 'browse'
62
      assert_not_nil assigns(:entries)
63
      assert_equal 2, assigns(:entries).size
64
      entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
65
      assert_not_nil entry
66
      assert_equal 'file', entry.kind
67
      assert_equal 'images/edit.png', entry.path
68
    end
69
    
70
    def test_changes
71
      get :changes, :id => 3, :path => ['images', 'edit.png']
72
      assert_response :success
73
      assert_template 'changes'
74
      assert_tag :tag => 'h2', :content => 'edit.png'
75
    end
76
  
77
    def test_diff
78
      Project.find(3).repository.fetch_changesets
79
      # Full diff of changeset 5
80
      get :diff, :id => 3, :rev => 5
81
      assert_response :success
82
      assert_template 'diff'
83
      # Line 22 removed
84
      assert_tag :tag => 'th',
85
                 :content => /22/,
86
                 :sibling => { :tag => 'td', 
87
                               :attributes => { :class => /diff_out/ },
88
                               :content => /def remove/ }
89
    end
90
  else
91
    puts "Darcs test repository NOT FOUND. Skipping functional tests !!!"
92
    def test_fake; assert true end
93
  end
94
end
0 95

  
trunk/test/functional/repositories_git_controller_test.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.dirname(__FILE__) + '/../test_helper'
19
require 'repositories_controller'
20

  
21
# Re-raise errors caught by the controller.
22
class RepositoriesController; def rescue_action(e) raise e end; end
23

  
24
class RepositoriesGitControllerTest < Test::Unit::TestCase
25
  fixtures :projects, :users, :roles, :members, :repositories, :enabled_modules
26

  
27
  # No '..' in the repository path
28
  REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
29
  REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
30

  
31
  def setup
32
    @controller = RepositoriesController.new
33
    @request    = ActionController::TestRequest.new
34
    @response   = ActionController::TestResponse.new
35
    User.current = nil
36
    Repository::Git.create(:project => Project.find(3), :url => REPOSITORY_PATH)
37
  end
38
  
39
  if File.directory?(REPOSITORY_PATH)
40
    def test_show
41
      get :show, :id => 3
42
      assert_response :success
43
      assert_template 'show'
44
      assert_not_nil assigns(:entries)
45
      assert_not_nil assigns(:changesets)
46
    end
47
    
48
    def test_browse_root
49
      get :browse, :id => 3
50
      assert_response :success
51
      assert_template 'browse'
52
      assert_not_nil assigns(:entries)
53
      assert_equal 3, assigns(:entries).size
54
      assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
55
      assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
56
      assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
57
    end
58
    
59
    def test_browse_directory
60
      get :browse, :id => 3, :path => ['images']
61
      assert_response :success
62
      assert_template 'browse'
63
      assert_not_nil assigns(:entries)
64
      assert_equal 2, assigns(:entries).size
65
      entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
66
      assert_not_nil entry
67
      assert_equal 'file', entry.kind
68
      assert_equal 'images/edit.png', entry.path
69
    end
70
    
71
    def test_changes
72
      get :changes, :id => 3, :path => ['images', 'edit.png']
73
      assert_response :success
74
      assert_template 'changes'
75
      assert_tag :tag => 'h2', :content => 'edit.png'
76
    end
77
    
78
    def test_entry_show
79
      get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
80
      assert_response :success
81
      assert_template 'entry'
82
      # Line 19
83
      assert_tag :tag => 'th',
84
                 :content => /10/,
85
                 :attributes => { :class => /line-num/ },
86
                 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
87
    end
88
    
89
    def test_entry_download
90
      get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
91
      assert_response :success
92
      # File content
93
      assert @response.body.include?('WITHOUT ANY WARRANTY')
94
    end
95
  
96
    def test_diff
97
      # Full diff of changeset 2f9c0091
98
      get :diff, :id => 3, :rev => '2f9c0091c754a91af7a9c478e36556b4bde8dcf7'
99
      assert_response :success
100
      assert_template 'diff'
101
      # Line 22 removed
102
      assert_tag :tag => 'th',
103
                 :content => /22/,
104
                 :sibling => { :tag => 'td', 
105
                               :attributes => { :class => /diff_out/ },
106
                               :content => /def remove/ }
107
    end
108

  
109
    def test_annotate
110
      get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
111
      assert_response :success
112
      assert_template 'annotate'
113
      # Line 23, changeset 2f9c0091
114
      assert_tag :tag => 'th', :content => /23/,
115
                 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } },
116
                 :sibling => { :tag => 'td', :content => /jsmith/ },
117
                 :sibling => { :tag => 'td', :content => /watcher =/ }
118
    end
119
  else
120
    puts "Git test repository NOT FOUND. Skipping functional tests !!!"
121
    def test_fake; assert true end
122
  end
123
end
0 124

  
trunk/test/unit/repository_cvs_test.rb
40 40
      
41 41
      assert_equal 5, @repository.changesets.count
42 42
      assert_equal 14, @repository.changes.count
43
      assert_equal 'Two files changed', @repository.changesets.find_by_revision(3).comments
43
      assert_not_nil @repository.changesets.find_by_comments('Two files changed')
44 44
    end
45 45
    
46 46
    def test_fetch_changesets_incremental
47 47
      @repository.fetch_changesets
48
      # Remove changesets with revision > 2
49
      @repository.changesets.find(:all, :conditions => 'revision > 2').each(&:destroy)
48
      # Remove the 3 latest changesets
49
      @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy)
50 50
      @repository.reload
51 51
      assert_equal 2, @repository.changesets.count
52 52
      
trunk/test/unit/repository_darcs_test.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2008  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.dirname(__FILE__) + '/../test_helper'
19

  
20
class RepositoryDarcsTest < Test::Unit::TestCase
21
  fixtures :projects
22
  
23
  # No '..' in the repository path
24
  REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/darcs_repository'
25
  
26
  def setup
27
    @project = Project.find(1)
28
    assert @repository = Repository::Darcs.create(:project => @project, :url => REPOSITORY_PATH)
29
  end
30
  
31
  if File.directory?(REPOSITORY_PATH)  
32
    def test_fetch_changesets_from_scratch
33
      @repository.fetch_changesets
34
      @repository.reload
35
      
36
      assert_equal 6, @repository.changesets.count
37
      assert_equal 13, @repository.changes.count
38
      assert_equal "Initial commit.", @repository.changesets.find_by_revision(1).comments
39
    end
40
    
41
    def test_fetch_changesets_incremental
42
      @repository.fetch_changesets
43
      # Remove changesets with revision > 3
44
      @repository.changesets.find(:all, :conditions => 'revision > 3').each(&:destroy)
45
      @repository.reload
46
      assert_equal 3, @repository.changesets.count
47
      
48
      @repository.fetch_changesets
49
      assert_equal 6, @repository.changesets.count
50
    end
51
  else
52
    puts "Darcs test repository NOT FOUND. Skipping unit tests !!!"
53
    def test_fake; assert true end
54
  end
55
end
0 56

  
trunk/test/unit/repository_git_test.rb
1
# redMine - project management software
2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
# 
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
# 
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
require File.dirname(__FILE__) + '/../test_helper'
19

  
20
class RepositoryGitTest < Test::Unit::TestCase
21
  fixtures :projects
22
  
23
  # No '..' in the repository path
24
  REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
25
  REPOSITORY_PATH.gsub!(/\//, "\\") if RUBY_PLATFORM =~ /mswin/
26
  
27
  def setup
28
    @project = Project.find(1)
29
    assert @repository = Repository::Git.create(:project => @project, :url => REPOSITORY_PATH)
30
  end
31
  
32
  if File.directory?(REPOSITORY_PATH)  
33
    def test_fetch_changesets_from_scratch
34
      @repository.fetch_changesets
35
      @repository.reload
36
      
37
      assert_equal 6, @repository.changesets.count
38
      assert_equal 11, @repository.changes.count
39
      assert_equal "Initial import.\nThe repository contains 3 files.", @repository.changesets.find(:first, :order => 'id ASC').comments
40
    end
41
    
42
    def test_fetch_changesets_incremental
43
      @repository.fetch_changesets
44
      # Remove the 3 latest changesets
45
      @repository.changesets.find(:all, :order => 'id DESC', :limit => 3).each(&:destroy)
46
      @repository.reload
47
      assert_equal 3, @repository.changesets.count
48
      
49
      @repository.fetch_changesets
50
      assert_equal 6, @repository.changesets.count
51
    end
52
  else
53
    puts "Git test repository NOT FOUND. Skipping unit tests !!!"
54
    def test_fake; assert true end
55
  end
56
end
0 57

  

Also available in: Unified diff