Merge pull request #175 from warpc/133-editing_from_web

[Refs #133] Edit files in git-repo with web editor
This commit is contained in:
Vladimir Sharshov 2012-02-13 13:07:55 -08:00
commit 141a078cce
15 changed files with 225 additions and 18 deletions

View File

@ -54,14 +54,14 @@ GEM
activerecord (>= 2.2.2) activerecord (>= 2.2.2)
arel (2.0.10) arel (2.0.10)
bcrypt-ruby (3.0.1) bcrypt-ruby (3.0.1)
bluepill (0.0.52) bluepill (0.0.55)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
daemons (~> 1.1.0) daemons (~> 1.1.4)
i18n (>= 0.5.0) i18n (>= 0.5.0)
state_machine (~> 1.1.0) state_machine (~> 1.1.0)
builder (2.1.2) builder (2.1.2)
cancan (1.6.7) cancan (1.6.7)
cape (1.2.0) cape (1.4.0)
capistrano (2.9.0) capistrano (2.9.0)
highline highline
net-scp (>= 1.0.0) net-scp (>= 1.0.0)
@ -74,7 +74,7 @@ GEM
chronic (0.6.7) chronic (0.6.7)
cocaine (0.2.1) cocaine (0.2.1)
creole (0.4.2) creole (0.4.2)
daemons (1.1.6) daemons (1.1.8)
delayed_job (2.1.4) delayed_job (2.1.4)
activesupport (~> 3.0) activesupport (~> 3.0)
daemons daemons
@ -92,7 +92,7 @@ GEM
factory_girl_rails (1.4.0) factory_girl_rails (1.4.0)
factory_girl (~> 2.3.0) factory_girl (~> 2.3.0)
railties (>= 3.0.0) railties (>= 3.0.0)
github-markup (0.7.0) github-markup (0.7.1)
gollum (1.3.1) gollum (1.3.1)
albino (~> 1.3.2) albino (~> 1.3.2)
github-markup (>= 0.4.0, < 1.0.0) github-markup (>= 0.4.0, < 1.0.0)
@ -152,7 +152,7 @@ GEM
omniauth (~> 1.0) omniauth (~> 1.0)
rack-openid (~> 1.3.1) rack-openid (~> 1.3.1)
orm_adapter (0.0.6) orm_adapter (0.0.6)
paperclip (2.5.2) paperclip (2.6.0)
activerecord (>= 2.3.0) activerecord (>= 2.3.0)
activesupport (>= 2.3.2) activesupport (>= 2.3.2)
cocaine (>= 0.0.2) cocaine (>= 0.0.2)
@ -179,7 +179,7 @@ GEM
rails-xmlrpc (0.3.6) rails-xmlrpc (0.3.6)
rails3-generators (0.17.4) rails3-generators (0.17.4)
railties (>= 3.0.0) railties (>= 3.0.0)
rails3-jquery-autocomplete (1.0.5) rails3-jquery-autocomplete (1.0.6)
rails (~> 3.0) rails (~> 3.0)
railties (3.0.11) railties (3.0.11)
actionpack (= 3.0.11) actionpack (= 3.0.11)

View File

@ -5,6 +5,7 @@ class Git::BlobsController < Git::BaseController
before_filter :set_commit_hash before_filter :set_commit_hash
def show def show
redirect_to project_repo_path(@project) and return unless @blob.present?
if params[:raw] if params[:raw]
image_url = Rails.root.to_s + "/" + @path image_url = Rails.root.to_s + "/" + @path
@ -16,11 +17,34 @@ class Git::BlobsController < Git::BaseController
end end
end end
def edit
redirect_to project_repo_path(@project) and return unless @blob.present?
authorize! :write, @project
end
def update
redirect_to project_repo_path(@project) and return unless @blob.present?
authorize! :write, @project
# Here might be callbacks for notification purposes:
# @git_repository.after_update_file do |repo, sha|
# end
res = @git_repository.update_file(params[:path], params[:content],
:message => params[:message], :actor => current_user, :head => @treeish)
if res
flash[:notice] = t("flash.blob.successfully_updated", :name => params[:path].encode_to_default)
else
flash[:notice] = t("flash.blob.updating_error", :name => params[:path].encode_to_default)
end
redirect_to :action => :show
end
def blame def blame
@blame = Grit::Blob.blame(@git_repository.repo, @commit.try(:id), @path) @blame = Grit::Blob.blame(@git_repository.repo, @commit.try(:id), @path)
end end
def raw def raw
redirect_to project_repo_path(@project) and return unless @blob.present?
headers["Content-Disposition"] = %[attachment;filename="#{@blob.name}"] headers["Content-Disposition"] = %[attachment;filename="#{@blob.name}"]
render :text => @blob.data, :content_type => @blob.mime_type render :text => @blob.data, :content_type => @blob.mime_type
end end
@ -28,7 +52,8 @@ class Git::BlobsController < Git::BaseController
protected protected
def set_path_blob def set_path_blob
@path = params[:path] @path = params[:path]
@blob = @tree / @path.encode_to_default @path.force_encoding(Encoding::ASCII_8BIT)
@blob = @tree / @path
end end
def set_commit_hash def set_commit_hash

View File

@ -3,6 +3,7 @@ class Git::TreesController < Git::BaseController
def show def show
@path = params[:path] @path = params[:path]
@path.force_encoding(Encoding::ASCII_8BIT) if @path
@tree = @git_repository.tree(@treeish) @tree = @git_repository.tree(@treeish)

View File

@ -29,6 +29,14 @@ module GitHelper
res.encode_to_default.html_safe res.encode_to_default.html_safe
end end
def blob_file_path
if @commit_hash.present?
blob_commit_path(@project, @commit_hash, @path)
else
blob_path(@project, @treeish, @path)
end
end
def render_line_numbers(n) def render_line_numbers(n)
res = "" res = ""
1.upto(n) {|i| res += "<span>#{i}</span>\n" } 1.upto(n) {|i| res += "<span>#{i}</span>\n" }

View File

@ -2,16 +2,17 @@
class Git::Repository class Git::Repository
delegate :commits, :commit, :tree, :tags, :heads, :commit_count, :log, :branches, :to => :repo delegate :commits, :commit, :tree, :tags, :heads, :commit_count, :log, :branches, :to => :repo
attr_accessor :path, :name attr_accessor :path, :name, :repo, :last_actor
def initialize(path) def initialize(path)
@path = path @path = path
@update_callbacks = []
end end
def master def master
commits('master', 1).first commits('master', 1).first
end end
def to_s def to_s
name name
end end
@ -20,6 +21,65 @@ class Git::Repository
@repo ||= Grit::Repo.new(path) @repo ||= Grit::Repo.new(path)
end end
# Adds a callback to be fired after update file.
#
# block - A block that expects this Git::Repository instance and the created
# commit's SHA1 as the arguments.
#
# For example:
#
# after_update_file do |repo, sha|
# # callback body
# end
#
# Returns nothing.
def after_update_file(&block)
@update_callbacks << block
end
# Writes file to repo and runs 'after_update_file' callbacks
#
# path - path to file in repository
# data - new content of file
# options - an optional Hash of options
# :head - branch name to write this commit to
# (Default: 'master')
# :actor - author of this commit. (See Git::Repository#get_actor)
# (Default: nil)
# :message - commit message
# (Default: "Updated file <filename>")
#
# Returns commits sha if committing was successful and false otherwise
def update_file(path, data, options = {})
path.force_encoding(Encoding::ASCII_8BIT) # some magic
head = options[:head].to_s || 'master'
actor = get_actor(options[:actor])
filename = File.split(path).last
message = options[:message]
message = "Updated file #{filename}" if message.nil? or message.empty?
# can not write to unexisted branch
return false if branches.select{|b| b.name == head}.size != 1
parent = commits(head).first
index = repo.index
index.read_tree(parent.tree.id)
# can not create new file
return false if (index.current_tree / path).nil?
index.add(path, data)
sha = index.commit(message, :parents => [parent], :actor => actor,
:last_tree => parent.tree.id, :head => head)
# call all defined callbacks
@update_callbacks.each do |cb|
cb.call(self, sha)
end
sha
end
def self.create(path) def self.create(path)
repo = Grit::Repo.init_bare(path) repo = Grit::Repo.init_bare(path)
repo.enable_daemon_serve repo.enable_daemon_serve
@ -38,4 +98,35 @@ class Git::Repository
[commits(treeish, options[:per_page], skip), options[:page], last_page] [commits(treeish, options[:per_page], skip), options[:page], last_page]
end end
# Pretty object inspection
def inspect
%Q{#<Git::Repository "#{@path}">}
end
protected
# Creates new Grit::Actor instance
#
# Might be:
# * A Hash containing :name and :email
# * An instance of Grit::Actor
# * A String like "John Doe <j.doe@example.com>
# * Any object that responds to `name` and `email` methods
def get_actor(actor = nil)
@last_actor = case actor.class.to_s
when 'Grit::Actor' then options[:actor]
when 'Hash' then Grit::Actor.new(actor[:name], actor[:email])
when 'String' then Grit::Actor.from_stirng(actor)
else begin
if actor.respond_to?(:name) and actor.respond_to?(:email)
Grit::Actor.new(actor.name, actor.email)
else
config = Grit::Config.new(repo)
Grit::Actor.new(config['user.name'], config['user.email'])
end
end
end
@last_actor
end
end end

View File

@ -0,0 +1,26 @@
#gollum-editor.edit{:'data-escaped-name' => @path.encode_to_default}
= form_tag blob_file_path, :name => 'blob-editor', :method => :put do
%fieldset#gollum-editor-fields
= text_area_tag :content, @blob.data.encode_to_default, :id => "gollum-editor-body"
#gollum-editor-edit-summary.singleline
= label_tag :message, t("layout.wiki.edit_commit_message"), :class => "jaws"
= text_field_tag :message, t("layout.wiki.commit_message_placeholder"), :id => "editor-commit-message-field"
%span.jaws
%br
= submit_tag t("layout.wiki.save_button"), :id => "gollum-editor-submit", :title => t("layout.wiki.save_changes")
= link_to t("layout.cancel"), blob_file_path, :class => 'minibutton', :id => 'gollum-editor-preview'
:javascript
$(function() {
$.BlobEditor();
});
- content_for :javascripts do
= javascript_include_tag 'gollum/gollum.placeholder.js', 'blob.editor.js'
- content_for :stylesheets do
= stylesheet_link_tag 'gollum/editor.css'

View File

@ -0,0 +1,20 @@
.block
= render :partial => "git/shared/navigation"
= render :partial => "git/shared/info"
- if @commit
.block
.content
.inner
= render :partial => "git/commits/commits", :object => [@commit]
.block
.content
.inner
%h3 #{render_path} (#{@blob.mime_type})
= render :partial => 'editor'
- content_for :sidebar, render(:partial => 'git/shared/sidebar')

View File

@ -19,9 +19,14 @@
.size #{(@blob.size / 1024.0).round(3)} Kb .size #{(@blob.size / 1024.0).round(3)} Kb
.buttons .buttons
- if @commit_hash - if @commit_hash
#{link_to "Raw", raw_commit_path(@project, @commit_hash, @path)} #{link_to "Blame", blame_commit_path(@project, @commit_hash, @path)} #{link_to "History", commits_path(@project, @treeish, @path)} #{link_to "Raw", raw_commit_path(@project, @commit_hash, @path)}
#{link_to "Blame", blame_commit_path(@project, @commit_hash, @path)}
#{link_to "History", commits_path(@project, @treeish, @path)}
- else - else
#{link_to "Raw", raw_path(@project, @treeish, @path)} #{link_to "Blame", blame_path(@project, @treeish, @path)} #{link_to "History", commits_path(@project, @treeish, @path)} #{link_to "Edit", edit_blob_path(@project, @treeish, @path) if choose_render_way(@blob) == :text and can? :write, @project}
#{link_to "Raw", raw_path(@project, @treeish, @path)}
#{link_to "Blame", blame_path(@project, @treeish, @path)}
#{link_to "History", commits_path(@project, @treeish, @path)}
.clear .clear
- case choose_render_way(@blob) - case choose_render_way(@blob)
- when :image - when :image

View File

@ -36,10 +36,13 @@
- else - else
= image_tag("git/icons/folder_16.png") = image_tag("git/icons/folder_16.png")
%td.tree_element %td.tree_element
- entry_path = File.join([@path.present? ? @path.encode_to_default : nil, entry.name.encode_to_default].compact)
- if entry.is_a?(Grit::Blob) - if entry.is_a?(Grit::Blob)
= link_to entry.name.encode_to_default, blob_path(@project, @treeish, File.join([@path, entry.name.encode_to_default].compact)) = link_to entry.name.encode_to_default,
blob_path(@project, @treeish.encode_to_default, entry_path)
- else - else
= link_to "#{entry.name.encode_to_default}/", tree_path(@project, @treeish, File.join([@path, entry.name.encode_to_default].compact)) = link_to "#{entry.name.encode_to_default}/",
tree_path(@project, @treeish.encode_to_default, entry_path)
%td== &nbsp; %td== &nbsp;
%td.last== &nbsp; %td.last== &nbsp;

View File

@ -692,6 +692,10 @@ en:
revert_success: Changes successfully reverted revert_success: Changes successfully reverted
patch_does_not_apply: Patch does not apply patch_does_not_apply: Patch does not apply
blob:
successfully_updated: File '%{name}' successfully updated
updating_error: Error updating file '%{name}'
attributes: attributes:
password: Password password: Password
password_confirmation: Confirmation password_confirmation: Confirmation

View File

@ -549,6 +549,10 @@ ru:
revert_success: Изменения успешно откачены revert_success: Изменения успешно откачены
patch_does_not_apply: Не удалось откатить изменения patch_does_not_apply: Не удалось откатить изменения
blob:
successfully_updated: Файл '%{name}' успешно обновлен
updating_error: Ошибка обновления файла '%{name}'
attributes: attributes:
password: Пароль password: Пароль
password_confirmation: Подтверждение password_confirmation: Подтверждение

View File

@ -188,12 +188,18 @@ Rosa::Application.routes.draw do
match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :update, :as => :project_commit_comment, :via => :put match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :update, :as => :project_commit_comment, :via => :put
match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :destroy, :via => :delete match '/projects/:project_id/git/commit/:commit_id/comments/:id(.:format)', :controller => "comments", :action => :destroy, :via => :delete
match '/projects/:project_id/git/commit/:commit_id/comments(.:format)', :controller => "comments", :action => :create, :as => :project_commit_comments, :via => :post match '/projects/:project_id/git/commit/:commit_id/comments(.:format)', :controller => "comments", :action => :create, :as => :project_commit_comments, :via => :post
# Commits subscribe # Commits subscribe
match '/projects/:project_id/git/commit/:commit_id/subscribe', :controller => "commit_subscribes", :action => :create, :defaults => { :format => :html }, :as => :subscribe_commit, :via => :post match '/projects/:project_id/git/commit/:commit_id/subscribe', :controller => "commit_subscribes", :action => :create, :defaults => { :format => :html }, :as => :subscribe_commit, :via => :post
match '/projects/:project_id/git/commit/:commit_id/unsubscribe', :controller => "commit_subscribes", :action => :destroy, :defaults => { :format => :html }, :as => :unsubscribe_commit, :via => :delete match '/projects/:project_id/git/commit/:commit_id/unsubscribe', :controller => "commit_subscribes", :action => :destroy, :defaults => { :format => :html }, :as => :unsubscribe_commit, :via => :delete
# Editing files
match '/projects/:project_id/git/blob/:treeish/*path/edit', :controller => "git/blobs", :action => :edit, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :edit_blob, :via => :get
match '/projects/:project_id/git/blob/:treeish/*path', :controller => "git/blobs", :action => :update, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :via => :put
# Blobs # Blobs
match '/projects/:project_id/git/blob/:treeish/*path', :controller => "git/blobs", :action => :show, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blob match '/projects/:project_id/git/blob/:treeish/*path', :controller => "git/blobs", :action => :show, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blob, :via => :get
match '/projects/:project_id/git/commit/blob/:commit_hash/*path', :controller => "git/blobs", :action => :show, :project_name => /[0-9a-zA-Z_.\-]*/, :as => :blob_commit match '/projects/:project_id/git/commit/blob/:commit_hash/*path', :controller => "git/blobs", :action => :show, :project_id => /[0-9a-zA-Z_.\-]*/, :as => :blob_commit, :via => :get
# Blame # Blame
match '/projects/:project_id/git/blame/:treeish/*path', :controller => "git/blobs", :action => :blame, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blame match '/projects/:project_id/git/blame/:treeish/*path', :controller => "git/blobs", :action => :blame, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blame

View File

@ -254,11 +254,11 @@ ActiveRecord::Schema.define(:version => 20120210141153) do
t.text "description" t.text "description"
t.string "ancestry" t.string "ancestry"
t.boolean "has_issues", :default => true t.boolean "has_issues", :default => true
t.boolean "has_wiki", :default => false
t.string "srpm_file_name" t.string "srpm_file_name"
t.string "srpm_content_type" t.string "srpm_content_type"
t.integer "srpm_file_size" t.integer "srpm_file_size"
t.datetime "srpm_updated_at" t.datetime "srpm_updated_at"
t.boolean "has_wiki", :default => false
end end
add_index "projects", ["category_id"], :name => "index_projects_on_category_id" add_index "projects", ["category_id"], :name => "index_projects_on_category_id"

View File

@ -0,0 +1,14 @@
(function($) {
$.BlobEditor = function() {
$.BlobEditor.Placeholder.add($('#gollum-editor-edit-summary input'));
$('#gollum-editor form[name="blob-editor"]').submit(function( e ) {
e.preventDefault();
$.BlobEditor.Placeholder.clearAll();
//debug('submitting');
$(this).unbind('submit');
$(this).submit();
});
};
$.BlobEditor.Placeholder = $.GollumPlaceholder;
})(jQuery);

View File

@ -37,7 +37,7 @@ li.commit .message {
li.commit .trees { li.commit .trees {
padding-left: 5px; padding-left: 5px;
width: 180px; width: 200px;
} }
li.commit .message a { li.commit .message a {