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 | |