diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2013-11-05 11:35:22 +0000 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2013-11-05 11:35:22 +0000 |
commit | 376dd3a3838622f73a444e5621257c831b284809 (patch) | |
tree | d924e72bbaa4b6dd2fbea631620a22dd5253df8f | |
parent | 2ee04b120a142ee227cb860ffddb991ef31a6910 (diff) | |
parent | 3083e5e4cdd0d158bc1c092f3e339eb8f83b2c4a (diff) | |
download | gitlab-ce-376dd3a3838622f73a444e5621257c831b284809.tar.gz |
Merge branch 'feature/create_file' of /home/git/repositories/gitlab/gitlabhq
18 files changed, 315 insertions, 80 deletions
diff --git a/app/contexts/base_context.rb b/app/contexts/base_context.rb index 101be50d54b..6accd9b2457 100644 --- a/app/contexts/base_context.rb +++ b/app/contexts/base_context.rb @@ -17,4 +17,3 @@ class BaseContext abilities.allowed?(object, action, subject) end end - diff --git a/app/contexts/files/base_context.rb b/app/contexts/files/base_context.rb new file mode 100644 index 00000000000..44f9826652c --- /dev/null +++ b/app/contexts/files/base_context.rb @@ -0,0 +1,31 @@ +module Files + class BaseContext < ::BaseContext + attr_reader :ref, :path + + def initialize(project, user, params, ref, path = nil) + @project, @current_user, @params = project, user, params.dup + @ref = ref + @path = path + end + + private + + def error(message) + { + error: message, + status: :error + } + end + + def success + { + error: '', + status: :success + } + end + + def repository + project.repository + end + end +end diff --git a/app/contexts/files/create_context.rb b/app/contexts/files/create_context.rb new file mode 100644 index 00000000000..e1554c47bd6 --- /dev/null +++ b/app/contexts/files/create_context.rb @@ -0,0 +1,50 @@ +module Files + class CreateContext < BaseContext + def execute + allowed = if project.protected_branch?(ref) + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + unless allowed + return error("You are not allowed to create file in this branch") + end + + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end + + file_name = params[:file_name] + + unless file_name =~ Gitlab::Regex.path_regex + return error("Your changes could not be commited, because file name contains not allowed characters") + end + + file_path = if path.blank? + file_name + else + File.join(path, file_name) + end + + blob = repository.blob_at(ref, file_path) + + if blob + return error("Your changes could not be commited, because file with such name exists") + end + + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, path) + created_successfully = new_file_action.commit!( + params[:content], + params[:commit_message], + file_name, + ) + + if created_successfully + success + else + error("Your changes could not be commited, because the file has been changed") + end + end + end +end diff --git a/app/contexts/files/update_context.rb b/app/contexts/files/update_context.rb new file mode 100644 index 00000000000..000d3d02f12 --- /dev/null +++ b/app/contexts/files/update_context.rb @@ -0,0 +1,38 @@ +module Files + class UpdateContext < BaseContext + def execute + allowed = if project.protected_branch?(ref) + can?(current_user, :push_code_to_protected_branches, project) + else + can?(current_user, :push_code, project) + end + + unless allowed + return error("You are not allowed to push into this branch") + end + + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end + + blob = repository.blob_at(ref, path) + + unless blob + return error("You can only edit text files") + end + + new_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) + created_successfully = new_file_action.commit!( + params[:content], + params[:commit_message], + params[:last_commit] + ) + + if created_successfully + success + else + error("Your changes could not be commited, because the file has been changed") + end + end + end +end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 8fd4565f367..80aeb5cd6cc 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -23,4 +23,10 @@ class Projects::ApplicationController < ApplicationController 'public_projects' end end + + def require_branch_head + unless @repository.branch_names.include?(@ref) + redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch" + end + end end diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb new file mode 100644 index 00000000000..5e305934433 --- /dev/null +++ b/app/controllers/projects/base_tree_controller.rb @@ -0,0 +1,8 @@ +class Projects::BaseTreeController < Projects::ApplicationController + include ExtractsPath + + before_filter :authorize_read_project! + before_filter :authorize_code_access! + before_filter :require_non_empty_project +end + diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb index 0e51ff59f39..f6c547a020c 100644 --- a/app/controllers/projects/edit_tree_controller.rb +++ b/app/controllers/projects/edit_tree_controller.rb @@ -1,53 +1,26 @@ -# Controller for edit a repository's file -class Projects::EditTreeController < Projects::ApplicationController - include ExtractsPath - - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project - - before_filter :edit_requirements, only: [:show, :update] +class Projects::EditTreeController < Projects::BaseTreeController + before_filter :require_branch_head + before_filter :blob def show @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha end def update - edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, @project, @ref, @path) - updated_successfully = edit_file_action.commit!( - params[:content], - params[:commit_message], - params[:last_commit] - ) + result = Files::UpdateContext.new(@project, current_user, params, @ref, @path).execute - if updated_successfully - redirect_to project_blob_path(@project, @id), notice: "Your changes have been successfully commited" + if result[:status] == :success + flash[:notice] = "Your changes have been successfully commited" + redirect_to project_blob_path(@project, @id) else - flash[:notice] = "Your changes could not be commited, because the file has been changed" + flash[:alert] = result[:error] render :show end end private - def edit_requirements - @blob = @repository.blob_at(@commit.id, @path) - - unless @blob - redirect_to project_blob_path(@project, @id), notice: "You can only edit text files" - end - - allowed = if project.protected_branch? @ref - can?(current_user, :push_code_to_protected_branches, project) - else - can?(current_user, :push_code, project) - end - - return access_denied! unless allowed - - unless @repository.branch_names.include?(@ref) - redirect_to project_blob_path(@project, @id), notice: "You can only edit this file if you are on top of a branch" - end + def blob + @blob ||= @repository.blob_at(@commit.id, @path) end end diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb new file mode 100644 index 00000000000..9f9e0191e98 --- /dev/null +++ b/app/controllers/projects/new_tree_controller.rb @@ -0,0 +1,18 @@ +class Projects::NewTreeController < Projects::BaseTreeController + before_filter :require_branch_head + + def show + end + + def update + result = Files::CreateContext.new(@project, current_user, params, @ref, @path).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully commited" + redirect_to project_blob_path(@project, File.join(@id, params[:file_name])) + else + flash[:alert] = result[:error] + render :show + end + end +end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 1150efbea9d..30c94ec6da0 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,12 +1,5 @@ # Controller for viewing a repository's file structure -class Projects::TreeController < Projects::ApplicationController - include ExtractsPath - - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project - +class Projects::TreeController < Projects::BaseTreeController def show return not_found! if tree.entries.empty? diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e9d535f6972..1f70cf17987 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -4,7 +4,7 @@ %i.icon-home - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree)) do + = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref) - if project_nav_tab? :commits diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml new file mode 100644 index 00000000000..d5525303d76 --- /dev/null +++ b/app/views/projects/new_tree/show.html.haml @@ -0,0 +1,47 @@ +%h3.page-title New file +%hr +.file-editor + = form_tag(project_new_tree_path(@project, @id), method: :put, class: "form-horizontal") do + .control-group.commit_message-group + = label_tag 'file_name', class: "control-label" do + File name + .controls + %span.monospace= @path[-1] == "/" ? @path : @path + "/" + + = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true + %span + + on + %span.label-branch= @ref + + .control-group.commit_message-group + = label_tag 'commit_message', class: "control-label" do + Commit message + .controls + = text_area_tag 'commit_message', params[:commit_message], placeholder: "Added new file", required: true, rows: 3 + + .file-holder + .file-title + %i.icon-file + .file-content.code + %pre#editor= params[:content] + + .form-actions + = hidden_field_tag 'content', '', id: "file-content" + .commit-button-annotation + = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create' + .message + to branch + %strong= @ref + = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message + +:javascript + ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") + var editor = ace.edit("editor"); + + disableButtonIfEmptyField("#commit_message", ".js-commit-button"); + + $(".js-commit-button").click(function(){ + $("#file-content").val(editor.getValue()); + $(".file-editor form").submit(); + }); diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index eadfd33bd3c..6b5b84f83b0 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -10,6 +10,12 @@ = link_to truncate(title, length: 40), project_tree_path(@project, path) - else = link_to title, '#' + - if @repository.branch_names.include?(@ref) + \/ + %li + = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do + %small + %i.icon-plus.light %div#tree-content-holder.tree-content-holder %table#tree-slider{class: "table_#{@hex_path} tree-table" } diff --git a/config/routes.rb b/config/routes.rb index 78f75d11835..8f1758394b6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -166,16 +166,18 @@ Gitlab::Application.routes.draw do end scope module: :projects do - resources :blob, only: [:show], constraints: {id: /.+/} - resources :raw, only: [:show], constraints: {id: /.+/} - resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } - resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit' - resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} - resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} - resources :compare, only: [:index, :create] - resources :blame, only: [:show], constraints: {id: /.+/} + resources :blob, only: [:show], constraints: {id: /.+/} + resources :raw, only: [:show], constraints: {id: /.+/} + resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } + resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit' + resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new' + resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} + resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} + resources :compare, only: [:index, :create] + resources :blame, only: [:show], constraints: {id: /.+/} resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} - resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} + resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} + match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} resources :snippets, constraints: {id: /\d+/} do diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index ee26f5371a9..fd9a2f01a28 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -20,6 +20,10 @@ Feature: Project Browse files And I click link "raw" Then I should see raw file content + Scenario: I can create file + Given I click on "new file" link in repo + Then I can see new file page + @javascript Scenario: I can edit file Given I click on "Gemfile.lock" file in repo diff --git a/features/steps/project/project_browse_files.rb b/features/steps/project/project_browse_files.rb index 71360fb6bd5..069086d5eac 100644 --- a/features/steps/project/project_browse_files.rb +++ b/features/steps/project/project_browse_files.rb @@ -3,42 +3,51 @@ class ProjectBrowseFiles < Spinach::FeatureSteps include SharedProject include SharedPaths - Then 'I should see files from repository' do + step 'I should see files from repository' do page.should have_content "app" page.should have_content "history" page.should have_content "Gemfile" end - Then 'I should see files from repository for "8470d70"' do + step 'I should see files from repository for "8470d70"' do current_path.should == project_tree_path(@project, "8470d70") page.should have_content "app" page.should have_content "history" page.should have_content "Gemfile" end - Given 'I click on "Gemfile.lock" file in repo' do + step 'I click on "Gemfile.lock" file in repo' do click_link "Gemfile.lock" end - Then 'I should see it content' do + step 'I should see it content' do page.should have_content "DEPENDENCIES" end - And 'I click link "raw"' do + step 'I click link "raw"' do click_link "raw" end - Then 'I should see raw file content' do + step 'I should see raw file content' do page.source.should == ValidCommit::BLOB_FILE end - Given 'I click button "edit"' do + step 'I click button "edit"' do click_link 'edit' end - Then 'I can edit code' do + step 'I can edit code' do page.execute_script('editor.setValue("GitlabFileEditor")') page.evaluate_script('editor.getValue()').should == "GitlabFileEditor" end + step 'I click on "new file" link in repo' do + click_link 'new-file-link' + end + + step 'I can see new file page' do + page.should have_content "New file" + page.should have_content "File name" + page.should have_content "Commit message" + end end diff --git a/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb index d793d0ba8dc..72e12fb077c 100644 --- a/lib/gitlab/satellite/edit_file_action.rb +++ b/lib/gitlab/satellite/files/edit_file_action.rb @@ -1,15 +1,9 @@ +require_relative 'file_action' + module Gitlab module Satellite # GitLab server-side file update and commit - class EditFileAction < Action - attr_accessor :file_path, :ref - - def initialize(user, project, ref, file_path) - super user, project, git_timeout: 10.seconds - @file_path = file_path - @ref = ref - end - + class EditFileAction < FileAction # Updates the files content and creates a new commit for it # # Returns false if the ref has been updated while editing the file @@ -45,13 +39,6 @@ module Gitlab Gitlab::GitLogger.error(ex.message) false end - - protected - - def can_edit?(last_commit) - current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha - last_commit == current_last_commit - end end end end diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb new file mode 100644 index 00000000000..4ac53c2cd5a --- /dev/null +++ b/lib/gitlab/satellite/files/file_action.rb @@ -0,0 +1,20 @@ +module Gitlab + module Satellite + class FileAction < Action + attr_accessor :file_path, :ref + + def initialize(user, project, ref, file_path) + super user, project, git_timeout: 10.seconds + @file_path = file_path + @ref = ref + end + + protected + + def can_edit?(last_commit) + current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha + last_commit == current_last_commit + end + end + end +end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb new file mode 100644 index 00000000000..9fe5a38eb80 --- /dev/null +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -0,0 +1,44 @@ +require_relative 'file_action' + +module Gitlab + module Satellite + class NewFileAction < FileAction + # Updates the files content and creates a new commit for it + # + # Returns false if the ref has been updated while editing the file + # Returns false if committing the change fails + # Returns false if pushing from the satellite to Gitolite failed or was rejected + # Returns true otherwise + def commit!(content, commit_message, file_name) + in_locked_and_timed_satellite do |repo| + prepare_satellite!(repo) + + # create target branch in satellite at the corresponding commit from Gitolite + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + + # update the file in the satellite's working dir + file_path_in_satellite = File.join(repo.working_dir, file_path, file_name) + File.open(file_path_in_satellite, 'w') { |f| f.write(content) } + + # add new file + repo.add(file_path_in_satellite) + + # commit the changes + # will raise CommandFailed when commit fails + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + + + # push commit back to Gitolite + # will raise CommandFailed when push fails + repo.git.push({raise: true, timeout: true}, :origin, ref) + + # everything worked + true + end + rescue Grit::Git::CommandFailed => ex + Gitlab::GitLogger.error(ex.message) + false + end + end + end +end |