Project

General

Profile

Patch #259 » gitnew.diff

John Goerzen, 2008-03-07 03:20

View differences:

app/helpers/repositories_helper.rb
76 76
      content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
77 77
  end
78 78

  
79
  def git_field_tags(form, repository)
80
      content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
81
  end
82

  
79 83
  def cvs_field_tags(form, repository)
80 84
      content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
81 85
      content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
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 entries(path=nil, identifier=nil)
33
    entries=scm.entries(path, identifier)
34
    if entries
35
      entries.each do |entry|
36
        next unless entry.is_file?
37
        # Search the DB for the entry's last change
38
        change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC")
39
        if change
40
          entry.lastrev.identifier = change.changeset.revision
41
          entry.lastrev.name = change.changeset.revision
42
          entry.lastrev.author = change.changeset.committer
43
          entry.lastrev.revision = change.revision
44
        end
45
      end
46
    end
47
    entries
48
  end
49

  
50
  def changesets_for_path(path)
51
    path = "#{path}" unless path.starts_with?('/')
52
    Change.find(:all, :include => :changeset, 
53
                :conditions => ["repository_id = ? AND path = ?", id, path],
54
                :order => "committed_on DESC, #{Changeset.table_name}.revision DESC").collect(&:changeset)
55
  end
56

  
57
  def fetch_changesets
58
    scm_info = scm.info
59
    
60
    if scm_info
61
      # latest revision found in database
62
      db_revision = latest_changeset ? latest_changeset.scmid : nil
63
      next_rev = latest_changeset ? latest_changeset.revision + 1 : 1
64
      # latest revision in the repository
65
      scm_revision = scm_info.lastrev.scmid
66

  
67
      unless changesets.find_by_scmid(scm_revision)
68

  
69
        revisions = scm.revisions('', db_revision, nil)
70
        transaction do
71
          revisions.reverse_each do |revision|
72
            changeset = Changeset.create(:repository => self,
73
                                         :revision => next_rev,
74
                                         :scmid => revision.scmid,
75
                                         :committer => revision.author, 
76
                                         :committed_on => revision.time,
77
                                         :comments => revision.message)
78
            
79
            revision.paths.each do |change|
80
              Change.create(:changeset => changeset,
81
                            :action => change[:action],
82
                            :path => change[:path],
83
                            :from_path => change[:from_path],
84
                            :from_revision => change[:from_revision])
85
            end
86
            next_rev += 1
87
          end
88
        end
89
      end
90
    end
91
  end
92
end
app/views/repositories/_dir_list_content.rhtml
25 25
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td>
26 26
<td class="revision"><%= link_to(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
<td class="author"><%=h(entry.lastrev.author) if entry.lastrev %></td>
28
<td class="author"><%=h(entry.lastrev.author.split('<').first) if entry.lastrev %></td>
29 29
<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev %>
30 30
<td class="comments"><%=h truncate(changeset.comments, 50) unless changeset.nil? %></td>
31 31
</tr>
app/views/repositories/_revisions.rhtml
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>
20
<td class="author"><%=h changeset.committer %></td>
20
<td class="author"><%=h changeset.committer.split('<').first %></td>
21 21
<td class="comments"><%= textilizable(changeset.comments) %></td>
22 22
</tr>
23 23
<% line_num += 1 %>
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.
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|
lib/redmine/scm/adapters/abstract_adapter.rb
183 183
          }.last
184 184
        end 
185 185
      end
186
      
186

  
187 187
      class Revision
188 188
        attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch
189 189
        def initialize(attributes={})
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

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

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

  
91
          end
92

  
93
          get_rev('latest',path) if rev == []
94

  
95
          return nil if $? && $?.exitstatus != 0
96
          return rev
97
          #         rescue Errno::ENOENT => e
98
          #           raise CommandFailed
99
        end
100

  
101

  
102
        def info
103
          #           cmd = "#{GIT_BIN} -R #{target('')} root"
104
          #           root_url = nil
105
          #           shellout(cmd) do |io|
106
          root_url = target('')
107
          #           end
108
          info = Info.new({:root_url => target(''),
109
                            :lastrev => revisions(root_url,nil,nil,nil).first
110
                          })
111
          info
112
        rescue Errno::ENOENT => e
113
          return nil
114
        end
115
        
116
        def entries(path=nil, identifier=nil)
117
          path ||= ''
118
          entries = Entries.new
119
          cmd = "cd #{target('')} && #{GIT_BIN} show HEAD:#{path}" if identifier.nil?
120
          cmd = "cd #{target('')} && #{GIT_BIN} show #{identifier}:#{path}" if identifier
121
          shellout(cmd)  do |io|
122
            io.each_line do |line|
123
              e = line.chomp.split('\\')
124
              unless e.to_s.strip=='' or line[0..3]=='tree'
125
                name=e.first.split('/')[0]
126
                entries << Entry.new({:name => name,
127
                                       :path => (path.empty? ? name : "#{path}/#{name}"),
128
                                       :kind => ((e.first.include? '/') ? 'dir' : 'file'),
129
                                       :lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}"))
130
                                     }) unless entries.detect{|entry| entry.name == name}
131
              end
132
            end
133
          end
134
          return nil if $? && $?.exitstatus != 0
135
          entries.sort_by_name
136
          #         rescue Errno::ENOENT => e
137
          #           raise CommandFailed
138
        end
139
        
140
        def entry(path=nil, identifier=nil)
141
          path ||= ''
142
          search_path = path.split('/')[0..-2].join('/')
143
          entry_name = path.split('/').last
144
          e = entries(search_path, identifier)
145
          e ? e.detect{|entry| entry.name == entry_name} : nil
146
        end
147
        
148
        def revisions(path, identifier_from, identifier_to, options={})
149
          revisions = Revisions.new
150
          cmd = "cd #{target('')} && #{GIT_BIN} whatchanged "
151
          cmd << " #{identifier_from}.. " if identifier_from
152
          cmd << " #{identifier_to} " if identifier_to
153
          #cmd << " HEAD " if !identifier_to
154
          puts "revisions"
155
          puts cmd
156
          shellout(cmd) do |io|
157
            files=[]
158
            changeset = {}
159
            parsing_descr = 0  #0: not parsing desc or files, 1: parsing desc, 2: parsing files
160
            line_feeds = 0
161

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

  
209
          end
210

  
211
          return nil if $? && $?.exitstatus != 0
212
          puts "RETURNING REVISIONS"
213
          revisions
214
        rescue Errno::ENOENT => e
215
          raise CommandFailed
216
        end
217
        
218
        def diff(path, identifier_from, identifier_to=nil, type="inline")
219
          path ||= ''
220
          if identifier_to
221
            identifier_to = identifier_to 
222
          else
223
            identifier_to = nil
224
          end
225
          cmd = "cd #{target('')} && #{GIT_BIN}  diff   #{identifier_from}^!" if identifier_to.nil?
226
          cmd = "cd #{target('')} && #{GIT_BIN}  diff #{identifier_to}  #{identifier_from}" if !identifier_to.nil?
227
          cmd << " #{path}" unless path.empty?
228
          diff = []
229
          shellout(cmd) do |io|
230
            io.each_line do |line|
231
              diff << line
232
            end
233
          end
234
          return nil if $? && $?.exitstatus != 0
235
          DiffTableList.new diff, type
236
          
237
        rescue Errno::ENOENT => e
238
          raise CommandFailed
239
        end
240
        
241
        def cat(path, identifier=nil)
242
          cmd = "cd #{target('')} && #{GIT_BIN} show #{identifier}:#{path}"
243
          cat = nil
244
          shellout(cmd) do |io|
245
            io.binmode
246
            cat = io.read
247
          end
248
          return nil if $? && $?.exitstatus != 0
249
          cat
250
        rescue Errno::ENOENT => e
251
          raise CommandFailed
252
        end
253
      end
254
    end
255
  end
256

  
257
end
258

  
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

  
30
  def setup
31
    @controller = RepositoriesController.new
32
    @request    = ActionController::TestRequest.new
33
    @response   = ActionController::TestResponse.new
34
    User.current = nil
35
    Repository::Git.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_entry_show
78
      get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb']
79
      assert_response :success
80
      assert_template 'entry'
81
      # Line 19
82
      assert_tag :tag => 'th',
83
                 :content => /10/,
84
                 :attributes => { :class => /line-num/ },
85
                 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
86
    end
87
    
88
    def test_entry_download
89
      get :entry, :id => 3, :path => ['sources', 'watchers_controller.rb'], :format => 'raw'
90
      assert_response :success
91
      # File content
92
      assert @response.body.include?('WITHOUT ANY WARRANTY')
93
    end
94
  
95
    def test_diff
96
      # Full diff of changeset 4
97
      get :diff, :id => 3, :rev => 4
98
      assert_response :success
99
      assert_template 'diff'
100
      # Line 22 removed
101
      assert_tag :tag => 'th',
102
                 :content => /22/,
103
                 :sibling => { :tag => 'td', 
104
                               :attributes => { :class => /diff_out/ },
105
                               :content => /def remove/ }
106
    end
107
    
108
    def test_annotate
109
      get :annotate, :id => 3, :path => ['sources', 'watchers_controller.rb']
110
      assert_response :success
111
      assert_template 'annotate'
112
      # Line 23, revision 4
113
      assert_tag :tag => 'th', :content => /23/,
114
                 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /4/ } },
115
                 :sibling => { :tag => 'td', :content => /jsmith/ },
116
                 :sibling => { :tag => 'td', :content => /watcher =/ }
117
    end
118
  else
119
    puts "Git test repository NOT FOUND. Skipping functional tests !!!"
120
    def test_fake; assert true end
121
  end
122
end
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
  
26
  def setup
27
    @project = Project.find(1)
28
    assert @repository = Repository::Git.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 11, @repository.changes.count
38
      assert_equal "Initial import.\nThe repository contains 3 files.", @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 "Git test repository NOT FOUND. Skipping unit tests !!!"
53
    def test_fake; assert true end
54
  end
55
end
(4-4/9)