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, @branch) |
|
| 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) |
| ... | ... | |
| 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? ? nil : params[:rev] |
|
| 204 |
@branch = @rev.nil? ? @repository.default_branch : @rev |
|
| 211 | 205 |
@rev_to = params[:rev_to] |
| 212 |
raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) |
|
| 213 | 206 |
rescue ActiveRecord::RecordNotFound |
| 214 | 207 |
render_404 |
| 215 | 208 |
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 default_branch |
|
| 71 |
scm.default_branch |
|
| 72 |
end |
|
| 65 | 73 |
|
| 66 | 74 |
def properties(path, identifier=nil) |
| 67 | 75 |
scm.properties(path, identifier) |
| ... | ... | |
| 92 | 100 |
def latest_changeset |
| 93 | 101 |
@latest_changeset ||= changesets.find(:first) |
| 94 | 102 |
end |
| 103 | ||
| 104 |
def latest_changesets(rev, path) |
|
| 105 |
@latest_changesets ||= changesets.find(:all, :limit => 10, :order => "committed_on DESC") |
|
| 106 |
end |
|
| 95 | 107 |
|
| 96 | 108 |
def scan_changesets_for_issue_ids |
| 97 | 109 |
self.changesets.each(&:scan_comment_for_issue_ids) |
| 98 | 110 |
end |
| 99 |
|
|
| 111 | ||
| 100 | 112 |
# Returns an array of committers usernames and associated user_id |
| 101 | 113 |
def committers |
| 102 | 114 |
@committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}")
|
| app/models/repository/git.rb | ||
|---|---|---|
| 37 | 37 |
end |
| 38 | 38 | |
| 39 | 39 |
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 |
|
| 40 |
# latest revision found in database |
|
| 41 |
db_revision = latest_changeset ? latest_changeset.revision : nil |
|
| 46 | 42 | |
| 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 |
|
| 43 |
# latest revision in the repository |
|
| 44 |
if scm.info.nil? || scm.info.lastrev.nil? |
|
| 45 |
scm_revision = nil |
|
| 46 |
else |
|
| 47 |
scm_revision = scm.info.lastrev.scmid |
|
| 48 |
end |
|
| 49 | ||
| 50 |
unless scm_revision.nil? || changesets.find_by_scmid(scm_revision) |
|
| 51 |
scm.revisions('', db_revision, nil, :reverse => true).each do |revision|
|
|
| 52 |
revision.save(self) |
|
| 68 | 53 |
end |
| 69 | 54 |
end |
| 70 | 55 |
end |
| 56 | ||
| 57 |
def branches |
|
| 58 |
scm.branches |
|
| 59 |
end |
|
| 60 | ||
| 61 |
def latest_changesets(path,rev) |
|
| 62 |
@latest_changesets ||= changesets.find( |
|
| 63 |
:all, |
|
| 64 |
:conditions => ["scmid IN (?)", scm.repo.log(rev,path, :n => 10).collect{|c| c.id}],
|
|
| 65 |
:order => 'committed_on DESC' |
|
| 66 |
) |
|
| 67 |
end |
|
| 71 | 68 |
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 %>
|
|
| 17 |
<% end %> |
|
| 1 |
<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %>
|
|
| 18 | 2 | |
| 19 |
<%= "@ #{revision}" if revision %>
|
|
| 3 |
<% if !@entries.nil? && authorize_for('repositories', 'browse') -%>
|
|
| 4 |
<% form_tag({:action => 'show', :id => @project, :path => @path}, :method => :get, :id => 'revision_selector') do -%>
|
|
| 5 |
<!-- Branches Dropdown --> |
|
| 6 |
<% if !@repository.branches.nil? -%> |
|
| 7 |
| |
|
| 8 |
<% content_for :header_tags do %> |
|
| 9 |
<%= javascript_include_tag 'repository_navigation' %> |
|
| 10 |
<% end %> |
|
| 20 | 11 | |
| 21 |
<% html_title(with_leading_slash(path)) -%> |
|
| 12 |
<%= l(:label_branch) %>: |
|
| 13 |
<%= select_tag :rev, options_for_select(@repository.branches.map{|b| b.name},@branch), :id => 'branch' %>
|
|
| 14 |
<% end -%> |
|
| 15 | ||
| 16 |
| <%= l(:label_revision) %>: |
|
| 17 |
<%= text_field_tag 'rev', @rev, :size => 8 %> |
|
| 18 |
<% end -%> |
|
| 19 |
<% end -%> |
|
| 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 |
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2>
|
|
| 2 | 2 | |
| 3 | 3 |
<p><%= render :partial => 'link_to_functions' %></p> |
| 4 | 4 | |
| app/views/repositories/entry.rhtml | ||
|---|---|---|
| 1 |
<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
|
|
| 1 |
<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2>
|
|
| 2 | 2 | |
| 3 | 3 |
<p><%= render :partial => 'link_to_functions' %></p> |
| 4 | 4 | |
| 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' %> |
| 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 |
|
| 546 | 547 |
label_revision: Revision |
| 547 | 548 |
label_revision_plural: Revisions |
| 548 | 549 |
label_associated_revisions: Associated revisions |
| 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 default_branch |
|
| 109 |
return nil |
|
| 110 |
end |
|
| 103 | 111 |
|
| 104 | 112 |
def properties(path, identifier=nil) |
| 105 | 113 |
return nil |
| ... | ... | |
| 260 | 268 |
|
| 261 | 269 |
class Revision |
| 262 | 270 |
attr_accessor :identifier, :scmid, :name, :author, :time, :message, :paths, :revision, :branch |
| 271 | ||
| 263 | 272 |
def initialize(attributes={})
|
| 264 | 273 |
self.identifier = attributes[:identifier] |
| 265 | 274 |
self.scmid = attributes[:scmid] |
| ... | ... | |
| 271 | 280 |
self.revision = attributes[:revision] |
| 272 | 281 |
self.branch = attributes[:branch] |
| 273 | 282 |
end |
| 274 |
|
|
| 283 | ||
| 284 |
def save(repo) |
|
| 285 |
if repo.changesets.find_by_scmid(scmid.to_s).nil? |
|
| 286 |
changeset = Changeset.create!( |
|
| 287 |
:repository => repo, |
|
| 288 |
:revision => identifier, |
|
| 289 |
:scmid => scmid, |
|
| 290 |
:committer => author, |
|
| 291 |
:committed_on => time, |
|
| 292 |
:comments => message) |
|
| 293 | ||
| 294 |
paths.each do |file| |
|
| 295 |
Change.create!( |
|
| 296 |
:changeset => changeset, |
|
| 297 |
:action => file[:action], |
|
| 298 |
:path => file[:path]) |
|
| 299 |
end |
|
| 300 |
end |
|
| 301 |
end |
|
| 275 | 302 |
end |
| 276 | 303 |
|
| 277 | 304 |
class Annotate |
| lib/redmine/scm/adapters/git_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 'grit' |
|
| 20 | ||
| 21 | ||
| 19 | 22 | |
| 20 | 23 |
module Redmine |
| 21 | 24 |
module Scm |
| 22 | 25 |
module Adapters |
| 23 | 26 |
class GitAdapter < AbstractAdapter |
| 24 |
|
|
| 27 |
attr_accessor :repo |
|
| 28 | ||
| 25 | 29 |
# Git executable name |
| 26 | 30 |
GIT_BIN = "git" |
| 27 | 31 | |
| 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}"
|
|
| 36 |
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 |
|
| 43 | ||
| 44 |
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 |
}) |
|
| 32 |
def initialize(*args) |
|
| 33 |
args[1] = args[0] |
|
| 34 |
super(*args) |
|
| 90 | 35 | |
| 36 |
begin |
|
| 37 |
@repo = Grit::Repo.new(url, :is_bare => true) |
|
| 38 |
rescue |
|
| 39 |
Rails::logger.error "Repository could not be created" |
|
| 91 | 40 |
end |
| 41 |
end |
|
| 92 | 42 | |
| 93 |
get_rev('latest',path) if rev == []
|
|
| 43 |
def info |
|
| 44 |
begin |
|
| 45 |
Info.new(:root_url => url, :lastrev => @repo.log('all', nil, :n => 1).first.to_revision)
|
|
| 46 |
rescue |
|
| 47 |
nil |
|
| 48 |
end |
|
| 49 |
end |
|
| 94 | 50 | |
| 95 |
return nil if $? && $?.exitstatus != 0
|
|
| 96 |
return rev
|
|
| 51 |
def branches
|
|
| 52 |
@repo.branches
|
|
| 97 | 53 |
end |
| 98 | 54 | |
| 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 |
|
| 55 |
def default_branch |
|
| 56 |
begin |
|
| 57 |
@repo.default_branch |
|
| 58 |
rescue |
|
| 104 | 59 |
nil |
| 105 | 60 |
end |
| 106 |
rescue Errno::ENOENT => e |
|
| 107 |
return nil |
|
| 108 | 61 |
end |
| 109 | 62 |
|
| 110 | 63 |
def entries(path=nil, identifier=nil) |
| 111 |
path ||= '' |
|
| 64 |
return nil if repo.nil? |
|
| 65 |
path = nil if path.empty? |
|
| 66 | ||
| 112 | 67 |
entries = Entries.new |
| 113 |
cmd = "#{GIT_BIN} --git-dir #{target('')} ls-tree -l "
|
|
| 114 |
cmd << shell_quote("HEAD:" + path) if identifier.nil?
|
|
| 115 |
cmd << shell_quote(identifier + ":" + path) if identifier |
|
| 116 |
shellout(cmd) do |io| |
|
| 117 |
io.each_line do |line| |
|
| 118 |
e = line.chomp.to_s |
|
| 119 |
if e =~ /^\d+\s+(\w+)\s+([0-9a-f]{40})\s+([0-9-]+)\s+(.+)$/
|
|
| 120 |
type = $1 |
|
| 121 |
sha = $2 |
|
| 122 |
size = $3 |
|
| 123 |
name = $4 |
|
| 124 |
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}
|
|
| 131 |
end |
|
| 132 |
end |
|
| 68 |
|
|
| 69 |
tree = repo.log(identifier, path, :n => 1).first.tree |
|
| 70 |
tree = tree / path if path |
|
| 71 | ||
| 72 |
tree.contents.each do |file| |
|
| 73 |
file_path = path ? "#{path}/#{file.name}" : file.name
|
|
| 74 |
commit = repo.log(identifier, file_path, :n => 1).first |
|
| 75 | ||
| 76 |
entries << Entry.new({
|
|
| 77 |
:name => file.name, |
|
| 78 |
:path => file_path, |
|
| 79 |
:kind => file.class == Grit::Blob ? 'file' : 'dir', |
|
| 80 |
:size => file.respond_to?('size') ? file.size : nil,
|
|
| 81 |
:lastrev => commit.to_revision |
|
| 82 |
}) |
|
| 133 | 83 |
end |
| 134 |
return nil if $? && $?.exitstatus != 0 |
|
| 84 | ||
| 135 | 85 |
entries.sort_by_name |
| 136 | 86 |
end |
| 137 |
|
|
| 87 | ||
| 138 | 88 |
def revisions(path, identifier_from, identifier_to, options={})
|
| 139 | 89 |
revisions = Revisions.new |
| 140 |
cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
|
|
| 90 |
cmd = "#{GIT_BIN} --git-dir #{target('')} log -M -C --all --raw --date=iso --pretty=fuller --no-merges"
|
|
| 141 | 91 |
cmd << " --reverse" if options[:reverse] |
| 142 | 92 |
cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
|
| 143 | 93 |
cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
|
| ... | ... | |
| 192 | 142 |
elsif (parsing_descr == 1) |
| 193 | 143 |
changeset[:description] << line[4..-1] |
| 194 | 144 |
end |
| 195 |
end
|
|
| 145 |
end
|
|
| 196 | 146 | |
| 197 | 147 |
if changeset[:commit] |
| 198 | 148 |
revision = Revision.new({:identifier => changeset[:commit],
|
| ... | ... | |
| 213 | 163 |
return nil if $? && $?.exitstatus != 0 |
| 214 | 164 |
revisions |
| 215 | 165 |
end |
| 216 |
|
|
| 166 | ||
| 217 | 167 |
def diff(path, identifier_from, identifier_to=nil) |
| 218 | 168 |
path ||= '' |
| 219 | 169 |
if !identifier_to |
| ... | ... | |
| 265 | 215 |
end |
| 266 | 216 |
end |
| 267 | 217 |
end |
| 268 | ||
| 269 | 218 |
end |
| 270 | 219 | |
| 220 |
module Grit |
|
| 221 |
class Repo |
|
| 222 |
def log(commit = 'all', path = nil, options = {})
|
|
| 223 |
default_options = {:pretty => "raw", "no-merges" => true}
|
|
| 224 |
commit = default_branch if commit.nil? |
|
| 225 | ||
| 226 |
if commit == 'all' |
|
| 227 |
commit = default_branch |
|
| 228 |
default_options.merge!(:all => true) |
|
| 229 |
end |
|
| 230 | ||
| 231 |
actual_options = default_options.merge(options) |
|
| 232 |
arg = path ? [commit, '--', path] : [commit] |
|
| 233 |
commits = self.git.log(actual_options, *arg) |
|
| 234 |
Commit.list_from_string(self, commits) |
|
| 235 |
end |
|
| 236 | ||
| 237 |
def default_branch |
|
| 238 |
if branches.map{|h| h.name}.include?('master')
|
|
| 239 |
'master' |
|
| 240 |
else |
|
| 241 |
branches.first.name |
|
| 242 |
end |
|
| 243 |
end |
|
| 244 |
end |
|
| 245 | ||
| 246 |
class Diff |
|
| 247 |
def action |
|
| 248 |
return 'A' if new_file |
|
| 249 |
return 'D' if deleted_file |
|
| 250 |
return 'M' |
|
| 251 |
end |
|
| 252 | ||
| 253 |
def path |
|
| 254 |
return a_path if a_path |
|
| 255 |
return b_path if b_path |
|
| 256 |
end |
|
| 257 |
end |
|
| 258 | ||
| 259 |
class Commit |
|
| 260 |
def to_revision |
|
| 261 |
Redmine::Scm::Adapters::Revision.new({
|
|
| 262 |
:identifier => id, |
|
| 263 |
:scmid => id, |
|
| 264 |
:author => "#{author.name} <#{author.email}>",
|
|
| 265 |
:time => committed_date, |
|
| 266 |
:message => message |
|
| 267 |
}) |
|
| 268 |
end |
|
| 269 |
end |
|
| 270 |
end |
|
| public/javascripts/repository_navigation.js | ||
|---|---|---|
| 1 |
Event.observe(window,'load',function() {
|
|
| 2 |
/* |
|
| 3 |
If we're viewing a named branch, don't display it in the |
|
| 4 |
revision box |
|
| 5 |
*/ |
|
| 6 |
if ($('rev').getValue() == $('branch').getValue()) {
|
|
| 7 |
$('rev').setValue('');
|
|
| 8 |
} |
|
| 9 | ||
| 10 |
/* |
|
| 11 |
Temporarily disable the revision box if the branch drop-down |
|
| 12 |
is changed since both fields are named 'rev' |
|
| 13 |
*/ |
|
| 14 |
$('branch').observe('change',function(e) {
|
|
| 15 |
$('rev').disable();
|
|
| 16 |
e.element().parentNode.submit(); |
|
| 17 |
$('rev').enable();
|
|
| 18 |
}) |
|
| 19 | ||
| 20 |
/* |
|
| 21 |
Temporarily disable the branch drop-down if 'Enter' is pressed |
|
| 22 |
in the revision box since both fields are named 'rev' |
|
| 23 |
*/ |
|
| 24 |
$('rev').observe('keydown',function(e) {
|
|
| 25 |
if (e.keyCode == 13) {
|
|
| 26 |
$('branch').disable();
|
|
| 27 |
e.element().parentNode.submit(); |
|
| 28 |
$('branch').enable();
|
|
| 29 |
} |
|
| 30 |
}) |
|
| 31 |
}) |
|
| public/stylesheets/application.css | ||
|---|---|---|
| 176 | 176 |
width: .6em; height: .6em; |
| 177 | 177 |
} |
| 178 | 178 |
.contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
|
| 179 |
.contextual input {font-size:0.9em;}
|
|
| 179 |
.contextual input,select {font-size:0.9em;}
|
|
| 180 | 180 |
.message .contextual { margin-top: 0; }
|
| 181 | 181 | |
| 182 | 182 |
.splitcontentleft{float:left; width:49%;}
|
| test/functional/repositories_bazaar_controller_test.rb | ||
|---|---|---|
| 45 | 45 |
end |
| 46 | 46 |
|
| 47 | 47 |
def test_browse_root |
| 48 |
get :browse, :id => 3
|
|
| 48 |
get :show, :id => 3
|
|
| 49 | 49 |
assert_response :success |
| 50 |
assert_template 'browse'
|
|
| 50 |
assert_template 'show'
|
|
| 51 | 51 |
assert_not_nil assigns(:entries) |
| 52 | 52 |
assert_equal 2, assigns(:entries).size |
| 53 | 53 |
assert assigns(:entries).detect {|e| e.name == 'directory' && e.kind == 'dir'}
|
| ... | ... | |
| 55 | 55 |
end |
| 56 | 56 |
|
| 57 | 57 |
def test_browse_directory |
| 58 |
get :browse, :id => 3, :path => ['directory']
|
|
| 58 |
get :show, :id => 3, :path => ['directory']
|
|
| 59 | 59 |
assert_response :success |
| 60 |
assert_template 'browse'
|
|
| 60 |
assert_template 'show'
|
|
| 61 | 61 |
assert_not_nil assigns(:entries) |
| 62 | 62 |
assert_equal ['doc-ls.txt', 'document.txt', 'edit.png'], assigns(:entries).collect(&:name) |
| 63 | 63 |
entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
|
| ... | ... | |
| 67 | 67 |
end |
| 68 | 68 |
|
| 69 | 69 |
def test_browse_at_given_revision |
| 70 |
get :browse, :id => 3, :path => [], :rev => 3
|
|
| 70 |
get :show, :id => 3, :path => [], :rev => 3
|
|
| 71 | 71 |
assert_response :success |
| 72 |
assert_template 'browse'
|
|
| 72 |
assert_template 'show'
|
|
| 73 | 73 |
assert_not_nil assigns(:entries) |
| 74 | 74 |
assert_equal ['directory', 'doc-deleted.txt', 'doc-ls.txt', 'doc-mkdir.txt'], assigns(:entries).collect(&:name) |
| 75 | 75 |
end |
| ... | ... | |
| 102 | 102 |
def test_directory_entry |
| 103 | 103 |
get :entry, :id => 3, :path => ['directory'] |
| 104 | 104 |
assert_response :success |
| 105 |
assert_template 'browse'
|
|
| 105 |
assert_template 'show'
|
|
| 106 | 106 |
assert_not_nil assigns(:entry) |
| 107 | 107 |
assert_equal 'directory', assigns(:entry).name |
| 108 | 108 |
end |
| test/functional/repositories_cvs_controller_test.rb | ||
|---|---|---|
| 51 | 51 |
end |
| 52 | 52 |
|
| 53 | 53 |
def test_browse_root |
| 54 |
get :browse, :id => 1
|
|
| 54 |
get :show, :id => 1
|
|
| 55 | 55 |
assert_response :success |
| 56 |
assert_template 'browse'
|
|
| 56 |
assert_template 'show'
|
|
| 57 | 57 |
assert_not_nil assigns(:entries) |
| 58 | 58 |
assert_equal 3, assigns(:entries).size |
| 59 | 59 |
|
| ... | ... | |
| 65 | 65 |
end |
| 66 | 66 |
|
| 67 | 67 |
def test_browse_directory |
| 68 |
get :browse, :id => 1, :path => ['images']
|
|
| 68 |
get :show, :id => 1, :path => ['images']
|
|
| 69 | 69 |
assert_response :success |
| 70 |
assert_template 'browse'
|
|
| 70 |
assert_template 'show'
|
|
| 71 | 71 |
assert_not_nil assigns(:entries) |
| 72 | 72 |
assert_equal ['add.png', 'delete.png', 'edit.png'], assigns(:entries).collect(&:name) |
| 73 | 73 |
entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
|
| ... | ... | |
| 78 | 78 |
|
| 79 | 79 |
def test_browse_at_given_revision |
| 80 | 80 |
Project.find(1).repository.fetch_changesets |
| 81 |
get :browse, :id => 1, :path => ['images'], :rev => 1
|
|
| 81 |
get :show, :id => 1, :path => ['images'], :rev => 1
|
|
| 82 | 82 |
assert_response :success |
| 83 |
assert_template 'browse'
|
|
| 83 |
assert_template 'show'
|
|
| 84 | 84 |
assert_not_nil assigns(:entries) |
| 85 | 85 |
assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) |
| 86 | 86 |
end |
| ... | ... | |
| 118 | 118 |
def test_directory_entry |
| 119 | 119 |
get :entry, :id => 1, :path => ['sources'] |
| 120 | 120 |
assert_response :success |
| 121 |
assert_template 'browse'
|
|
| 121 |
assert_template 'show'
|
|
| 122 | 122 |
assert_not_nil assigns(:entry) |
| 123 | 123 |
assert_equal 'sources', assigns(:entry).name |
| 124 | 124 |
end |
| test/functional/repositories_darcs_controller_test.rb | ||
|---|---|---|
| 45 | 45 |
end |
| 46 | 46 |
|
| 47 | 47 |
def test_browse_root |
| 48 |
get :browse, :id => 3
|
|
| 48 |
get :show, :id => 3
|
|
| 49 | 49 |
assert_response :success |
| 50 |
assert_template 'browse'
|
|
| 50 |
assert_template 'show'
|
|
| 51 | 51 |
assert_not_nil assigns(:entries) |
| 52 | 52 |
assert_equal 3, assigns(:entries).size |
| 53 | 53 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
|
| ... | ... | |
| 56 | 56 |
end |
| 57 | 57 |
|
| 58 | 58 |
def test_browse_directory |
| 59 |
get :browse, :id => 3, :path => ['images']
|
|
| 59 |
get :show, :id => 3, :path => ['images']
|
|
| 60 | 60 |
assert_response :success |
| 61 |
assert_template 'browse'
|
|
| 61 |
assert_template 'show'
|
|
| 62 | 62 |
assert_not_nil assigns(:entries) |
| 63 | 63 |
assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) |
| 64 | 64 |
entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
|
| ... | ... | |
| 69 | 69 |
|
| 70 | 70 |
def test_browse_at_given_revision |
| 71 | 71 |
Project.find(3).repository.fetch_changesets |
| 72 |
get :browse, :id => 3, :path => ['images'], :rev => 1
|
|
| 72 |
get :show, :id => 3, :path => ['images'], :rev => 1
|
|
| 73 | 73 |
assert_response :success |
| 74 |
assert_template 'browse'
|
|
| 74 |
assert_template 'show'
|
|
| 75 | 75 |
assert_not_nil assigns(:entries) |
| 76 | 76 |
assert_equal ['delete.png'], assigns(:entries).collect(&:name) |
| 77 | 77 |
end |
| test/functional/repositories_git_controller_test.rb | ||
|---|---|---|
| 46 | 46 |
end |
| 47 | 47 |
|
| 48 | 48 |
def test_browse_root |
| 49 |
get :browse, :id => 3
|
|
| 49 |
get :show, :id => 3
|
|
| 50 | 50 |
assert_response :success |
| 51 |
assert_template 'browse'
|
|
| 51 |
assert_template 'show'
|
|
| 52 | 52 |
assert_not_nil assigns(:entries) |
| 53 |
assert_equal 3, assigns(:entries).size
|
|
| 53 |
assert_equal 6, assigns(:entries).size
|
|
| 54 | 54 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
|
| 55 | 55 |
assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
|
| 56 | 56 |
assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
|
| 57 |
assert assigns(:entries).detect {|e| e.name == 'copied_README' && e.kind == 'file'}
|
|
| 58 |
assert assigns(:entries).detect {|e| e.name == 'new_file.txt' && e.kind == 'file'}
|
|
| 59 |
assert assigns(:entries).detect {|e| e.name == 'renamed_test.txt' && e.kind == 'file'}
|
|
| 57 | 60 |
end |
| 58 |
|
|
| 61 | ||
| 62 |
def test_browse_branch |
|
| 63 |
get :show, :id => 3, :rev => 'test_branch' |
|
| 64 |
assert_response :success |
|
| 65 |
assert_template 'show' |
|
| 66 |
assert_not_nil assigns(:entries) |
|
| 67 |
assert_equal 4, assigns(:entries).size |
|
| 68 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
|
|
| 69 |
assert assigns(:entries).detect {|e| e.name == 'sources' && e.kind == 'dir'}
|
|
| 70 |
assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
|
|
| 71 |
assert assigns(:entries).detect {|e| e.name == 'test.txt' && e.kind == 'file'}
|
|
| 72 |
end |
|
| 73 | ||
| 59 | 74 |
def test_browse_directory |
| 60 |
get :browse, :id => 3, :path => ['images']
|
|
| 75 |
get :show, :id => 3, :path => ['images']
|
|
| 61 | 76 |
assert_response :success |
| 62 |
assert_template 'browse'
|
|
| 77 |
assert_template 'show'
|
|
| 63 | 78 |
assert_not_nil assigns(:entries) |
| 64 |
assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name)
|
|
| 79 |
assert_equal ['edit.png'], assigns(:entries).collect(&:name) |
|
| 65 | 80 |
entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
|
| 66 | 81 |
assert_not_nil entry |
| 67 | 82 |
assert_equal 'file', entry.kind |
| ... | ... | |
| 69 | 84 |
end |
| 70 | 85 |
|
| 71 | 86 |
def test_browse_at_given_revision |
| 72 |
get :browse, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
|
|
| 87 |
get :show, :id => 3, :path => ['images'], :rev => '7234cb2750b63f47bff735edc50a1c0a433c2518'
|
|
| 73 | 88 |
assert_response :success |
| 74 |
assert_template 'browse'
|
|
| 89 |
assert_template 'show'
|
|
| 75 | 90 |
assert_not_nil assigns(:entries) |
| 76 | 91 |
assert_equal ['delete.png'], assigns(:entries).collect(&:name) |
| 77 | 92 |
end |
| 78 | 93 | |
| 94 |
=begin |
|
| 95 |
Even with a rev param, this seems to render the list page rather than a single revision |
|
| 96 |
assert_template 'revision' passes though...? |
|
| 97 | ||
| 98 |
def test_view_revision |
|
| 99 |
get :revisions, :id => 3, :rev => 'deff712f05a90d96edbd70facc47d944be5897e3' |
|
| 100 |
assert_response :success |
|
| 101 |
assert_template 'revision' |
|
| 102 |
assert_tag :tag => 'h2', :child => "Revision #{assigns(:rev)[0,8]}"
|
|
| 103 |
assert_tag :tag => 'li', |
|
| 104 |
:attributes => {:class => /change-A/},
|
|
| 105 |
:child => { :tag => 'a', :child => 'new_file.txt' }
|
|
| 106 |
end |
|
| 107 |
=end |
|
| 108 | ||
| 79 | 109 |
def test_changes |
| 80 | 110 |
get :changes, :id => 3, :path => ['images', 'edit.png'] |
| 81 | 111 |
assert_response :success |
| ... | ... | |
| 89 | 119 |
assert_template 'entry' |
| 90 | 120 |
# Line 19 |
| 91 | 121 |
assert_tag :tag => 'th', |
| 92 |
:content => /10/,
|
|
| 122 |
:content => /11/,
|
|
| 93 | 123 |
:attributes => { :class => /line-num/ },
|
| 94 | 124 |
:sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
|
| 95 | 125 |
end |
| ... | ... | |
| 104 | 134 |
def test_directory_entry |
| 105 | 135 |
get :entry, :id => 3, :path => ['sources'] |
| 106 | 136 |
assert_response :success |
| 107 |
assert_template 'browse'
|
|
| 137 |
assert_template 'show'
|
|
| 108 | 138 |
assert_not_nil assigns(:entry) |
| 109 | 139 |
assert_equal 'sources', assigns(:entry).name |
| 110 | 140 |
end |
| ... | ... | |
| 127 | 157 |
assert_response :success |
| 128 | 158 |
assert_template 'annotate' |
| 129 | 159 |
# Line 23, changeset 2f9c0091 |
| 130 |
assert_tag :tag => 'th', :content => /23/,
|
|
| 160 |
assert_tag :tag => 'th', :content => /24/,
|
|
| 131 | 161 |
:sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } },
|
| 132 | 162 |
:sibling => { :tag => 'td', :content => /jsmith/ },
|
| 133 | 163 |
:sibling => { :tag => 'td', :content => /watcher =/ }
|
| 134 | 164 |
end |
| 135 | 165 |
|
| 136 | 166 |
def test_annotate_binary_file |
| 137 |
get :annotate, :id => 3, :path => ['images', 'delete.png']
|
|
| 167 |
get :annotate, :id => 3, :path => ['images', 'edit.png']
|
|
| 138 | 168 |
assert_response 500 |
| 139 | 169 |
assert_tag :tag => 'div', :attributes => { :class => /error/ },
|
| 140 | 170 |
:content => /can not be annotated/ |
| test/functional/repositories_mercurial_controller_test.rb | ||
|---|---|---|
| 45 | 45 |
end |
| 46 | 46 |
|
| 47 | 47 |
def test_browse_root |
| 48 |
get :browse, :id => 3
|
|
| 48 |
get :show, :id => 3
|
|
| 49 | 49 |
assert_response :success |
| 50 |
assert_template 'browse'
|
|
| 50 |
assert_template 'show'
|
|
| 51 | 51 |
assert_not_nil assigns(:entries) |
| 52 | 52 |
assert_equal 3, assigns(:entries).size |
| 53 | 53 |
assert assigns(:entries).detect {|e| e.name == 'images' && e.kind == 'dir'}
|
| ... | ... | |
| 56 | 56 |
end |
| 57 | 57 |
|
| 58 | 58 |
def test_browse_directory |
| 59 |
get :browse, :id => 3, :path => ['images']
|
|
| 59 |
get :show, :id => 3, :path => ['images']
|
|
| 60 | 60 |
assert_response :success |
| 61 |
assert_template 'browse'
|
|
| 61 |
assert_template 'show'
|
|
| 62 | 62 |
assert_not_nil assigns(:entries) |
| 63 | 63 |
assert_equal ['delete.png', 'edit.png'], assigns(:entries).collect(&:name) |
| 64 | 64 |
entry = assigns(:entries).detect {|e| e.name == 'edit.png'}
|
| ... | ... | |
| 68 | 68 |
end |
| 69 | 69 |
|
| 70 | 70 |
def test_browse_at_given_revision |
| 71 |
get :browse, :id => 3, :path => ['images'], :rev => 0
|
|
| 71 |
get :show, :id => 3, :path => ['images'], :rev => 0
|
|
| 72 | 72 |
assert_response :success |
| 73 |
assert_template 'browse'
|
|
| 73 |
assert_template 'show'
|
|
| 74 | 74 |
assert_not_nil assigns(:entries) |
| 75 | 75 |
assert_equal ['delete.png'], assigns(:entries).collect(&:name) |
| 76 | 76 |
end |
| ... | ... | |
| 103 | 103 |
def test_directory_entry |
| 104 | 104 |
get :entry, :id => 3, :path => ['sources'] |
| 105 | 105 |
assert_response :success |
| 106 |
assert_template 'browse'
|
|
| 106 |
assert_template 'show'
|
|
| 107 | 107 |
assert_not_nil assigns(:entry) |
| 108 | 108 |
assert_equal 'sources', assigns(:entry).name |
| 109 | 109 |
end |
| test/functional/repositories_subversion_controller_test.rb | ||
|---|---|---|
| 47 | 47 |
end |
| 48 | 48 |
|
| 49 | 49 |
def test_browse_root |
| 50 |
get :browse, :id => 1
|
|
| 50 |
get :show, :id => 1
|
|
| 51 | 51 |
assert_response :success |
| 52 |
assert_template 'browse'
|
|
| 52 |
assert_template 'show'
|
|
| 53 | 53 |
assert_not_nil assigns(:entries) |
| 54 | 54 |
entry = assigns(:entries).detect {|e| e.name == 'subversion_test'}
|
| 55 | 55 |
assert_equal 'dir', entry.kind |
| 56 | 56 |
end |
| 57 | 57 |
|
| 58 | 58 |
def test_browse_directory |
| 59 |
get :browse, :id => 1, :path => ['subversion_test']
|
|
| 59 |
get :show, :id => 1, :path => ['subversion_test']
|
|
| 60 | 60 |
assert_response :success |
| 61 |
assert_template 'browse'
|
|
| 61 |
assert_template 'show'
|
|
| 62 | 62 |
assert_not_nil assigns(:entries) |
| 63 | 63 |
assert_equal ['folder', '.project', 'helloworld.c', 'textfile.txt'], assigns(:entries).collect(&:name) |
| 64 | 64 |
entry = assigns(:entries).detect {|e| e.name == 'helloworld.c'}
|
| ... | ... | |
| 68 | 68 |
end |
| 69 | 69 | |
| 70 | 70 |
def test_browse_at_given_revision |
| 71 |
get :browse, :id => 1, :path => ['subversion_test'], :rev => 4
|
|
| 71 |
get :show, :id => 1, :path => ['subversion_test'], :rev => 4
|
|
| 72 | 72 |
assert_response :success |
| 73 |
assert_template 'browse'
|
|
| 73 |
assert_template 'show'
|
|
| 74 | 74 |
assert_not_nil assigns(:entries) |
| 75 | 75 |
assert_equal ['folder', '.project', 'helloworld.c', 'helloworld.rb', 'textfile.txt'], assigns(:entries).collect(&:name) |
| 76 | 76 |
end |
| ... | ... | |
| 131 | 131 |
def test_directory_entry |
| 132 | 132 |
get :entry, :id => 1, :path => ['subversion_test', 'folder'] |
| 133 | 133 |
assert_response :success |
| 134 |
assert_template 'browse'
|
|
| 134 |
assert_template 'show'
|
|
| 135 | 135 |
assert_not_nil assigns(:entry) |
| 136 | 136 |
assert_equal 'folder', assigns(:entry).name |
| 137 | 137 |
end |