diff options
author | Eric Davis <edavis@littlestreamsoftware.com> | 2009-08-15 22:41:40 +0000 |
---|---|---|
committer | Eric Davis <edavis@littlestreamsoftware.com> | 2009-08-15 22:41:40 +0000 |
commit | c28b044d6802559a9a2a07af1b7661a1122e5f48 (patch) | |
tree | 393dda01ed0fcd20ed98b2154f471a3a33440644 /app | |
parent | a39bc8f1f4ec7c3b3ce2f27a02467cf497cef03a (diff) | |
download | redmine-c28b044d6802559a9a2a07af1b7661a1122e5f48.tar.gz redmine-c28b044d6802559a9a2a07af1b7661a1122e5f48.zip |
Added branch and tag support to the git repository viewer. (#1406)
Many thanks to Adam Soltys and everyone else who tested this patch.
* Updated git test repository so it has a branch with some differences from the master branch
* Moved redmine diff class into a module so as not to clash with diff-lcs gem which is required by grit
* Find changesets from all branches, not just master
* Got revision browsing working
* Got file actions working properly
* Allow browsing by short form of commit identifier
* Added a method to retrieve repository branches
* Allow browsing by branch names as well as commit numbers
* Handle the case where a git repository has no master branch
* Expand revision box and handle finding revisions by first 8 characters
* Added branches dropdown to repository show page
* Combined repository browse and show into a single action. Moved branch/revision navigation into a partial.
* Renamed partial navigation -> breadcrumbs
* Made it so latest revisions list uses branch and path context
* Preserve current path when changing branch or revision
* Perform slightly more graceful error handling in the case of invalid repository URLs
* Allow branch names to contain periods
* Allow dashes in branch names
* Sort branches by name
* Adding tags dropdown
* Need to disable both branches and tags dropdowns before submitting revision form
* Support underscores in revision (branch/tag) names
* Making file history sensitive to current branch/tag/revision, adding common navigation to changes page
* Updated translation files to include labels for 'branch', 'tag', and 'view all revisions'
* Reenable fields after submit so they don't look disabled and don't stay disabled on browser back button
* Instead of dashes just use empty string for default dropdown value
* Individual entry views now sport the upgraded revision navigation
* Don't display dropdowns with no entries
* Consider all revisions when doing initial load
* Fixed bug grabbing changesets. Thanks to Bernhard Furtmueller for catching.
* Always check the entire log to find new revisions, rather than trying to go forward from the latest known one
* Added some cleverness to avoid selecting the whole changesets table any time someone views the repository root
* File copies and renames being detected properly
* Return gracefully if no revisions are found in the git log
* Applied patch from Babar Le Lapin to improve Windows compatibility
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@2840 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/repositories_controller.rb | 28 | ||||
-rw-r--r-- | app/models/repository.rb | 18 | ||||
-rw-r--r-- | app/models/repository/git.rb | 83 | ||||
-rw-r--r-- | app/views/repositories/_breadcrumbs.rhtml | 21 | ||||
-rw-r--r-- | app/views/repositories/_dir_list_content.rhtml | 4 | ||||
-rw-r--r-- | app/views/repositories/_navigation.rhtml | 36 | ||||
-rw-r--r-- | app/views/repositories/annotate.rhtml | 8 | ||||
-rw-r--r-- | app/views/repositories/browse.rhtml | 6 | ||||
-rw-r--r-- | app/views/repositories/changes.rhtml | 10 | ||||
-rw-r--r-- | app/views/repositories/entry.rhtml | 8 | ||||
-rw-r--r-- | app/views/repositories/revision.rhtml | 2 | ||||
-rw-r--r-- | app/views/repositories/revisions.rhtml | 2 | ||||
-rw-r--r-- | app/views/repositories/show.rhtml | 13 |
13 files changed, 149 insertions, 90 deletions
diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 201845fa5..bddaa77d8 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -64,31 +64,26 @@ class RepositoriesController < ApplicationController redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'repository' end - def show - # check if new revisions have been committed in the repository - @repository.fetch_changesets if Setting.autofetch_changesets? - # root entries - @entries = @repository.entries('', @rev) - # latest changesets - @changesets = @repository.changesets.find(:all, :limit => 10, :order => "committed_on DESC") - show_error_not_found unless @entries || @changesets.any? - end - - def browse + def show + @repository.fetch_changesets if Setting.autofetch_changesets? && @path.empty? + @entries = @repository.entries(@path, @rev) if request.xhr? @entries ? render(:partial => 'dir_list_content') : render(:nothing => true) else show_error_not_found and return unless @entries + @changesets = @repository.latest_changesets(@path, @rev) @properties = @repository.properties(@path, @rev) - render :action => 'browse' + render :action => 'show' end end + + alias_method :browse, :show def changes @entry = @repository.entry(@path, @rev) show_error_not_found and return unless @entry - @changesets = @repository.changesets_for_path(@path, :limit => Setting.repository_log_display_limit.to_i) + @changesets = @repository.latest_changesets(@path, @rev, Setting.repository_log_display_limit.to_i) @properties = @repository.properties(@path, @rev) end @@ -135,7 +130,7 @@ class RepositoriesController < ApplicationController end def revision - @changeset = @repository.changesets.find_by_revision(@rev) + @changeset = @repository.changesets.find(:first, :conditions => ["revision LIKE ?", @rev + '%']) raise ChangesetNotFound unless @changeset respond_to do |format| @@ -199,17 +194,14 @@ private render_404 end - REV_PARAM_RE = %r{^[a-f0-9]*$} - def find_repository @project = Project.find(params[:id]) @repository = @project.repository render_404 and return false unless @repository @path = params[:path].join('/') unless params[:path].nil? @path ||= '' - @rev = params[:rev] + @rev = params[:rev].blank? ? @repository.default_branch : params[:rev].strip @rev_to = params[:rev_to] - raise InvalidRevisionParam unless @rev.to_s.match(REV_PARAM_RE) && @rev.to_s.match(REV_PARAM_RE) rescue ActiveRecord::RecordNotFound render_404 rescue InvalidRevisionParam diff --git a/app/models/repository.rb b/app/models/repository.rb index bf181bfad..860395b5c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -62,6 +62,18 @@ class Repository < ActiveRecord::Base def entries(path=nil, identifier=nil) scm.entries(path, identifier) end + + def branches + scm.branches + end + + def tags + scm.tags + end + + def default_branch + scm.default_branch + end def properties(path, identifier=nil) scm.properties(path, identifier) @@ -92,11 +104,15 @@ class Repository < ActiveRecord::Base def latest_changeset @latest_changeset ||= changesets.find(:first) end + + def latest_changesets(path,rev,limit=10) + @latest_changesets ||= changesets.find(:all, limit, :order => "committed_on DESC") + end def scan_changesets_for_issue_ids self.changesets.each(&:scan_comment_for_issue_ids) end - + # Returns an array of committers usernames and associated user_id def committers @committers ||= Changeset.connection.select_rows("SELECT DISTINCT committer, user_id FROM #{Changeset.table_name} WHERE repository_id = #{id}") diff --git a/app/models/repository/git.rb b/app/models/repository/git.rb index f721b938f..b3cdf3643 100644 --- a/app/models/repository/git.rb +++ b/app/models/repository/git.rb @@ -29,43 +29,60 @@ class Repository::Git < Repository 'Git' end + def branches + scm.branches + end + + def tags + scm.tags + end + def changesets_for_path(path, options={}) - Change.find(:all, :include => {:changeset => :user}, - :conditions => ["repository_id = ? AND path = ?", id, path], - :order => "committed_on DESC, #{Changeset.table_name}.revision DESC", - :limit => options[:limit]).collect(&:changeset) + Change.find( + :all, + :include => {:changeset => :user}, + :conditions => ["repository_id = ? AND path = ?", id, path], + :order => "committed_on DESC, #{Changeset.table_name}.revision DESC", + :limit => options[:limit] + ).collect(&:changeset) end + # With SCM's that have a sequential commit numbering, redmine is able to be + # clever and only fetch changesets going forward from the most recent one + # it knows about. However, with git, you never know if people have merged + # commits into the middle of the repository history, so we always have to + # parse the entire log. def fetch_changesets - scm_info = scm.info - if scm_info - # latest revision found in database - db_revision = latest_changeset ? latest_changeset.revision : nil - # latest revision in the repository - scm_revision = scm_info.lastrev.scmid + # Save ourselves an expensive operation if we're already up to date + return if scm.num_revisions == changesets.count + + revisions = scm.revisions('', nil, nil, :all => true) + return if revisions.nil? || revisions.empty? + + # Find revisions that redmine knows about already + existing_revisions = changesets.find(:all).map!{|c| c.scmid} + + # Clean out revisions that are no longer in git + Changeset.delete_all(["scmid NOT IN (?) AND repository_id = (?)", revisions.map{|r| r.scmid}, self.id]) + + # Subtract revisions that redmine already knows about + revisions.reject!{|r| existing_revisions.include?(r.scmid)} + + # Save the remaining ones to the database + revisions.each{|r| r.save(self)} unless revisions.nil? + end + + def latest_changesets(path,rev,limit=10) + revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) + return [] if revisions.nil? || revisions.empty? - unless changesets.find_by_scmid(scm_revision) - scm.revisions('', db_revision, nil, :reverse => true) do |revision| - if changesets.find_by_scmid(revision.scmid.to_s).nil? - transaction do - changeset = Changeset.create!(:repository => self, - :revision => revision.identifier, - :scmid => revision.scmid, - :committer => revision.author, - :committed_on => revision.time, - :comments => revision.message) - - revision.paths.each do |change| - Change.create!(:changeset => changeset, - :action => change[:action], - :path => change[:path], - :from_path => change[:from_path], - :from_revision => change[:from_revision]) - end - end - end - end - end - end + changesets.find( + :all, + :conditions => [ + "scmid IN (?)", + revisions.map!{|c| c.scmid} + ], + :order => 'committed_on DESC' + ) end end diff --git a/app/views/repositories/_breadcrumbs.rhtml b/app/views/repositories/_breadcrumbs.rhtml new file mode 100644 index 000000000..42d11e1a4 --- /dev/null +++ b/app/views/repositories/_breadcrumbs.rhtml @@ -0,0 +1,21 @@ +<%= link_to 'root', :action => 'show', :id => @project, :path => '', :rev => @rev %> +<% +dirs = path.split('/') +if 'file' == kind + filename = dirs.pop +end +link_path = '' +dirs.each do |dir| + next if dir.blank? + link_path << '/' unless link_path.empty? + link_path << "#{dir}" + %> + / <%= link_to h(dir), :action => 'show', :id => @project, :path => to_path_param(link_path), :rev => @rev %> +<% end %> +<% if filename %> + / <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> +<% end %> + +<%= "@ #{revision}" if revision %> + +<% html_title(with_leading_slash(path)) -%> diff --git a/app/views/repositories/_dir_list_content.rhtml b/app/views/repositories/_dir_list_content.rhtml index bcffed4a5..8b6a067b3 100644 --- a/app/views/repositories/_dir_list_content.rhtml +++ b/app/views/repositories/_dir_list_content.rhtml @@ -4,7 +4,7 @@ <tr id="<%= tr_id %>" class="<%= params[:parent_id] %> entry <%= entry.kind %>"> <td style="padding-left: <%=18 * depth%>px;" class="filename"> <% if entry.is_dir? %> -<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}, +<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}, :method => :get, :update => { :success => tr_id }, :position => :after, @@ -12,7 +12,7 @@ :condition => "scmEntryClick('#{tr_id}')"%>"> </span> <% end %> <%= link_to h(entry.name), - {:action => (entry.is_dir? ? 'browse' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev}, + {:action => (entry.is_dir? ? 'show' : 'changes'), :id => @project, :path => to_path_param(entry.path), :rev => @rev}, :class => (entry.is_dir? ? 'icon icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(entry.name)}")%> </td> <td class="size"><%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %></td> diff --git a/app/views/repositories/_navigation.rhtml b/app/views/repositories/_navigation.rhtml index 25a15f496..d1417a61c 100644 --- a/app/views/repositories/_navigation.rhtml +++ b/app/views/repositories/_navigation.rhtml @@ -1,21 +1,21 @@ -<%= link_to 'root', :action => 'browse', :id => @project, :path => '', :rev => @rev %> -<% -dirs = path.split('/') -if 'file' == kind - filename = dirs.pop -end -link_path = '' -dirs.each do |dir| - next if dir.blank? - link_path << '/' unless link_path.empty? - link_path << "#{dir}" - %> - / <%= link_to h(dir), :action => 'browse', :id => @project, :path => to_path_param(link_path), :rev => @rev %> -<% end %> -<% if filename %> - / <%= link_to h(filename), :action => 'changes', :id => @project, :path => to_path_param("#{link_path}/#{filename}"), :rev => @rev %> +<% content_for :header_tags do %> + <%= javascript_include_tag 'repository_navigation' %> <% end %> -<%= "@ #{revision}" if revision %> +<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> + +<% form_tag({:action => controller.action_name, :id => @project, :path => @path, :rev => ''}, {:method => :get, :id => 'revision_selector'}) do -%> + <!-- Branches Dropdown --> + <% if !@repository.branches.nil? && @repository.branches.length > 0 -%> + | <%= l(:label_branch) %>: + <%= select_tag :branch, options_for_select([''] + @repository.branches,@rev), :id => 'branch' %> + <% end -%> + + <% if !@repository.tags.nil? && @repository.tags.length > 0 -%> + | <%= l(:label_tag) %>: + <%= select_tag :tag, options_for_select([''] + @repository.tags,@rev), :id => 'tag' %> + <% end -%> -<% html_title(with_leading_slash(path)) -%> + | <%= l(:label_revision) %>: + <%= text_field_tag 'rev', @rev, :size => 8 %> +<% end -%> diff --git a/app/views/repositories/annotate.rhtml b/app/views/repositories/annotate.rhtml index d0fb8cbf9..fd4d63f3a 100644 --- a/app/views/repositories/annotate.rhtml +++ b/app/views/repositories/annotate.rhtml @@ -1,4 +1,10 @@ -<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> +<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> + +<div class="contextual"> + <%= render :partial => 'navigation' %> +</div> + +<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> <p><%= render :partial => 'link_to_functions' %></p> diff --git a/app/views/repositories/browse.rhtml b/app/views/repositories/browse.rhtml index 3bf320cef..fc769aa22 100644 --- a/app/views/repositories/browse.rhtml +++ b/app/views/repositories/browse.rhtml @@ -1,10 +1,8 @@ <div class="contextual"> -<% form_tag({}, :method => :get) do %> -<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> -<% end %> +<%= render :partial => 'navigation' %> </div> -<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2> +<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2> <%= render :partial => 'dir_list' %> <%= render_properties(@properties) %> diff --git a/app/views/repositories/changes.rhtml b/app/views/repositories/changes.rhtml index aa359ef4d..3d4c7a96b 100644 --- a/app/views/repositories/changes.rhtml +++ b/app/views/repositories/changes.rhtml @@ -1,4 +1,12 @@ -<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %></h2> +<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> + +<div class="contextual"> + <%= render :partial => 'navigation' %> +</div> + +<h2> + <%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => (@entry ? @entry.kind : nil), :revision => @rev } %> +</h2> <p><%= render :partial => 'link_to_functions' %></p> diff --git a/app/views/repositories/entry.rhtml b/app/views/repositories/entry.rhtml index 12ba9f428..1e198806d 100644 --- a/app/views/repositories/entry.rhtml +++ b/app/views/repositories/entry.rhtml @@ -1,4 +1,10 @@ -<h2><%= render :partial => 'navigation', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> +<%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> + +<div class="contextual"> + <%= render :partial => 'navigation' %> +</div> + +<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'file', :revision => @rev } %></h2> <p><%= render :partial => 'link_to_functions' %></p> diff --git a/app/views/repositories/revision.rhtml b/app/views/repositories/revision.rhtml index b60b2a22a..f992f046d 100644 --- a/app/views/repositories/revision.rhtml +++ b/app/views/repositories/revision.rhtml @@ -14,7 +14,7 @@ » <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %> - <%= text_field_tag 'rev', @rev, :size => 5 %> + <%= text_field_tag 'rev', @rev[0,8], :size => 8 %> <%= submit_tag 'OK', :name => nil %> <% end %> </div> diff --git a/app/views/repositories/revisions.rhtml b/app/views/repositories/revisions.rhtml index c06c204cd..255cb6221 100644 --- a/app/views/repositories/revisions.rhtml +++ b/app/views/repositories/revisions.rhtml @@ -1,6 +1,6 @@ <div class="contextual"> <% form_tag({:action => 'revision', :id => @project}) do %> -<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> +<%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 8 %> <%= submit_tag 'OK' %> <% end %> </div> diff --git a/app/views/repositories/show.rhtml b/app/views/repositories/show.rhtml index a0f7dc33c..aae6571f0 100644 --- a/app/views/repositories/show.rhtml +++ b/app/views/repositories/show.rhtml @@ -1,15 +1,10 @@ -<div class="contextual"> <%= call_hook(:view_repositories_show_contextual, { :repository => @repository, :project => @project }) %> -<%= link_to l(:label_statistics), {:action => 'stats', :id => @project}, :class => 'icon icon-stats' %> -<% if !@entries.nil? && authorize_for('repositories', 'browse') -%> -<% form_tag({:action => 'browse', :id => @project}, :method => :get) do -%> -| <%= l(:label_revision) %>: <%= text_field_tag 'rev', @rev, :size => 5 %> -<% end -%> -<% end -%> +<div class="contextual"> + <%= render :partial => 'navigation' %> </div> -<h2><%= l(:label_repository) %> (<%= @repository.scm_name %>)</h2> +<h2><%= render :partial => 'breadcrumbs', :locals => { :path => @path, :kind => 'dir', :revision => @rev } %></h2> <% if !@entries.nil? && authorize_for('repositories', 'browse') %> <%= render :partial => 'dir_list' %> @@ -18,7 +13,7 @@ <% if !@changesets.empty? && authorize_for('repositories', 'revisions') %> <h3><%= l(:label_latest_revision_plural) %></h3> <%= render :partial => 'revisions', :locals => {:project => @project, :path => '', :revisions => @changesets, :entry => nil }%> -<p><%= link_to l(:label_view_revisions), :action => 'revisions', :id => @project %></p> +<p><%= link_to l(:label_view_all_revisions), :action => 'revisions', :id => @project %></p> <% content_for :header_tags do %> <%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :action => 'revisions', :id => @project, :page => nil, :key => User.current.rss_key})) %> <% end %> |