From bd2b6f59445919cdcef627f7f1b1fca5d402168b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> Date: Tue, 5 Nov 2013 10:28:49 +0200 Subject: [PATCH] New feature: Create file from UI Now you are able to create a new file in repository from your browser. You are not allowed to create a file if file with same name already exists in the repo. Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> --- .../projects/new_tree_controller.rb | 65 +++++++++++++++++++ app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/new_tree/show.html.haml | 47 ++++++++++++++ app/views/projects/tree/_tree.html.haml | 5 ++ config/routes.rb | 20 +++--- .../satellite/{ => files}/edit_file_action.rb | 19 +----- lib/gitlab/satellite/files/file_action.rb | 20 ++++++ lib/gitlab/satellite/files/new_file_action.rb | 44 +++++++++++++ 8 files changed, 196 insertions(+), 26 deletions(-) create mode 100644 app/controllers/projects/new_tree_controller.rb create mode 100644 app/views/projects/new_tree/show.html.haml rename lib/gitlab/satellite/{ => files}/edit_file_action.rb (77%) create mode 100644 lib/gitlab/satellite/files/file_action.rb create mode 100644 lib/gitlab/satellite/files/new_file_action.rb diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb new file mode 100644 index 00000000000..9003f2df5fb --- /dev/null +++ b/app/controllers/projects/new_tree_controller.rb @@ -0,0 +1,65 @@ +class Projects::NewTreeController < Projects::ApplicationController + include ExtractsPath + + # Authorize + before_filter :authorize_read_project! + before_filter :authorize_code_access! + before_filter :require_non_empty_project + + before_filter :create_requirements, only: [:show, :update] + + def show + end + + def update + file_name = params[:file_name] + + unless file_name =~ Gitlab::Regex.path_regex + flash[:notice] = "Your changes could not be commited, because file name contains not allowed characters" + render :show and return + end + + file_path = if @path.blank? + file_name + else + File.join(@path, file_name) + end + + blob = @repository.blob_at(@commit.id, file_path) + + if blob + flash[:notice] = "Your changes could not be commited, because file with such name exists" + render :show and return + end + + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, @project, @ref, @path) + updated_successfully = new_file_action.commit!( + params[:content], + params[:commit_message], + file_name, + ) + + if updated_successfully + redirect_to project_blob_path(@project, File.join(@id, params[:file_name])), notice: "Your changes have been successfully commited" + else + flash[:notice] = "Your changes could not be commited, because the file has been changed" + render :show + end + end + + private + + def create_requirements + 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 create files if you are on top of a branch" + end + end +end 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..4837b8e3b82 --- /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', '', 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', '', placeholder: "Added new file", required: true, rows: 3 + + .file-holder + .file-title + %i.icon-file + .file-content.code + %pre#editor= "" + + .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..7a41089a3f2 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -10,6 +10,11 @@ = link_to truncate(title, length: 40), project_tree_path(@project, path) - else = link_to title, '#' + \/ + %li + = link_to project_new_tree_path(@project, @id) 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/lib/gitlab/satellite/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb similarity index 77% rename from lib/gitlab/satellite/edit_file_action.rb rename to 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 -- GitLab