##// END OF EJS Templates
scm: git: use the model value of whether reporting last commit in repository tree (#7047)....
Toshi MARUYAMA -
r5537:114e3f3b826c
parent child
Show More
@@ -1,167 +1,169
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 self.human_attribute_name(attribute_key_name)
24 def self.human_attribute_name(attribute_key_name)
25 attr_name = attribute_key_name
25 attr_name = attribute_key_name
26 if attr_name == "url"
26 if attr_name == "url"
27 attr_name = "path_to_repository"
27 attr_name = "path_to_repository"
28 end
28 end
29 super(attr_name)
29 super(attr_name)
30 end
30 end
31
31
32 def self.scm_adapter_class
32 def self.scm_adapter_class
33 Redmine::Scm::Adapters::GitAdapter
33 Redmine::Scm::Adapters::GitAdapter
34 end
34 end
35
35
36 def self.scm_name
36 def self.scm_name
37 'Git'
37 'Git'
38 end
38 end
39
39
40 def report_last_commit
40 def report_last_commit
41 true
41 true
42 end
42 end
43
43
44 def supports_directory_revisions?
44 def supports_directory_revisions?
45 true
45 true
46 end
46 end
47
47
48 def repo_log_encoding
48 def repo_log_encoding
49 'UTF-8'
49 'UTF-8'
50 end
50 end
51
51
52 # Returns the identifier for the given git changeset
52 # Returns the identifier for the given git changeset
53 def self.changeset_identifier(changeset)
53 def self.changeset_identifier(changeset)
54 changeset.scmid
54 changeset.scmid
55 end
55 end
56
56
57 # Returns the readable identifier for the given git changeset
57 # Returns the readable identifier for the given git changeset
58 def self.format_changeset_identifier(changeset)
58 def self.format_changeset_identifier(changeset)
59 changeset.revision[0, 8]
59 changeset.revision[0, 8]
60 end
60 end
61
61
62 def branches
62 def branches
63 scm.branches
63 scm.branches
64 end
64 end
65
65
66 def tags
66 def tags
67 scm.tags
67 scm.tags
68 end
68 end
69
69
70 def find_changeset_by_name(name)
70 def find_changeset_by_name(name)
71 return nil if name.nil? || name.empty?
71 return nil if name.nil? || name.empty?
72 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
72 e = changesets.find(:first, :conditions => ['revision = ?', name.to_s])
73 return e if e
73 return e if e
74 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
74 changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"])
75 end
75 end
76
76
77 def entries(path=nil, identifier=nil)
77 def entries(path=nil, identifier=nil)
78 scm.entries(path, identifier)
78 scm.entries(path,
79 identifier,
80 options = {:report_last_commit => report_last_commit})
79 end
81 end
80
82
81 # In Git and Mercurial, revisions are not in date order.
83 # In Git and Mercurial, revisions are not in date order.
82 # Mercurial fixed issues.
84 # Mercurial fixed issues.
83 # * Redmine Takes Too Long On Large Mercurial Repository
85 # * Redmine Takes Too Long On Large Mercurial Repository
84 # http://www.redmine.org/issues/3449
86 # http://www.redmine.org/issues/3449
85 # * Sorting for changesets might go wrong on Mercurial repos
87 # * Sorting for changesets might go wrong on Mercurial repos
86 # http://www.redmine.org/issues/3567
88 # http://www.redmine.org/issues/3567
87 # Database revision column is text, so Redmine can not sort by revision.
89 # Database revision column is text, so Redmine can not sort by revision.
88 # Mercurial has revision number, and revision number guarantees revision order.
90 # Mercurial has revision number, and revision number guarantees revision order.
89 # Mercurial adapter uses "hg log -r 0:tip --limit 10"
91 # Mercurial adapter uses "hg log -r 0:tip --limit 10"
90 # to get limited revisions from old to new.
92 # to get limited revisions from old to new.
91 # And Mercurial model stored revisions ordered by database id in database.
93 # And Mercurial model stored revisions ordered by database id in database.
92 # So, Mercurial can use correct order revisions.
94 # So, Mercurial can use correct order revisions.
93 #
95 #
94 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
96 # But, Git 1.7.3.4 does not support --reverse with -n or --skip.
95 #
97 #
96 # With SCM's that have a sequential commit numbering, redmine is able to be
98 # With SCM's that have a sequential commit numbering, redmine is able to be
97 # clever and only fetch changesets going forward from the most recent one
99 # clever and only fetch changesets going forward from the most recent one
98 # it knows about.
100 # it knows about.
99 # However, with git, you never know if people have merged
101 # However, with git, you never know if people have merged
100 # commits into the middle of the repository history, so we should parse
102 # commits into the middle of the repository history, so we should parse
101 # the entire log.
103 # the entire log.
102 #
104 #
103 # Since it's way too slow for large repositories,
105 # Since it's way too slow for large repositories,
104 # we only parse 1 week before the last known commit.
106 # we only parse 1 week before the last known commit.
105 #
107 #
106 # The repository can still be fully reloaded by calling #clear_changesets
108 # The repository can still be fully reloaded by calling #clear_changesets
107 # before fetching changesets (eg. for offline resync)
109 # before fetching changesets (eg. for offline resync)
108 def fetch_changesets
110 def fetch_changesets
109 c = changesets.find(:first, :order => 'committed_on DESC')
111 c = changesets.find(:first, :order => 'committed_on DESC')
110 since = (c ? c.committed_on - 7.days : nil)
112 since = (c ? c.committed_on - 7.days : nil)
111
113
112 revisions = scm.revisions('', nil, nil, {:all => true, :since => since, :reverse => true})
114 revisions = scm.revisions('', nil, nil, {:all => true, :since => since, :reverse => true})
113 return if revisions.nil? || revisions.empty?
115 return if revisions.nil? || revisions.empty?
114
116
115 recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
117 recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since])
116
118
117 # Clean out revisions that are no longer in git
119 # Clean out revisions that are no longer in git
118 recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
120 recent_changesets.each {|c| c.destroy unless revisions.detect {|r| r.scmid.to_s == c.scmid.to_s }}
119
121
120 # Subtract revisions that redmine already knows about
122 # Subtract revisions that redmine already knows about
121 recent_revisions = recent_changesets.map{|c| c.scmid}
123 recent_revisions = recent_changesets.map{|c| c.scmid}
122 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
124 revisions.reject!{|r| recent_revisions.include?(r.scmid)}
123
125
124 # Save the remaining ones to the database
126 # Save the remaining ones to the database
125 unless revisions.nil?
127 unless revisions.nil?
126 revisions.each do |rev|
128 revisions.each do |rev|
127 transaction do
129 transaction do
128 save_revision(rev)
130 save_revision(rev)
129 end
131 end
130 end
132 end
131 end
133 end
132 end
134 end
133
135
134 def save_revision(rev)
136 def save_revision(rev)
135 changeset = Changeset.new(
137 changeset = Changeset.new(
136 :repository => self,
138 :repository => self,
137 :revision => rev.identifier,
139 :revision => rev.identifier,
138 :scmid => rev.scmid,
140 :scmid => rev.scmid,
139 :committer => rev.author,
141 :committer => rev.author,
140 :committed_on => rev.time,
142 :committed_on => rev.time,
141 :comments => rev.message
143 :comments => rev.message
142 )
144 )
143 if changeset.save
145 if changeset.save
144 rev.paths.each do |file|
146 rev.paths.each do |file|
145 Change.create(
147 Change.create(
146 :changeset => changeset,
148 :changeset => changeset,
147 :action => file[:action],
149 :action => file[:action],
148 :path => file[:path])
150 :path => file[:path])
149 end
151 end
150 end
152 end
151 end
153 end
152 private :save_revision
154 private :save_revision
153
155
154 def latest_changesets(path,rev,limit=10)
156 def latest_changesets(path,rev,limit=10)
155 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
157 revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
156 return [] if revisions.nil? || revisions.empty?
158 return [] if revisions.nil? || revisions.empty?
157
159
158 changesets.find(
160 changesets.find(
159 :all,
161 :all,
160 :conditions => [
162 :conditions => [
161 "scmid IN (?)",
163 "scmid IN (?)",
162 revisions.map!{|c| c.scmid}
164 revisions.map!{|c| c.scmid}
163 ],
165 ],
164 :order => 'committed_on DESC'
166 :order => 'committed_on DESC'
165 )
167 )
166 end
168 end
167 end
169 end
@@ -1,382 +1,374
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
24
25 SCM_GIT_REPORT_LAST_COMMIT = true
26
27 # Git executable name
25 # Git executable name
28 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
26 GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
29
27
30 # raised if scm command exited with error, e.g. unknown revision.
28 # raised if scm command exited with error, e.g. unknown revision.
31 class ScmCommandAborted < CommandFailed; end
29 class ScmCommandAborted < CommandFailed; end
32
30
33 class << self
31 class << self
34 def client_command
32 def client_command
35 @@bin ||= GIT_BIN
33 @@bin ||= GIT_BIN
36 end
34 end
37
35
38 def sq_bin
36 def sq_bin
39 @@sq_bin ||= shell_quote(GIT_BIN)
37 @@sq_bin ||= shell_quote(GIT_BIN)
40 end
38 end
41
39
42 def client_version
40 def client_version
43 @@client_version ||= (scm_command_version || [])
41 @@client_version ||= (scm_command_version || [])
44 end
42 end
45
43
46 def client_available
44 def client_available
47 !client_version.empty?
45 !client_version.empty?
48 end
46 end
49
47
50 def scm_command_version
48 def scm_command_version
51 scm_version = scm_version_from_command_line.dup
49 scm_version = scm_version_from_command_line.dup
52 if scm_version.respond_to?(:force_encoding)
50 if scm_version.respond_to?(:force_encoding)
53 scm_version.force_encoding('ASCII-8BIT')
51 scm_version.force_encoding('ASCII-8BIT')
54 end
52 end
55 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
53 if m = scm_version.match(%r{\A(.*?)((\d+\.)+\d+)})
56 m[2].scan(%r{\d+}).collect(&:to_i)
54 m[2].scan(%r{\d+}).collect(&:to_i)
57 end
55 end
58 end
56 end
59
57
60 def scm_version_from_command_line
58 def scm_version_from_command_line
61 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
59 shellout("#{sq_bin} --version --no-color") { |io| io.read }.to_s
62 end
60 end
63 end
61 end
64
62
65 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
63 def initialize(url, root_url=nil, login=nil, password=nil, path_encoding=nil)
66 super
64 super
67 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
65 @path_encoding = path_encoding.blank? ? 'UTF-8' : path_encoding
68 @flag_report_last_commit = SCM_GIT_REPORT_LAST_COMMIT
69 end
66 end
70
67
71 def info
68 def info
72 begin
69 begin
73 Info.new(:root_url => url, :lastrev => lastrev('',nil))
70 Info.new(:root_url => url, :lastrev => lastrev('',nil))
74 rescue
71 rescue
75 nil
72 nil
76 end
73 end
77 end
74 end
78
75
79 def branches
76 def branches
80 return @branches if @branches
77 return @branches if @branches
81 @branches = []
78 @branches = []
82 cmd_args = %w|branch --no-color|
79 cmd_args = %w|branch --no-color|
83 scm_cmd(*cmd_args) do |io|
80 scm_cmd(*cmd_args) do |io|
84 io.each_line do |line|
81 io.each_line do |line|
85 @branches << line.match('\s*\*?\s*(.*)$')[1]
82 @branches << line.match('\s*\*?\s*(.*)$')[1]
86 end
83 end
87 end
84 end
88 @branches.sort!
85 @branches.sort!
89 rescue ScmCommandAborted
86 rescue ScmCommandAborted
90 nil
87 nil
91 end
88 end
92
89
93 def tags
90 def tags
94 return @tags if @tags
91 return @tags if @tags
95 cmd_args = %w|tag|
92 cmd_args = %w|tag|
96 scm_cmd(*cmd_args) do |io|
93 scm_cmd(*cmd_args) do |io|
97 @tags = io.readlines.sort!.map{|t| t.strip}
94 @tags = io.readlines.sort!.map{|t| t.strip}
98 end
95 end
99 rescue ScmCommandAborted
96 rescue ScmCommandAborted
100 nil
97 nil
101 end
98 end
102
99
103 def default_branch
100 def default_branch
104 bras = self.branches
101 bras = self.branches
105 return nil if bras.nil?
102 return nil if bras.nil?
106 bras.include?('master') ? 'master' : bras.first
103 bras.include?('master') ? 'master' : bras.first
107 end
104 end
108
105
109 def entry(path=nil, identifier=nil)
106 def entry(path=nil, identifier=nil)
110 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
107 parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
111 search_path = parts[0..-2].join('/')
108 search_path = parts[0..-2].join('/')
112 search_name = parts[-1]
109 search_name = parts[-1]
113 if search_path.blank? && search_name.blank?
110 if search_path.blank? && search_name.blank?
114 # Root entry
111 # Root entry
115 Entry.new(:path => '', :kind => 'dir')
112 Entry.new(:path => '', :kind => 'dir')
116 else
113 else
117 # Search for the entry in the parent directory
114 # Search for the entry in the parent directory
118 es = entries_git(search_path, identifier)
115 es = entries(search_path, identifier,
116 options = {:report_last_commit => false})
119 es ? es.detect {|e| e.name == search_name} : nil
117 es ? es.detect {|e| e.name == search_name} : nil
120 end
118 end
121 end
119 end
122
120
123 def entries(path=nil, identifier=nil, options={})
121 def entries(path=nil, identifier=nil, options={})
124 entries_git(path, identifier,
125 {:report_last_commit => @flag_report_last_commit})
126 end
127
128 def entries_git(path=nil, identifier=nil, options={})
129 path ||= ''
122 path ||= ''
130 p = scm_iconv(@path_encoding, 'UTF-8', path)
123 p = scm_iconv(@path_encoding, 'UTF-8', path)
131 entries = Entries.new
124 entries = Entries.new
132 cmd_args = %w|ls-tree -l|
125 cmd_args = %w|ls-tree -l|
133 cmd_args << "HEAD:#{p}" if identifier.nil?
126 cmd_args << "HEAD:#{p}" if identifier.nil?
134 cmd_args << "#{identifier}:#{p}" if identifier
127 cmd_args << "#{identifier}:#{p}" if identifier
135 scm_cmd(*cmd_args) do |io|
128 scm_cmd(*cmd_args) do |io|
136 io.each_line do |line|
129 io.each_line do |line|
137 e = line.chomp.to_s
130 e = line.chomp.to_s
138 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
131 if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\t(.+)$/
139 type = $1
132 type = $1
140 sha = $2
133 sha = $2
141 size = $3
134 size = $3
142 name = $4
135 name = $4
143 if name.respond_to?(:force_encoding)
136 if name.respond_to?(:force_encoding)
144 name.force_encoding(@path_encoding)
137 name.force_encoding(@path_encoding)
145 end
138 end
146 full_path = p.empty? ? name : "#{p}/#{name}"
139 full_path = p.empty? ? name : "#{p}/#{name}"
147 n = scm_iconv('UTF-8', @path_encoding, name)
140 n = scm_iconv('UTF-8', @path_encoding, name)
148 full_p = scm_iconv('UTF-8', @path_encoding, full_path)
141 full_p = scm_iconv('UTF-8', @path_encoding, full_path)
149 entries << Entry.new({:name => n,
142 entries << Entry.new({:name => n,
150 :path => full_p,
143 :path => full_p,
151 :kind => (type == "tree") ? 'dir' : 'file',
144 :kind => (type == "tree") ? 'dir' : 'file',
152 :size => (type == "tree") ? nil : size,
145 :size => (type == "tree") ? nil : size,
153 :lastrev => options[:report_last_commit] ?
146 :lastrev => options[:report_last_commit] ?
154 lastrev(full_path, identifier) : Revision.new
147 lastrev(full_path, identifier) : Revision.new
155 }) unless entries.detect{|entry| entry.name == name}
148 }) unless entries.detect{|entry| entry.name == name}
156 end
149 end
157 end
150 end
158 end
151 end
159 entries.sort_by_name
152 entries.sort_by_name
160 rescue ScmCommandAborted
153 rescue ScmCommandAborted
161 nil
154 nil
162 end
155 end
163 private :entries_git
164
156
165 def lastrev(path, rev)
157 def lastrev(path, rev)
166 return nil if path.nil?
158 return nil if path.nil?
167 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
159 cmd_args = %w|log --no-color --encoding=UTF-8 --date=iso --pretty=fuller --no-merges -n 1|
168 cmd_args << rev if rev
160 cmd_args << rev if rev
169 cmd_args << "--" << path unless path.empty?
161 cmd_args << "--" << path unless path.empty?
170 lines = []
162 lines = []
171 scm_cmd(*cmd_args) { |io| lines = io.readlines }
163 scm_cmd(*cmd_args) { |io| lines = io.readlines }
172 begin
164 begin
173 id = lines[0].split[1]
165 id = lines[0].split[1]
174 author = lines[1].match('Author:\s+(.*)$')[1]
166 author = lines[1].match('Author:\s+(.*)$')[1]
175 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
167 time = Time.parse(lines[4].match('CommitDate:\s+(.*)$')[1])
176
168
177 Revision.new({
169 Revision.new({
178 :identifier => id,
170 :identifier => id,
179 :scmid => id,
171 :scmid => id,
180 :author => author,
172 :author => author,
181 :time => time,
173 :time => time,
182 :message => nil,
174 :message => nil,
183 :paths => nil
175 :paths => nil
184 })
176 })
185 rescue NoMethodError => e
177 rescue NoMethodError => e
186 logger.error("The revision '#{path}' has a wrong format")
178 logger.error("The revision '#{path}' has a wrong format")
187 return nil
179 return nil
188 end
180 end
189 rescue ScmCommandAborted
181 rescue ScmCommandAborted
190 nil
182 nil
191 end
183 end
192
184
193 def revisions(path, identifier_from, identifier_to, options={})
185 def revisions(path, identifier_from, identifier_to, options={})
194 revisions = Revisions.new
186 revisions = Revisions.new
195 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller|
187 cmd_args = %w|log --no-color --encoding=UTF-8 --raw --date=iso --pretty=fuller|
196 cmd_args << "--reverse" if options[:reverse]
188 cmd_args << "--reverse" if options[:reverse]
197 cmd_args << "--all" if options[:all]
189 cmd_args << "--all" if options[:all]
198 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
190 cmd_args << "-n" << "#{options[:limit].to_i}" if options[:limit]
199 from_to = ""
191 from_to = ""
200 from_to << "#{identifier_from}.." if identifier_from
192 from_to << "#{identifier_from}.." if identifier_from
201 from_to << "#{identifier_to}" if identifier_to
193 from_to << "#{identifier_to}" if identifier_to
202 cmd_args << from_to if !from_to.empty?
194 cmd_args << from_to if !from_to.empty?
203 cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since]
195 cmd_args << "--since=#{options[:since].strftime("%Y-%m-%d %H:%M:%S")}" if options[:since]
204 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
196 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) if path && !path.empty?
205
197
206 scm_cmd *cmd_args do |io|
198 scm_cmd *cmd_args do |io|
207 files=[]
199 files=[]
208 changeset = {}
200 changeset = {}
209 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
201 parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files
210
202
211 io.each_line do |line|
203 io.each_line do |line|
212 if line =~ /^commit ([0-9a-f]{40})$/
204 if line =~ /^commit ([0-9a-f]{40})$/
213 key = "commit"
205 key = "commit"
214 value = $1
206 value = $1
215 if (parsing_descr == 1 || parsing_descr == 2)
207 if (parsing_descr == 1 || parsing_descr == 2)
216 parsing_descr = 0
208 parsing_descr = 0
217 revision = Revision.new({
209 revision = Revision.new({
218 :identifier => changeset[:commit],
210 :identifier => changeset[:commit],
219 :scmid => changeset[:commit],
211 :scmid => changeset[:commit],
220 :author => changeset[:author],
212 :author => changeset[:author],
221 :time => Time.parse(changeset[:date]),
213 :time => Time.parse(changeset[:date]),
222 :message => changeset[:description],
214 :message => changeset[:description],
223 :paths => files
215 :paths => files
224 })
216 })
225 if block_given?
217 if block_given?
226 yield revision
218 yield revision
227 else
219 else
228 revisions << revision
220 revisions << revision
229 end
221 end
230 changeset = {}
222 changeset = {}
231 files = []
223 files = []
232 end
224 end
233 changeset[:commit] = $1
225 changeset[:commit] = $1
234 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
226 elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/
235 key = $1
227 key = $1
236 value = $2
228 value = $2
237 if key == "Author"
229 if key == "Author"
238 changeset[:author] = value
230 changeset[:author] = value
239 elsif key == "CommitDate"
231 elsif key == "CommitDate"
240 changeset[:date] = value
232 changeset[:date] = value
241 end
233 end
242 elsif (parsing_descr == 0) && line.chomp.to_s == ""
234 elsif (parsing_descr == 0) && line.chomp.to_s == ""
243 parsing_descr = 1
235 parsing_descr = 1
244 changeset[:description] = ""
236 changeset[:description] = ""
245 elsif (parsing_descr == 1 || parsing_descr == 2) \
237 elsif (parsing_descr == 1 || parsing_descr == 2) \
246 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
238 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\t(.+)$/
247 parsing_descr = 2
239 parsing_descr = 2
248 fileaction = $1
240 fileaction = $1
249 filepath = $2
241 filepath = $2
250 p = scm_iconv('UTF-8', @path_encoding, filepath)
242 p = scm_iconv('UTF-8', @path_encoding, filepath)
251 files << {:action => fileaction, :path => p}
243 files << {:action => fileaction, :path => p}
252 elsif (parsing_descr == 1 || parsing_descr == 2) \
244 elsif (parsing_descr == 1 || parsing_descr == 2) \
253 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
245 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\t(.+)$/
254 parsing_descr = 2
246 parsing_descr = 2
255 fileaction = $1
247 fileaction = $1
256 filepath = $3
248 filepath = $3
257 p = scm_iconv('UTF-8', @path_encoding, filepath)
249 p = scm_iconv('UTF-8', @path_encoding, filepath)
258 files << {:action => fileaction, :path => p}
250 files << {:action => fileaction, :path => p}
259 elsif (parsing_descr == 1) && line.chomp.to_s == ""
251 elsif (parsing_descr == 1) && line.chomp.to_s == ""
260 parsing_descr = 2
252 parsing_descr = 2
261 elsif (parsing_descr == 1)
253 elsif (parsing_descr == 1)
262 changeset[:description] << line[4..-1]
254 changeset[:description] << line[4..-1]
263 end
255 end
264 end
256 end
265
257
266 if changeset[:commit]
258 if changeset[:commit]
267 revision = Revision.new({
259 revision = Revision.new({
268 :identifier => changeset[:commit],
260 :identifier => changeset[:commit],
269 :scmid => changeset[:commit],
261 :scmid => changeset[:commit],
270 :author => changeset[:author],
262 :author => changeset[:author],
271 :time => Time.parse(changeset[:date]),
263 :time => Time.parse(changeset[:date]),
272 :message => changeset[:description],
264 :message => changeset[:description],
273 :paths => files
265 :paths => files
274 })
266 })
275
267
276 if block_given?
268 if block_given?
277 yield revision
269 yield revision
278 else
270 else
279 revisions << revision
271 revisions << revision
280 end
272 end
281 end
273 end
282 end
274 end
283 revisions
275 revisions
284 rescue ScmCommandAborted
276 rescue ScmCommandAborted
285 revisions
277 revisions
286 end
278 end
287
279
288 def diff(path, identifier_from, identifier_to=nil)
280 def diff(path, identifier_from, identifier_to=nil)
289 path ||= ''
281 path ||= ''
290 cmd_args = []
282 cmd_args = []
291 if identifier_to
283 if identifier_to
292 cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
284 cmd_args << "diff" << "--no-color" << identifier_to << identifier_from
293 else
285 else
294 cmd_args << "show" << "--no-color" << identifier_from
286 cmd_args << "show" << "--no-color" << identifier_from
295 end
287 end
296 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
288 cmd_args << "--" << scm_iconv(@path_encoding, 'UTF-8', path) unless path.empty?
297 diff = []
289 diff = []
298 scm_cmd *cmd_args do |io|
290 scm_cmd *cmd_args do |io|
299 io.each_line do |line|
291 io.each_line do |line|
300 diff << line
292 diff << line
301 end
293 end
302 end
294 end
303 diff
295 diff
304 rescue ScmCommandAborted
296 rescue ScmCommandAborted
305 nil
297 nil
306 end
298 end
307
299
308 def annotate(path, identifier=nil)
300 def annotate(path, identifier=nil)
309 identifier = 'HEAD' if identifier.blank?
301 identifier = 'HEAD' if identifier.blank?
310 cmd_args = %w|blame|
302 cmd_args = %w|blame|
311 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
303 cmd_args << "-p" << identifier << "--" << scm_iconv(@path_encoding, 'UTF-8', path)
312 blame = Annotate.new
304 blame = Annotate.new
313 content = nil
305 content = nil
314 scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
306 scm_cmd(*cmd_args) { |io| io.binmode; content = io.read }
315 # git annotates binary files
307 # git annotates binary files
316 return nil if content.is_binary_data?
308 return nil if content.is_binary_data?
317 identifier = ''
309 identifier = ''
318 # git shows commit author on the first occurrence only
310 # git shows commit author on the first occurrence only
319 authors_by_commit = {}
311 authors_by_commit = {}
320 content.split("\n").each do |line|
312 content.split("\n").each do |line|
321 if line =~ /^([0-9a-f]{39,40})\s.*/
313 if line =~ /^([0-9a-f]{39,40})\s.*/
322 identifier = $1
314 identifier = $1
323 elsif line =~ /^author (.+)/
315 elsif line =~ /^author (.+)/
324 authors_by_commit[identifier] = $1.strip
316 authors_by_commit[identifier] = $1.strip
325 elsif line =~ /^\t(.*)/
317 elsif line =~ /^\t(.*)/
326 blame.add_line($1, Revision.new(
318 blame.add_line($1, Revision.new(
327 :identifier => identifier,
319 :identifier => identifier,
328 :revision => identifier,
320 :revision => identifier,
329 :scmid => identifier,
321 :scmid => identifier,
330 :author => authors_by_commit[identifier]
322 :author => authors_by_commit[identifier]
331 ))
323 ))
332 identifier = ''
324 identifier = ''
333 author = ''
325 author = ''
334 end
326 end
335 end
327 end
336 blame
328 blame
337 rescue ScmCommandAborted
329 rescue ScmCommandAborted
338 nil
330 nil
339 end
331 end
340
332
341 def cat(path, identifier=nil)
333 def cat(path, identifier=nil)
342 if identifier.nil?
334 if identifier.nil?
343 identifier = 'HEAD'
335 identifier = 'HEAD'
344 end
336 end
345 cmd_args = %w|show --no-color|
337 cmd_args = %w|show --no-color|
346 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
338 cmd_args << "#{identifier}:#{scm_iconv(@path_encoding, 'UTF-8', path)}"
347 cat = nil
339 cat = nil
348 scm_cmd(*cmd_args) do |io|
340 scm_cmd(*cmd_args) do |io|
349 io.binmode
341 io.binmode
350 cat = io.read
342 cat = io.read
351 end
343 end
352 cat
344 cat
353 rescue ScmCommandAborted
345 rescue ScmCommandAborted
354 nil
346 nil
355 end
347 end
356
348
357 class Revision < Redmine::Scm::Adapters::Revision
349 class Revision < Redmine::Scm::Adapters::Revision
358 # Returns the readable identifier
350 # Returns the readable identifier
359 def format_identifier
351 def format_identifier
360 identifier[0,8]
352 identifier[0,8]
361 end
353 end
362 end
354 end
363
355
364 def scm_cmd(*args, &block)
356 def scm_cmd(*args, &block)
365 repo_path = root_url || url
357 repo_path = root_url || url
366 full_args = [GIT_BIN, '--git-dir', repo_path]
358 full_args = [GIT_BIN, '--git-dir', repo_path]
367 if self.class.client_version_above?([1, 7, 2])
359 if self.class.client_version_above?([1, 7, 2])
368 full_args << '-c' << 'core.quotepath=false'
360 full_args << '-c' << 'core.quotepath=false'
369 full_args << '-c' << 'log.decorate=no'
361 full_args << '-c' << 'log.decorate=no'
370 end
362 end
371 full_args += args
363 full_args += args
372 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
364 ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block)
373 if $? && $?.exitstatus != 0
365 if $? && $?.exitstatus != 0
374 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
366 raise ScmCommandAborted, "git exited with non-zero status: #{$?.exitstatus}"
375 end
367 end
376 ret
368 ret
377 end
369 end
378 private :scm_cmd
370 private :scm_cmd
379 end
371 end
380 end
372 end
381 end
373 end
382 end
374 end
General Comments 0
You need to be logged in to leave comments. Login now