summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/index.rb
blob: 1add037fa5f154b6da583f839571458aae015314 (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
142
143
144
145
module Gitlab
  module Git
    class Index
      IndexError = Class.new(StandardError)

      DEFAULT_MODE = 0o100644

      ACTIONS = %w(create create_dir update move delete).freeze

      attr_reader :repository, :raw_index

      def initialize(repository)
        @repository = repository
        @raw_index = repository.rugged.index
      end

      delegate :read_tree, :get, to: :raw_index

      def write_tree
        raw_index.write_tree(repository.rugged)
      end

      def dir_exists?(path)
        raw_index.find { |entry| entry[:path].start_with?("#{path}/") }
      end

      def create(options)
        options = normalize_options(options)

        if get(options[:file_path])
          raise IndexError, "A file with this name already exists"
        end

        add_blob(options)
      end

      def create_dir(options)
        options = normalize_options(options)

        if get(options[:file_path])
          raise IndexError, "A file with this name already exists"
        end

        if dir_exists?(options[:file_path])
          raise IndexError, "A directory with this name already exists"
        end

        options = options.dup
        options[:file_path] += '/.gitkeep'
        options[:content] = ''

        add_blob(options)
      end

      def update(options)
        options = normalize_options(options)

        file_entry = get(options[:file_path])
        unless file_entry
          raise IndexError, "A file with this name doesn't exist"
        end

        add_blob(options, mode: file_entry[:mode])
      end

      def move(options)
        options = normalize_options(options)

        file_entry = get(options[:previous_path])
        unless file_entry
          raise IndexError, "A file with this name doesn't exist"
        end

        if get(options[:file_path])
          raise IndexError, "A file with this name already exists"
        end

        raw_index.remove(options[:previous_path])

        add_blob(options, mode: file_entry[:mode])
      end

      def delete(options)
        options = normalize_options(options)

        unless get(options[:file_path])
          raise IndexError, "A file with this name doesn't exist"
        end

        raw_index.remove(options[:file_path])
      end

      private

      def normalize_options(options)
        options = options.dup
        options[:file_path] = normalize_path(options[:file_path]) if options[:file_path]
        options[:previous_path] = normalize_path(options[:previous_path]) if options[:previous_path]
        options
      end

      def normalize_path(path)
        unless path
          raise IndexError, "You must provide a file path"
        end

        pathname = Gitlab::Git::PathHelper.normalize_path(path.dup)

        pathname.each_filename do |segment|
          if segment == '..'
            raise IndexError, 'Path cannot include directory traversal'
          end

          unless segment =~ Gitlab::Regex.file_name_regex
            raise IndexError, "Path #{Gitlab::Regex.file_name_regex_message}"
          end
        end

        pathname.to_s
      end

      def add_blob(options, mode: nil)
        content = options[:content]
        unless content
          raise IndexError, "You must provide content"
        end

        content = Base64.decode64(content) if options[:encoding] == 'base64'

        detect = CharlockHolmes::EncodingDetector.new.detect(content)
        unless detect && detect[:type] == :binary
          # When writing to the repo directly as we are doing here,
          # the `core.autocrlf` config isn't taken into account.
          content.gsub!("\r\n", "\n") if repository.autocrlf
        end

        oid = repository.rugged.write(content, :blob)

        raw_index.add(path: options[:file_path], oid: oid, mode: mode || DEFAULT_MODE)
      rescue Rugged::IndexError => e
        raise IndexError, e.message
      end
    end
  end
end