Feature #1406 » branch_support.diff
app/controllers/repositories_controller.rb | ||
---|---|---|
64 | 64 |
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' |
65 | 65 |
end |
66 | 66 |
|
67 |
def show |
|
68 |
# check if new revisions have been committed in the repository |
|
69 |
@repository.fetch_changesets if Setting.autofetch_changesets? |
|
70 |
# root entries |
|
71 |
@entries = @repository.entries('', @rev) |
|
72 |
# latest changesets |
|
73 |
@changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") |
|
74 |
show_error_not_found unless @entries || @changesets.any? |
|
75 |
end |
|
76 |
|
|
77 |
def browse |
|
67 |
def show |
|
68 |
@repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? |
|
69 | ||
78 | 70 |
@entries = @repository.entries(@path, @rev) |
79 | 71 |
if request.xhr? |
80 | 72 |
@entries ? render(:partial => 'dir_list_content') : render(:nothing => true) |
81 | 73 |
else |
82 | 74 |
show_error_not_found and return unless @entries |
75 |
@changesets = @repository.latest_changesets(@path, @rev) |
|
83 | 76 |
@properties = @repository.properties(@path, @rev) |
84 |
render :action => 'browse'
|
|
77 |
render :action => 'show'
|
|
85 | 78 |
end |
86 | 79 |
end |
80 | ||
81 |
alias_method :browse, :show |
|
87 | 82 |
|
88 | 83 |
def changes |
89 | 84 |
@entry = @repository.entry(@path, @rev) |
90 | 85 |
show_error_not_found and return unless @entry |
91 |
@changesets = @repository.changesets_for_path(@path, :limit => Setting.repository_log_display_limit.to_i)
|
|
86 |
@changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i)
|
|
92 | 87 |
@properties = @repository.properties(@path, @rev) |
93 | 88 |
end |
94 | 89 |
|
... | ... | |
135 | 130 |
end |
136 | 131 |
|
137 | 132 |
def revision |
138 |
@changeset = @repository.changesets.find_by_revision(@rev)
|
|
133 |
@changeset = @repository.changesets.find(:first, :conditions => ["revision LIKE ?", @rev + '%'])
|
|
139 | 134 |
raise ChangesetNotFound unless @changeset |
140 | 135 | |
141 | 136 |
respond_to do |format| |
... | ... | |
199 | 194 |
render_404 |
200 | 195 |
end |
201 | 196 |
|
202 |
REV_PARAM_RE = %r{^[a-f0-9]*$} |
|
203 |
|
|
204 | 197 |
def find_repository |
205 | 198 |
@project = Project.find(params[:id]) |
206 | 199 |
@repository = @project.repository |
207 | 200 |
render_404 and return false unless @repository |
208 | 201 |
@path = params[:path].join('/') unless params[:path].nil? |
209 | 202 |
@path ||= '' |
210 |
@rev = params[:rev] |
|
203 |
@rev = params[:rev].nil? || params[:rev].empty? ? @repository.default_branch : params[:rev].strip
|
|
211 | 204 |
@rev_to = params[:rev_to] |
212 |
raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) |
|
213 | 205 |
rescue ActiveRecord::RecordNotFound |
214 | 206 |
render_404 |
215 | 207 |
rescue InvalidRevisionParam |
app/models/repository.rb | ||
---|---|---|
62 | 62 |
def entries(path=nil, identifier=nil) |
63 | 63 |
scm.entries(path, identifier) |
64 | 64 |
end |
65 | ||
66 |
def branches |
|
67 |
scm.branches |
|
68 |
end |
|
69 | ||
70 |
def tags |
|
71 |
scm.tags |
|
72 |
end |
|
73 | ||
74 |
def default_branch |
|
75 |
scm.default_branch |
|
76 |
end |
|
65 | 77 |
|
66 | 78 |
def properties(path, identifier=nil) |
67 | 79 |
scm.properties(path, identifier) |
... | ... | |
92 | 104 |
def latest_changeset |
93 | 105 |
@latest_changeset ||= changesets.find(:first) |
94 | 106 |
end |
107 | ||
108 |
def latest_changesets(path,rev,limit=10) |
|
109 |
@latest_changesets ||= changesets.find(:all, limit, :order => "committed_on DESC") |
|
110 |
end |
|
95 | 111 |
|
96 | 112 |
def scan_changesets_for_issue_ids |
97 | 113 |
self.changesets.each(&:scan_comment_for_issue_ids) |
98 | 114 |
end |
99 |
|
|
115 | ||
100 | 116 |
# Returns an array of committers usernames and associated user_id |
101 | 117 |
def committers |
102 | 118 |
@committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}") |
app/models/repository/git.rb | ||
---|---|---|
29 | 29 |
'Git' |
30 | 30 |
end |
31 | 31 | |
32 |
def branches |
|
33 |
scm.branches |
|
34 |
end |
|
35 | ||
36 |
def tags |
|
37 |
scm.tags |
|
38 |
end |
|
39 | ||
32 | 40 |
def changesets_for_path(path, options={}) |
33 |
Change.find(:all, :include => {:changeset => :user}, |
|
34 |
:conditions => ["repository_id = ? AND path = ?", id, path], |
|
35 |
:order => "committed_on DESC, #{Changeset.table_name}.revision DESC", |
|
36 |
:limit => options[:limit]).collect(&:changeset) |
|
41 |
Change.find( |
|
42 |
:all, |
|
43 |
:include => {:changeset => :user}, |
|
44 |
:conditions => ["repository_id = ? AND path = ?", id, path], |
|
45 |
:order => "committed_on DESC, #{Changeset.table_name}.revision DESC", |
|
46 |
:limit => options[:limit] |
|
47 |
).collect(&:changeset) |
|
37 | 48 |
end |
38 | 49 | |
50 |
# With SCM's that have a sequential commit numbering, redmine is able to be |
|
51 |
# clever and only fetch changesets going forward from the most recent one |
|
52 |
# it knows about. However, with git, you never know if people have merged |
|
53 |
# commits into the middle of the repository history, so we always have to |
|
54 |
# parse the entire log. |
|
39 | 55 |
def fetch_changesets |
40 |
scm_info = scm.info |
|
41 |
if scm_info |
|
42 |
# latest revision found in database |
|
43 |
db_revision = latest_changeset ? latest_changeset.revision : nil |
|
44 |
# latest revision in the repository |
|
45 |
scm_revision = scm_info.lastrev.scmid |
|
56 |
# Save ourselves an expensive operation if we're already up to date |
|
57 |
return if scm.num_revisions == changesets.count |
|
58 | ||
59 |
revisions = scm.revisions('', nil, nil, :all => true) |
|
60 |
return if revisions.nil? || revisions.empty? |
|
61 | ||
62 |
# Find revisions that redmine knows about already |
|
63 |
existing_revisions = changesets.find(:all).map!{|c| c.scmid} |
|
64 | ||
65 |
# Clean out revisions that are no longer in git |
|
66 |
Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", revisions.map{|r| r.scmid}, self.id]) |
|
67 | ||
68 |
# Subtract revisions that redmine already knows about |
|
69 |
revisions.reject!{|r| existing_revisions.include?(r.scmid)} |
|
70 | ||
71 |
# Save the remaining ones to the database |
|
72 |
revisions.each{|r| r.save(self)} unless revisions.nil? |
|
73 |
end |
|
74 | ||
75 |
def latest_changesets(path,rev,limit=10) |
|
76 |
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) |
|
77 |
return [] if revisions.nil? || revisions.empty? |
|
46 | 78 | |
47 |
unless changesets.find_by_scmid(scm_revision) |
|
48 |
scm.revisions('', db_revision, nil, :reverse => true) do |revision| |
|
49 |
if changesets.find_by_scmid(revision.scmid.to_s).nil? |
|
50 |
transaction do |
|
51 |
changeset = Changeset.create!(:repository => self, |
|
52 |
:revision => revision.identifier, |
|
53 |
:scmid => revision.scmid, |
|
54 |
:committer => revision.author, |
|
55 |
:committed_on => revision.time, |
|
56 |
:comments => revision.message) |
|
57 |
|
|
58 |
revision.paths.each do |change| |
|
59 |
Change.create!(:changeset => changeset, |
|
60 |
:action => change[:action], |
|
61 |
:path => change[:path], |
|
62 |
:from_path => change[:from_path], |
|
63 |
:from_revision => change[:from_revision]) |
|
64 |
end |
|
65 |
end |
|
66 |
end |
|
67 |
end |
|
68 |
end |
|
69 |
end |
|
79 |
changesets.find( |
|
80 |
:all, |
|
81 |
:conditions => [ |
|
82 |
"scmid IN (?)", |
|
83 |
revisions.map!{|c| c.scmid} |
|
84 |
], |
|
85 |
:order => 'committed_on DESC' |
|
86 |
) |
|
70 | 87 |
end |
71 | 88 |
end |
app/views/repositories/_breadcrumbs.rhtml | ||
---|---|---|
1 |
<%= link_to 'root', :action => 'show', :id => @project, :path => '', :rev => @rev %> |
|
2 |
<% |
|
3 |
dirs = path.split('/') |
|
4 |
if 'file' == kind |
|
5 |
filename = dirs.pop |
|
6 |
end |
|
7 |
link_path = '' |
|
8 |
dirs.each do |dir| |
|
9 |
next if dir.blank? |
|
10 |
link_path << '/' unless link_path.empty? |
|
11 |
link_path << "#{dir}" |
|
12 |
%> |
|
13 |
/ <%= link_to h(dir), :action => 'show', :id => @project, :path => to_path_param(link_path), :rev => @rev %> |
|
14 |
<% end %> |
|
15 |
<% if filename %> |
|
16 |
/ <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> |
|
17 |
<% end %> |
|
18 | ||
19 |
<%= "@ #{revision}" if revision %> |
|
20 | ||
21 |
<% html_title(with_leading_slash(path)) -%> |
app/views/repositories/_dir_list_content.rhtml | ||
---|---|---|
4 | 4 |
<tr id="<%= tr_id %>" class="<%= params[:parent_id] %> entry <%= entry.kind %>"> |
5 | 5 |
<td style="padding-left: <%=18 * depth%>px;" class="filename"> |
6 | 6 |
<% if entry.is_dir? %> |
7 |
<span class="expander" onclick="<%= remote_function :url => {:action => 'browse', :id => @project, :path => to_path_param(entry.path), :rev => @rev, :depth => (depth + 1), :parent_id => tr_id},
|
|
7 |
<span class="expander" onclick="<%= remote_function :url => {:action => 'show', :id => @project, :path => to_path_param(entry.path), :rev => @rev, :depth => (depth + 1), :parent_id => tr_id},
|
|
8 | 8 |
:method => :get, |
9 | 9 |
:update => { :success => tr_id }, |
10 | 10 |
:position => :after, |
... | ... | |
12 | 12 |
:condition => "scmEntryClick('#{tr_id}')"%>"> </span> |
13 | 13 |
<% end %> |
14 | 14 |
<%= link_to h(entry.name), |
15 |
{:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev},
|
|
15 |
{:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev},
|
|
16 | 16 |
:class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(entry.name)}")%> |
17 | 17 |
</td> |
18 | 18 |
<td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td> |
app/views/repositories/_navigation.rhtml | ||
---|---|---|
1 |
<%= link_to 'root', :action => 'browse', :id => @project, :path => '', :rev => @rev %> |
|
2 |
<% |
|
3 |
dirs = path.split('/') |
|
4 |
if 'file' == kind |
|
5 |
filename = dirs.pop |
|
6 |
end |
|
7 |
link_path = '' |
|
8 |
dirs.each do |dir| |
|
9 |
next if dir.blank? |
|
10 |
link_path << '/' unless link_path.empty? |
|
11 |
link_path << "#{dir}" |
|
12 |
%> |
|
13 |
/ <%= link_to h(dir), :action => 'browse', :id => @project, :path => to_path_param(link_path), :rev => @rev %> |
|
14 |
<% end %> |
|
15 |
<% if filename %> |
|
16 |
/ <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> |
|
1 |
<% content_for :header_tags do %> |
|
2 |
<%= javascript_include_tag 'repository_navigation' %> |
|
17 | 3 |
<% end %> |
18 | 4 | |
19 |
<%= "@ #{revision}" if revision %> |
|
5 |
<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> |
|
6 | ||
7 |
<% form_tag({:action => controller.action_name, :id => @project, :path => @path, :rev => ''}, {:method => :get, :id => 'revision_selector'}) do -%> |
|
8 |
<!-- Branches Dropdown --> |
|
9 |
<% if !@repository.branches.nil? && @repository.branches.length > 0 -%> |
|
10 |
| <%= l(:label_branch) %>: |
|
11 |
<%= select_tag :branch, options_for_select([''] + @repository.branches,@rev), :id => 'branch' %> |
|
12 |
<% end -%> |
|
13 | ||
14 |
<% if !@repository.tags.nil? && @repository.tags.length > 0 -%> |
|
15 |
| <%= l(:label_tag) %>: |
|
16 |
<%= select_tag :tag, options_for_select([''] + @repository.tags,@rev), :id => 'tag' %> |
|
17 |
<% end -%> |
|
20 | 18 | |
21 |
<% html_title(with_leading_slash(path)) -%> |
|
19 |
| <%= l(:label_revision) %>: |
|
20 |
<%= text_field_tag 'rev', @rev, :size => 8 %> |
|
21 |
<% end -%> |
app/views/repositories/annotate.rhtml | ||
---|---|---|
1 |
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> |
|
1 |
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> |
|
2 | ||
3 |
<div class="contextual"> |
|
4 |
<%= render :partial => 'navigation' %> |
|
5 |
</div> |
|
6 | ||
7 |
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> |
|
2 | 8 | |
3 | 9 |
<p><%= render :partial => 'link_to_functions' %></p> |
4 | 10 |
app/views/repositories/browse.rhtml | ||
---|---|---|
1 | 1 |
<div class="contextual"> |
2 |
<% form_tag({}, :method => :get) do %> |
|
3 |
<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> |
|
4 |
<% end %> |
|
2 |
<%= render :partial => 'navigation' %> |
|
5 | 3 |
</div> |
6 | 4 | |
7 |
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2>
|
|
5 |
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2>
|
|
8 | 6 | |
9 | 7 |
<%= render :partial => 'dir_list' %> |
10 | 8 |
<%= render_properties(@properties) %> |
app/views/repositories/changes.rhtml | ||
---|---|---|
1 |
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> |
|
1 |
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> |
|
2 | ||
3 |
<div class="contextual"> |
|
4 |
<%= render :partial => 'navigation' %> |
|
5 |
</div> |
|
6 | ||
7 |
<h2> |
|
8 |
<%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %> |
|
9 |
</h2> |
|
2 | 10 | |
3 | 11 |
<p><%= render :partial => 'link_to_functions' %></p> |
4 | 12 |
app/views/repositories/entry.rhtml | ||
---|---|---|
1 |
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> |
|
1 |
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> |
|
2 | ||
3 |
<div class="contextual"> |
|
4 |
<%= render :partial => 'navigation' %> |
|
5 |
</div> |
|
6 | ||
7 |
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> |
|
2 | 8 | |
3 | 9 |
<p><%= render :partial => 'link_to_functions' %></p> |
4 | 10 |
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, :size => 5 %>
|
|
17 |
<%= text_field_tag 'rev', @rev[0,8], :size => 8 %>
|
|
18 | 18 |
<%= submit_tag 'OK', :name => nil %> |
19 | 19 |
<% end %> |
20 | 20 |
</div> |
app/views/repositories/revisions.rhtml | ||
---|---|---|
1 | 1 |
<div class="contextual"> |
2 | 2 |
<% form_tag({:action => 'revision', :id => @project}) do %> |
3 |
<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %>
|
|
3 |
<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %>
|
|
4 | 4 |
<%= submit_tag 'OK' %> |
5 | 5 |
<% end %> |
6 | 6 |
</div> |
app/views/repositories/show.rhtml | ||
---|---|---|
1 |
<div class="contextual"> |
|
2 | 1 |
<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> |
3 |
<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> |
|
4 | 2 | |
5 |
<% if !@entries.nil? && authorize_for('repositories', 'browse') -%> |
|
6 |
<% form_tag({:action => 'browse', :id => @project}, :method => :get) do -%> |
|
7 |
| <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> |
|
8 |
<% end -%> |
|
9 |
<% end -%> |
|
3 |
<div class="contextual"> |
|
4 |
<%= render :partial => 'navigation' %> |
|
10 | 5 |
</div> |
11 | 6 | |
12 |
<h2><%= l(:label_repository) %> (<%= @repository.scm_name %>)</h2>
|
|
7 |
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2>
|
|
13 | 8 | |
14 | 9 |
<% if !@entries.nil? && authorize_for('repositories', 'browse') %> |
15 | 10 |
<%= render :partial => 'dir_list' %> |
... | ... | |
18 | 13 |
<% if !@changesets.empty? && authorize_for('repositories', 'revisions') %> |
19 | 14 |
<h3><%= l(:label_latest_revision_plural) %></h3> |
20 | 15 |
<%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :revisions => @changesets, :entry => nil }%> |
21 |
<p><%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %></p> |
|
16 |
<p><%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project %></p>
|
|
22 | 17 |
<% content_for :header_tags do %> |
23 | 18 |
<%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :action => 'revisions', :id => @project, :page => nil, :key => User.current.rss_key})) %> |
24 | 19 |
<% end %> |
config/locales/bg.yml | ||
---|---|---|
798 | 798 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
799 | 799 |
permission_add_project: Create project |
800 | 800 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
801 |
label_view_all_revisions: View all revisions |
|
802 |
label_tag: Tag |
|
803 |
label_branch: Branch |
config/locales/bs.yml | ||
---|---|---|
831 | 831 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
832 | 832 |
permission_add_project: Create project |
833 | 833 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
834 |
label_view_all_revisions: View all revisions |
|
835 |
label_tag: Tag |
|
836 |
label_branch: Branch |
config/locales/ca.yml | ||
---|---|---|
801 | 801 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
802 | 802 |
permission_add_project: Create project |
803 | 803 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
804 |
label_view_all_revisions: View all revisions |
|
805 |
label_tag: Tag |
|
806 |
label_branch: Branch |
config/locales/cs.yml | ||
---|---|---|
804 | 804 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
805 | 805 |
permission_add_project: Create project |
806 | 806 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
807 |
label_view_all_revisions: View all revisions |
|
808 |
label_tag: Tag |
|
809 |
label_branch: Branch |
config/locales/da.yml | ||
---|---|---|
831 | 831 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
832 | 832 |
permission_add_project: Create project |
833 | 833 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
834 |
label_view_all_revisions: View all revisions |
|
835 |
label_tag: Tag |
|
836 |
label_branch: Branch |
config/locales/de.yml | ||
---|---|---|
830 | 830 |
mail_body_wiki_content_updated: "Die Wiki-Seite '{{page}}' wurde von {{author}} aktualisiert." |
831 | 831 |
permission_add_project: Erstelle Projekt |
832 | 832 |
setting_new_project_user_role_id: Rolle einem Nicht-Administrator zugeordnet, welcher ein Projekt erstellt |
833 |
label_view_all_revisions: View all revisions |
|
834 |
label_tag: Tag |
|
835 |
label_branch: Branch |
config/locales/en.yml | ||
---|---|---|
543 | 543 |
label_browse: Browse |
544 | 544 |
label_modification: "{{count}} change" |
545 | 545 |
label_modification_plural: "{{count}} changes" |
546 |
label_branch: Branch |
|
547 |
label_tag: Tag |
|
546 | 548 |
label_revision: Revision |
547 | 549 |
label_revision_plural: Revisions |
548 | 550 |
label_associated_revisions: Associated revisions |
... | ... | |
554 | 556 |
label_latest_revision: Latest revision |
555 | 557 |
label_latest_revision_plural: Latest revisions |
556 | 558 |
label_view_revisions: View revisions |
559 |
label_view_all_revisions: View all revisions |
|
557 | 560 |
label_max_size: Maximum size |
558 | 561 |
label_sort_highest: Move to top |
559 | 562 |
label_sort_higher: Move up |
config/locales/es.yml | ||
---|---|---|
851 | 851 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
852 | 852 |
permission_add_project: Create project |
853 | 853 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
854 |
label_view_all_revisions: View all revisions |
|
855 |
label_tag: Tag |
|
856 |
label_branch: Branch |
config/locales/fi.yml | ||
---|---|---|
841 | 841 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
842 | 842 |
permission_add_project: Create project |
843 | 843 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
844 |
label_view_all_revisions: View all revisions |
|
845 |
label_tag: Tag |
|
846 |
label_branch: Branch |
config/locales/fr.yml | ||
---|---|---|
832 | 832 |
enumeration_doc_categories: Catégories des documents |
833 | 833 |
enumeration_activities: Activités (suivi du temps) |
834 | 834 |
label_greater_or_equal: ">=" |
835 |
label_less_or_equal: <= |
|
835 |
label_less_or_equal: "<=" |
|
836 |
label_view_all_revisions: View all revisions |
|
837 |
label_tag: Tag |
|
838 |
label_branch: Branch |
config/locales/gl.yml | ||
---|---|---|
830 | 830 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
831 | 831 |
permission_add_project: Create project |
832 | 832 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
833 |
label_view_all_revisions: View all revisions |
|
834 |
label_tag: Tag |
|
835 |
label_branch: Branch |
config/locales/he.yml | ||
---|---|---|
813 | 813 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
814 | 814 |
permission_add_project: Create project |
815 | 815 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
816 |
label_view_all_revisions: View all revisions |
|
817 |
label_tag: Tag |
|
818 |
label_branch: Branch |
config/locales/hu.yml | ||
---|---|---|
836 | 836 |
mail_body_wiki_content_updated: A '{{page}}' wiki oldalt {{author}} frissítette. |
837 | 837 |
permission_add_project: Projekt létrehozása |
838 | 838 |
setting_new_project_user_role_id: Projekt létrehozási jog nem adminisztrátor felhasználóknak |
839 |
label_view_all_revisions: View all revisions |
|
840 |
label_tag: Tag |
|
841 |
label_branch: Branch |
config/locales/it.yml | ||
---|---|---|
816 | 816 |
mail_body_wiki_content_updated: La pagina '{{page}}' wiki è stata aggiornata da{{author}}. |
817 | 817 |
permission_add_project: Crea progetto |
818 | 818 |
setting_new_project_user_role_id: Ruolo assegnato agli utenti non amministratori che creano un progetto |
819 |
label_view_all_revisions: View all revisions |
|
820 |
label_tag: Tag |
|
821 |
label_branch: Branch |
config/locales/ja.yml | ||
---|---|---|
838 | 838 |
enumeration_issue_priorities: チケットの優先度 |
839 | 839 |
enumeration_doc_categories: 文書カテゴリ |
840 | 840 |
enumeration_activities: 作業分類 (時間トラッキング) |
841 |
label_view_all_revisions: View all revisions |
|
842 |
label_tag: Tag |
|
843 |
label_branch: Branch |
config/locales/ko.yml | ||
---|---|---|
860 | 860 |
mail_body_wiki_content_updated: 위키페이지 '{{page}}' 가 {{author}}에 의하여 고쳐젔습니다. |
861 | 861 |
permission_add_project: 프로젝트 생성 |
862 | 862 |
setting_new_project_user_role_id: 프로젝트를 만든 일반사용자에게 주어질 권한 |
863 |
label_view_all_revisions: View all revisions |
|
864 |
label_tag: Tag |
|
865 |
label_branch: Branch |
config/locales/lt.yml | ||
---|---|---|
841 | 841 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
842 | 842 |
permission_add_project: Create project |
843 | 843 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
844 |
label_view_all_revisions: View all revisions |
|
845 |
label_tag: Tag |
|
846 |
label_branch: Branch |
config/locales/nl.yml | ||
---|---|---|
786 | 786 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
787 | 787 |
permission_add_project: Create project |
788 | 788 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
789 |
label_view_all_revisions: View all revisions |
|
790 |
label_tag: Tag |
|
791 |
label_branch: Branch |
config/locales/no.yml | ||
---|---|---|
803 | 803 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
804 | 804 |
permission_add_project: Create project |
805 | 805 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
806 |
label_view_all_revisions: View all revisions |
|
807 |
label_tag: Tag |
|
808 |
label_branch: Branch |
config/locales/pl.yml | ||
---|---|---|
834 | 834 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
835 | 835 |
permission_add_project: Create project |
836 | 836 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
837 |
label_view_all_revisions: View all revisions |
|
838 |
label_tag: Tag |
|
839 |
label_branch: Branch |
config/locales/pt-BR.yml | ||
---|---|---|
836 | 836 |
mail_body_wiki_content_updated: A página wiki '{{page}}' foi atualizada por {{author}}. |
837 | 837 |
permission_add_project: Criar projeto |
838 | 838 |
setting_new_project_user_role_id: Papel dado a um usuário não administrador que crie um projeto |
839 |
label_view_all_revisions: View all revisions |
|
840 |
label_tag: Tag |
|
841 |
label_branch: Branch |
config/locales/pt.yml | ||
---|---|---|
822 | 822 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
823 | 823 |
permission_add_project: Create project |
824 | 824 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
825 |
label_view_all_revisions: View all revisions |
|
826 |
label_tag: Tag |
|
827 |
label_branch: Branch |
config/locales/ro.yml | ||
---|---|---|
801 | 801 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
802 | 802 |
permission_add_project: Create project |
803 | 803 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
804 |
label_view_all_revisions: View all revisions |
|
805 |
label_tag: Tag |
|
806 |
label_branch: Branch |
config/locales/ru.yml | ||
---|---|---|
928 | 928 |
mail_body_wiki_content_updated: "{{author}} обновил(а) wiki-страницу '{{page}}'." |
929 | 929 |
permission_add_project: Создание проекта |
930 | 930 |
setting_new_project_user_role_id: Роль, назначаемая пользователю, создавшему проект |
931 |
label_view_all_revisions: View all revisions |
|
932 |
label_tag: Tag |
|
933 |
label_branch: Branch |
config/locales/sk.yml | ||
---|---|---|
802 | 802 |
label_wiki_content_updated: Wiki stránka aktualizovaná |
803 | 803 |
mail_body_wiki_content_updated: Wiki stránka '{{page}}' bola aktualizovaná užívateľom {{author}}. |
804 | 804 |
setting_repositories_encodings: Kódovanie repozitára |
805 |
setting_new_project_user_role_id: Rola dána non-admin užívateľovi, ktorý vytvorí projekt |
|
805 |
setting_new_project_user_role_id: Rola dána non-admin užívateľovi, ktorý vytvorí projekt |
|
806 |
label_view_all_revisions: View all revisions |
|
807 |
label_tag: Tag |
|
808 |
label_branch: Branch |
config/locales/sl.yml | ||
---|---|---|
800 | 800 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
801 | 801 |
permission_add_project: Create project |
802 | 802 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
803 |
label_view_all_revisions: View all revisions |
|
804 |
label_tag: Tag |
|
805 |
label_branch: Branch |
config/locales/sr.yml | ||
---|---|---|
824 | 824 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
825 | 825 |
permission_add_project: Create project |
826 | 826 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
827 |
label_view_all_revisions: View all revisions |
|
828 |
label_tag: Tag |
|
829 |
label_branch: Branch |
config/locales/sv.yml | ||
---|---|---|
858 | 858 |
enumeration_issue_priorities: Ärendeprioriteter |
859 | 859 |
enumeration_doc_categories: Dokumentkategorier |
860 | 860 |
enumeration_activities: Aktiviteter (tidsuppföljning) |
861 |
label_view_all_revisions: View all revisions |
|
862 |
label_tag: Tag |
|
863 |
label_branch: Branch |
config/locales/th.yml | ||
---|---|---|
801 | 801 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
802 | 802 |
permission_add_project: Create project |
803 | 803 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
804 |
label_view_all_revisions: View all revisions |
|
805 |
label_tag: Tag |
|
806 |
label_branch: Branch |
config/locales/tr.yml | ||
---|---|---|
837 | 837 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
838 | 838 |
permission_add_project: Create project |
839 | 839 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
840 |
label_view_all_revisions: View all revisions |
|
841 |
label_tag: Tag |
|
842 |
label_branch: Branch |
config/locales/uk.yml | ||
---|---|---|
800 | 800 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
801 | 801 |
permission_add_project: Create project |
802 | 802 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
803 |
label_view_all_revisions: View all revisions |
|
804 |
label_tag: Tag |
|
805 |
label_branch: Branch |
config/locales/vi.yml | ||
---|---|---|
870 | 870 |
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}. |
871 | 871 |
permission_add_project: Create project |
872 | 872 |
setting_new_project_user_role_id: Role given to a non-admin user who creates a project |
873 |
label_view_all_revisions: View all revisions |
|
874 |
label_tag: Tag |
|
875 |
label_branch: Branch |
config/locales/zh-TW.yml | ||
---|---|---|
908 | 908 |
enumeration_issue_priorities: 項目優先權 |
909 | 909 |
enumeration_doc_categories: 文件分類 |
910 | 910 |
enumeration_activities: 活動 (時間追蹤) |
911 |
label_view_all_revisions: View all revisions |
|
912 |
label_tag: Tag |
|
913 |
label_branch: Branch |
config/locales/zh.yml | ||
---|---|---|
833 | 833 |
enumeration_issue_priorities: 问题优先级 |
834 | 834 |
enumeration_doc_categories: 文档类别 |
835 | 835 |
enumeration_activities: 活动(时间跟踪) |
836 |
label_view_all_revisions: View all revisions |
|
837 |
label_tag: Tag |
|
838 |
label_branch: Branch |
config/routes.rb | ||
---|---|---|
218 | 218 |
repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision' |
219 | 219 |
repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff' |
220 | 220 |
repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff' |
221 |
repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path' |
|
221 |
repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
|
|
222 | 222 |
repository_views.connect 'projects/:id/repository/:action/*path' |
223 | 223 |
end |
224 | 224 |
|
lib/diff.rb | ||
---|---|---|
1 |
class Diff |
|
1 |
module RedmineDiff |
|
2 |
class Diff |
|
2 | 3 | |
3 |
VERSION = 0.3 |
|
4 |
VERSION = 0.3
|
|
4 | 5 | |
5 |
def Diff.lcs(a, b) |
|
6 |
astart = 0 |
|
7 |
bstart = 0 |
|
8 |
afinish = a.length-1 |
|
9 |
bfinish = b.length-1 |
|
10 |
mvector = [] |
|
11 |
|
|
12 |
# First we prune off any common elements at the beginning |
|
13 |
while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart]) |
|
14 |
mvector[astart] = bstart |
|
15 |
astart += 1 |
|
16 |
bstart += 1 |
|
17 |
end |
|
18 |
|
|
19 |
# now the end |
|
20 |
while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish]) |
|
21 |
mvector[afinish] = bfinish |
|
22 |
afinish -= 1 |
|
23 |
bfinish -= 1 |
|
24 |
end |
|
6 |
def Diff.lcs(a, b)
|
|
7 |
astart = 0
|
|
8 |
bstart = 0
|
|
9 |
afinish = a.length-1
|
|
10 |
bfinish = b.length-1
|
|
11 |
mvector = []
|
|
12 |
|
|
13 |
# First we prune off any common elements at the beginning
|
|
14 |
while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart])
|
|
15 |
mvector[astart] = bstart
|
|
16 |
astart += 1
|
|
17 |
bstart += 1
|
|
18 |
end
|
|
19 |
|
|
20 |
# now the end
|
|
21 |
while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish])
|
|
22 |
mvector[afinish] = bfinish
|
|
23 |
afinish -= 1
|
|
24 |
bfinish -= 1
|
|
25 |
end
|
|
25 | 26 | |
26 |
bmatches = b.reverse_hash(bstart..bfinish) |
|
27 |
thresh = [] |
|
28 |
links = [] |
|
29 |
|
|
30 |
(astart..afinish).each { |aindex| |
|
31 |
aelem = a[aindex] |
|
32 |
next unless bmatches.has_key? aelem |
|
33 |
k = nil |
|
34 |
bmatches[aelem].reverse.each { |bindex| |
|
35 |
if k && (thresh[k] > bindex) && (thresh[k-1] < bindex) |
|
36 |
thresh[k] = bindex |
|
37 |
else |
|
38 |
k = thresh.replacenextlarger(bindex, k) |
|
39 |
end |
|
40 |
links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k |
|
27 |
bmatches = b.reverse_hash(bstart..bfinish) |
|
28 |
thresh = [] |
|
29 |
links = [] |
|
30 |
|
|
31 |
(astart..afinish).each { |aindex| |
|
32 |
aelem = a[aindex] |
|
33 |
next unless bmatches.has_key? aelem |
|
34 |
k = nil |
|
35 |
bmatches[aelem].reverse.each { |bindex| |
|
36 |
if k && (thresh[k] > bindex) && (thresh[k-1] < bindex) |
|
37 |
thresh[k] = bindex |
|
38 |
else |
|
39 |
k = thresh.replacenextlarger(bindex, k) |
|
40 |
end |
|
41 |
links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k |
|
42 |
} |
|
41 | 43 |
} |
42 |
} |
|
43 | 44 | |
44 |
if !thresh.empty? |
|
45 |
link = links[thresh.length-1] |
|
46 |
while link |
|
47 |
mvector[link[1]] = link[2] |
|
48 |
link = link[0] |
|
45 |
if !thresh.empty? |
|
46 |
link = links[thresh.length-1] |
|
47 |
while link |
|
48 |
mvector[link[1]] = link[2] |
|
49 |
link = link[0] |
|
50 |
end |
|
49 | 51 |
end |
50 |
end |
|
51 | 52 | |
52 |
return mvector |
|
53 |
end |
|
54 | ||
55 |
def makediff(a, b) |
|
56 |
mvector = Diff.lcs(a, b) |
|
57 |
ai = bi = 0 |
|
58 |
while ai < mvector.length |
|
59 |
bline = mvector[ai] |
|
60 |
if bline |
|
61 |
while bi < bline |
|
62 |
discardb(bi, b[bi]) |
|
63 |
bi += 1 |
|
64 |
end |
|
65 |
match(ai, bi) |
|
66 |
bi += 1 |
|
67 |
else |
|
68 |
discarda(ai, a[ai]) |
|
69 |
end |
|
70 |
ai += 1 |
|
71 |
end |
|
72 |
while ai < a.length |
|
73 |
discarda(ai, a[ai]) |
|
74 |
ai += 1 |
|
53 |
return mvector |
|
75 | 54 |
end |
76 |
while bi < b.length |
|
55 | ||
56 |
def makediff(a, b) |
|
57 |
mvector = Diff.lcs(a, b) |
|
58 |
ai = bi = 0 |
|
59 |
while ai < mvector.length |
|
60 |
bline = mvector[ai] |
|
61 |
if bline |
|
62 |
while bi < bline |
|
77 | 63 |
discardb(bi, b[bi]) |
78 | 64 |
bi += 1 |
79 | 65 |
end |
80 | 66 |
match(ai, bi) |
81 |
1 |
|
82 |
end |
|
83 | ||
84 |
def compactdiffs |
|
85 |
diffs = [] |
|
86 |
@diffs.each { |df| |
|
87 |
i = 0 |
|
88 |
curdiff = [] |
|
89 |
while i < df.length |
|
90 |
whot = df[i][0] |
|
91 |
s = @isstring ? df[i][2].chr : [df[i][2]] |
|
92 |
p = df[i][1] |
|
93 |
last = df[i][1] |
|
94 |
i += 1 |
|
95 |
while df[i] && df[i][0] == whot && df[i][1] == last+1 |
|
96 |
s << df[i][2] |
|
97 |
last = df[i][1] |
|
98 |
i += 1 |
|
99 |
end |
|
100 |
curdiff.push [whot, p, s] |
|
67 |
bi += 1 |
|
68 |
else |
|
69 |
discarda(ai, a[ai]) |
|
70 |
end |
|
71 |
ai += 1 |
|
101 | 72 |
end |
102 |
diffs.push curdiff |
|
103 |
} |
|
104 |
return diffs |
|
105 |
end |
|
73 |
while ai < a.length |
|
74 |
discarda(ai, a[ai]) |
|
75 |
ai += 1 |
|
76 |
end |
|
77 |
while bi < b.length |
|
78 |
discardb(bi, b[bi]) |
|
79 |
bi += 1 |
|
80 |
end |
|
81 |
match(ai, bi) |
|
82 |
1 |
|
83 |
end |
|
106 | 84 | |
107 |
attr_reader :diffs, :difftype |
|
85 |
def compactdiffs |
|
86 |
diffs = [] |
|
87 |
@diffs.each { |df| |
|
88 |
i = 0 |
|
89 |
curdiff = [] |
|
90 |
while i < df.length |
|
91 |
whot = df[i][0] |
|
92 |
s = @isstring ? df[i][2].chr : [df[i][2]] |
|
93 |
p = df[i][1] |
|
94 |
last = df[i][1] |
|
95 |
i += 1 |
|
96 |
while df[i] && df[i][0] == whot && df[i][1] == last+1 |
|
97 |
s << df[i][2] |
|
98 |
last = df[i][1] |
|
99 |
i += 1 |
|
100 |
end |
|
101 |
curdiff.push [whot, p, s] |
|
102 |
end |
|
103 |
diffs.push curdiff |
|
104 |
} |
|
105 |
return diffs |
|
106 |
end |
|
108 | 107 | |
109 |
def initialize(diffs_or_a, b = nil, isstring = nil) |
|
110 |
if b.nil? |
|
111 |
@diffs = diffs_or_a |
|
112 |
@isstring = isstring |
|
113 |
else |
|
114 |
@diffs = [] |
|
108 |
attr_reader :diffs, :difftype |
|
109 | ||
110 |
def initialize(diffs_or_a, b = nil, isstring = nil) |
|
111 |
if b.nil? |
|
112 |
@diffs = diffs_or_a |
|
113 |
@isstring = isstring |
|
114 |
else |
|
115 |
@diffs = [] |
|
116 |
@curdiffs = [] |
|
117 |
makediff(diffs_or_a, b) |
|
118 |
@difftype = diffs_or_a.class |
|
119 |
end |
|
120 |
end |
|
121 |
|
|
122 |
def match(ai, bi) |
|
123 |
@diffs.push @curdiffs unless @curdiffs.empty? |
|
115 | 124 |
@curdiffs = [] |
116 |
makediff(diffs_or_a, b) |
|
117 |
@difftype = diffs_or_a.class |
|
118 | 125 |
end |
119 |
end |
|
120 |
|
|
121 |
def match(ai, bi) |
|
122 |
@diffs.push @curdiffs unless @curdiffs.empty? |
|
123 |
@curdiffs = [] |
|
124 |
end |
|
125 | 126 | |
126 |
def discarda(i, elem) |
|
127 |
@curdiffs.push ['-', i, elem] |
|
128 |
end |
|
127 |
def discarda(i, elem)
|
|
128 |
@curdiffs.push ['-', i, elem]
|
|
129 |
end
|
|
129 | 130 | |
130 |
def discardb(i, elem) |
|
131 |
@curdiffs.push ['+', i, elem] |
|
132 |
end |
|
131 |
def discardb(i, elem)
|
|
132 |
@curdiffs.push ['+', i, elem]
|
|
133 |
end
|
|
133 | 134 | |
134 |
def compact |
|
135 |
return Diff.new(compactdiffs) |
|
136 |
end |
|
135 |
def compact
|
|
136 |
return Diff.new(compactdiffs)
|
|
137 |
end
|
|
137 | 138 | |
138 |
def compact! |
|
139 |
@diffs = compactdiffs |
|
140 |
end |
|
139 |
def compact!
|
|
140 |
@diffs = compactdiffs
|
|
141 |
end
|
|
141 | 142 | |
142 |
def inspect |
|
143 |
@diffs.inspect |
|
144 |
end |
|
143 |
def inspect
|
|
144 |
@diffs.inspect
|
|
145 |
end
|
|
145 | 146 | |
147 |
end |
|
146 | 148 |
end |
147 | 149 | |
148 | 150 |
module Diffable |
149 | 151 |
def diff(b) |
150 |
Diff.new(self, b) |
|
152 |
RedmineDiff::Diff.new(self, b)
|
|
151 | 153 |
end |
152 | 154 | |
153 | 155 |
# Create a hash that maps elements of the array to arrays of indices |
... | ... | |
158 | 160 |
range.each { |i| |
159 | 161 |
elem = self[i] |
160 | 162 |
if revmap.has_key? elem |
161 |
revmap[elem].push i
|
|
163 |
revmap[elem].push i
|
|
162 | 164 |
else |
163 |
revmap[elem] = [i]
|
|
165 |
revmap[elem] = [i]
|
|
164 | 166 |
end |
165 | 167 |
} |
166 | 168 |
return revmap |
... | ... | |
179 | 181 |
found = self[index] |
180 | 182 |
return nil if value == found |
181 | 183 |
if value > found |
182 |
low = index + 1
|
|
184 |
low = index + 1
|
|
183 | 185 |
else |
184 |
high = index
|
|
186 |
high = index
|
|
185 | 187 |
end |
186 | 188 |
end |
187 | 189 | |
... | ... | |
204 | 206 |
bi = 0 |
205 | 207 |
diff.diffs.each { |d| |
206 | 208 |
d.each { |mod| |
207 |
case mod[0]
|
|
208 |
when '-'
|
|
209 |
while ai < mod[1]
|
|
210 |
newary << self[ai]
|
|
211 |
ai += 1
|
|
212 |
bi += 1
|
|
213 |
end
|
|
214 |
ai += 1
|
|
215 |
when '+'
|
|
216 |
while bi < mod[1]
|
|
217 |
newary << self[ai]
|
|
218 |
ai += 1
|
|
219 |
bi += 1
|
|
220 |
end
|
|
221 |
newary << mod[2]
|
|
222 |
bi += 1
|
|
223 |
else
|
|
224 |
raise "Unknown diff action"
|
|
225 |
end
|
|
209 |
case mod[0]
|
|
210 |
when '-'
|
|
211 |
while ai < mod[1]
|
|
212 |
newary << self[ai]
|
|
213 |
ai += 1
|
|
214 |
bi += 1
|
|
215 |
end
|
|
216 |
ai += 1
|
|
217 |
when '+'
|
|
218 |
while bi < mod[1]
|
|
219 |
newary << self[ai]
|
|
220 |
ai += 1
|
|
221 |
bi += 1
|
|
222 |
end
|
|
223 |
newary << mod[2]
|
|
224 |
bi += 1
|
|
225 |
else
|
|
226 |
raise "Unknown diff action"
|
|
227 |
end
|
|
226 | 228 |
} |
227 | 229 |
} |
228 | 230 |
while ai < self.length |
... | ... | |
243 | 245 |
end |
244 | 246 | |
245 | 247 |
=begin |
246 |
= Diff |
|
247 |
(({diff.rb})) - computes the differences between two arrays or |
|
248 |
strings. Copyright (C) 2001 Lars Christensen |
|
248 |
= Diff
|
|
249 |
(({diff.rb})) - computes the differences between two arrays or
|
|
250 |
strings. Copyright (C) 2001 Lars Christensen
|
|
249 | 251 | |
250 |
== Synopsis |
|
252 |
== Synopsis
|
|
251 | 253 | |
252 |
diff = Diff.new(a, b) |
|
253 |
b = a.patch(diff) |
|
254 |
diff = Diff.new(a, b)
|
|
255 |
b = a.patch(diff)
|
|
254 | 256 | |
255 |
== Class Diff |
|
256 |
=== Class Methods |
|
257 |
--- Diff.new(a, b) |
|
258 |
--- a.diff(b) |
|
259 |
Creates a Diff object which represent the differences between |
|
260 |
((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays |
|
261 |
of any objects, strings, or object of any class that include |
|
262 |
module ((|Diffable|)) |
|
257 |
== Class Diff
|
|
258 |
=== Class Methods
|
|
259 |
--- Diff.new(a, b)
|
|
260 |
--- a.diff(b)
|
|
261 |
Creates a Diff object which represent the differences between
|
|
262 |
((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
|
|
263 |
of any objects, strings, or object of any class that include
|
|
264 |
module ((|Diffable|))
|
|
263 | 265 | |
264 |
== Module Diffable |
|
265 |
The module ((|Diffable|)) is intended to be included in any class for |
|
266 |
which differences are to be computed. Diffable is included into String |
|
267 |
and Array when (({diff.rb})) is (({require}))'d. |
|
266 |
== Module Diffable
|
|
267 |
The module ((|Diffable|)) is intended to be included in any class for
|
|
268 |
which differences are to be computed. Diffable is included into String
|
|
269 |
and Array when (({diff.rb})) is (({require}))'d.
|
|
268 | 270 | |
269 |
Classes including Diffable should implement (({[]})) to get element at |
|
270 |
integer indices, (({<<})) to append elements to the object and |
|
271 |
(({ClassName#new})) should accept 0 arguments to create a new empty |
|
272 |
object. |
|
271 |
Classes including Diffable should implement (({[]})) to get element at
|
|
272 |
integer indices, (({<<})) to append elements to the object and
|
|
273 |
(({ClassName#new})) should accept 0 arguments to create a new empty
|
|
274 |
object.
|
|
273 | 275 | |
274 |
=== Instance Methods |
|
275 |
--- Diffable#patch(diff) |
|
276 |
Applies the differences from ((|diff|)) to the object ((|obj|)) |
|
277 |
and return the result. ((|obj|)) is not changed. ((|obj|)) and |
|
278 |
can be either an array or a string, but must match the object |
|
279 |
from which the ((|diff|)) was created. |
|
276 |
=== Instance Methods
|
|
277 |
--- Diffable#patch(diff)
|
|
278 |
Applies the differences from ((|diff|)) to the object ((|obj|))
|
|
279 |
and return the result. ((|obj|)) is not changed. ((|obj|)) and
|
|
280 |
can be either an array or a string, but must match the object
|
|
281 |
from which the ((|diff|)) was created.
|
|
280 | 282 |
=end |
lib/redmine/scm/adapters/abstract_adapter.rb | ||
---|---|---|
100 | 100 |
def entries(path=nil, identifier=nil) |
101 | 101 |
return nil |
102 | 102 |
end |
103 | ||
104 |
def branches |
|
105 |
return nil |
|
106 |
end |
|
107 | ||
108 |
def tags |
|
109 |
return nil |
|
110 |
end |
|
111 | ||
112 |
def default_branch |
|
113 |
return nil |
|
114 |
end |
|
103 | 115 |
|
104 | 116 |
def properties(path, identifier=nil) |
105 | 117 |
return nil |
... | ... | |
260 | 272 |
|
261 | 273 |
class Revision |
262 | 274 |
attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch |
275 | ||
263 | 276 |
def initialize(attributes={}) |
264 | 277 |
self.identifier = attributes[:identifier] |
265 | 278 |
self.scmid = attributes[:scmid] |
... | ... | |
271 | 284 |
self.revision = attributes[:revision] |
272 | 285 |
self.branch = attributes[:branch] |
273 | 286 |
end |
274 |
|
|
287 | ||
288 |
def save(repo) |
|
289 |
if repo.changesets.find_by_scmid(scmid.to_s).nil? |
|
290 |
changeset = Changeset.create!( |
|
291 |
:repository => repo, |
|
292 |
:revision => identifier, |
|
293 |
:scmid => scmid, |
|
294 |
:committer => author, |
|
295 |
:committed_on => time, |
|
296 |
:comments => message) |
|
297 | ||
298 |
paths.each do |file| |
|
299 |
Change.create!( |
|
300 |
:changeset => changeset, |
|
301 |
:action => file[:action], |
|
302 |
:path => file[:path]) |
|
303 |
end |
|
304 |
end |
|
305 |
end |
|
275 | 306 |
end |
276 | 307 |
|
277 | 308 |
class Annotate |
lib/redmine/scm/adapters/git_adapter.rb | ||
---|---|---|
21 | 21 |
module Scm |
22 | 22 |
module Adapters |
23 | 23 |
class GitAdapter < AbstractAdapter |
24 |
|
|
25 | 24 |
# Git executable name |
26 | 25 |
GIT_BIN = "git" |
27 | 26 | |
28 |
# Get the revision of a particuliar file |
|
29 |
def get_rev (rev,path) |
|
30 |
|
|
31 |
if rev != 'latest' && !rev.nil? |
|
32 |
cmd="#{GIT_BIN} --git-dir #{target('')} show --date=iso --pretty=fuller #{shell_quote rev} -- #{shell_quote path}" |
|
33 |
else |
|
34 |
@branch ||= shellout("#{GIT_BIN} --git-dir #{target('')} branch") { |io| io.grep(/\*/)[0].strip.match(/\* (.*)/)[1] } |
|
35 |
cmd="#{GIT_BIN} --git-dir #{target('')} log --date=iso --pretty=fuller -1 #{@branch} -- #{shell_quote path}" |
|
27 |
def info |
|
28 |
begin |
|
29 |
Info.new(:root_url => url, :lastrev => lastrev('',nil)) |
|
30 |
rescue |
|
31 |
nil |
|
36 | 32 |
end |
37 |
rev=[] |
|
38 |
i=0 |
|
39 |
shellout(cmd) do |io| |
|
40 |
files=[] |
|
41 |
changeset = {} |
|
42 |
parsing_descr = 0 #0: not parsing desc or files, 1: parsing desc, 2: parsing files |
|
33 |
end |
|
43 | 34 | |
35 |
def branches |
|
36 |
branches = [] |
|
37 |
cmd = "cd #{target('')} && #{GIT_BIN} branch" |
|
38 |
shellout(cmd) do |io| |
|
44 | 39 |
io.each_line do |line| |
45 |
if line =~ /^commit ([0-9a-f]{40})$/ |
|
46 |
key = "commit" |
|
47 |
value = $1 |
|
48 |
if (parsing_descr == 1 || parsing_descr == 2) |
|
49 |
parsing_descr = 0 |
|
50 |
rev = Revision.new({:identifier => changeset[:commit], |
|
51 |
:scmid => changeset[:commit], |
|
52 |
:author => changeset[:author], |
|
53 |
:time => Time.parse(changeset[:date]), |
|
54 |
:message => changeset[:description], |
|
55 |
:paths => files |
|
56 |
}) |
|
57 |
changeset = {} |
|
58 |
files = [] |
|
59 |
end |
|
60 |
changeset[:commit] = $1 |
|
61 |
elsif (parsing_descr == 0) && line =~ /^(\w+):\s*(.*)$/ |
|
62 |
key = $1 |
|
63 |
value = $2 |
|
64 |
if key == "Author" |
|
65 |
changeset[:author] = value |
|
66 |
elsif key == "CommitDate" |
|
67 |
changeset[:date] = value |
|
68 |
end |
|
69 |
elsif (parsing_descr == 0) && line.chomp.to_s == "" |
|
70 |
parsing_descr = 1 |
|
71 |
changeset[:description] = "" |
|
72 |
elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/ |
|
73 |
parsing_descr = 2 |
|
74 |
fileaction = $1 |
|
75 |
filepath = $2 |
|
76 |
files << {:action => fileaction, :path => filepath} |
|
77 |
elsif (parsing_descr == 1) && line.chomp.to_s == "" |
|
78 |
parsing_descr = 2 |
|
79 |
elsif (parsing_descr == 1) |
|
80 |
changeset[:description] << line |
|
81 |
end |
|
82 |
end |
|
83 |
rev = Revision.new({:identifier => changeset[:commit], |
|
84 |
:scmid => changeset[:commit], |
|
85 |
:author => changeset[:author], |
|
86 |
:time => (changeset[:date] ? Time.parse(changeset[:date]) : nil), |
|
87 |
:message => changeset[:description], |
|
88 |
:paths => files |
|
89 |
}) |
|
90 | ||
40 |
branches << line.match('\s*\*?\s*(.*)$')[1] |
|
41 |
end |
|
91 | 42 |
end |
92 | ||
93 |
get_rev('latest',path) if rev == [] |
|
94 | ||
95 |
return nil if $? && $?.exitstatus != 0 |
|
96 |
return rev |
|
43 |
branches.sort! |
|
97 | 44 |
end |
98 | 45 | |
99 |
def info |
|
100 |
revs = revisions(url,nil,nil,{:limit => 1}) |
|
101 |
if revs && revs.any? |
|
102 |
Info.new(:root_url => url, :lastrev => revs.first) |
|
103 |
else |
|
104 |
nil |
|
46 |
def tags |
|
47 |
tags = [] |
|
48 |
cmd = "cd #{target('')} && #{GIT_BIN} tag" |
|
49 |
shellout(cmd) do |io| |
|
50 |
io.readlines.sort!.map{|t| t.strip} |
|
105 | 51 |
end |
106 |
rescue Errno::ENOENT => e |
|
107 |
return nil |
|
52 |
end |
|
53 | ||
54 |
def default_branch |
|
55 |
branches.include?('master') ? 'master' : branches.first |
|
108 | 56 |
end |
109 | 57 |
|
110 | 58 |
def entries(path=nil, identifier=nil) |
... | ... | |
121 | 69 |
sha = $2 |
122 | 70 |
size = $3 |
123 | 71 |
name = $4 |
72 |
full_path = path.empty? ? name : "#{path}/#{name}" |
|
124 | 73 |
entries << Entry.new({:name => name, |
125 |
:path => (path.empty? ? name : "#{path}/#{name}"), |
|
126 |
:kind => ((type == "tree") ? 'dir' : 'file'), |
|
127 |
:size => ((type == "tree") ? nil : size), |
|
128 |
:lastrev => get_rev(identifier,(path.empty? ? name : "#{path}/#{name}")) |
|
129 |
|
|
130 |
}) unless entries.detect{|entry| entry.name == name} |
|
74 |
:path => full_path, |
|
75 |
:kind => (type == "tree") ? 'dir' : 'file', |
|
76 |
:size => (type == "tree") ? nil : size, |
|
77 |
:lastrev => lastrev(full_path,identifier) |
|
78 |
}) unless entries.detect{|entry| entry.name == name} |
|
131 | 79 |
end |
132 | 80 |
end |
133 | 81 |
end |
134 | 82 |
return nil if $? && $?.exitstatus != 0 |
135 | 83 |
entries.sort_by_name |
136 | 84 |
end |
137 |
|
|
85 | ||
86 |
def lastrev(path,rev) |
|
87 |
return nil if path.nil? |
|
88 |
cmd = "#{GIT_BIN} --git-dir #{target('')} log --pretty=fuller --no-merges -n 1 " |
|
89 |
cmd << " #{shell_quote rev} " if rev |
|
90 |
cmd << "-- #{path} " unless path.empty? |
|
91 |
shellout(cmd) do |io| |
|
92 |
id = io.gets.split[1] |
|
93 |
author = io.gets.match('Author:\s+(.*)$')[1] |
|
94 |
2.times { io.gets } |
|
95 |
time = io.gets.match('CommitDate:\s+(.*)$')[1] |
|
96 | ||
97 |
Revision.new({ |
|
98 |
:identifier => id, |
|
99 |
:scmid => id, |
|
100 |
:author => author, |
|
101 |
:time => time, |
|
102 |
:message => nil, |
|
103 |
:paths => nil |
|
104 |
}) |
|
105 |
end |
|
106 |
end |
|
107 | ||
108 |
def num_revisions |
|
109 |
cmd = "#{GIT_BIN} --git-dir #{target('')} log --all --pretty=format:'' | wc -l" |
|
110 |
shellout(cmd) {|io| io.gets.chomp.to_i + 1 } |
|
111 |
end |
|
112 | ||
138 | 113 |
def revisions(path, identifier_from, identifier_to, options={}) |
139 | 114 |
revisions = Revisions.new |
140 |
cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller" |
|
115 | ||
116 |
cmd = "#{GIT_BIN} --git-dir #{target('')} log --find-copies-harder --raw --date=iso --pretty=fuller" |
|
141 | 117 |
cmd << " --reverse" if options[:reverse] |
142 |
cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit] |
|
118 |
cmd << " --all" if options[:all] |
|
119 |
cmd << " -n #{options[:limit]} " if options[:limit] |
|
143 | 120 |
cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from |
144 | 121 |
cmd << " #{shell_quote identifier_to} " if identifier_to |
122 |
cmd << " -- #{path}" if path && !path.empty? |
|
123 | ||
145 | 124 |
shellout(cmd) do |io| |
146 | 125 |
files=[] |
147 | 126 |
changeset = {} |
... | ... | |
154 | 133 |
value = $1 |
155 | 134 |
if (parsing_descr == 1 || parsing_descr == 2) |
156 | 135 |
parsing_descr = 0 |
157 |
revision = Revision.new({:identifier => changeset[:commit], |
|
158 |
:scmid => changeset[:commit], |
|
159 |
:author => changeset[:author], |
|
160 |
:time => Time.parse(changeset[:date]), |
|
161 |
:message => changeset[:description], |
|
162 |
:paths => files |