##// END OF EJS Templates
Added branch and tag support to the git repository viewer. (#1406)...
Eric Davis -
r2735:c28b044d6802
parent child
Show More
@@ -0,0 +1,21
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)) -%>
@@ -0,0 +1,35
1 Event.observe(window,'load',function() {
2 /*
3 If we're viewing a tag or branch, don't display it in the
4 revision box
5 */
6 var branch_selected = $('branch') && $('rev').getValue() == $('branch').getValue();
7 var tag_selected = $('tag') && $('rev').getValue() == $('tag').getValue();
8 if (branch_selected || tag_selected) {
9 $('rev').setValue('');
10 }
11
12 /*
13 Copy the branch/tag value into the revision box, then disable
14 the dropdowns before submitting the form
15 */
16 $$('#branch,#tag').each(function(e) {
17 e.observe('change',function(e) {
18 $('rev').setValue(e.element().getValue());
19 $$('#branch,#tag').invoke('disable');
20 e.element().parentNode.submit();
21 $$('#branch,#tag').invoke('enable');
22 });
23 });
24
25 /*
26 Disable the branch/tag dropdowns before submitting the revision form
27 */
28 $('rev').observe('keydown', function(e) {
29 if (e.keyCode == 13) {
30 $$('#branch,#tag').invoke('disable');
31 e.element().parentNode.submit();
32 $$('#branch,#tag').invoke('enable');
33 }
34 });
35 })
@@ -0,0 +1,22
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class GitAdapterTest < Test::Unit::TestCase
4 REPOSITORY_PATH = RAILS_ROOT.gsub(%r{config\/\.\.}, '') + '/tmp/test/git_repository'
5
6 if File.directory?(REPOSITORY_PATH)
7 def setup
8 @adapter = Redmine::Scm::Adapters::GitAdapter.new(REPOSITORY_PATH)
9 end
10
11 def test_branches
12 assert_equal @adapter.branches, ['master', 'test_branch']
13 end
14
15 def test_getting_all_revisions
16 assert_equal 12, @adapter.revisions('',nil,nil,:all => true).length
17 end
18 else
19 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
20 def test_fake; assert true end
21 end
22 end
@@ -64,31 +64,26 class RepositoriesController < ApplicationController
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,7 +130,7 class RepositoriesController < ApplicationController
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,17 +194,14 private
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].blank? ? @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
@@ -62,6 +62,18 class Repository < ActiveRecord::Base
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,11 +104,15 class Repository < ActiveRecord::Base
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}")
@@ -29,43 +29,60 class Repository::Git < Repository
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
@@ -4,7 +4,7
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,7 +12,7
12 12 :condition => "scmEntryClick('#{tr_id}')"%>">&nbsp</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>
@@ -1,21 +1,21
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 -%>
@@ -1,4 +1,10
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
@@ -1,10 +1,8
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) %>
@@ -1,4 +1,12
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
@@ -1,4 +1,10
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
@@ -14,7 +14,7
14 14 &#187;&nbsp;
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>
@@ -1,6 +1,6
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>
@@ -1,15 +1,10
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,7 +13,7
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 %>
@@ -798,3 +798,6 bg:
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
@@ -831,3 +831,6 bs:
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
@@ -801,3 +801,6 ca:
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
@@ -804,3 +804,6 cs:
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
@@ -831,3 +831,6 da:
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
@@ -830,3 +830,6 de:
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
@@ -543,6 +543,8 en:
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,6 +556,7 en:
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
@@ -851,3 +851,6 es:
851 851 mail_body_wiki_content_updated: La página wiki '{{page}}' ha sido actualizada por {{author}}.
852 852 permission_add_project: Crear proyecto
853 853 setting_new_project_user_role_id: Permiso asignado a un usuario no-administrador para crear proyectos
854 label_view_all_revisions: View all revisions
855 label_tag: Tag
856 label_branch: Branch
@@ -841,3 +841,6 fi:
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
@@ -832,4 +832,7 fr:
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
@@ -830,3 +830,6 gl:
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
@@ -813,3 +813,6 he:
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
@@ -836,3 +836,6
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
@@ -816,3 +816,6 it:
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
@@ -838,3 +838,6 ja:
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
@@ -869,3 +869,6 ko:
869 869 # by Kihyun Yoon(ddumbugie@gmail.com)
870 870 # by John Hwang (jhwang@tavon.org),http://github.com/tavon
871 871 field_issue_to: Related issue
872 label_view_all_revisions: View all revisions
873 label_tag: Tag
874 label_branch: Branch
@@ -841,3 +841,6 lt:
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
@@ -786,3 +786,6 nl:
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
@@ -803,3 +803,6
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
@@ -834,3 +834,6 pl:
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
@@ -836,3 +836,6 pt-BR:
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
@@ -822,3 +822,6 pt:
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
@@ -801,3 +801,6 ro:
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
@@ -928,3 +928,6 ru:
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
@@ -802,4 +802,7 sk:
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 No newline at end of file
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
@@ -800,3 +800,6 sl:
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
@@ -824,3 +824,6
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
@@ -858,3 +858,6 sv:
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
@@ -801,3 +801,6 th:
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
@@ -837,3 +837,6 tr:
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
@@ -800,3 +800,6 uk:
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
@@ -870,3 +870,6 vi:
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
@@ -908,3 +908,6
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
@@ -833,3 +833,6 zh:
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
@@ -218,7 +218,7 ActionController::Routing::Routes.draw do |map|
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
@@ -1,153 +1,155
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,9 +160,9 module Diffable
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,9 +181,9 module Diffable
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,25 +206,25 module Diffable
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,38 +245,38 class String
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
@@ -100,6 +100,18 module Redmine
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,6 +272,7 module Redmine
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,7 +284,25 module Redmine
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
@@ -21,90 +21,38 module Redmine
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 = "#{GIT_BIN} --git-dir #{target('')} 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 = "#{GIT_BIN} --git-dir #{target('')} 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,27 +69,63 module Redmine
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 begin
93 id = io.gets.split[1]
94 author = io.gets.match('Author:\s+(.*)$')[1]
95 2.times { io.gets }
96 time = io.gets.match('CommitDate:\s+(.*)$')[1]
97
98 Revision.new({
99 :identifier => id,
100 :scmid => id,
101 :author => author,
102 :time => time,
103 :message => nil,
104 :paths => nil
105 })
106 rescue NoMethodError => e
107 logger.error("The revision '#{path}' has a wrong format")
108 return nil
109 end
110 end
111 end
112
113 def num_revisions
114 cmd = "#{GIT_BIN} --git-dir #{target('')} log --all --pretty=format:'' | wc -l"
115 shellout(cmd) {|io| io.gets.chomp.to_i + 1}
116 end
117
138 118 def revisions(path, identifier_from, identifier_to, options={})
139 119 revisions = Revisions.new
140 cmd = "#{GIT_BIN} --git-dir #{target('')} log --raw --date=iso --pretty=fuller"
120
121 cmd = "#{GIT_BIN} --git-dir #{target('')} log --find-copies-harder --raw --date=iso --pretty=fuller"
141 122 cmd << " --reverse" if options[:reverse]
142 cmd << " -n #{options[:limit].to_i} " if (!options.nil?) && options[:limit]
123 cmd << " --all" if options[:all]
124 cmd << " -n #{options[:limit]} " if options[:limit]
143 125 cmd << " #{shell_quote(identifier_from + '..')} " if identifier_from
144 126 cmd << " #{shell_quote identifier_to} " if identifier_to
127 cmd << " -- #{path}" if path && !path.empty?
128
145 129 shellout(cmd) do |io|
146 130 files=[]
147 131 changeset = {}
@@ -154,13 +138,14 module Redmine
154 138 value = $1
155 139 if (parsing_descr == 1 || parsing_descr == 2)
156 140 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
163 })
141 revision = Revision.new({
142 :identifier => changeset[:commit],
143 :scmid => changeset[:commit],
144 :author => changeset[:author],
145 :time => Time.parse(changeset[:date]),
146 :message => changeset[:description],
147 :paths => files
148 })
164 149 if block_given?
165 150 yield revision
166 151 else
@@ -182,26 +167,35 module Redmine
182 167 elsif (parsing_descr == 0) && line.chomp.to_s == ""
183 168 parsing_descr = 1
184 169 changeset[:description] = ""
185 elsif (parsing_descr == 1 || parsing_descr == 2) && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
170 elsif (parsing_descr == 1 || parsing_descr == 2) \
171 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\s+(.+)$/
186 172 parsing_descr = 2
187 173 fileaction = $1
188 174 filepath = $2
189 175 files << {:action => fileaction, :path => filepath}
176 elsif (parsing_descr == 1 || parsing_descr == 2) \
177 && line =~ /^:\d+\s+\d+\s+[0-9a-f.]+\s+[0-9a-f.]+\s+(\w)\d+\s+(\S+)\s+(.+)$/
178 parsing_descr = 2
179 fileaction = $1
180 filepath = $3
181 files << {:action => fileaction, :path => filepath}
190 182 elsif (parsing_descr == 1) && line.chomp.to_s == ""
191 183 parsing_descr = 2
192 184 elsif (parsing_descr == 1)
193 185 changeset[:description] << line[4..-1]
194 186 end
195 end
187 end
196 188
197 189 if changeset[:commit]
198 revision = Revision.new({:identifier => changeset[:commit],
199 :scmid => changeset[:commit],
200 :author => changeset[:author],
201 :time => Time.parse(changeset[:date]),
202 :message => changeset[:description],
203 :paths => files
204 })
190 revision = Revision.new({
191 :identifier => changeset[:commit],
192 :scmid => changeset[:commit],
193 :author => changeset[:author],
194 :time => Time.parse(changeset[:date]),
195 :message => changeset[:description],
196 :paths => files
197 })
198
205 199 if block_given?
206 200 yield revision
207 201 else
@@ -213,15 +207,16 module Redmine
213 207 return nil if $? && $?.exitstatus != 0
214 208 revisions
215 209 end
216
210
217 211 def diff(path, identifier_from, identifier_to=nil)
218 212 path ||= ''
219 if !identifier_to
220 identifier_to = nil
213
214 if identifier_to
215 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}"
216 else
217 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}"
221 218 end
222
223 cmd = "#{GIT_BIN} --git-dir #{target('')} show #{shell_quote identifier_from}" if identifier_to.nil?
224 cmd = "#{GIT_BIN} --git-dir #{target('')} diff #{shell_quote identifier_to} #{shell_quote identifier_from}" if !identifier_to.nil?
219
225 220 cmd << " -- #{shell_quote path}" unless path.empty?
226 221 diff = []
227 222 shellout(cmd) do |io|
@@ -265,6 +260,4 module Redmine
265 260 end
266 261 end
267 262 end
268
269 263 end
270
@@ -181,7 +181,7 div.square {
181 181 width: .6em; height: .6em;
182 182 }
183 183 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
184 .contextual input {font-size:0.9em;}
184 .contextual input,select {font-size:0.9em;}
185 185 .message .contextual { margin-top: 0; }
186 186
187 187 .splitcontentleft{float:left; width:49%;}
1 NO CONTENT: modified file, binary diff hidden
@@ -45,9 +45,9 class RepositoriesBazaarControllerTest < Test::Unit::TestCase
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,9 +55,9 class RepositoriesBazaarControllerTest < Test::Unit::TestCase
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,9 +67,9 class RepositoriesBazaarControllerTest < Test::Unit::TestCase
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,7 +102,7 class RepositoriesBazaarControllerTest < Test::Unit::TestCase
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
@@ -51,9 +51,9 class RepositoriesCvsControllerTest < Test::Unit::TestCase
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,9 +65,9 class RepositoriesCvsControllerTest < Test::Unit::TestCase
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,9 +78,9 class RepositoriesCvsControllerTest < Test::Unit::TestCase
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,7 +118,7 class RepositoriesCvsControllerTest < Test::Unit::TestCase
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
@@ -45,9 +45,9 class RepositoriesDarcsControllerTest < Test::Unit::TestCase
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,9 +56,9 class RepositoriesDarcsControllerTest < Test::Unit::TestCase
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,9 +69,9 class RepositoriesDarcsControllerTest < Test::Unit::TestCase
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
@@ -46,22 +46,37 class RepositoriesGitControllerTest < Test::Unit::TestCase
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,9 +84,9 class RepositoriesGitControllerTest < Test::Unit::TestCase
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
@@ -89,7 +104,7 class RepositoriesGitControllerTest < Test::Unit::TestCase
89 104 assert_template 'entry'
90 105 # Line 19
91 106 assert_tag :tag => 'th',
92 :content => /10/,
107 :content => /11/,
93 108 :attributes => { :class => /line-num/ },
94 109 :sibling => { :tag => 'td', :content => /WITHOUT ANY WARRANTY/ }
95 110 end
@@ -104,7 +119,7 class RepositoriesGitControllerTest < Test::Unit::TestCase
104 119 def test_directory_entry
105 120 get :entry, :id => 3, :path => ['sources']
106 121 assert_response :success
107 assert_template 'browse'
122 assert_template 'show'
108 123 assert_not_nil assigns(:entry)
109 124 assert_equal 'sources', assigns(:entry).name
110 125 end
@@ -127,14 +142,14 class RepositoriesGitControllerTest < Test::Unit::TestCase
127 142 assert_response :success
128 143 assert_template 'annotate'
129 144 # Line 23, changeset 2f9c0091
130 assert_tag :tag => 'th', :content => /23/,
145 assert_tag :tag => 'th', :content => /24/,
131 146 :sibling => { :tag => 'td', :child => { :tag => 'a', :content => /2f9c0091/ } },
132 147 :sibling => { :tag => 'td', :content => /jsmith/ },
133 148 :sibling => { :tag => 'td', :content => /watcher =/ }
134 149 end
135 150
136 151 def test_annotate_binary_file
137 get :annotate, :id => 3, :path => ['images', 'delete.png']
152 get :annotate, :id => 3, :path => ['images', 'edit.png']
138 153 assert_response 500
139 154 assert_tag :tag => 'div', :attributes => { :class => /error/ },
140 155 :content => /can not be annotated/
@@ -44,10 +44,10 class RepositoriesMercurialControllerTest < Test::Unit::TestCase
44 44 assert_not_nil assigns(:changesets)
45 45 end
46 46
47 def test_browse_root
48 get :browse, :id => 3
47 def test_show_root
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'}
@@ -55,10 +55,10 class RepositoriesMercurialControllerTest < Test::Unit::TestCase
55 55 assert assigns(:entries).detect {|e| e.name == 'README' && e.kind == 'file'}
56 56 end
57 57
58 def test_browse_directory
59 get :browse, :id => 3, :path => ['images']
58 def test_show_directory
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'}
@@ -67,10 +67,10 class RepositoriesMercurialControllerTest < Test::Unit::TestCase
67 67 assert_equal 'images/edit.png', entry.path
68 68 end
69 69
70 def test_browse_at_given_revision
71 get :browse, :id => 3, :path => ['images'], :rev => 0
70 def test_show_at_given_revision
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,7 +103,7 class RepositoriesMercurialControllerTest < Test::Unit::TestCase
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
@@ -47,18 +47,18 class RepositoriesSubversionControllerTest < Test::Unit::TestCase
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,9 +68,9 class RepositoriesSubversionControllerTest < Test::Unit::TestCase
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,7 +131,7 class RepositoriesSubversionControllerTest < Test::Unit::TestCase
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
@@ -34,8 +34,8 class RepositoryGitTest < Test::Unit::TestCase
34 34 @repository.fetch_changesets
35 35 @repository.reload
36 36
37 assert_equal 6, @repository.changesets.count
38 assert_equal 11, @repository.changes.count
37 assert_equal 12, @repository.changesets.count
38 assert_equal 20, @repository.changes.count
39 39
40 40 commit = @repository.changesets.find(:first, :order => 'committed_on ASC')
41 41 assert_equal "Initial import.\nThe repository contains 3 files.", commit.comments
@@ -57,10 +57,10 class RepositoryGitTest < Test::Unit::TestCase
57 57 # Remove the 3 latest changesets
58 58 @repository.changesets.find(:all, :order => 'committed_on DESC', :limit => 3).each(&:destroy)
59 59 @repository.reload
60 assert_equal 3, @repository.changesets.count
60 assert_equal 9, @repository.changesets.count
61 61
62 62 @repository.fetch_changesets
63 assert_equal 6, @repository.changesets.count
63 assert_equal 12, @repository.changesets.count
64 64 end
65 65 else
66 66 puts "Git test repository NOT FOUND. Skipping unit tests !!!"
General Comments 0
You need to be logged in to leave comments. Login now