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:
commit
141a078cce
14
Gemfile.lock
14
Gemfile.lock
|
@ -54,14 +54,14 @@ GEM
|
|||
activerecord (>= 2.2.2)
|
||||
arel (2.0.10)
|
||||
bcrypt-ruby (3.0.1)
|
||||
bluepill (0.0.52)
|
||||
bluepill (0.0.55)
|
||||
activesupport (>= 3.0.0)
|
||||
daemons (~> 1.1.0)
|
||||
daemons (~> 1.1.4)
|
||||
i18n (>= 0.5.0)
|
||||
state_machine (~> 1.1.0)
|
||||
builder (2.1.2)
|
||||
cancan (1.6.7)
|
||||
cape (1.2.0)
|
||||
cape (1.4.0)
|
||||
capistrano (2.9.0)
|
||||
highline
|
||||
net-scp (>= 1.0.0)
|
||||
|
@ -74,7 +74,7 @@ GEM
|
|||
chronic (0.6.7)
|
||||
cocaine (0.2.1)
|
||||
creole (0.4.2)
|
||||
daemons (1.1.6)
|
||||
daemons (1.1.8)
|
||||
delayed_job (2.1.4)
|
||||
activesupport (~> 3.0)
|
||||
daemons
|
||||
|
@ -92,7 +92,7 @@ GEM
|
|||
factory_girl_rails (1.4.0)
|
||||
factory_girl (~> 2.3.0)
|
||||
railties (>= 3.0.0)
|
||||
github-markup (0.7.0)
|
||||
github-markup (0.7.1)
|
||||
gollum (1.3.1)
|
||||
albino (~> 1.3.2)
|
||||
github-markup (>= 0.4.0, < 1.0.0)
|
||||
|
@ -152,7 +152,7 @@ GEM
|
|||
omniauth (~> 1.0)
|
||||
rack-openid (~> 1.3.1)
|
||||
orm_adapter (0.0.6)
|
||||
paperclip (2.5.2)
|
||||
paperclip (2.6.0)
|
||||
activerecord (>= 2.3.0)
|
||||
activesupport (>= 2.3.2)
|
||||
cocaine (>= 0.0.2)
|
||||
|
@ -179,7 +179,7 @@ GEM
|
|||
rails-xmlrpc (0.3.6)
|
||||
rails3-generators (0.17.4)
|
||||
railties (>= 3.0.0)
|
||||
rails3-jquery-autocomplete (1.0.5)
|
||||
rails3-jquery-autocomplete (1.0.6)
|
||||
rails (~> 3.0)
|
||||
railties (3.0.11)
|
||||
actionpack (= 3.0.11)
|
||||
|
|
|
@ -5,6 +5,7 @@ class Git::BlobsController < Git::BaseController
|
|||
before_filter :set_commit_hash
|
||||
|
||||
def show
|
||||
redirect_to project_repo_path(@project) and return unless @blob.present?
|
||||
if params[:raw]
|
||||
image_url = Rails.root.to_s + "/" + @path
|
||||
|
||||
|
@ -16,11 +17,34 @@ class Git::BlobsController < Git::BaseController
|
|||
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
|
||||
@blame = Grit::Blob.blame(@git_repository.repo, @commit.try(:id), @path)
|
||||
end
|
||||
|
||||
def raw
|
||||
redirect_to project_repo_path(@project) and return unless @blob.present?
|
||||
headers["Content-Disposition"] = %[attachment;filename="#{@blob.name}"]
|
||||
render :text => @blob.data, :content_type => @blob.mime_type
|
||||
end
|
||||
|
@ -28,7 +52,8 @@ class Git::BlobsController < Git::BaseController
|
|||
protected
|
||||
def set_path_blob
|
||||
@path = params[:path]
|
||||
@blob = @tree / @path.encode_to_default
|
||||
@path.force_encoding(Encoding::ASCII_8BIT)
|
||||
@blob = @tree / @path
|
||||
end
|
||||
|
||||
def set_commit_hash
|
||||
|
|
|
@ -3,6 +3,7 @@ class Git::TreesController < Git::BaseController
|
|||
|
||||
def show
|
||||
@path = params[:path]
|
||||
@path.force_encoding(Encoding::ASCII_8BIT) if @path
|
||||
|
||||
@tree = @git_repository.tree(@treeish)
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@ module GitHelper
|
|||
res.encode_to_default.html_safe
|
||||
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)
|
||||
res = ""
|
||||
1.upto(n) {|i| res += "<span>#{i}</span>\n" }
|
||||
|
|
|
@ -2,16 +2,17 @@
|
|||
class Git::Repository
|
||||
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)
|
||||
@path = path
|
||||
@update_callbacks = []
|
||||
end
|
||||
|
||||
def master
|
||||
commits('master', 1).first
|
||||
end
|
||||
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
@ -20,6 +21,65 @@ class Git::Repository
|
|||
@repo ||= Grit::Repo.new(path)
|
||||
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)
|
||||
repo = Grit::Repo.init_bare(path)
|
||||
repo.enable_daemon_serve
|
||||
|
@ -38,4 +98,35 @@ class Git::Repository
|
|||
[commits(treeish, options[:per_page], skip), options[:page], last_page]
|
||||
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
|
||||
|
|
|
@ -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'
|
|
@ -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')
|
||||
|
|
@ -19,9 +19,14 @@
|
|||
.size #{(@blob.size / 1024.0).round(3)} Kb
|
||||
.buttons
|
||||
- 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
|
||||
#{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
|
||||
- case choose_render_way(@blob)
|
||||
- when :image
|
||||
|
|
|
@ -36,10 +36,13 @@
|
|||
- else
|
||||
= image_tag("git/icons/folder_16.png")
|
||||
%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)
|
||||
= 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
|
||||
= 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==
|
||||
%td.last==
|
||||
|
||||
|
|
|
@ -692,6 +692,10 @@ en:
|
|||
revert_success: Changes successfully reverted
|
||||
patch_does_not_apply: Patch does not apply
|
||||
|
||||
blob:
|
||||
successfully_updated: File '%{name}' successfully updated
|
||||
updating_error: Error updating file '%{name}'
|
||||
|
||||
attributes:
|
||||
password: Password
|
||||
password_confirmation: Confirmation
|
||||
|
|
|
@ -549,6 +549,10 @@ ru:
|
|||
revert_success: Изменения успешно откачены
|
||||
patch_does_not_apply: Не удалось откатить изменения
|
||||
|
||||
blob:
|
||||
successfully_updated: Файл '%{name}' успешно обновлен
|
||||
updating_error: Ошибка обновления файла '%{name}'
|
||||
|
||||
attributes:
|
||||
password: Пароль
|
||||
password_confirmation: Подтверждение
|
||||
|
|
|
@ -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 => :destroy, :via => :delete
|
||||
match '/projects/:project_id/git/commit/:commit_id/comments(.:format)', :controller => "comments", :action => :create, :as => :project_commit_comments, :via => :post
|
||||
|
||||
# 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/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
|
||||
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/commit/blob/:commit_hash/*path', :controller => "git/blobs", :action => :show, :project_name => /[0-9a-zA-Z_.\-]*/, :as => :blob_commit
|
||||
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_id => /[0-9a-zA-Z_.\-]*/, :as => :blob_commit, :via => :get
|
||||
|
||||
# Blame
|
||||
match '/projects/:project_id/git/blame/:treeish/*path', :controller => "git/blobs", :action => :blame, :treeish => /[0-9a-zA-Z_.\-]*/, :defaults => { :treeish => :master }, :as => :blame
|
||||
|
|
|
@ -254,11 +254,11 @@ ActiveRecord::Schema.define(:version => 20120210141153) do
|
|||
t.text "description"
|
||||
t.string "ancestry"
|
||||
t.boolean "has_issues", :default => true
|
||||
t.boolean "has_wiki", :default => false
|
||||
t.string "srpm_file_name"
|
||||
t.string "srpm_content_type"
|
||||
t.integer "srpm_file_size"
|
||||
t.datetime "srpm_updated_at"
|
||||
t.boolean "has_wiki", :default => false
|
||||
end
|
||||
|
||||
add_index "projects", ["category_id"], :name => "index_projects_on_category_id"
|
||||
|
|
|
@ -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);
|
|
@ -37,7 +37,7 @@ li.commit .message {
|
|||
|
||||
li.commit .trees {
|
||||
padding-left: 5px;
|
||||
width: 180px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
li.commit .message a {
|
||||
|
|
Loading…
Reference in New Issue