summaryrefslogtreecommitdiff
path: root/lib/oxidized/output/git.rb
blob: 8acfd467c40867c8ea692c9739460a9a52622d86 (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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
module Oxidized
  class Git < Output
    class GitError < OxidizedError; end
    begin
      require 'rugged'
    rescue LoadError
      raise OxidizedError, 'rugged not found: sudo gem install rugged'
    end

    attr_reader :commitref

    def initialize
      @cfg = Oxidized.config.output.git
    end

    def setup
      if @cfg.empty?
        Oxidized.asetus.user.output.git.user  = 'Oxidized'
        Oxidized.asetus.user.output.git.email = 'o@example.com'
        Oxidized.asetus.user.output.git.repo = File.join(Config::Root, 'oxidized.git')
        Oxidized.asetus.save :user
        raise NoConfig, 'no output git config, edit ~/.config/oxidized/config'
      end

      if @cfg.repo.respond_to?(:each)
        @cfg.repo.each do |group, repo|
          @cfg.repo["#{group}="] = File.expand_path repo
        end
      else
        @cfg.repo = File.expand_path @cfg.repo
      end
    end

    def store file, outputs, opt = {}
      @msg   = opt[:msg]
      @user  = (opt[:user]  or @cfg.user)
      @email = (opt[:email] or @cfg.email)
      @opt   = opt
      @commitref = nil
      repo = @cfg.repo

      outputs.types.each do |type|
        type_cfg = ''
        type_repo = File.join(File.dirname(repo), type + '.git')
        outputs.type(type).each do |output|
          (type_cfg << output; next) if not output.name
          type_file = file + '--' + output.name
          if @cfg.type_as_directory?
            type_file = type + '/' + type_file
            type_repo = repo
          end
          update type_repo, type_file, output
        end
        update type_repo, file, type_cfg
      end

      update repo, file, outputs.to_cfg
    end

    def fetch node, group
      begin
        repo, path = yield_repo_and_path(node, group)
        repo = Rugged::Repository.new repo
        index = repo.index
        index.read_tree repo.head.target.tree unless repo.empty?
        repo.read(index.get(path)[:oid]).data
      rescue
        'node not found'
      end
    end

    # give a hash of all oid revision for the given node, and the date of the commit
    def version node, group
      begin
        repo, path = yield_repo_and_path(node, group)

        repo = Rugged::Repository.new repo
        walker = Rugged::Walker.new(repo)
        walker.sorting(Rugged::SORT_DATE)
        walker.push(repo.head.target)
        i = -1
        tab = []
        walker.each do |commit|
          if commit.diff(paths: [path]).size > 0
            hash = {}
            hash[:date] = commit.time.to_s
            hash[:oid] = commit.oid
            hash[:author] = commit.author
            hash[:message] = commit.message
            tab[i += 1] = hash
          end
        end
        walker.reset
        tab
      rescue
        'node not found'
      end
    end

    # give the blob of a specific revision
    def get_version node, group, oid
      begin
        repo, path = yield_repo_and_path(node, group)
        repo = Rugged::Repository.new repo
        repo.blob_at(oid, path).content
      rescue
        'version not found'
      end
    end

    # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines)
    def get_diff node, group, oid1, oid2
      begin
        diff_commits = nil
        repo, _ = yield_repo_and_path(node, group)
        repo = Rugged::Repository.new repo
        commit = repo.lookup(oid1)

        if oid2
          commit_old = repo.lookup(oid2)
          diff = repo.diff(commit_old, commit)
          diff.each do |patch|
            if /#{node.name}\s+/.match(patch.to_s.lines.first)
              diff_commits = { :patch => patch.to_s, :stat => patch.stat }
              break
            end
          end
        else
          stat = commit.parents[0].diff(commit).stat
          stat = [stat[1], stat[2]]
          patch = commit.parents[0].diff(commit).patch
          diff_commits = { :patch => patch, :stat => stat }
        end

        diff_commits
      rescue
        'no diffs'
      end
    end

    private

    def yield_repo_and_path(node, group)
      repo, path = node.repo, node.name

      if group and @cfg.single_repo?
        path = "#{group}/#{node.name}"
      end

      [repo, path]
    end

    def update repo, file, data
      return if data.empty?

      if @opt[:group]
        if @cfg.single_repo?
          file = File.join @opt[:group], file
        else
          repo = if repo.is_a?(::String)
                   File.join File.dirname(repo), @opt[:group] + '.git'
                 else
                   repo[@opt[:group]]
                 end
        end
      end

      begin
        repo = Rugged::Repository.new repo
        update_repo repo, file, data, @msg, @user, @email
      rescue Rugged::OSError, Rugged::RepositoryError => open_error
        begin
          Rugged::Repository.init_at repo, :bare
        rescue => create_error
          raise GitError, "first '#{open_error.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo"
        end
        retry
      end
    end

    def update_repo repo, file, data, msg, user, email
      oid = repo.write data, :blob
      index = repo.index
      index.read_tree repo.head.target.tree unless repo.empty?

      tree_old = index.write_tree repo
      index.add :path => file, :oid => oid, :mode => 0100644
      tree_new = index.write_tree repo

      if tree_old != tree_new
        repo.config['user.name']  = user
        repo.config['user.email'] = email
        @commitref = Rugged::Commit.create(repo,
                                           :tree       => index.write_tree(repo),
                                           :message    => msg,
                                           :parents    => repo.empty? ? [] : [repo.head.target].compact,
                                           :update_ref => 'HEAD',)

        index.write
        true
      end
    end
  end
end