summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/index.rb
blob: af1744c9c463aac33904245ad3aa5b5ae333a327 (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
module Gitlab
  module Git
    class Index
      DEFAULT_MODE = 0o100644

      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)

        file_entry = get(options[:file_path])
        if file_entry
          raise Gitlab::Git::Repository::InvalidBlobName.new("Filename already exists")
        end

        add_blob(options)
      end

      def create_dir(options)
        options = normalize_options(options)

        file_entry = get(options[:file_path])
        if file_entry
          raise Gitlab::Git::Repository::InvalidBlobName.new("Directory already exists as a file")
        end

        if dir_exists?(options[:file_path])
          raise Gitlab::Git::Repository::InvalidBlobName.new("Directory 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 Gitlab::Git::Repository::InvalidBlobName.new("File 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 Gitlab::Git::Repository::InvalidBlobName.new("File doesn't exist")
        end

        raw_index.remove(options[:previous_path])

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

      def delete(options)
        options = normalize_options(options)

        file_entry = get(options[:file_path])
        unless file_entry
          raise Gitlab::Git::Repository::InvalidBlobName.new("File 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)
        pathname = Gitlab::Git::PathHelper.normalize_path(path.dup)

        if pathname.each_filename.include?('..')
          raise Gitlab::Git::Repository::InvalidBlobName.new('Invalid path')
        end

        pathname.to_s
      end

      def add_blob(options, mode: nil)
        content = options[:content]
        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 Gitlab::Git::Repository::InvalidBlobName.new(e.message)
      end
    end
  end
end