Feature #4455 » ya-hg-overhaul-0.9-stable-2010-05-17.patch
app/models/changeset.rb | ||
---|---|---|
152 | 152 |
def self.normalize_comments(str) |
153 | 153 |
to_utf8(str.to_s.strip) |
154 | 154 |
end |
155 | ||
156 |
# Creates a new Change from it's common parameters |
|
157 |
def create_change(change) |
|
158 |
Change.create(:changeset => self, |
|
159 |
:action => change[:action], |
|
160 |
:path => change[:path], |
|
161 |
:from_path => change[:from_path], |
|
162 |
:from_revision => change[:from_revision]) |
|
163 |
end |
|
155 | 164 |
|
156 | 165 |
private |
157 | 166 |
app/models/repository/darcs.rb | ||
---|---|---|
85 | 85 |
:comments => revision.message) |
86 | 86 |
|
87 | 87 |
revision.paths.each do |change| |
88 |
Change.create(:changeset => changeset, |
|
89 |
:action => change[:action], |
|
90 |
:path => change[:path], |
|
91 |
:from_path => change[:from_path], |
|
92 |
:from_revision => change[:from_revision]) |
|
88 |
changeset.create_change(change) |
|
93 | 89 |
end |
94 | 90 |
next_rev += 1 |
95 | 91 |
end if revisions |
app/models/repository/mercurial.rb | ||
---|---|---|
78 | 78 |
:comments => revision.message) |
79 | 79 |
|
80 | 80 |
revision.paths.each do |change| |
81 |
Change.create(:changeset => changeset, |
|
82 |
:action => change[:action], |
|
83 |
:path => change[:path], |
|
84 |
:from_path => change[:from_path], |
|
85 |
:from_revision => change[:from_revision]) |
|
81 |
changeset.create_change(change) |
|
86 | 82 |
end |
87 | 83 |
end |
88 | 84 |
end unless revisions.nil? |
app/models/repository/subversion.rb | ||
---|---|---|
63 | 63 |
:comments => revision.message) |
64 | 64 |
|
65 | 65 |
revision.paths.each do |change| |
66 |
Change.create(:changeset => changeset, |
|
67 |
:action => change[:action], |
|
68 |
:path => change[:path], |
|
69 |
:from_path => change[:from_path], |
|
70 |
:from_revision => change[:from_revision]) |
|
66 |
changeset.create_change(change) |
|
71 | 67 |
end unless changeset.new_record? |
72 | 68 |
end |
73 | 69 |
end unless revisions.nil? |
app/helpers/repositories_helper.rb | ||
---|---|---|
52 | 52 |
else |
53 | 53 |
change |
54 | 54 |
end |
55 |
end.compact
|
|
55 |
end.compact |
|
56 | 56 |
|
57 | 57 |
tree = { } |
58 | 58 |
changes.each do |change| |
59 | 59 |
p = tree |
60 | 60 |
dirs = change.path.to_s.split('/').select {|d| !d.blank?} |
61 |
path = '' |
|
61 | 62 |
dirs.each do |dir| |
63 |
path += '/' + dir |
|
62 | 64 |
p[:s] ||= {} |
63 | 65 |
p = p[:s] |
64 |
p[dir] ||= {}
|
|
65 |
p = p[dir]
|
|
66 |
p[path] ||= {}
|
|
67 |
p = p[path]
|
|
66 | 68 |
end |
67 | 69 |
p[:c] = change |
68 | 70 |
end |
... | ... | |
76 | 78 |
output = '' |
77 | 79 |
output << '<ul>' |
78 | 80 |
tree.keys.sort.each do |file| |
79 |
s = !tree[file][:s].nil? |
|
80 |
c = tree[file][:c] |
|
81 |
|
|
82 | 81 |
style = 'change' |
83 |
style << ' folder' if s |
|
84 |
style << " change-#{c.action}" if c |
|
85 |
|
|
86 |
text = h(file) |
|
87 |
unless c.nil? |
|
82 |
text = File.basename(h(file)) |
|
83 |
if s = tree[file][:s] |
|
84 |
style << ' folder' |
|
85 |
path_param = to_path_param(@repository.relative_path(file)) |
|
86 |
text = link_to(text, :controller => 'repositories', |
|
87 |
:action => 'show', |
|
88 |
:id => @project, |
|
89 |
:path => path_param, |
|
90 |
:rev => @changeset.revision) |
|
91 |
output << "<li class='#{style}'>#{text}</li>" |
|
92 |
output << render_changes_tree(s) |
|
93 |
elsif c = tree[file][:c] |
|
94 |
style << " change-#{c.action}" |
|
88 | 95 |
path_param = to_path_param(@repository.relative_path(c.path)) |
89 | 96 |
text = link_to(text, :controller => 'repositories', |
90 | 97 |
:action => 'entry', |
91 | 98 |
:id => @project, |
92 | 99 |
:path => path_param, |
93 |
:rev => @changeset.revision) unless s || c.action == 'D'
|
|
100 |
:rev => @changeset.revision) unless c.action == 'D' |
|
94 | 101 |
text << " - #{c.revision}" unless c.revision.blank? |
95 | 102 |
text << ' (' + link_to('diff', :controller => 'repositories', |
96 | 103 |
:action => 'diff', |
... | ... | |
98 | 105 |
:path => path_param, |
99 | 106 |
:rev => @changeset.revision) + ') ' if c.action == 'M' |
100 | 107 |
text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank? |
108 |
output << "<li class='#{style}'>#{text}</li>" |
|
101 | 109 |
end |
102 |
output << "<li class='#{style}'>#{text}</li>" |
|
103 |
output << render_changes_tree(tree[file][:s]) if s |
|
104 | 110 |
end |
105 | 111 |
output << '</ul>' |
106 | 112 |
output |
app/models/repository/git.rb | ||
---|---|---|
49 | 49 |
c = changesets.find(:first, :order => 'committed_on DESC') |
50 | 50 |
since = (c ? c.committed_on - 7.days : nil) |
51 | 51 | |
52 |
revisions = scm.revisions('', nil, nil, :all => true, :since => since) |
|
52 |
revisions = scm.revisions('', nil, nil, :all => true, :since => since, :reverse => true)
|
|
53 | 53 |
return if revisions.nil? || revisions.empty? |
54 | 54 | |
55 | 55 |
recent_changesets = changesets.find(:all, :conditions => ['committed_on >= ?', since]) |
... | ... | |
75 | 75 |
"scmid IN (?)", |
76 | 76 |
revisions.map!{|c| c.scmid} |
77 | 77 |
], |
78 |
:order => 'committed_on DESC'
|
|
78 |
:order => 'id DESC'
|
|
79 | 79 |
) |
80 | 80 |
end |
81 | 81 |
end |
app/models/issue.rb | ||
---|---|---|
27 | 27 | |
28 | 28 |
has_many :journals, :as => :journalized, :dependent => :destroy |
29 | 29 |
has_many :time_entries, :dependent => :delete_all |
30 |
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
|
|
30 |
has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.id ASC" |
|
31 | 31 |
|
32 | 32 |
has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all |
33 | 33 |
has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all |
app/models/repository.rb | ||
---|---|---|
17 | 17 | |
18 | 18 |
class Repository < ActiveRecord::Base |
19 | 19 |
belongs_to :project |
20 |
has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"
|
|
20 |
has_many :changesets, :order => "#{Changeset.table_name}.id DESC" |
|
21 | 21 |
has_many :changes, :through => :changesets |
22 | 22 |
|
23 | 23 |
# Raw SQL to delete changesets and changes in the database |
... | ... | |
106 | 106 |
def latest_changesets(path, rev, limit=10) |
107 | 107 |
if path.blank? |
108 | 108 |
changesets.find(:all, :include => :user, |
109 |
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC", |
|
110 | 109 |
:limit => limit) |
111 | 110 |
else |
112 | 111 |
changes.find(:all, :include => {:changeset => :user}, |
113 | 112 |
:conditions => ["path = ?", path.with_leading_slash], |
114 |
:order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC",
|
|
113 |
:order => "#{Changeset.table_name}.id DESC", |
|
115 | 114 |
:limit => limit).collect(&:changeset) |
116 | 115 |
end |
117 | 116 |
end |
app/models/changeset.rb | ||
---|---|---|
47 | 47 |
def revision=(r) |
48 | 48 |
write_attribute :revision, (r.nil? ? nil : r.to_s) |
49 | 49 |
end |
50 | ||
51 |
# Returns the identifier of this changeset. |
|
52 |
# e.g. revision number for centralized system; hash id for DVCS |
|
53 |
def identifier |
|
54 |
scmid || revision |
|
55 |
end |
|
50 | 56 |
|
51 | 57 |
def comments=(comment) |
52 | 58 |
write_attribute(:comments, Changeset.normalize_comments(comment)) |
lib/redmine/scm/adapters/abstract_adapter.rb | ||
---|---|---|
271 | 271 |
end |
272 | 272 |
|
273 | 273 |
class Revision |
274 |
attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch |
|
274 |
attr_accessor :scmid, :name, :author, :time, :message, :paths, :revision, :branch |
|
275 |
attr_writer :identifier |
|
275 | 276 | |
276 | 277 |
def initialize(attributes={}) |
277 | 278 |
self.identifier = attributes[:identifier] |
... | ... | |
285 | 286 |
self.branch = attributes[:branch] |
286 | 287 |
end |
287 | 288 | |
289 |
# Returns the identifier of this revision. |
|
290 |
# e.g. revision number for centralized system; hash id for DVCS |
|
291 |
def identifier |
|
292 |
@identifier || scmid || revision |
|
293 |
end |
|
294 | ||
288 | 295 |
def save(repo) |
289 | 296 |
Changeset.transaction do |
290 | 297 |
changeset = Changeset.new( |
app/models/repository.rb | ||
---|---|---|
94 | 94 |
|
95 | 95 |
# Finds and returns a revision with a number or the beginning of a hash |
96 | 96 |
def find_changeset_by_name(name) |
97 |
changesets.find(:first, :conditions => (name.match(/^\d*$/) ? ["revision = ?", name.to_s] : ["revision LIKE ?", name + '%'])) |
|
97 |
# TODO: is this query efficient enough? can we write as single query? |
|
98 |
e = changesets.find(:first, :conditions => ['revision = ? OR scmid = ?', name.to_s, name.to_s]) |
|
99 |
return e if e |
|
100 |
changesets.find(:first, :conditions => ['scmid LIKE ?', "#{name}%"]) |
|
98 | 101 |
end |
99 | 102 |
|
100 | 103 |
def latest_changeset |
app/helpers/application_helper.rb | ||
---|---|---|
101 | 101 |
# * :text - Link text (default to the formatted revision) |
102 | 102 |
def link_to_revision(revision, project, options={}) |
103 | 103 |
text = options.delete(:text) || format_revision(revision) |
104 |
rev = revision.respond_to?(:identifier) ? revision.identifier : revision |
|
104 | 105 | |
105 |
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision)) |
|
106 |
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev}, |
|
107 |
:title => l(:label_revision_id, format_revision(revision))) |
|
106 | 108 |
end |
107 | 109 | |
108 | 110 |
def toggle_link(name, id, options={}) |
app/helpers/repositories_helper.rb | ||
---|---|---|
18 | 18 |
require 'iconv' |
19 | 19 | |
20 | 20 |
module RepositoriesHelper |
21 |
def format_revision(txt) |
|
22 |
txt.to_s[0,8] |
|
21 |
# truncate rev to 8 chars if it's quite long |
|
22 |
def truncate_long_revision_name(rev) |
|
23 |
rev.to_s.size <= 12 ? rev.to_s : rev.to_s[0, 8] |
|
24 |
end |
|
25 |
private :truncate_long_revision_name |
|
26 | ||
27 |
def format_revision(revision) |
|
28 |
if [:identifier, :revision, :scmid].all? { |e| revision.respond_to? e } |
|
29 |
if revision.scmid and revision.revision != revision.scmid and /[^\d]/ !~ revision.revision |
|
30 |
"#{revision.revision}:#{revision.scmid}" # number:hashid |
|
31 |
else |
|
32 |
truncate_long_revision_name(revision.identifier) |
|
33 |
end |
|
34 |
else |
|
35 |
truncate_long_revision_name(revision) |
|
36 |
end |
|
23 | 37 |
end |
24 | 38 |
|
25 | 39 |
def truncate_at_line_break(text, length = 255) |
app/helpers/application_helper.rb | ||
---|---|---|
563 | 563 |
end |
564 | 564 |
when 'commit' |
565 | 565 |
if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"])) |
566 |
link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
|
|
566 |
link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
|
|
567 | 567 |
:class => 'changeset', |
568 | 568 |
:title => truncate_single_line(changeset.comments, :length => 100) |
569 | 569 |
end |
app/models/changeset.rb | ||
---|---|---|
53 | 53 |
def identifier |
54 | 54 |
scmid || revision |
55 | 55 |
end |
56 | ||
57 |
# Returns the wiki identifier, "rN" or "commit:ABCDEF" |
|
58 |
def wiki_identifier |
|
59 |
if scmid # hash-like |
|
60 |
"commit:#{scmid}" |
|
61 |
else # numeric |
|
62 |
"r#{revision}" |
|
63 |
end |
|
64 |
end |
|
65 |
private :wiki_identifier |
|
56 | 66 |
|
57 | 67 |
def comments=(comment) |
58 | 68 |
write_attribute(:comments, Changeset.normalize_comments(comment)) |
... | ... | |
115 | 125 |
issue.reload |
116 | 126 |
# don't change the status is the issue is closed |
117 | 127 |
next if issue.status.is_closed? |
118 |
csettext = "r#{self.revision}" |
|
119 |
if self.scmid && (! (csettext =~ /^r[0-9]+$/)) |
|
120 |
csettext = "commit:\"#{self.scmid}\"" |
|
121 |
end |
|
122 |
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, csettext)) |
|
128 |
journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, wiki_identifier)) |
|
123 | 129 |
issue.status = fix_status |
124 | 130 |
unless Setting.commit_fix_done_ratio.blank? |
125 | 131 |
issue.done_ratio = Setting.commit_fix_done_ratio.to_i |
app/helpers/repositories_helper.rb | ||
---|---|---|
35 | 35 |
truncate_long_revision_name(revision) |
36 | 36 |
end |
37 | 37 |
end |
38 |
module_function :format_revision # callable as RepositoriesHelper.format_revision |
|
38 | 39 |
|
39 | 40 |
def truncate_at_line_break(text, length = 255) |
40 | 41 |
if text |
app/models/changeset.rb | ||
---|---|---|
23 | 23 |
has_many :changes, :dependent => :delete_all |
24 | 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)} #{RepositoriesHelper.format_revision(o)}" + (o.short_comments.blank? ? '' : (': ' + o.short_comments))},
|
|
27 | 27 |
:description => :long_comments, |
28 | 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.identifier}}
|
|
30 | 30 |
|
31 | 31 |
acts_as_searchable :columns => 'comments', |
32 | 32 |
:include => {:repository => :project}, |
app/helpers/repositories_helper.rb | ||
---|---|---|
102 | 102 |
:action => 'show', |
103 | 103 |
:id => @project, |
104 | 104 |
:path => path_param, |
105 |
:rev => @changeset.revision)
|
|
105 |
:rev => @changeset.identifier)
|
|
106 | 106 |
output << "<li class='#{style}'>#{text}</li>" |
107 | 107 |
output << render_changes_tree(s) |
108 | 108 |
elsif c = tree[file][:c] |
... | ... | |
112 | 112 |
:action => 'entry', |
113 | 113 |
:id => @project, |
114 | 114 |
:path => path_param, |
115 |
:rev => @changeset.revision) unless c.action == 'D'
|
|
115 |
:rev => @changeset.identifier) unless c.action == 'D'
|
|
116 | 116 |
text << " - #{c.revision}" unless c.revision.blank? |
117 | 117 |
text << ' (' + link_to('diff', :controller => 'repositories', |
118 | 118 |
:action => 'diff', |
119 | 119 |
:id => @project, |
120 | 120 |
:path => path_param, |
121 |
:rev => @changeset.revision) + ') ' if c.action == 'M'
|
|
121 |
:rev => @changeset.identifier) + ') ' if c.action == 'M'
|
|
122 | 122 |
text << ' ' + content_tag('span', c.from_path, :class => 'copied-from') unless c.from_path.blank? |
123 | 123 |
output << "<li class='#{style}'>#{text}</li>" |
124 | 124 |
end |
app/views/repositories/_dir_list_content.rhtml | ||
---|---|---|
17 | 17 |
</td> |
18 | 18 |
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td> |
19 | 19 |
<% changeset = @project.repository.changesets.find_by_revision(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %> |
20 |
<td class="revision"><%= link_to_revision(changeset.revision, @project) if changeset %></td>
|
|
20 |
<td class="revision"><%= link_to_revision(changeset, @project) if changeset %></td> |
|
21 | 21 |
<td class="age"><%= distance_of_time_in_words(entry.lastrev.time, Time.now) if entry.lastrev && entry.lastrev.time %></td> |
22 | 22 |
<td class="author"><%= changeset.nil? ? h(entry.lastrev.author.to_s.split('<').first) : changeset.author if entry.lastrev %></td> |
23 | 23 |
<td class="comments"><%=h truncate(changeset.comments, :length => 50) unless changeset.nil? %></td> |
app/views/repositories/_revisions.rhtml | ||
---|---|---|
13 | 13 |
<% line_num = 1 %> |
14 | 14 |
<% revisions.each do |changeset| %> |
15 | 15 |
<tr class="changeset <%= cycle 'odd', 'even' %>"> |
16 |
<td class="id"><%= link_to_revision(changeset.revision, project) %></td>
|
|
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 |
<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>
|
|
16 |
<td class="id"><%= link_to_revision(changeset, project) %></td> |
|
17 |
<td class="checkbox"><%= radio_button_tag('rev', changeset.identifier, (line_num==1), :id => "cb-#{line_num}", :onclick => "$('cbto-#{line_num+1}').checked=true;") if show_diff && (line_num < revisions.size) %></td>
|
|
18 |
<td class="checkbox"><%= radio_button_tag('rev_to', changeset.identifier, (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 | 20 |
<td class="author"><%=h changeset.author %></td> |
21 | 21 |
<td class="comments"><%= textilizable(truncate_at_line_break(changeset.comments)) %></td> |
app/views/repositories/annotate.rhtml | ||
---|---|---|
19 | 19 |
<tr class="bloc-<%= revision.nil? ? 0 : colors[revision.identifier || revision.revision] %>"> |
20 | 20 |
<th class="line-num" id="L<%= line_num %>"><a href="#L<%= line_num %>"><%= line_num %></a></th> |
21 | 21 |
<td class="revision"> |
22 |
<%= (revision.identifier ? link_to(format_revision(revision.identifier), :action => 'revision', :id => @project, :rev => revision.identifier) : format_revision(revision.revision)) if revision %></td>
|
|
22 |
<%= (revision.identifier ? link_to_revision(revision, @project) : format_revision(revision)) if revision %></td>
|
|
23 | 23 |
<td class="author"><%= h(revision.author.to_s.split('<').first) if revision %></td> |
24 | 24 |
<td class="line-code"><pre><%= line %></pre></td> |
25 | 25 |
</tr> |
app/views/repositories/revision.rhtml | ||
---|---|---|
1 | 1 |
<div class="contextual"> |
2 | 2 |
« |
3 | 3 |
<% unless @changeset.previous.nil? -%> |
4 |
<%= link_to_revision(@changeset.previous.revision, @project, :text => l(:label_previous)) %>
|
|
4 |
<%= link_to_revision(@changeset.previous, @project, :text => l(:label_previous)) %> |
|
5 | 5 |
<% else -%> |
6 | 6 |
<%= l(:label_previous) %> |
7 | 7 |
<% end -%> |
8 | 8 |
| |
9 | 9 |
<% unless @changeset.next.nil? -%> |
10 |
<%= link_to_revision(@changeset.next.revision, @project, :text => l(:label_next)) %>
|
|
10 |
<%= link_to_revision(@changeset.next, @project, :text => l(:label_next)) %> |
|
11 | 11 |
<% else -%> |
12 | 12 |
<%= l(:label_next) %> |
13 | 13 |
<% end -%> |
... | ... | |
19 | 19 |
<% end %> |
20 | 20 |
</div> |
21 | 21 | |
22 |
<h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
|
|
22 |
<h2><%= l(:label_revision) %> <%= format_revision(@changeset) %></h2> |
|
23 | 23 | |
24 | 24 |
<p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %> |
25 | 25 |
<span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p> |
... | ... | |
45 | 45 |
<li class="change change-D"><%= l(:label_deleted) %></li> |
46 | 46 |
</ul> |
47 | 47 | |
48 |
<p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
|
|
48 |
<p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.identifier) if @changeset.changes.any? %></p>
|
|
49 | 49 | |
50 | 50 |
<div class="changeset-changes"> |
51 | 51 |
<%= render_changeset_changes %> |
app/views/repositories/revision.rhtml | ||
---|---|---|
14 | 14 |
» |
15 | 15 | |
16 | 16 |
<% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %> |
17 |
<%= text_field_tag 'rev', @rev[0,8], :size => 8 %>
|
|
17 |
<%= text_field_tag 'rev', @rev, :size => 8 %> |
|
18 | 18 |
<%= submit_tag 'OK', :name => nil %> |
19 | 19 |
<% end %> |
20 | 20 |
</div> |
app/models/repository.rb | ||
---|---|---|
174 | 174 |
def self.fetch_changesets |
175 | 175 |
Project.active.has_module(:repository).find(:all, :include => :repository).each do |project| |
176 | 176 |
if project.repository |
177 |
project.repository.fetch_changesets |
|
177 |
begin |
|
178 |
project.repository.fetch_changesets |
|
179 |
rescue Redmine::Scm::Adapters::CommandFailed => e |
|
180 |
logger.error "Repository: error during fetching changesets: #{e.message}" |
|
181 |
end |
|
178 | 182 |
end |
179 | 183 |
end |
180 | 184 |
end |
extra/mercurial/redminehelper.py | ||
---|---|---|
1 |
# redminehelper: draft extension for Mercurial |
|
2 |
# it's a draft to show a possible way to explore repository by the Redmine overhaul patch |
|
3 |
# see: http://www.redmine.org/issues/4455 |
|
4 |
# |
|
5 |
# Copyright 2010 Alessio Franceschelli (alefranz.net) |
|
6 |
# |
|
7 |
# This software may be used and distributed according to the terms of the |
|
8 |
# GNU General Public License version 2 or any later version. |
|
9 | ||
10 |
'''command to list revision of each file |
|
11 |
''' |
|
12 | ||
13 |
from mercurial import cmdutil, commands |
|
14 |
from mercurial.i18n import _ |
|
15 | ||
16 |
def overhaul(ui, repo, rev=None, **opts): |
|
17 |
mf = repo[rev].manifest() |
|
18 |
for f in repo[rev]: |
|
19 |
try: |
|
20 |
fctx = repo.filectx(f, fileid=mf[f]) |
|
21 |
ctx = fctx.changectx() |
|
22 |
ui.write('%s\t%d\t%s\n' % |
|
23 |
(ctx,fctx.size(),f)) |
|
24 |
except LookupError: |
|
25 |
pass |
|
26 | ||
27 |
cmdtable = { |
|
28 |
'overhaul': (overhaul,commands.templateopts, _('hg overhaul [rev]')) |
|
29 |
} |
extra/mercurial/redminehelper.py | ||
---|---|---|
1 |
# redminehelper: draft extension for Mercurial
|
|
1 |
# redminehelper: Redmine helper extension for Mercurial
|
|
2 | 2 |
# it's a draft to show a possible way to explore repository by the Redmine overhaul patch |
3 | 3 |
# see: http://www.redmine.org/issues/4455 |
4 | 4 |
# |
5 | 5 |
# Copyright 2010 Alessio Franceschelli (alefranz.net) |
6 |
# Copyright 2010 Yuya Nishihara <yuya@tcha.org> |
|
6 | 7 |
# |
7 | 8 |
# This software may be used and distributed according to the terms of the |
8 | 9 |
# GNU General Public License version 2 or any later version. |
... | ... | |
10 | 11 |
'''command to list revision of each file |
11 | 12 |
''' |
12 | 13 | |
13 |
from mercurial import cmdutil, commands |
|
14 |
from mercurial.i18n import _
|
|
14 |
import re, time |
|
15 |
from mercurial import cmdutil, commands, node, error
|
|
15 | 16 | |
16 |
def overhaul(ui, repo, rev=None, **opts): |
|
17 |
SPECIAL_TAGS = ('tip',) |
|
18 | ||
19 |
def rhsummary(ui, repo, **opts): |
|
20 |
"""output the summary of the repository""" |
|
21 |
# see mercurial/commands.py:tip |
|
22 |
ui.write(':tip: rev node\n') |
|
23 |
tipctx = repo[len(repo) - 1] |
|
24 |
ui.write('%d %s\n' % (tipctx.rev(), tipctx)) |
|
25 | ||
26 |
# see mercurial/commands.py:root |
|
27 |
ui.write(':root: path\n') |
|
28 |
ui.write(repo.root + '\n') |
|
29 | ||
30 |
# see mercurial/commands.py:tags |
|
31 |
ui.write(':tags: rev node name\n') |
|
32 |
for t, n in reversed(repo.tagslist()): |
|
33 |
if t in SPECIAL_TAGS: |
|
34 |
continue |
|
35 |
try: |
|
36 |
r = repo.changelog.rev(n) |
|
37 |
except error.LookupError: |
|
38 |
r = -1 |
|
39 |
ui.write('%d %s %s\n' % (r, node.short(n), t)) |
|
40 | ||
41 |
# see mercurial/commands.py:branches |
|
42 |
def iterbranches(): |
|
43 |
for t, n in repo.branchtags().iteritems(): |
|
44 |
yield t, n, repo.changelog.rev(n) |
|
45 | ||
46 |
ui.write(':branches: rev node name\n') |
|
47 |
for t, n, r in sorted(iterbranches(), key=lambda e: e[2], reverse=True): |
|
48 |
if repo.lookup(r) in repo.branchheads(t, closed=False): |
|
49 |
ui.write('%d %s %s\n' % (r, node.short(n), t)) # only open branch |
|
50 | ||
51 |
def rhentries(ui, repo, path='', **opts): |
|
52 |
"""output the entries of the specified directory""" |
|
53 |
rev = opts.get('rev') |
|
54 |
pathprefix = (path.rstrip('/') + '/').lstrip('/') |
|
55 | ||
56 |
# TODO: clean up |
|
57 |
dirs, files = {}, {} |
|
17 | 58 |
mf = repo[rev].manifest() |
18 | 59 |
for f in repo[rev]: |
19 |
try: |
|
20 |
fctx = repo.filectx(f, fileid=mf[f]) |
|
21 |
ctx = fctx.changectx() |
|
22 |
ui.write('%s\t%d\t%s\n' % |
|
23 |
(ctx,fctx.size(),f)) |
|
24 |
except LookupError: |
|
25 |
pass |
|
60 |
if not f.startswith(pathprefix): |
|
61 |
continue |
|
62 | ||
63 |
name = re.sub(r'/.*', '', f[len(pathprefix):]) |
|
64 |
if '/' in f[len(pathprefix):]: |
|
65 |
dirs[name] = (name,) |
|
66 |
else: |
|
67 |
try: |
|
68 |
fctx = repo.filectx(f, fileid=mf[f]) |
|
69 |
ctx = fctx.changectx() |
|
70 |
tm, tzoffset = ctx.date() |
|
71 |
localtime = int(tm) + tzoffset - time.timezone |
|
72 |
files[name] = (ctx.rev(), node.short(ctx.node()), localtime, |
|
73 |
fctx.size(), name) |
|
74 |
except LookupError: # TODO: when this occurs? |
|
75 |
pass |
|
76 | ||
77 |
ui.write(':dirs: name\n') |
|
78 |
for n, v in sorted(dirs.iteritems(), key=lambda e: e[0]): |
|
79 |
ui.write(' '.join(v) + '\n') |
|
80 | ||
81 |
ui.write(':files: rev node time size name\n') |
|
82 |
for n, v in sorted(files.iteritems(), key=lambda e: e[0]): |
|
83 |
ui.write(' '.join(str(e) for e in v) + '\n') |
|
84 | ||
26 | 85 | |
27 | 86 |
cmdtable = { |
28 |
'overhaul': (overhaul,commands.templateopts, _('hg overhaul [rev]')) |
|
87 |
'rhsummary': (rhsummary, [], 'hg rhsummary'), |
|
88 |
'rhentries': (rhentries, |
|
89 |
[('r', 'rev', '', 'show the specified revision')], |
|
90 |
'hg rhentries [path]'), |
|
29 | 91 |
} |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
24 | 24 |
|
25 | 25 |
# Mercurial executable name |
26 | 26 |
HG_BIN = "hg" |
27 |
HG_HELPER_EXT = "#{RAILS_ROOT}/extra/mercurial/redminehelper.py" |
|
27 | 28 |
TEMPLATES_DIR = File.dirname(__FILE__) + "/mercurial" |
28 | 29 |
TEMPLATE_NAME = "hg-template" |
29 | 30 |
TEMPLATE_EXTENSION = "tmpl" |
30 | 31 |
|
32 |
# raised if hg command exited with error, e.g. unknown revision. |
|
33 |
class HgCommandAborted < CommandFailed; end |
|
34 | ||
31 | 35 |
class << self |
32 | 36 |
def client_version |
33 | 37 |
@@client_version ||= (hgversion || []) |
... | ... | |
170 | 174 |
end |
171 | 175 |
|
172 | 176 |
def cat(path, identifier=nil) |
173 |
cmd = "#{HG_BIN} -R #{target('')} cat" |
|
174 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
175 |
cmd << " #{target(path)}" |
|
176 |
cat = nil |
|
177 |
shellout(cmd) do |io| |
|
177 |
hg 'cat', '-r', hgrev(identifier), without_leading_slash(path) do |io| |
|
178 | 178 |
io.binmode |
179 |
cat = io.read
|
|
179 |
io.read |
|
180 | 180 |
end |
181 |
return nil if $? && $?.exitstatus != 0
|
|
182 |
cat
|
|
181 |
rescue HgCommandAborted
|
|
182 |
nil # means not found
|
|
183 | 183 |
end |
184 | 184 |
|
185 | 185 |
def annotate(path, identifier=nil) |
... | ... | |
199 | 199 |
return nil if $? && $?.exitstatus != 0 |
200 | 200 |
blame |
201 | 201 |
end |
202 | ||
203 |
# Runs 'hg' command with the given args |
|
204 |
def hg(*args, &block) |
|
205 |
full_args = [HG_BIN, '--cwd', url] |
|
206 |
full_args << '--config' << "extensions.redminehelper=#{HG_HELPER_EXT}" |
|
207 |
full_args += args |
|
208 |
ret = shellout(full_args.map { |e| shell_quote e.to_s }.join(' '), &block) |
|
209 |
if $? && $?.exitstatus != 0 |
|
210 |
raise HgCommandAborted, "hg exited with non-zero status: #{$?.exitstatus}" |
|
211 |
end |
|
212 |
ret |
|
213 |
end |
|
214 |
private :hg |
|
215 | ||
216 |
# Returns correct revision identifier |
|
217 |
def hgrev(identifier) |
|
218 |
identifier.blank? ? 'tip' : identifier.to_s |
|
219 |
end |
|
220 |
private :hgrev |
|
202 | 221 |
end |
203 | 222 |
end |
204 | 223 |
end |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
155 | 155 |
end |
156 | 156 |
|
157 | 157 |
def diff(path, identifier_from, identifier_to=nil) |
158 |
path ||= ''
|
|
158 |
hg_args = ['diff', '--nodates']
|
|
159 | 159 |
if identifier_to |
160 |
identifier_to = identifier_to.to_i
|
|
160 |
hg_args << '-r' << hgrev(identifier_to) << '-r' << hgrev(identifier_from)
|
|
161 | 161 |
else |
162 |
identifier_to = identifier_from.to_i - 1
|
|
162 |
hg_args << '-c' << hgrev(identifier_from)
|
|
163 | 163 |
end |
164 |
cmd = "#{HG_BIN} -R #{target('')} diff -r #{identifier_to} -r #{identifier_from} --nodates" |
|
165 |
cmd << " -I #{target(path)}" unless path.empty? |
|
166 |
diff = [] |
|
167 |
shellout(cmd) do |io| |
|
168 |
io.each_line do |line| |
|
169 |
diff << line |
|
170 |
end |
|
164 |
hg_args << without_leading_slash(path) unless path.blank? |
|
165 | ||
166 |
hg *hg_args do |io| |
|
167 |
io.collect |
|
171 | 168 |
end |
172 |
return nil if $? && $?.exitstatus != 0
|
|
173 |
diff
|
|
169 |
rescue HgCommandAborted
|
|
170 |
nil # means not found
|
|
174 | 171 |
end |
175 | 172 |
|
176 | 173 |
def cat(path, identifier=nil) |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
180 | 180 |
end |
181 | 181 |
|
182 | 182 |
def annotate(path, identifier=nil) |
183 |
path ||= '' |
|
184 |
cmd = "#{HG_BIN} -R #{target('')}" |
|
185 |
cmd << " annotate -n -u" |
|
186 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
187 |
cmd << " -r #{identifier.to_i}" if identifier |
|
188 |
cmd << " #{target(path)}" |
|
189 | 183 |
blame = Annotate.new |
190 |
shellout(cmd) do |io| |
|
191 |
io.each_line do |line| |
|
192 |
next unless line =~ %r{^([^:]+)\s(\d+):(.*)$} |
|
193 |
blame.add_line($3.rstrip, Revision.new(:identifier => $2.to_i, :author => $1.strip)) |
|
184 |
hg 'annotate', '-ncu', '-r', hgrev(identifier), without_leading_slash(path) do |io| |
|
185 |
io.each do |line| |
|
186 |
next unless line =~ %r{^([^:]+)\s(\d+)\s([0-9a-f]+):(.*)$} |
|
187 |
r = Revision.new(:author => $1.strip, :revision => $2, :scmid => $3) |
|
188 |
blame.add_line($4.rstrip, r) |
|
194 | 189 |
end |
195 | 190 |
end |
196 |
return nil if $? && $?.exitstatus != 0 |
|
197 | 191 |
blame |
192 |
rescue HgCommandAborted |
|
193 |
nil # means not found or cannot be annotated |
|
198 | 194 |
end |
199 | 195 | |
200 | 196 |
# Runs 'hg' command with the given args |
lib/redmine/scm/adapters/mercurial/hg-template-0.9.5.tmpl | ||
---|---|---|
9 | 9 |
file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n' |
10 | 10 |
tag = '<tag>{tag|escape}</tag>\n' |
11 | 11 |
header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' |
12 |
# footer="</log>" |
|
12 |
footer='</log>' |
lib/redmine/scm/adapters/mercurial/hg-template-1.0.tmpl | ||
---|---|---|
9 | 9 |
file_copy = '<path-copied copyfrom-path="{source|escape}">{name|urlescape}</path-copied>\n' |
10 | 10 |
tag = '<tag>{tag|escape}</tag>\n' |
11 | 11 |
header='<?xml version="1.0" encoding="UTF-8" ?>\n<log>\n\n' |
12 |
# footer="</log>" |
|
12 |
footer='</log>' |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | |
18 | 18 |
require 'redmine/scm/adapters/abstract_adapter' |
19 |
require 'rexml/document' |
|
19 | 20 | |
20 | 21 |
module Redmine |
21 | 22 |
module Scm |
... | ... | |
105 | 106 |
entries.sort_by_name |
106 | 107 |
end |
107 | 108 |
|
108 |
# Fetch the revisions by using a template file that |
|
109 |
# TODO: is this api necessary? |
|
110 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
111 |
revisions = Revisions.new |
|
112 |
each_revision { |e| revisions << e } |
|
113 |
revisions |
|
114 |
end |
|
115 | ||
116 |
# Iterates the revisions by using a template file that |
|
109 | 117 |
# makes Mercurial produce a xml output. |
110 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
111 |
revisions = Revisions.new |
|
112 |
cmd = "#{HG_BIN} --debug --encoding utf8 -R #{target('')} log -C --style #{shell_quote self.class.template_path}" |
|
113 |
if identifier_from && identifier_to |
|
114 |
cmd << " -r #{identifier_from.to_i}:#{identifier_to.to_i}" |
|
115 |
elsif identifier_from |
|
116 |
cmd << " -r #{identifier_from.to_i}:" |
|
118 |
def each_revision(path=nil, identifier_from=nil, identifier_to=nil, options={}) |
|
119 |
hg_args = ['log', '--debug', '-C', '--style', self.class.template_path] |
|
120 |
hg_args << '-r' << "#{hgrev(identifier_from)}:#{hgrev(identifier_to)}" |
|
121 |
hg_args << '--limit' << options[:limit] if options[:limit] |
|
122 |
hg_args << without_leading_slash(path) unless path.blank? |
|
123 |
doc = hg(*hg_args) { |io| REXML::Document.new(io.read) } |
|
124 |
# TODO: ??? HG doesn't close the XML Document... |
|
125 | ||
126 |
doc.each_element('log/logentry') do |le| |
|
127 |
cpalist = le.get_elements('paths/path-copied').map do |e| |
|
128 |
[e.text, e.attributes['copyfrom-path']] |
|
129 |
end |
|
130 |
cpmap = Hash[*cpalist.flatten] |
|
131 | ||
132 |
paths = le.get_elements('paths/path').map do |e| |
|
133 |
{:action => e.attributes['action'], :path => with_leading_slash(e.text), |
|
134 |
:from_path => (cpmap.member?(e.text) ? with_leading_slash(cpmap[e.text]) : nil), |
|
135 |
:from_revision => (cpmap.member?(e.text) ? le.attributes['revision'] : nil)} |
|
136 |
end.sort { |a, b| a[:path] <=> b[:path] } |
|
137 | ||
138 |
yield Revision.new(:identifier => le.attributes['revision'], |
|
139 |
:revision => le.attributes['revision'], |
|
140 |
:scmid => le.attributes['node'], |
|
141 |
:author => (le.elements['author'].text rescue ''), |
|
142 |
:time => Time.parse(le.elements['date'].text).localtime, |
|
143 |
:message => le.elements['msg'].text, |
|
144 |
:paths => paths) |
|
117 | 145 |
end |
118 |
cmd << " --limit #{options[:limit].to_i}" if options[:limit] |
|
119 |
cmd << " #{path}" if path |
|
120 |
shellout(cmd) do |io| |
|
121 |
begin |
|
122 |
# HG doesn't close the XML Document... |
|
123 |
doc = REXML::Document.new(io.read << "</log>") |
|
124 |
doc.elements.each("log/logentry") do |logentry| |
|
125 |
paths = [] |
|
126 |
copies = logentry.get_elements('paths/path-copied') |
|
127 |
logentry.elements.each("paths/path") do |path| |
|
128 |
# Detect if the added file is a copy |
|
129 |
if path.attributes['action'] == 'A' and c = copies.find{ |e| e.text == path.text } |
|
130 |
from_path = c.attributes['copyfrom-path'] |
|
131 |
from_rev = logentry.attributes['revision'] |
|
132 |
end |
|
133 |
paths << {:action => path.attributes['action'], |
|
134 |
:path => "/#{path.text}", |
|
135 |
:from_path => from_path ? "/#{from_path}" : nil, |
|
136 |
:from_revision => from_rev ? from_rev : nil |
|
137 |
} |
|
138 |
end |
|
139 |
paths.sort! { |x,y| x[:path] <=> y[:path] } |
|
140 |
|
|
141 |
revisions << Revision.new({:identifier => logentry.attributes['revision'], |
|
142 |
:scmid => logentry.attributes['node'], |
|
143 |
:author => (logentry.elements['author'] ? logentry.elements['author'].text : ""), |
|
144 |
:time => Time.parse(logentry.elements['date'].text).localtime, |
|
145 |
:message => logentry.elements['msg'].text, |
|
146 |
:paths => paths |
|
147 |
}) |
|
148 |
end |
|
149 |
rescue |
|
150 |
logger.debug($!) |
|
151 |
end |
|
152 |
end |
|
153 |
return nil if $? && $?.exitstatus != 0 |
|
154 |
revisions |
|
146 |
self |
|
155 | 147 |
end |
156 | 148 |
|
157 | 149 |
def diff(path, identifier_from, identifier_to=nil) |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
67 | 67 |
end |
68 | 68 |
|
69 | 69 |
def info |
70 |
cmd = "#{HG_BIN} -R #{target('')} root" |
|
71 |
root_url = nil |
|
72 |
shellout(cmd) do |io| |
|
73 |
root_url = io.gets |
|
74 |
end |
|
75 |
return nil if $? && $?.exitstatus != 0 |
|
76 |
info = Info.new({:root_url => root_url.chomp, |
|
77 |
:lastrev => revisions(nil,nil,nil,{:limit => 1}).last |
|
78 |
}) |
|
79 |
info |
|
80 |
rescue CommandFailed |
|
81 |
return nil |
|
70 |
tip = summary['tip'].first |
|
71 |
Info.new(:root_url => summary['root'].first['path'], |
|
72 |
:lastrev => Revision.new(:identifier => tip['rev'].to_i, |
|
73 |
:revision => tip['rev'], |
|
74 |
:scmid => tip['node'])) |
|
82 | 75 |
end |
76 | ||
77 |
def summary |
|
78 |
@summary ||= fetchg 'rhsummary' |
|
79 |
end |
|
80 |
private :summary |
|
83 | 81 |
|
84 | 82 |
def entries(path=nil, identifier=nil) |
85 | 83 |
path ||= '' |
... | ... | |
198 | 196 |
end |
199 | 197 |
private :hg |
200 | 198 | |
199 |
# Runs 'hg' helper, then parses output to return |
|
200 |
def fetchg(*args) |
|
201 |
# command output example: |
|
202 |
# :tip: rev node |
|
203 |
# 100 abcdef012345 |
|
204 |
# :tags: rev node name |
|
205 |
# 100 abcdef012345 tip |
|
206 |
# ... |
|
207 |
data = Hash.new { |h, k| h[k] = [] } |
|
208 |
hg(*args) do |io| |
|
209 |
key, attrs = nil, nil |
|
210 |
io.each do |line| |
|
211 |
next if line.chomp.empty? |
|
212 |
if /^:(\w+): ([\w ]+)/ =~ line |
|
213 |
key = $1 |
|
214 |
attrs = $2.split(/ /) |
|
215 |
elsif key |
|
216 |
alist = attrs.zip(line.chomp.split(/ /, attrs.size)) |
|
217 |
data[key] << Hash[*alist.flatten] |
|
218 |
end |
|
219 |
end |
|
220 |
end |
|
221 |
data |
|
222 |
end |
|
223 |
private :fetchg |
|
224 | ||
201 | 225 |
# Returns correct revision identifier |
202 | 226 |
def hgrev(identifier) |
203 | 227 |
identifier.blank? ? 'tip' : identifier.to_s |
app/models/repository/mercurial.rb | ||
---|---|---|
21 | 21 |
attr_protected :root_url |
22 | 22 |
validates_presence_of :url |
23 | 23 | |
24 |
FETCH_AT_ONCE = 100 # number of changesets to fetch at once |
|
25 | ||
24 | 26 |
def scm_adapter |
25 | 27 |
Redmine::Scm::Adapters::MercurialAdapter |
26 | 28 |
end |
... | ... | |
53 | 55 |
end |
54 | 56 | |
55 | 57 |
def fetch_changesets |
56 |
scm_info = scm.info |
|
57 |
if scm_info |
|
58 |
# latest revision found in database |
|
59 |
db_revision = latest_changeset ? latest_changeset.revision.to_i : -1 |
|
60 |
# latest revision in the repository |
|
61 |
latest_revision = scm_info.lastrev |
|
62 |
return if latest_revision.nil? |
|
63 |
scm_revision = latest_revision.identifier.to_i |
|
64 |
if db_revision < scm_revision |
|
65 |
logger.debug "Fetching changesets for repository #{url}" if logger && logger.debug? |
|
66 |
identifier_from = db_revision + 1 |
|
67 |
while (identifier_from <= scm_revision) |
|
68 |
# loads changesets by batches of 100 |
|
69 |
identifier_to = [identifier_from + 99, scm_revision].min |
|
70 |
revisions = scm.revisions('', identifier_from, identifier_to, :with_paths => true) |
|
71 |
transaction do |
|
72 |
revisions.each do |revision| |
|
73 |
changeset = Changeset.create(:repository => self, |
|
74 |
:revision => revision.identifier, |
|
75 |
:scmid => revision.scmid, |
|
76 |
:committer => revision.author, |
|
77 |
:committed_on => revision.time, |
|
78 |
:comments => revision.message) |
|
79 |
|
|
80 |
revision.paths.each do |change| |
|
81 |
changeset.create_change(change) |
|
82 |
end |
|
83 |
end |
|
84 |
end unless revisions.nil? |
|
85 |
identifier_from = identifier_to + 1 |
|
58 |
scm_rev = scm.info.lastrev.revision.to_i |
|
59 |
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 |
|
60 |
return unless db_rev < scm_rev # already up-to-date |
|
61 | ||
62 |
logger.debug "Fetching changesets for repository #{url}" if logger |
|
63 |
(db_rev + 1).step(scm_rev, FETCH_AT_ONCE) do |i| |
|
64 |
transaction do |
|
65 |
scm.each_revision('', i, [i + FETCH_AT_ONCE - 1, scm_rev].min) do |re| |
|
66 |
cs = Changeset.create(:repository => self, |
|
67 |
:revision => re.revision, |
|
68 |
:scmid => re.scmid, |
|
69 |
:committer => re.author, |
|
70 |
:committed_on => re.time, |
|
71 |
:comments => re.message) |
|
72 |
re.paths.each { |e| cs.create_change(e) } |
|
86 | 73 |
end |
87 | 74 |
end |
88 | 75 |
end |
76 |
self |
|
89 | 77 |
end |
90 | 78 |
end |
app/models/repository/mercurial.rb | ||
---|---|---|
54 | 54 |
entries |
55 | 55 |
end |
56 | 56 | |
57 |
# Returns the latest changesets for +path+ |
|
58 |
def latest_changesets(path, rev, limit=10) |
|
59 |
changesets.find(:all, :include => :user, |
|
60 |
:conditions => latest_changesets_cond(path, rev), |
|
61 |
:limit => limit) |
|
62 |
end |
|
63 | ||
64 |
def latest_changesets_cond(path, rev) |
|
65 |
cond, args = [], [] |
|
66 | ||
67 |
if last = rev ? find_changeset_by_name(rev) : nil |
|
68 |
cond << "#{Changeset.table_name}.id <= ?" |
|
69 |
args << last.id |
|
70 |
end |
|
71 | ||
72 |
unless path.blank? |
|
73 |
# TODO: there must be a better way to build sub-query |
|
74 |
cond << "EXISTS (SELECT * FROM #{Change.table_name} |
|
75 |
WHERE #{Change.table_name}.changeset_id = #{Changeset.table_name}.id |
|
76 |
AND (#{Change.table_name}.path = ? OR #{Change.table_name}.path LIKE ?))" |
|
77 |
args << path.with_leading_slash << "#{path.with_leading_slash}/%" |
|
78 |
end |
|
79 | ||
80 |
[cond.join(' AND '), *args] unless cond.empty? |
|
81 |
end |
|
82 |
private :latest_changesets_cond |
|
83 | ||
57 | 84 |
def fetch_changesets |
58 | 85 |
scm_rev = scm.info.lastrev.revision.to_i |
59 | 86 |
db_rev = latest_changeset ? latest_changeset.revision.to_i : -1 |
app/models/repository/mercurial.rb | ||
---|---|---|
64 | 64 |
def latest_changesets_cond(path, rev) |
65 | 65 |
cond, args = [], [] |
66 | 66 | |
67 |
if last = rev ? find_changeset_by_name(rev) : nil |
|
67 |
if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil
|
|
68 | 68 |
cond << "#{Changeset.table_name}.id <= ?" |
69 | 69 |
args << last.id |
70 | 70 |
end |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
74 | 74 |
:scmid => tip['node'])) |
75 | 75 |
end |
76 | 76 | |
77 |
def tags |
|
78 |
summary['tags'].map { |e| e['name'] } |
|
79 |
end |
|
80 | ||
81 |
# Returns map of {'tag' => 'nodeid', ...} |
|
82 |
def tagmap |
|
83 |
alist = summary['tags'].map { |e| e.values_at('name', 'node') } |
|
84 |
Hash[*alist.flatten] |
|
85 |
end |
|
86 | ||
77 | 87 |
def summary |
78 | 88 |
@summary ||= fetchg 'rhsummary' |
79 | 89 |
end |
app/models/repository/mercurial.rb | ||
---|---|---|
32 | 32 |
end |
33 | 33 |
|
34 | 34 |
def entries(path=nil, identifier=nil) |
35 |
entries=scm.entries(path, identifier) |
|
36 |
if entries |
|
37 |
entries.each do |entry| |
|
38 |
next unless entry.is_file? |
|
39 |
# Set the filesize unless browsing a specific revision |
|
40 |
if identifier.nil? |
|
41 |
full_path = File.join(root_url, entry.path) |
|
42 |
entry.size = File.stat(full_path).size if File.file?(full_path) |
|
43 |
end |
|
44 |
# Search the DB for the entry's last change |
|
45 |
change = changes.find(:first, :conditions => ["path = ?", scm.with_leading_slash(entry.path)], :order => "#{Changeset.table_name}.committed_on DESC") |
|
46 |
if change |
|
47 |
entry.lastrev.identifier = change.changeset.revision |
|
48 |
entry.lastrev.name = change.changeset.revision |
|
49 |
entry.lastrev.author = change.changeset.committer |
|
50 |
entry.lastrev.revision = change.revision |
|
51 |
end |
|
52 |
end |
|
53 |
end |
|
54 |
entries |
|
35 |
scm.entries(path, identifier) |
|
55 | 36 |
end |
56 | 37 | |
57 | 38 |
# Returns the latest changesets for +path+ |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
90 | 90 |
private :summary |
91 | 91 |
|
92 | 92 |
def entries(path=nil, identifier=nil) |
93 |
path ||= '' |
|
94 | 93 |
entries = Entries.new |
95 |
cmd = "#{HG_BIN} -R #{target('')} --cwd #{target('')} locate" |
|
96 |
cmd << " -r " + (identifier ? identifier.to_s : "tip") |
|
97 |
cmd << " " + shell_quote("path:#{path}") unless path.empty? |
|
98 |
shellout(cmd) do |io| |
|
99 |
io.each_line do |line| |
|
100 |
# HG uses antislashs as separator on Windows |
|
101 |
line = line.gsub(/\\/, "/") |
|
102 |
if path.empty? or e = line.gsub!(%r{^#{with_trailling_slash(path)}},'') |
|
103 |
e ||= line |
|
104 |
e = e.chomp.split(%r{[\/\\]}) |
|
105 |
entries << Entry.new({:name => e.first, |
|
106 |
:path => (path.nil? or path.empty? ? e.first : "#{with_trailling_slash(path)}#{e.first}"), |
|
107 |
:kind => (e.size > 1 ? 'dir' : 'file'), |
|
108 |
:lastrev => Revision.new |
|
109 |
}) unless e.empty? || entries.detect{|entry| entry.name == e.first} |
|
110 |
end |
|
111 |
end |
|
94 |
fetched_entries = fetchg('rhentries', '-r', hgrev(identifier), |
|
95 |
without_leading_slash(path.to_s)) |
|
96 | ||
97 |
fetched_entries['dirs'].each do |e| |
|
98 |
entries << Entry.new(:name => e['name'], |
|
99 |
:path => "#{with_trailling_slash(path)}#{e['name']}", |
|
100 |
:kind => 'dir') |
|
112 | 101 |
end |
113 |
return nil if $? && $?.exitstatus != 0 |
|
114 |
entries.sort_by_name |
|
102 | ||
103 |
fetched_entries['files'].each do |e| |
|
104 |
entries << Entry.new(:name => e['name'], |
|
105 |
:path => "#{with_trailling_slash(path)}#{e['name']}", |
|
106 |
:kind => 'file', |
|
107 |
:size => e['size'].to_i, |
|
108 |
:lastrev => Revision.new(:identifier => e['rev'].to_i, |
|
109 |
:time => Time.at(e['time'].to_i))) |
|
110 |
end |
|
111 | ||
112 |
entries |
|
113 |
rescue HgCommandAborted |
|
114 |
nil # means not found |
|
115 | 115 |
end |
116 | 116 |
|
117 | 117 |
# TODO: is this api necessary? |
app/models/repository/mercurial.rb | ||
---|---|---|
35 | 35 |
scm.entries(path, identifier) |
36 | 36 |
end |
37 | 37 | |
38 |
def branches |
|
39 |
bras = scm.branches |
|
40 |
bras.sort unless bras == %w|default| |
|
41 |
end |
|
42 | ||
38 | 43 |
# Returns the latest changesets for +path+ |
39 | 44 |
def latest_changesets(path, rev, limit=10) |
40 | 45 |
changesets.find(:all, :include => :user, |
41 |
:conditions => latest_changesets_cond(path, rev), |
|
46 |
:conditions => latest_changesets_cond(path, rev, limit),
|
|
42 | 47 |
:limit => limit) |
43 | 48 |
end |
44 | 49 | |
45 |
def latest_changesets_cond(path, rev) |
|
50 |
def latest_changesets_cond(path, rev, limit)
|
|
46 | 51 |
cond, args = [], [] |
47 | 52 | |
48 |
if last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil |
|
53 |
if scm.branchmap.member? rev |
|
54 |
# dirty hack to filter by branch. branch name should be in database. |
|
55 |
cond << "#{Changeset.table_name}.scmid IN (?)" |
|
56 |
args << scm.nodes_in_branch(rev, path, rev, 0, :limit => limit) |
|
57 |
elsif last = rev ? find_changeset_by_name(scm.tagmap[rev] || rev) : nil |
|
49 | 58 |
cond << "#{Changeset.table_name}.id <= ?" |
50 | 59 |
args << last.id |
51 | 60 |
end |
lib/redmine/scm/adapters/mercurial_adapter.rb | ||
---|---|---|
84 | 84 |
Hash[*alist.flatten] |
85 | 85 |
end |
86 | 86 | |
87 |
def branches |
|
88 |
summary['branches'].map { |e| e['name'] } |
|
89 |
end |
|
90 | ||
91 |
# Returns map of {'branch' => 'nodeid', ...} |
|
92 |
def branchmap |