summaryrefslogtreecommitdiff
path: root/lib/oxidized/output/gitcrypt.rb
blob: 242a85d87e5bd142eebe664d8731165ad3982b33 (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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
module Oxidized
  class GitCrypt < Output
    class GitCryptError < OxidizedError; end
    begin
      require 'git'
    rescue LoadError
      raise OxidizedError, 'git not found: sudo gem install git'
    end

    attr_reader :commitref

    def initialize
      @cfg = Oxidized.config.output.gitcrypt
      @gitcrypt_cmd = "/usr/bin/git-crypt"
      @gitcrypt_init = @gitcrypt_cmd + " init"
      @gitcrypt_unlock = @gitcrypt_cmd + " unlock"
      @gitcrypt_lock = @gitcrypt_cmd + " lock"
      @gitcrypt_adduser = @gitcrypt_cmd + " add-gpg-user --trusted "
    end

    def setup
      if @cfg.empty?
        Oxidized.asetus.user.output.gitcrypt.user  = 'Oxidized'
        Oxidized.asetus.user.output.gitcrypt.email = 'o@example.com'
        Oxidized.asetus.user.output.gitcrypt.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 crypt_init repo
      repo.chdir do
        system(@gitcrypt_init)
        @cfg.users.each do |user|
          system("#{@gitcrypt_adduser} #{user}")
        end
        File.write(".gitattributes", "* filter=git-crypt diff=git-crypt\n.gitattributes !filter !diff")
        repo.add(".gitattributes")
        repo.commit("Initial commit: crypt all config files")
      end
    end

    def lock repo
      repo.chdir do
        system(@gitcrypt_lock)
      end
    end

    def unlock repo
      repo.chdir do
        system(@gitcrypt_unlock)
      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 = Git.open repo
        unlock repo
        index = repo.index
        # Empty repo ?
        empty = File.exists? index.path
        if empty
          raise 'Empty git repo'
        else
          File.read path
        end

        lock repo
      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 = Git.open repo
        unlock repo
        walker = repo.log.path(path)
        i = -1
        tab = []
        walker.each do |commit|
          hash = {}
          hash[:date] = commit.date.to_s
          hash[:oid] = commit.objectish
          hash[:author] = commit.author
          hash[:message] = commit.message
          tab[i += 1] = hash
        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 = Git.open repo
        unlock repo
        repo.gtree(oid).files[path].contents
      rescue
        'version not found'
      ensure
        lock repo
      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, path = yield_repo_and_path(node, group)
        repo = Git.open repo
        unlock repo
        commit = repo.gcommit(oid1)

        if oid2
          commit_old = repo.gcommit(oid2)
          diff = repo.diff(commit_old, commit)
          stats = [diff.stats[:files][node.name][:insertions], diff.stats[:files][node.name][:deletions]]
          diff.each do |patch|
            if /#{node.name}\s+/.match(patch.patch.to_s.lines.first)
              diff_commits = { :patch => patch.patch.to_s, :stat => stats }
              break
            end
          end
        else
          stat = commit.parents[0].diff(commit).stats
          stat = [stat[:files][node.name][:insertions], stat[:files][node.name][:deletions]]
          patch = commit.parents[0].diff(commit).patch
          diff_commits = { :patch => patch, :stat => stat }
        end
        lock repo
        diff_commits
      rescue
        'no diffs'
      ensure
        lock repo
      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
        update_repo repo, file, data, @msg, @user, @email
      rescue Git::GitExecuteError, ArgumentError => open_error
        Oxidized.logger.debug "open_error #{open_error} #{file}"
        begin
          grepo = Git.init repo
          crypt_init grepo
        rescue => create_error
          raise GitCryptError, "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
      grepo = Git.open repo
      grepo.config('user.name', user)
      grepo.config('user.email', email)
      grepo.chdir do
        unlock grepo
        File.write(file, data)
        grepo.add(file)
        if grepo.status[file].nil?
          grepo.commit(msg)
          @commitref = grepo.log(1).first.objectish
          true
        elsif !grepo.status[file].type.nil?
          grepo.commit(msg)
          @commitref = grepo.log(1).first.objectish
          true
        end
        lock grepo
      end
    end
  end
end