Module: Tracker::Cmd

Defined in:
lib/command.rb

Defined Under Namespace

Classes: ChildCommandError

Constant Summary collapse

GIT_JSON_FORMAT =
'{ "hashes":'+
'{ "commit":"%H", "tree":"%T",'+' "parents":"%P" },'+
'"author":{ "date": "%ai", "name": "%an", "email":"%ae" },'+
'"committer":{ "date": "%ci", "name": "%cn", "email":"%ce" } },'
GIT_OPTS =
"--format='#{GIT_JSON_FORMAT}'"
GIT_CMD =
'git --no-pager log origin/master..HEAD %s ' % GIT_OPTS

Class Method Summary collapse

Class Method Details

.ack(directory, opts = {}) ⇒ Object



273
# File 'lib/command.rb', line 273

def self.ack(directory, opts={}); action(:ack, directory, opts); end

.action(name, directory, options = {}) ⇒ Object

Method perform given action on GIT branch with recorded commits. The patches need to be recorded on Tracker server to perfom any action.

  • name - Action name (:ack, :nack, :push)

  • directory - If given, cmd app will ‘chdir’ into that directory (default: nil)



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/command.rb', line 224

def self.action(name, directory, options={})
  if options[:set]
    begin
      RestClient.post(
        config[:url] + ('/set/%s/%s' % [options[:set], name]),
        {
          :message => options[:message]
        },
        {
          :content_type => 'application/json',
          'Authorization' => "Basic #{basic_auth}"
        }
      )
      'All patches in set %s marked as %s' % [options[:set], name.to_s.upcase]
    rescue => e
      '[ERR] %s' % e.message
    end
  else
    patches = JSON::parse(patches_to_json(directory))
    messages = patches.pop
    patches_counter = 0
    patches.each do |p|
      if messages[p['hashes']['commit']]['full_message'][/TrackedAt: (.*)./m]
        tracker_commit_url = $1.chop
      else
        tracker_commit_url = config[:url] + '/patch/' + p['hashes']['commit']
        warn "[warn] Patches not applied by tracker. Using current commit hash."
      end
      begin
        RestClient.post(
          tracker_commit_url + '/' + name.to_s,
          {
            :message => options[:message]
          },
          {
            :content_type => 'application/json',
            'Authorization' => "Basic #{basic_auth}"
          }
        )
        puts "\e[1m%s\e[0m send for \e[1m%s\e[0m" % [name.to_s.upcase, tracker_commit_url]
        patches_counter += 1
      rescue => e
        puts '[ERR] %s' % e.message
      end
    end
    '%i patches status updated.' % patches_counter
  end
end

.apply(directory, commit_id) ⇒ Object



93
94
95
96
97
98
99
# File 'lib/command.rb', line 93

def self.apply(directory, commit_id)
  patch_body = download_patch_body(commit_id)
  File.open(File.join(directory, "#{commit_id}.patch"), 'w') { |f| f.write patch_body }
  print '[%s] Are you sure you want to apply patch to current branch? [Y/n]' % commit_id
  exit if (STDIN.gets.chomp) == 'n'
  git_cmd("git am #{commit_id}.patch", directory)
end

.config(action = :get, opts = {}) ⇒ Object

Retrieve/Set the configuration from YAML file, if YAML file does not exists, use the default_configuration instead

  • action - If action :set then overide the default_configuration (default: :get)

  • :opts - Read YAML file from opts



45
46
47
48
# File 'lib/command.rb', line 45

def self.config(action=:get, opts={})
  configuration(YAML.load_file(opts[:file])) if (action == :set) && File.exists?(opts[:file])
  configuration || configuration(default_configuration)
end

.configuration(conf = nil) ⇒ Object

Set/Get configuration for the command-line client

  • conf - Set configuration:

  • :url - Tracker server URL (default: localhost:9292)

  • :user - Tracker username

  • :password* - Tracker password



37
# File 'lib/command.rb', line 37

def self.configuration(conf=nil); @configuration ||= conf; end

.default_configurationObject



21
22
23
24
25
26
27
# File 'lib/command.rb', line 21

def self.default_configuration
  {
    :url => 'http://localhost:9292',
    :user => '[email protected]',
    :password => 'test123'
  }
end

.download(directory, patchset_id, branch = nil) ⇒ Object



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
# File 'lib/command.rb', line 161

def self.download(directory, patchset_id, branch=nil)
  patches = []
  begin
    response = RestClient.get(
      config[:url] + ('/set/%s' % patchset_id),
      {
        'Accept' => 'application/json',
        'Authorization' => "Basic #{basic_auth}"
      }
    )
    patches = JSON::parse(response)['patches']
  rescue => e
    puts "ERR: #{e.message}"
    exit
  end
  counter = 0
  if !branch.nil?
    puts git_cmd("git checkout -b #{branch}", directory)
    exit 1 if $? != 0
  end
  patches.each do |commit|
    patch_filename = File.join(directory, "#{counter}-#{commit}.patch")
    File.open(patch_filename, 'w') { |f| f.puts download_patch_body(commit) }
    if !branch.nil?
      begin
        puts git_cmd("git am #{patch_filename}", directory)
      rescue ChildCommandError => e
        puts git_cmd "git am --abort", directory
        puts git_cmd "git checkout master", directory
        puts git_cmd "git branch -D #{branch}", directory
        puts "ERROR: #{e.message}. Reverting back to 'master' branch."
        puts "Downloaded patch saved to :#{patch_filename}"
        exit 1
      end
      FileUtils.rm_f(patch_filename)
    end
    counter += 1
  end
  if !branch.nil?
    "#{counter} patches successfully applied"
  else
    "#{counter} patches successfully downloaded"
  end
end

.download_patch_body(commit_id) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/command.rb', line 147

def self.download_patch_body(commit_id)
  begin
    RestClient.get(
      config[:url] + ('/patch/%s/download' % commit_id),
      {
        :content_type => 'text/plain',
        'Authorization' => "Basic #{basic_auth}"
      }
    )
  rescue => e
    puts "[ERR] #{e.message}"
  end
end

.list(value, opts = {}) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/command.rb', line 310

def self.list(value, opts={})
  filter = ''
  if !value.nil?
    if ['new', 'ack', 'nack', 'push'].include? value
      filter += '?filter=status&filter_value=%s' % value
    elsif !opts[:id].nil?
      filter += '?filter=%s&filter_value=%s' % [opts[:id], value]
    else
      puts "[ERR] To use filters other than status, you must use -i FILTER_NAME parameter"
      exit 1
    end
  end
  response = RestClient.get(
    config[:url] + ('/set%s' % filter),
    {
      'Accept' => 'application/json'
    }
  )
  set_arr = JSON::parse(response)
  puts
  set_arr.each do |set|
    puts "[%s][%s] \e[1m%s\e[0m (%s patches by %s)" % [
      set['id'],
      set['status'].upcase,
      set['first_patch_message'],
      set['num_of_patches'],
      set['author']
    ]
  end
  ''
end

.nack(directory, opts = {}) ⇒ Object



274
# File 'lib/command.rb', line 274

def self.nack(directory, opts={}); action(:nack, directory, opts); end

.note(directory, opts = {}) ⇒ Object



276
# File 'lib/command.rb', line 276

def self.note(directory, opts={}); action(:note, directory, opts); end

.obsolete_patchset(patchset_id) ⇒ Object



206
207
208
209
210
211
212
213
214
215
# File 'lib/command.rb', line 206

def self.obsolete_patchset(patchset_id)
  RestClient.post(
    config[:url] + ('/patchset/%s/obsolete' % patchset_id), '',
    {
      :content_type => 'application/json',
      'Authorization' => "Basic #{basic_auth}"
    }
  )
  puts 'Set %s obsoleted.' % patchset_id
end

.patches_to_json(directory = nil) ⇒ Object

Read commits between origin/master..HEAD and convert them to JSON string

  • directory - If given, cmd app will ‘chdir’ into that directory (default: nil)



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/command.rb', line 54

def self.patches_to_json(directory=nil)
  patches_in_json = git_cmd(GIT_CMD, directory)
  commit_messages_raw = git_cmd('git log --pretty=oneline origin/master..HEAD', directory)
  commit_messages = commit_messages_raw.each_line.map.inject({}) do |result, line|
    hash, message = line.split(' ', 2)
    full_message = git_cmd("git rev-list --format=%B --max-count=1 #{hash}", directory)
    result[hash] = { :msg => message.strip, :full_message => full_message }
    result
  end
  "[#{patches_in_json}#{JSON::dump(commit_messages)}]"
end

.push(directory, opts = {}) ⇒ Object



275
# File 'lib/command.rb', line 275

def self.push(directory, opts={}); action(:push, directory, opts); end

.record(directory, opts = {}) ⇒ Object

Method will call patches_to_json and POST the JSON array to Tracker server. Authentication stored in config is used.

  • directory - If given, cmd app will ‘chdir’ into that directory (default: nil)



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/command.rb', line 71

def self.record(directory, opts={})
  number_of_commits = JSON::parse(patches_to_json(directory)).pop.size
  begin
    response = RestClient.post(
      config[:url] + '/set',
      patches_to_json(directory),
      {
        :content_type => 'application/json',
        'Authorization' => "Basic #{basic_auth}",
        'X-Obsoletes' => opts[:obsolete] || 'no'
      }
    )
    response = JSON::parse(response)
    output = "#{number_of_commits} patches were recorded to the tracker server"+
      " [\e[1m#{config[:url]}set/#{response['id']}\e[0m] [revision #{response['revision']}]"
    output += "\n" + upload(directory) if opts[:upload]
    output
  rescue => e
    e.message
  end
end

.status(directory) ⇒ Object



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/command.rb', line 278

def self.status(directory)
  patches = JSON::parse(patches_to_json(directory))
  # Remove messages from Array
  patches.pop
  puts
  counter = 0
  patches.each do |p|
    begin
      response = RestClient.get(
        config[:url] + ('/patch/%s' % p['hashes']['commit']),
        {
          'Accept' => 'application/json',
          'Authorization' => "Basic #{basic_auth}"
        }
      )
      response = JSON::parse(response)
      puts "[%s] \e[1m%s\e[0m" % [
        response['status'].upcase,
        response['message']
      ]
      counter+=1
    rescue => e
      next if response == 'null'
      puts '[ERR][%s] %s' % [p['hashes']['commit'][-8, 8], e.message]
    end
  end
  if counter == 0
    "ERR: This branch is not recorded yet. ($ tracker record)\n\n"
  else
  end
end

.upload(directory) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/command.rb', line 101

def self.upload(directory)
  diffs = git_cmd('git format-patch --stdout master', directory)
  patches = {}
  current_patch_commit = ''
  diffs.each_line do |line|
    if line =~ %r[^From (\w{40}) ]
      current_patch_commit = $1
      patches[current_patch_commit] = line
    else
      patches[current_patch_commit] += line
    end
  end
  upload_counter = 0
  patches.each do |commit, body|
    begin
      upload_patch_body(commit, body)
      upload_counter += 1
    rescue;end
  end
  '%i patches successfully uploaded' % [upload_counter]
end

.upload_patch_body(commit_id, body) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/command.rb', line 123

def self.upload_patch_body(commit_id, body)
  # Inject TrackedAt header to the commit message
  body.sub!(/^---/m, "TrackedAt: #{config[:url].gsub(/\/$/, '')}/patch/#{commit_id}\n\n---")
  patch_file = Tempfile.new(commit_id)
  begin
    patch_file.write(body)
    patch_file.rewind
    RestClient.post(
      config[:url] + ('/patch/%s/body' % commit_id),
      {
        :diff => patch_file
      },
      {
        'Authorization' => "Basic #{basic_auth}"
      }
    )
  rescue => e
    puts "[ERR] Upload of #{commit_id} failed. (#{e.message})"
  ensure
    patch_file.close
    patch_file.unlink
  end
end