##// END OF EJS Templates
Merged r3394, r3466, r3467, r3468 and r3471 from trunk....
Jean-Philippe Lang -
r3387:17f60af49005
parent child
Show More
@@ -0,0 +1,9
1 class AddIndexOnChangesetsScmid < ActiveRecord::Migration
2 def self.up
3 add_index :changesets, [:repository_id, :scmid], :name => :changesets_repos_scmid
4 end
5
6 def self.down
7 remove_index :changesets, :name => :changesets_repos_scmid
8 end
9 end
@@ -0,0 +1,1
1 Texte encodοΏ½ en ISO-8859-1. No newline at end of file
@@ -1,181 +1,186
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'iconv'
18 require 'iconv'
19
19
20 class Changeset < ActiveRecord::Base
20 class Changeset < ActiveRecord::Base
21 belongs_to :repository
21 belongs_to :repository
22 belongs_to :user
22 belongs_to :user
23 has_many :changes, :dependent => :delete_all
23 has_many :changes, :dependent => :delete_all
24 has_and_belongs_to_many :issues
24 has_and_belongs_to_many :issues
25
25
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
26 acts_as_event :title => Proc.new {|o| "#{l(:label_revision)} #{o.revision}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
27 :description => :long_comments,
27 :description => :long_comments,
28 :datetime => :committed_on,
28 :datetime => :committed_on,
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
29 :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :rev => o.revision}}
30
30
31 acts_as_searchable :columns => 'comments',
31 acts_as_searchable :columns => 'comments',
32 :include => {:repository => :project},
32 :include => {:repository => :project},
33 :project_key => "#{Repository.table_name}.project_id",
33 :project_key => "#{Repository.table_name}.project_id",
34 :date_column => 'committed_on'
34 :date_column => 'committed_on'
35
35
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
36 acts_as_activity_provider :timestamp => "#{table_name}.committed_on",
37 :author_key => :user_id,
37 :author_key => :user_id,
38 :find_options => {:include => [:user, {:repository => :project}]}
38 :find_options => {:include => [:user, {:repository => :project}]}
39
39
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
40 validates_presence_of :repository_id, :revision, :committed_on, :commit_date
41 validates_uniqueness_of :revision, :scope => :repository_id
41 validates_uniqueness_of :revision, :scope => :repository_id
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
42 validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true
43
43
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
44 named_scope :visible, lambda {|*args| { :include => {:repository => :project},
45 :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
45 :conditions => Project.allowed_to_condition(args.first || User.current, :view_changesets) } }
46
46
47 def revision=(r)
47 def revision=(r)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
48 write_attribute :revision, (r.nil? ? nil : r.to_s)
49 end
49 end
50
50
51 def comments=(comment)
51 def comments=(comment)
52 write_attribute(:comments, Changeset.normalize_comments(comment))
52 write_attribute(:comments, Changeset.normalize_comments(comment))
53 end
53 end
54
54
55 def committed_on=(date)
55 def committed_on=(date)
56 self.commit_date = date
56 self.commit_date = date
57 super
57 super
58 end
58 end
59
59
60 def committer=(arg)
61 write_attribute(:committer, self.class.to_utf8(arg.to_s))
62 end
63
60 def project
64 def project
61 repository.project
65 repository.project
62 end
66 end
63
67
64 def author
68 def author
65 user || committer.to_s.split('<').first
69 user || committer.to_s.split('<').first
66 end
70 end
67
71
68 def before_create
72 def before_create
69 self.user = repository.find_committer_user(committer)
73 self.user = repository.find_committer_user(committer)
70 end
74 end
71
75
72 def after_create
76 def after_create
73 scan_comment_for_issue_ids
77 scan_comment_for_issue_ids
74 end
78 end
75 require 'pp'
79 require 'pp'
76
80
77 def scan_comment_for_issue_ids
81 def scan_comment_for_issue_ids
78 return if comments.blank?
82 return if comments.blank?
79 # keywords used to reference issues
83 # keywords used to reference issues
80 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
84 ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip)
81 # keywords used to fix issues
85 # keywords used to fix issues
82 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
86 fix_keywords = Setting.commit_fix_keywords.downcase.split(",").collect(&:strip)
83 # status and optional done ratio applied
87 # status and optional done ratio applied
84 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
88 fix_status = IssueStatus.find_by_id(Setting.commit_fix_status_id)
85 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
89 done_ratio = Setting.commit_fix_done_ratio.blank? ? nil : Setting.commit_fix_done_ratio.to_i
86
90
87 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
91 kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|")
88 return if kw_regexp.blank?
92 return if kw_regexp.blank?
89
93
90 referenced_issues = []
94 referenced_issues = []
91
95
92 if ref_keywords.delete('*')
96 if ref_keywords.delete('*')
93 # find any issue ID in the comments
97 # find any issue ID in the comments
94 target_issue_ids = []
98 target_issue_ids = []
95 comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
99 comments.scan(%r{([\s\(\[,-]|^)#(\d+)(?=[[:punct:]]|\s|<|$)}).each { |m| target_issue_ids << m[1] }
96 referenced_issues += find_referenced_issues_by_id(target_issue_ids)
100 referenced_issues += find_referenced_issues_by_id(target_issue_ids)
97 end
101 end
98
102
99 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
103 comments.scan(Regexp.new("(#{kw_regexp})[\s:]+(([\s,;&]*#?\\d+)+)", Regexp::IGNORECASE)).each do |match|
100 action = match[0]
104 action = match[0]
101 target_issue_ids = match[1].scan(/\d+/)
105 target_issue_ids = match[1].scan(/\d+/)
102 target_issues = find_referenced_issues_by_id(target_issue_ids)
106 target_issues = find_referenced_issues_by_id(target_issue_ids)
103 if fix_status && fix_keywords.include?(action.downcase)
107 if fix_status && fix_keywords.include?(action.downcase)
104 # update status of issues
108 # update status of issues
105 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
109 logger.debug "Issues fixed by changeset #{self.revision}: #{issue_ids.join(', ')}." if logger && logger.debug?
106 target_issues.each do |issue|
110 target_issues.each do |issue|
107 # the issue may have been updated by the closure of another one (eg. duplicate)
111 # the issue may have been updated by the closure of another one (eg. duplicate)
108 issue.reload
112 issue.reload
109 # don't change the status is the issue is closed
113 # don't change the status is the issue is closed
110 next if issue.status.is_closed?
114 next if issue.status.is_closed?
111 csettext = "r#{self.revision}"
115 csettext = "r#{self.revision}"
112 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
116 if self.scmid && (! (csettext =~ /^r[0-9]+$/))
113 csettext = "commit:\"#{self.scmid}\""
117 csettext = "commit:\"#{self.scmid}\""
114 end
118 end
115 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
119 journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext))
116 issue.status = fix_status
120 issue.status = fix_status
117 issue.done_ratio = done_ratio if done_ratio
121 issue.done_ratio = done_ratio if done_ratio
118 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
122 Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update,
119 { :changeset => self, :issue => issue })
123 { :changeset => self, :issue => issue })
120 issue.save
124 issue.save
121 end
125 end
122 end
126 end
123 referenced_issues += target_issues
127 referenced_issues += target_issues
124 end
128 end
125
129
126 self.issues = referenced_issues.uniq
130 self.issues = referenced_issues.uniq
127 end
131 end
128
132
129 def short_comments
133 def short_comments
130 @short_comments || split_comments.first
134 @short_comments || split_comments.first
131 end
135 end
132
136
133 def long_comments
137 def long_comments
134 @long_comments || split_comments.last
138 @long_comments || split_comments.last
135 end
139 end
136
140
137 # Returns the previous changeset
141 # Returns the previous changeset
138 def previous
142 def previous
139 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
143 @previous ||= Changeset.find(:first, :conditions => ['id < ? AND repository_id = ?', self.id, self.repository_id], :order => 'id DESC')
140 end
144 end
141
145
142 # Returns the next changeset
146 # Returns the next changeset
143 def next
147 def next
144 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
148 @next ||= Changeset.find(:first, :conditions => ['id > ? AND repository_id = ?', self.id, self.repository_id], :order => 'id ASC')
145 end
149 end
146
150
147 # Strips and reencodes a commit log before insertion into the database
151 # Strips and reencodes a commit log before insertion into the database
148 def self.normalize_comments(str)
152 def self.normalize_comments(str)
149 to_utf8(str.to_s.strip)
153 to_utf8(str.to_s.strip)
150 end
154 end
151
155
152 private
156 private
153
157
154 # Finds issues that can be referenced by the commit message
158 # Finds issues that can be referenced by the commit message
155 # i.e. issues that belong to the repository project, a subproject or a parent project
159 # i.e. issues that belong to the repository project, a subproject or a parent project
156 def find_referenced_issues_by_id(ids)
160 def find_referenced_issues_by_id(ids)
157 Issue.find_all_by_id(ids, :include => :project).select {|issue|
161 Issue.find_all_by_id(ids, :include => :project).select {|issue|
158 project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
162 project == issue.project || project.is_ancestor_of?(issue.project) || project.is_descendant_of?(issue.project)
159 }
163 }
160 end
164 end
161
165
162 def split_comments
166 def split_comments
163 comments =~ /\A(.+?)\r?\n(.*)$/m
167 comments =~ /\A(.+?)\r?\n(.*)$/m
164 @short_comments = $1 || comments
168 @short_comments = $1 || comments
165 @long_comments = $2.to_s.strip
169 @long_comments = $2.to_s.strip
166 return @short_comments, @long_comments
170 return @short_comments, @long_comments
167 end
171 end
168
172
169 def self.to_utf8(str)
173 def self.to_utf8(str)
170 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
174 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
171 encoding = Setting.commit_logs_encoding.to_s.strip
175 encoding = Setting.commit_logs_encoding.to_s.strip
172 unless encoding.blank? || encoding == 'UTF-8'
176 unless encoding.blank? || encoding == 'UTF-8'
173 begin
177 begin
174 return Iconv.conv('UTF-8', encoding, str)
178 str = Iconv.conv('UTF-8', encoding, str)
175 rescue Iconv::Failure
179 rescue Iconv::Failure
176 # do nothing here
180 # do nothing here
177 end
181 end
178 end
182 end
179 str
183 # removes invalid UTF8 sequences
184 Iconv.conv('UTF-8//IGNORE', 'UTF-8', str + ' ')[0..-3]
180 end
185 end
181 end
186 end
@@ -1,78 +1,81
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
3 # Copyright (C) 2007 Patrick Aljord patcito@Ε‹mail.com
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/git_adapter'
18 require 'redmine/scm/adapters/git_adapter'
19
19
20 class Repository::Git < Repository
20 class Repository::Git < Repository
21 attr_protected :root_url
21 attr_protected :root_url
22 validates_presence_of :url
22 validates_presence_of :url
23
23
24 def scm_adapter
24 def scm_adapter
25 Redmine::Scm::Adapters::GitAdapter
25 Redmine::Scm::Adapters::GitAdapter
26 end
26 end
27
27
28 def self.scm_name
28 def self.scm_name
29 'Git'
29 'Git'
30 end
30 end
31
31
32 def branches
32 def branches
33 scm.branches
33 scm.branches
34 end
34 end
35
35
36 def tags
36 def tags
37 scm.tags
37 scm.tags
38 end
38 end
39
39
40 # With SCM's that have a sequential commit numbering, redmine is able to be
40 # With SCM's that have a sequential commit numbering, redmine is able to be
41 # clever and only fetch changesets going forward from the most recent one
41 # clever and only fetch changesets going forward from the most recent one
42 # it knows about. However, with git, you never know if people have merged
42 # it knows about. However, with git, you never know if people have merged
43 # commits into the middle of the repository history, so we always have to
43 # commits into the middle of the repository history, so we should parse
44 # parse the entire log.
44 # the entire log. Since it's way too slow for large repositories, we only
45 # parse 1 week before the last known commit.
46 # The repository can still be fully reloaded by calling #clear_changesets
47 # before fetching changesets (eg. for offline resync)
45 def fetch_changesets
48 def fetch_changesets
46 # Save ourselves an expensive operation if we're already up to date
49 c = changesets.find(:first, :order => 'committed_on DESC')
47 return if scm.num_revisions == changesets.count
50 since = (c ? c.committed_on - 7.days : nil)
48
51
49 revisions = scm.revisions('', nil, nil, :all => true)
52 revisions = scm.revisions('', nil, nil, :all => true, :since => since)
50 return if revisions.nil? || revisions.empty?
53 return if revisions.nil? || revisions.empty?
51
54
52 # Find revisions that redmine knows about already
55 recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
53 existing_revisions = changesets.find(:all).map!{|c| c.scmid}
54
56
55 # Clean out revisions that are no longer in git
57 # Clean out revisions that are no longer in git
56 Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", revisions.map{|r| r.scmid}, self.id])
58 recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
57
59
58 # Subtract revisions that redmine already knows about
60 # Subtract revisions that redmine already knows about
59 revisions.reject!{|r| existing_revisions.include?(r.scmid)}
61 recent_revisions = recent_changesets.map{|c| c.scmid}
62 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
60
63
61 # Save the remaining ones to the database
64 # Save the remaining ones to the database
62 revisions.each{|r| r.save(self)} unless revisions.nil?
65 revisions.each{|r| r.save(self)} unless revisions.nil?
63 end
66 end
64
67
65 def latest_changesets(path,rev,limit=10)
68 def latest_changesets(path,rev,limit=10)
66 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
69 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
67 return [] if revisions.nil? || revisions.empty?
70 return [] if revisions.nil? || revisions.empty?
68
71
69 changesets.find(
72 changesets.find(
70 :all,
73 :all,
71 :conditions => [
74 :conditions => [
72 "scmid IN (?)",
75 "scmid IN (?)",
73 revisions.map!{|c| c.scmid}
76 revisions.map!{|c| c.scmid}
74 ],
77 ],
75 :order => 'committed_on DESC'
78 :order => 'committed_on DESC'
76 )
79 )
77 end
80 end
78 end
81 end
@@ -1,263 +1,260
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'redmine/scm/adapters/abstract_adapter'
18 require 'redmine/scm/adapters/abstract_adapter'
19
19
20 module Redmine
20 module Redmine
21 module Scm
21 module Scm
22 module Adapters
22 module Adapters
23 class GitAdapter < AbstractAdapter
23 class GitAdapter < AbstractAdapter
24 # Git executable name
24 # Git executable name
25 GIT_BIN = "git"
25 GIT_BIN = "git"
26
26
27 def info
27 def info
28 begin
28 begin
29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
29 Info.new(:root_url => url, :lastrev => lastrev('',nil))
30 rescue
30 rescue
31 nil
31 nil
32 end
32 end
33 end
33 end
34
34
35 def branches
35 def branches
36 branches = []
36 return @branches if @branches
37 @branches = []
37 cmd = "#{GIT_BIN} --git-dir #{target('')} branch"
38 cmd = "#{GIT_BIN} --git-dir #{target('')} branch"
38 shellout(cmd) do |io|
39 shellout(cmd) do |io|
39 io.each_line do |line|
40 io.each_line do |line|
40 branches << line.match('\s*\*?\s*(.*)$')[1]
41 @branches << line.match('\s*\*?\s*(.*)$')[1]
41 end
42 end
42 end
43 end
43 branches.sort!
44 @branches.sort!
44 end
45 end
45
46
46 def tags
47 def tags
47 tags = []
48 return @tags if @tags
48 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
49 cmd = "#{GIT_BIN} --git-dir #{target('')} tag"
49 shellout(cmd) do |io|
50 shellout(cmd) do |io|
50 io.readlines.sort!.map{|t| t.strip}
51 @tags = io.readlines.sort!.map{|t| t.strip}
51 end
52 end
52 end
53 end
53
54
54 def default_branch
55 def default_branch
55 branches.include?('master') ? 'master' : branches.first
56 branches.include?('master') ? 'master' : branches.first
56 end
57 end
57
58
58 def entries(path=nil, identifier=nil)
59 def entries(path=nil, identifier=nil)
59 path ||= ''
60 path ||= ''
60 entries = Entries.new
61 entries = Entries.new
61 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
62 cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
62 cmd << shell_quote("HEAD:" + path) if identifier.nil?
63 cmd << shell_quote("HEAD:" + path) if identifier.nil?
63 cmd << shell_quote(identifier + ":" + path) if identifier
64 cmd << shell_quote(identifier + ":" + path) if identifier
64 shellout(cmd) do |io|
65 shellout(cmd) do |io|
65 io.each_line do |line|
66 io.each_line do |line|
66 e = line.chomp.to_s
67 e = line.chomp.to_s
67 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
68 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
68 type = $1
69 type = $1
69 sha = $2
70 sha = $2
70 size = $3
71 size = $3
71 name = $4
72 name = $4
72 full_path = path.empty? ? name : "#{path}/#{name}"
73 full_path = path.empty? ? name : "#{path}/#{name}"
73 entries << Entry.new({:name => name,
74 entries << Entry.new({:name => name,
74 :path => full_path,
75 :path => full_path,
75 :kind => (type == "tree") ? 'dir' : 'file',
76 :kind => (type == "tree") ? 'dir' : 'file',
76 :size => (type == "tree") ? nil : size,
77 :size => (type == "tree") ? nil : size,
77 :lastrev => lastrev(full_path,identifier)
78 :lastrev => lastrev(full_path,identifier)
78 }) unless entries.detect{|entry| entry.name == name}
79 }) unless entries.detect{|entry| entry.name == name}
79 end
80 end
80 end
81 end
81 end
82 end
82 return nil if $? && $?.exitstatus != 0
83 return nil if $? && $?.exitstatus != 0
83 entries.sort_by_name
84 entries.sort_by_name
84 end
85 end
85
86
86 def lastrev(path,rev)
87 def lastrev(path,rev)
87 return nil if path.nil?
88 return nil if path.nil?
88 cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --no-merges -n 1 "
89 cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --no-merges -n 1 "
89 cmd << " #{shell_quote rev} " if rev
90 cmd << " #{shell_quote rev} " if rev
90 cmd << "-- #{path} " unless path.empty?
91 cmd << "-- #{path} " unless path.empty?
91 shellout(cmd) do |io|
92 shellout(cmd) do |io|
92 begin
93 begin
93 id = io.gets.split[1]
94 id = io.gets.split[1]
94 author = io.gets.match('Author:\s+(.*)$')[1]
95 author = io.gets.match('Author:\s+(.*)$')[1]
95 2.times { io.gets }
96 2.times { io.gets }
96 time = io.gets.match('CommitDate:\s+(.*)$')[1]
97 time = io.gets.match('CommitDate:\s+(.*)$')[1]
97
98
98 Revision.new({
99 Revision.new({
99 :identifier => id,
100 :identifier => id,
100 :scmid => id,
101 :scmid => id,
101 :author => author,
102 :author => author,
102 :time => time,
103 :time => time,
103 :message => nil,
104 :message => nil,
104 :paths => nil
105 :paths => nil
105 })
106 })
106 rescue NoMethodError => e
107 rescue NoMethodError => e
107 logger.error("The revision '#{path}' has a wrong format")
108 logger.error("The revision '#{path}' has a wrong format")
108 return nil
109 return nil
109 end
110 end
110 end
111 end
111 end
112 end
112
113
113 def num_revisions
114 cmd = "#{GIT_BIN} --git-dir #{target('')} log --all --pretty=format:'' | wc -l"
115 shellout(cmd) {|io| io.gets.chomp.to_i + 1}
116 end
117
118 def revisions(path, identifier_from, identifier_to, options={})
114 def revisions(path, identifier_from, identifier_to, options={})
119 revisions = Revisions.new
115 revisions = Revisions.new
120
116
121 cmd = "#{GIT_BIN} --git-dir #{target('')} log --find-copies-harder --raw --date=iso --pretty=fuller"
117 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
122 cmd << " --reverse" if options[:reverse]
118 cmd << " --reverse" if options[:reverse]
123 cmd << " --all" if options[:all]
119 cmd << " --all" if options[:all]
124 cmd << " -n #{options[:limit]} " if options[:limit]
120 cmd << " -n #{options[:limit]} " if options[:limit]
125 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
121 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
126 cmd << " #{shell_quote identifier_to} " if identifier_to
122 cmd << " #{shell_quote identifier_to} " if identifier_to
123 cmd << " --since=#{shell_quote(options[:since].strftime("%Y-%m-%d %H:%M:%S"))}" if options[:since]
127 cmd << " -- #{path}" if path && !path.empty?
124 cmd << " -- #{path}" if path && !path.empty?
128
125
129 shellout(cmd) do |io|
126 shellout(cmd) do |io|
130 files=[]
127 files=[]
131 changeset = {}
128 changeset = {}
132 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
129 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
133 revno = 1
130 revno = 1
134
131
135 io.each_line do |line|
132 io.each_line do |line|
136 if line =~ /^commit ([0-9a-f]{40})$/
133 if line =~ /^commit ([0-9a-f]{40})$/
137 key = "commit"
134 key = "commit"
138 value = $1
135 value = $1
139 if (parsing_descr == 1 || parsing_descr == 2)
136 if (parsing_descr == 1 || parsing_descr == 2)
140 parsing_descr = 0
137 parsing_descr = 0
141 revision = Revision.new({
138 revision = Revision.new({
142 :identifier => changeset[:commit],
139 :identifier => changeset[:commit],
143 :scmid => changeset[:commit],
140 :scmid => changeset[:commit],
144 :author => changeset[:author],
141 :author => changeset[:author],
145 :time => Time.parse(changeset[:date]),
142 :time => Time.parse(changeset[:date]),
146 :message => changeset[:description],
143 :message => changeset[:description],
147 :paths => files
144 :paths => files
148 })
145 })
149 if block_given?
146 if block_given?
150 yield revision
147 yield revision
151 else
148 else
152 revisions << revision
149 revisions << revision
153 end
150 end
154 changeset = {}
151 changeset = {}
155 files = []
152 files = []
156 revno = revno + 1
153 revno = revno + 1
157 end
154 end
158 changeset[:commit] = $1
155 changeset[:commit] = $1
159 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
156 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
160 key = $1
157 key = $1
161 value = $2
158 value = $2
162 if key == "Author"
159 if key == "Author"
163 changeset[:author] = value
160 changeset[:author] = value
164 elsif key == "CommitDate"
161 elsif key == "CommitDate"
165 changeset[:date] = value
162 changeset[:date] = value
166 end
163 end
167 elsif (parsing_descr == 0) && line.chomp.to_s == ""
164 elsif (parsing_descr == 0) && line.chomp.to_s == ""
168 parsing_descr = 1
165 parsing_descr = 1
169 changeset[:description] = ""
166 changeset[:description] = ""
170 elsif (parsing_descr == 1 || parsing_descr == 2) \
167 elsif (parsing_descr == 1 || parsing_descr == 2) \
171 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
168 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
172 parsing_descr = 2
169 parsing_descr = 2
173 fileaction = $1
170 fileaction = $1
174 filepath = $2
171 filepath = $2
175 files << {:action => fileaction, :path => filepath}
172 files << {:action => fileaction, :path => filepath}
176 elsif (parsing_descr == 1 || parsing_descr == 2) \
173 elsif (parsing_descr == 1 || parsing_descr == 2) \
177 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
174 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
178 parsing_descr = 2
175 parsing_descr = 2
179 fileaction = $1
176 fileaction = $1
180 filepath = $3
177 filepath = $3
181 files << {:action => fileaction, :path => filepath}
178 files << {:action => fileaction, :path => filepath}
182 elsif (parsing_descr == 1) && line.chomp.to_s == ""
179 elsif (parsing_descr == 1) && line.chomp.to_s == ""
183 parsing_descr = 2
180 parsing_descr = 2
184 elsif (parsing_descr == 1)
181 elsif (parsing_descr == 1)
185 changeset[:description] << line[4..-1]
182 changeset[:description] << line[4..-1]
186 end
183 end
187 end
184 end
188
185
189 if changeset[:commit]
186 if changeset[:commit]
190 revision = Revision.new({
187 revision = Revision.new({
191 :identifier => changeset[:commit],
188 :identifier => changeset[:commit],
192 :scmid => changeset[:commit],
189 :scmid => changeset[:commit],
193 :author => changeset[:author],
190 :author => changeset[:author],
194 :time => Time.parse(changeset[:date]),
191 :time => Time.parse(changeset[:date]),
195 :message => changeset[:description],
192 :message => changeset[:description],
196 :paths => files
193 :paths => files
197 })
194 })
198
195
199 if block_given?
196 if block_given?
200 yield revision
197 yield revision
201 else
198 else
202 revisions << revision
199 revisions << revision
203 end
200 end
204 end
201 end
205 end
202 end
206
203
207 return nil if $? && $?.exitstatus != 0
204 return nil if $? && $?.exitstatus != 0
208 revisions
205 revisions
209 end
206 end
210
207
211 def diff(path, identifier_from, identifier_to=nil)
208 def diff(path, identifier_from, identifier_to=nil)
212 path ||= ''
209 path ||= ''
213
210
214 if identifier_to
211 if identifier_to
215 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}"
212 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}"
216 else
213 else
217 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
214 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
218 end
215 end
219
216
220 cmd << " -- #{shell_quote path}" unless path.empty?
217 cmd << " -- #{shell_quote path}" unless path.empty?
221 diff = []
218 diff = []
222 shellout(cmd) do |io|
219 shellout(cmd) do |io|
223 io.each_line do |line|
220 io.each_line do |line|
224 diff << line
221 diff << line
225 end
222 end
226 end
223 end
227 return nil if $? && $?.exitstatus != 0
224 return nil if $? && $?.exitstatus != 0
228 diff
225 diff
229 end
226 end
230
227
231 def annotate(path, identifier=nil)
228 def annotate(path, identifier=nil)
232 identifier = 'HEAD' if identifier.blank?
229 identifier = 'HEAD' if identifier.blank?
233 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}"
230 cmd = "#{GIT_BIN} --git-dir #{target('')} blame -l #{shell_quote identifier} -- #{shell_quote path}"
234 blame = Annotate.new
231 blame = Annotate.new
235 content = nil
232 content = nil
236 shellout(cmd) { |io| io.binmode; content = io.read }
233 shellout(cmd) { |io| io.binmode; content = io.read }
237 return nil if $? && $?.exitstatus != 0
234 return nil if $? && $?.exitstatus != 0
238 # git annotates binary files
235 # git annotates binary files
239 return nil if content.is_binary_data?
236 return nil if content.is_binary_data?
240 content.split("\n").each do |line|
237 content.split("\n").each do |line|
241 next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)/
238 next unless line =~ /([0-9a-f]{39,40})\s\((\w*)[^\)]*\)(.*)/
242 blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip))
239 blame.add_line($3.rstrip, Revision.new(:identifier => $1, :author => $2.strip))
243 end
240 end
244 blame
241 blame
245 end
242 end
246
243
247 def cat(path, identifier=nil)
244 def cat(path, identifier=nil)
248 if identifier.nil?
245 if identifier.nil?
249 identifier = 'HEAD'
246 identifier = 'HEAD'
250 end
247 end
251 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
248 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote(identifier + ':' + path)}"
252 cat = nil
249 cat = nil
253 shellout(cmd) do |io|
250 shellout(cmd) do |io|
254 io.binmode
251 io.binmode
255 cat = io.read
252 cat = io.read
256 end
253 end
257 return nil if $? && $?.exitstatus != 0
254 return nil if $? && $?.exitstatus != 0
258 cat
255 cat
259 end
256 end
260 end
257 end
261 end
258 end
262 end
259 end
263 end
260 end
@@ -1,120 +1,136
1 # redMine - project management software
1 # encoding: utf-8
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 #
3 # Redmine - project management software
4 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
5 #
4 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
8 #
10 #
9 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # GNU General Public License for more details.
13 #
15 #
14 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
19
18 require File.dirname(__FILE__) + '/../test_helper'
20 require File.dirname(__FILE__) + '/../test_helper'
19
21
20 class ChangesetTest < ActiveSupport::TestCase
22 class ChangesetTest < ActiveSupport::TestCase
21 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :member_roles, :trackers
23 fixtures :projects, :repositories, :issues, :issue_statuses, :changesets, :changes, :issue_categories, :enumerations, :custom_fields, :custom_values, :users, :members, :member_roles, :trackers
22
24
23 def setup
25 def setup
24 end
26 end
25
27
26 def test_ref_keywords_any
28 def test_ref_keywords_any
27 ActionMailer::Base.deliveries.clear
29 ActionMailer::Base.deliveries.clear
28 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
30 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
29 Setting.commit_fix_done_ratio = '90'
31 Setting.commit_fix_done_ratio = '90'
30 Setting.commit_ref_keywords = '*'
32 Setting.commit_ref_keywords = '*'
31 Setting.commit_fix_keywords = 'fixes , closes'
33 Setting.commit_fix_keywords = 'fixes , closes'
32
34
33 c = Changeset.new(:repository => Project.find(1).repository,
35 c = Changeset.new(:repository => Project.find(1).repository,
34 :committed_on => Time.now,
36 :committed_on => Time.now,
35 :comments => 'New commit (#2). Fixes #1')
37 :comments => 'New commit (#2). Fixes #1')
36 c.scan_comment_for_issue_ids
38 c.scan_comment_for_issue_ids
37
39
38 assert_equal [1, 2], c.issue_ids.sort
40 assert_equal [1, 2], c.issue_ids.sort
39 fixed = Issue.find(1)
41 fixed = Issue.find(1)
40 assert fixed.closed?
42 assert fixed.closed?
41 assert_equal 90, fixed.done_ratio
43 assert_equal 90, fixed.done_ratio
42 assert_equal 1, ActionMailer::Base.deliveries.size
44 assert_equal 1, ActionMailer::Base.deliveries.size
43 end
45 end
44
46
45 def test_ref_keywords_any_line_start
47 def test_ref_keywords_any_line_start
46 Setting.commit_ref_keywords = '*'
48 Setting.commit_ref_keywords = '*'
47
49
48 c = Changeset.new(:repository => Project.find(1).repository,
50 c = Changeset.new(:repository => Project.find(1).repository,
49 :committed_on => Time.now,
51 :committed_on => Time.now,
50 :comments => '#1 is the reason of this commit')
52 :comments => '#1 is the reason of this commit')
51 c.scan_comment_for_issue_ids
53 c.scan_comment_for_issue_ids
52
54
53 assert_equal [1], c.issue_ids.sort
55 assert_equal [1], c.issue_ids.sort
54 end
56 end
55
57
56 def test_ref_keywords_allow_brackets_around_a_issue_number
58 def test_ref_keywords_allow_brackets_around_a_issue_number
57 Setting.commit_ref_keywords = '*'
59 Setting.commit_ref_keywords = '*'
58
60
59 c = Changeset.new(:repository => Project.find(1).repository,
61 c = Changeset.new(:repository => Project.find(1).repository,
60 :committed_on => Time.now,
62 :committed_on => Time.now,
61 :comments => '[#1] Worked on this issue')
63 :comments => '[#1] Worked on this issue')
62 c.scan_comment_for_issue_ids
64 c.scan_comment_for_issue_ids
63
65
64 assert_equal [1], c.issue_ids.sort
66 assert_equal [1], c.issue_ids.sort
65 end
67 end
66
68
67 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
69 def test_ref_keywords_allow_brackets_around_multiple_issue_numbers
68 Setting.commit_ref_keywords = '*'
70 Setting.commit_ref_keywords = '*'
69
71
70 c = Changeset.new(:repository => Project.find(1).repository,
72 c = Changeset.new(:repository => Project.find(1).repository,
71 :committed_on => Time.now,
73 :committed_on => Time.now,
72 :comments => '[#1 #2, #3] Worked on these')
74 :comments => '[#1 #2, #3] Worked on these')
73 c.scan_comment_for_issue_ids
75 c.scan_comment_for_issue_ids
74
76
75 assert_equal [1,2,3], c.issue_ids.sort
77 assert_equal [1,2,3], c.issue_ids.sort
76 end
78 end
77
79
78 def test_commit_referencing_a_subproject_issue
80 def test_commit_referencing_a_subproject_issue
79 c = Changeset.new(:repository => Project.find(1).repository,
81 c = Changeset.new(:repository => Project.find(1).repository,
80 :committed_on => Time.now,
82 :committed_on => Time.now,
81 :comments => 'refs #5, a subproject issue')
83 :comments => 'refs #5, a subproject issue')
82 c.scan_comment_for_issue_ids
84 c.scan_comment_for_issue_ids
83
85
84 assert_equal [5], c.issue_ids.sort
86 assert_equal [5], c.issue_ids.sort
85 assert c.issues.first.project != c.project
87 assert c.issues.first.project != c.project
86 end
88 end
87
89
88 def test_commit_referencing_a_parent_project_issue
90 def test_commit_referencing_a_parent_project_issue
89 # repository of child project
91 # repository of child project
90 r = Repository::Subversion.create!(:project => Project.find(3), :url => 'svn://localhost/test')
92 r = Repository::Subversion.create!(:project => Project.find(3), :url => 'svn://localhost/test')
91
93
92 c = Changeset.new(:repository => r,
94 c = Changeset.new(:repository => r,
93 :committed_on => Time.now,
95 :committed_on => Time.now,
94 :comments => 'refs #2, an issue of a parent project')
96 :comments => 'refs #2, an issue of a parent project')
95 c.scan_comment_for_issue_ids
97 c.scan_comment_for_issue_ids
96
98
97 assert_equal [2], c.issue_ids.sort
99 assert_equal [2], c.issue_ids.sort
98 assert c.issues.first.project != c.project
100 assert c.issues.first.project != c.project
99 end
101 end
100
102
101 def test_previous
103 def test_previous
102 changeset = Changeset.find_by_revision('3')
104 changeset = Changeset.find_by_revision('3')
103 assert_equal Changeset.find_by_revision('2'), changeset.previous
105 assert_equal Changeset.find_by_revision('2'), changeset.previous
104 end
106 end
105
107
106 def test_previous_nil
108 def test_previous_nil
107 changeset = Changeset.find_by_revision('1')
109 changeset = Changeset.find_by_revision('1')
108 assert_nil changeset.previous
110 assert_nil changeset.previous
109 end
111 end
110
112
111 def test_next
113 def test_next
112 changeset = Changeset.find_by_revision('2')
114 changeset = Changeset.find_by_revision('2')
113 assert_equal Changeset.find_by_revision('3'), changeset.next
115 assert_equal Changeset.find_by_revision('3'), changeset.next
114 end
116 end
115
117
116 def test_next_nil
118 def test_next_nil
117 changeset = Changeset.find_by_revision('10')
119 changeset = Changeset.find_by_revision('10')
118 assert_nil changeset.next
120 assert_nil changeset.next
119 end
121 end
122
123 def test_comments_should_be_converted_to_utf8
124 with_settings :commit_logs_encoding => 'ISO-8859-1' do
125 c = Changeset.new
126 c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
127 assert_equal "Texte encodΓ© en ISO-8859-1.", c.comments
128 end
129 end
130
131 def test_invalid_utf8_sequences_in_comments_should_be_stripped
132 c = Changeset.new
133 c.comments = File.read("#{RAILS_ROOT}/test/fixtures/encoding/iso-8859-1.txt")
134 assert_equal "Texte encod en ISO-8859-1.", c.comments
135 end
120 end
136 end
@@ -1,69 +1,69
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class RepositoryGitTest < ActiveSupport::TestCase
20 class RepositoryGitTest < ActiveSupport::TestCase
21 fixtures :projects
21 fixtures :projects
22
22
23 # No '..' in the repository path
23 # No '..' in the repository path
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
24 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
25 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
25 REPOSITORY_PATH.gsub!(/\//, "\\") if Redmine::Platform.mswin?
26
26
27 def setup
27 def setup
28 @project = Project.find(1)
28 @project = Project.find(1)
29 assert @repository = Repository::Git.create(:project => @project, :url => REPOSITORY_PATH)
29 assert @repository = Repository::Git.create(:project => @project, :url => REPOSITORY_PATH)
30 end
30 end
31
31
32 if File.directory?(REPOSITORY_PATH)
32 if File.directory?(REPOSITORY_PATH)
33 def test_fetch_changesets_from_scratch
33 def test_fetch_changesets_from_scratch
34 @repository.fetch_changesets
34 @repository.fetch_changesets
35 @repository.reload
35 @repository.reload
36
36
37 assert_equal 12, @repository.changesets.count
37 assert_equal 12, @repository.changesets.count
38 assert_equal 20, @repository.changes.count
38 assert_equal 21, @repository.changes.count
39
39
40 commit = @repository.changesets.find(:first, :order => 'committed_on ASC')
40 commit = @repository.changesets.find(:first, :order => 'committed_on ASC')
41 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
41 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
42 assert_equal "jsmith <jsmith@foo.bar>", commit.committer
42 assert_equal "jsmith <jsmith@foo.bar>", commit.committer
43 assert_equal User.find_by_login('jsmith'), commit.user
43 assert_equal User.find_by_login('jsmith'), commit.user
44 # TODO: add a commit with commit time <> author time to the test repository
44 # TODO: add a commit with commit time <> author time to the test repository
45 assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on
45 assert_equal "2007-12-14 09:22:52".to_time, commit.committed_on
46 assert_equal "2007-12-14".to_date, commit.commit_date
46 assert_equal "2007-12-14".to_date, commit.commit_date
47 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.revision
47 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.revision
48 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
48 assert_equal "7234cb2750b63f47bff735edc50a1c0a433c2518", commit.scmid
49 assert_equal 3, commit.changes.count
49 assert_equal 3, commit.changes.count
50 change = commit.changes.sort_by(&:path).first
50 change = commit.changes.sort_by(&:path).first
51 assert_equal "README", change.path
51 assert_equal "README", change.path
52 assert_equal "A", change.action
52 assert_equal "A", change.action
53 end
53 end
54
54
55 def test_fetch_changesets_incremental
55 def test_fetch_changesets_incremental
56 @repository.fetch_changesets
56 @repository.fetch_changesets
57 # Remove the 3 latest changesets
57 # Remove the 3 latest changesets
58 @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy)
58 @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy)
59 @repository.reload
59 @repository.reload
60 assert_equal 9, @repository.changesets.count
60 assert_equal 9, @repository.changesets.count
61
61
62 @repository.fetch_changesets
62 @repository.fetch_changesets
63 assert_equal 12, @repository.changesets.count
63 assert_equal 12, @repository.changesets.count
64 end
64 end
65 else
65 else
66 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
66 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
67 def test_fake; assert true end
67 def test_fake; assert true end
68 end
68 end
69 end
69 end
General Comments 0
You need to be logged in to leave comments. Login now