summaryrefslogtreecommitdiff
path: root/lib/gitlab/git/tree.rb
blob: ba6058fd3c912903b816ebb4342f1a22a9954982 (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
# Gitaly note: JV: needs 1 RPC, migration is in progress.

module Gitlab
  module Git
    class Tree
      include Gitlab::EncodingHelper

      attr_accessor :id, :root_id, :name, :path, :flat_path, :type,
        :mode, :commit_id, :submodule_url

      class << self
        # Get list of tree objects
        # for repository based on commit sha and path
        # Uses rugged for raw objects
        #
        # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/320
        def where(repository, sha, path = nil)
          path = nil if path == '' || path == '/'

          Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
            if is_enabled
              repository.gitaly_commit_client.tree_entries(repository, sha, path)
            else
              tree_entries_from_rugged(repository, sha, path)
            end
          end
        end

        private

        # Recursive search of tree id for path
        #
        # Ex.
        #   blog/            # oid: 1a
        #     app/           # oid: 2a
        #       models/      # oid: 3a
        #       views/       # oid: 4a
        #
        #
        # Tree.find_id_by_path(repo, '1a', 'app/models') # => '3a'
        #
        def find_id_by_path(repository, root_id, path)
          root_tree = repository.lookup(root_id)
          path_arr = path.split('/')

          entry = root_tree.find do |entry|
            entry[:name] == path_arr[0] && entry[:type] == :tree
          end

          return nil unless entry

          if path_arr.size > 1
            path_arr.shift
            find_id_by_path(repository, entry[:oid], path_arr.join('/'))
          else
            entry[:oid]
          end
        end

        def tree_entries_from_rugged(repository, sha, path)
          commit = repository.lookup(sha)
          root_tree = commit.tree

          tree = if path
                   id = find_id_by_path(repository, root_tree.oid, path)
                   if id
                     repository.lookup(id)
                   else
                     []
                   end
                 else
                   root_tree
                 end

          tree.map do |entry|
            new(
              id: entry[:oid],
              root_id: root_tree.oid,
              name: entry[:name],
              type: entry[:type],
              mode: entry[:filemode].to_s(8),
              path: path ? File.join(path, entry[:name]) : entry[:name],
              commit_id: sha
            )
          end
        rescue Rugged::ReferenceError
          []
        end
      end

      def initialize(options)
        %w(id root_id name path flat_path type mode commit_id).each do |key|
          self.send("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
        end
      end

      def name
        encode! @name
      end

      def path
        encode! @path
      end

      def flat_path
        encode! @flat_path
      end

      def dir?
        type == :tree
      end

      def file?
        type == :blob
      end

      def submodule?
        type == :commit
      end

      def readme?
        name =~ /^readme/i
      end

      def contributing?
        name =~ /^contributing/i
      end
    end
  end
end