Commit bd2b6f59445919cdcef627f7f1b1fca5d402168b

Authored by Dmitriy Zaporozhets
1 parent 2ee04b12

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>
app/controllers/projects/new_tree_controller.rb 0 → 100644
... ... @@ -0,0 +1,65 @@
  1 +class Projects::NewTreeController < Projects::ApplicationController
  2 + include ExtractsPath
  3 +
  4 + # Authorize
  5 + before_filter :authorize_read_project!
  6 + before_filter :authorize_code_access!
  7 + before_filter :require_non_empty_project
  8 +
  9 + before_filter :create_requirements, only: [:show, :update]
  10 +
  11 + def show
  12 + end
  13 +
  14 + def update
  15 + file_name = params[:file_name]
  16 +
  17 + unless file_name =~ Gitlab::Regex.path_regex
  18 + flash[:notice] = "Your changes could not be commited, because file name contains not allowed characters"
  19 + render :show and return
  20 + end
  21 +
  22 + file_path = if @path.blank?
  23 + file_name
  24 + else
  25 + File.join(@path, file_name)
  26 + end
  27 +
  28 + blob = @repository.blob_at(@commit.id, file_path)
  29 +
  30 + if blob
  31 + flash[:notice] = "Your changes could not be commited, because file with such name exists"
  32 + render :show and return
  33 + end
  34 +
  35 + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, @project, @ref, @path)
  36 + updated_successfully = new_file_action.commit!(
  37 + params[:content],
  38 + params[:commit_message],
  39 + file_name,
  40 + )
  41 +
  42 + if updated_successfully
  43 + redirect_to project_blob_path(@project, File.join(@id, params[:file_name])), notice: "Your changes have been successfully commited"
  44 + else
  45 + flash[:notice] = "Your changes could not be commited, because the file has been changed"
  46 + render :show
  47 + end
  48 + end
  49 +
  50 + private
  51 +
  52 + def create_requirements
  53 + allowed = if project.protected_branch? @ref
  54 + can?(current_user, :push_code_to_protected_branches, project)
  55 + else
  56 + can?(current_user, :push_code, project)
  57 + end
  58 +
  59 + return access_denied! unless allowed
  60 +
  61 + unless @repository.branch_names.include?(@ref)
  62 + redirect_to project_blob_path(@project, @id), notice: "You can only create files if you are on top of a branch"
  63 + end
  64 + end
  65 +end
... ...
app/views/layouts/nav/_project.html.haml
... ... @@ -4,7 +4,7 @@
4 4 %i.icon-home
5 5  
6 6 - if project_nav_tab? :files
7   - = nav_link(controller: %w(tree blob blame edit_tree)) do
  7 + = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
8 8 = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref)
9 9  
10 10 - if project_nav_tab? :commits
... ...
app/views/projects/new_tree/show.html.haml 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +%h3.page-title New file
  2 +%hr
  3 +.file-editor
  4 + = form_tag(project_new_tree_path(@project, @id), method: :put, class: "form-horizontal") do
  5 + .control-group.commit_message-group
  6 + = label_tag 'file_name', class: "control-label" do
  7 + File name
  8 + .controls
  9 + %span.monospace= @path[-1] == "/" ? @path : @path + "/"
  10 + &nbsp;
  11 + = text_field_tag 'file_name', '', placeholder: "sample.rb", required: true
  12 + %span
  13 + &nbsp;
  14 + on
  15 + %span.label-branch= @ref
  16 +
  17 + .control-group.commit_message-group
  18 + = label_tag 'commit_message', class: "control-label" do
  19 + Commit message
  20 + .controls
  21 + = text_area_tag 'commit_message', '', placeholder: "Added new file", required: true, rows: 3
  22 +
  23 + .file-holder
  24 + .file-title
  25 + %i.icon-file
  26 + .file-content.code
  27 + %pre#editor= ""
  28 +
  29 + .form-actions
  30 + = hidden_field_tag 'content', '', id: "file-content"
  31 + .commit-button-annotation
  32 + = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create'
  33 + .message
  34 + to branch
  35 + %strong= @ref
  36 + = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", confirm: leave_edit_message
  37 +
  38 +:javascript
  39 + ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict")
  40 + var editor = ace.edit("editor");
  41 +
  42 + disableButtonIfEmptyField("#commit_message", ".js-commit-button");
  43 +
  44 + $(".js-commit-button").click(function(){
  45 + $("#file-content").val(editor.getValue());
  46 + $(".file-editor form").submit();
  47 + });
... ...
app/views/projects/tree/_tree.html.haml
... ... @@ -10,6 +10,11 @@
10 10 = link_to truncate(title, length: 40), project_tree_path(@project, path)
11 11 - else
12 12 = link_to title, '#'
  13 + \/
  14 + %li
  15 + = link_to project_new_tree_path(@project, @id) do
  16 + %small
  17 + %i.icon-plus.light
13 18  
14 19 %div#tree-content-holder.tree-content-holder
15 20 %table#tree-slider{class: "table_#{@hex_path} tree-table" }
... ...
config/routes.rb
... ... @@ -166,16 +166,18 @@ Gitlab::Application.routes.draw do
166 166 end
167 167  
168 168 scope module: :projects do
169   - resources :blob, only: [:show], constraints: {id: /.+/}
170   - resources :raw, only: [:show], constraints: {id: /.+/}
171   - resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
172   - resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit'
173   - resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
174   - resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
175   - resources :compare, only: [:index, :create]
176   - resources :blame, only: [:show], constraints: {id: /.+/}
  169 + resources :blob, only: [:show], constraints: {id: /.+/}
  170 + resources :raw, only: [:show], constraints: {id: /.+/}
  171 + resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
  172 + resources :edit_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'edit'
  173 + resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new'
  174 + resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
  175 + resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
  176 + resources :compare, only: [:index, :create]
  177 + resources :blame, only: [:show], constraints: {id: /.+/}
177 178 resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
178   - resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
  179 + resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
  180 +
179 181 match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
180 182  
181 183 resources :snippets, constraints: {id: /\d+/} do
... ...
lib/gitlab/satellite/edit_file_action.rb
... ... @@ -1,57 +0,0 @@
1   -module Gitlab
2   - module Satellite
3   - # GitLab server-side file update and commit
4   - class EditFileAction < Action
5   - attr_accessor :file_path, :ref
6   -
7   - def initialize(user, project, ref, file_path)
8   - super user, project, git_timeout: 10.seconds
9   - @file_path = file_path
10   - @ref = ref
11   - end
12   -
13   - # Updates the files content and creates a new commit for it
14   - #
15   - # Returns false if the ref has been updated while editing the file
16   - # Returns false if committing the change fails
17   - # Returns false if pushing from the satellite to Gitolite failed or was rejected
18   - # Returns true otherwise
19   - def commit!(content, commit_message, last_commit)
20   - return false unless can_edit?(last_commit)
21   -
22   - in_locked_and_timed_satellite do |repo|
23   - prepare_satellite!(repo)
24   -
25   - # create target branch in satellite at the corresponding commit from Gitolite
26   - repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
27   -
28   - # update the file in the satellite's working dir
29   - file_path_in_satellite = File.join(repo.working_dir, file_path)
30   - File.open(file_path_in_satellite, 'w') { |f| f.write(content) }
31   -
32   - # commit the changes
33   - # will raise CommandFailed when commit fails
34   - repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
35   -
36   -
37   - # push commit back to Gitolite
38   - # will raise CommandFailed when push fails
39   - repo.git.push({raise: true, timeout: true}, :origin, ref)
40   -
41   - # everything worked
42   - true
43   - end
44   - rescue Grit::Git::CommandFailed => ex
45   - Gitlab::GitLogger.error(ex.message)
46   - false
47   - end
48   -
49   - protected
50   -
51   - def can_edit?(last_commit)
52   - current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha
53   - last_commit == current_last_commit
54   - end
55   - end
56   - end
57   -end
lib/gitlab/satellite/files/edit_file_action.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +require_relative 'file_action'
  2 +
  3 +module Gitlab
  4 + module Satellite
  5 + # GitLab server-side file update and commit
  6 + class EditFileAction < FileAction
  7 + # Updates the files content and creates a new commit for it
  8 + #
  9 + # Returns false if the ref has been updated while editing the file
  10 + # Returns false if committing the change fails
  11 + # Returns false if pushing from the satellite to Gitolite failed or was rejected
  12 + # Returns true otherwise
  13 + def commit!(content, commit_message, last_commit)
  14 + return false unless can_edit?(last_commit)
  15 +
  16 + in_locked_and_timed_satellite do |repo|
  17 + prepare_satellite!(repo)
  18 +
  19 + # create target branch in satellite at the corresponding commit from Gitolite
  20 + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
  21 +
  22 + # update the file in the satellite's working dir
  23 + file_path_in_satellite = File.join(repo.working_dir, file_path)
  24 + File.open(file_path_in_satellite, 'w') { |f| f.write(content) }
  25 +
  26 + # commit the changes
  27 + # will raise CommandFailed when commit fails
  28 + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
  29 +
  30 +
  31 + # push commit back to Gitolite
  32 + # will raise CommandFailed when push fails
  33 + repo.git.push({raise: true, timeout: true}, :origin, ref)
  34 +
  35 + # everything worked
  36 + true
  37 + end
  38 + rescue Grit::Git::CommandFailed => ex
  39 + Gitlab::GitLogger.error(ex.message)
  40 + false
  41 + end
  42 + end
  43 + end
  44 +end
... ...
lib/gitlab/satellite/files/file_action.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +module Gitlab
  2 + module Satellite
  3 + class FileAction < Action
  4 + attr_accessor :file_path, :ref
  5 +
  6 + def initialize(user, project, ref, file_path)
  7 + super user, project, git_timeout: 10.seconds
  8 + @file_path = file_path
  9 + @ref = ref
  10 + end
  11 +
  12 + protected
  13 +
  14 + def can_edit?(last_commit)
  15 + current_last_commit = Gitlab::Git::Commit.last_for_path(@project.repository, ref, file_path).sha
  16 + last_commit == current_last_commit
  17 + end
  18 + end
  19 + end
  20 +end
... ...
lib/gitlab/satellite/files/new_file_action.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +require_relative 'file_action'
  2 +
  3 +module Gitlab
  4 + module Satellite
  5 + class NewFileAction < FileAction
  6 + # Updates the files content and creates a new commit for it
  7 + #
  8 + # Returns false if the ref has been updated while editing the file
  9 + # Returns false if committing the change fails
  10 + # Returns false if pushing from the satellite to Gitolite failed or was rejected
  11 + # Returns true otherwise
  12 + def commit!(content, commit_message, file_name)
  13 + in_locked_and_timed_satellite do |repo|
  14 + prepare_satellite!(repo)
  15 +
  16 + # create target branch in satellite at the corresponding commit from Gitolite
  17 + repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}")
  18 +
  19 + # update the file in the satellite's working dir
  20 + file_path_in_satellite = File.join(repo.working_dir, file_path, file_name)
  21 + File.open(file_path_in_satellite, 'w') { |f| f.write(content) }
  22 +
  23 + # add new file
  24 + repo.add(file_path_in_satellite)
  25 +
  26 + # commit the changes
  27 + # will raise CommandFailed when commit fails
  28 + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
  29 +
  30 +
  31 + # push commit back to Gitolite
  32 + # will raise CommandFailed when push fails
  33 + repo.git.push({raise: true, timeout: true}, :origin, ref)
  34 +
  35 + # everything worked
  36 + true
  37 + end
  38 + rescue Grit::Git::CommandFailed => ex
  39 + Gitlab::GitLogger.error(ex.message)
  40 + false
  41 + end
  42 + end
  43 + end
  44 +end
... ...