summaryrefslogtreecommitdiff
path: root/app/services/files/multi_service.rb
blob: 0609c6219e72b483799b07502ecd325a11629814 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
module Files
  class MultiService < Files::BaseService
    class FileChangedError < StandardError; end

    ACTIONS = %w[create update delete move].freeze

    def commit
      repository.multi_action(
        user: current_user,
        message: @commit_message,
        branch_name: @target_branch,
        actions: params[:actions],
        author_email: @author_email,
        author_name: @author_name,
        start_project: @start_project,
        start_branch_name: @start_branch
      )
    end

    private

    def validate
      super

      params[:actions].each_with_index do |action, index|
        if ACTIONS.include?(action[:action].to_s)
          action[:action] = action[:action].to_sym
        else
          raise_error("Unknown action type `#{action[:action]}`.")
        end

        unless action[:file_path].present?
          raise_error("You must specify a file_path.")
        end

        action[:file_path].slice!(0) if action[:file_path] && action[:file_path].start_with?('/')
        action[:previous_path].slice!(0) if action[:previous_path] && action[:previous_path].start_with?('/')

        regex_check(action[:file_path])
        regex_check(action[:previous_path]) if action[:previous_path]

        if project.empty_repo? && action[:action] != :create
          raise_error("No files to #{action[:action]}.")
        end

        validate_file_exists(action)

        case action[:action]
        when :create
          validate_create(action)
        when :update
          validate_update(action)
        when :delete
          validate_delete(action)
        when :move
          validate_move(action, index)
        end
      end
    end

    def validate_file_exists(action)
      return if action[:action] == :create

      file_path = action[:file_path]
      file_path = action[:previous_path] if action[:action] == :move

      blob = repository.blob_at_branch(params[:branch], file_path)

      unless blob
        raise_error("File to be #{action[:action]}d `#{file_path}` does not exist.")
      end
    end

    def last_commit
      Gitlab::Git::Commit.last_for_path(repository, @start_branch, @file_path)
    end

    def regex_check(file)
      if file =~ Gitlab::Regex.directory_traversal_regex
        raise_error(
          'Your changes could not be committed, because the file name, `' +
          file +
          '` ' +
          Gitlab::Regex.directory_traversal_regex_message
        )
      end

      unless file =~ Gitlab::Regex.file_path_regex
        raise_error(
          'Your changes could not be committed, because the file name, `' +
          file +
          '` ' +
          Gitlab::Regex.file_path_regex_message
        )
      end
    end

    def validate_create(action)
      return if project.empty_repo?

      if repository.blob_at_branch(params[:branch], action[:file_path])
        raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.")
      end

      if action[:content].nil?
        raise_error("You must provide content.")
      end
    end

    def validate_update(action)
      if action[:content].nil?
        raise_error("You must provide content.")
      end

      if file_has_changed?
        raise FileChangedError.new("You are attempting to update a file `#{action[:file_path]}` that has changed since you started editing it.")
      end
    end

    def validate_delete(action)
    end

    def validate_move(action, index)
      if action[:previous_path].nil?
        raise_error("You must supply the original file path when moving file `#{action[:file_path]}`.")
      end

      blob = repository.blob_at_branch(params[:branch], action[:file_path])

      if blob
        raise_error("Move destination `#{action[:file_path]}` already exists.")
      end

      if action[:content].nil?
        blob = repository.blob_at_branch(params[:branch], action[:previous_path])
        blob.load_all_data!(repository) if blob.truncated?
        params[:actions][index][:content] = blob.data
      end
    end
  end
end