Class: GitMaintain::Repo

Inherits:
Object
  • Object
show all
Defined in:
lib/repo.rb

Direct Known Subclasses

GitMaintainRepo, RDMACoreRepo

Constant Summary collapse

ACTION_LIST =
[
    :list_branches,
    :summary,
    # Internal commands for completion
    :list_suffixes, :submit_release
]
ACTION_HELP =
{
    :submit_release => "Push the tags to 'stable' remote and create the release packages",
    :summary => "Displays a summary of the configuration and the branches git-maintain sees"
}
@@VALID_REPO =
"github"
@@STABLE_REPO =
"stable"
@@SUBMIT_BINARY =
"git-release"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = nil) ⇒ Repo

Returns a new instance of Repo.



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

def initialize(path=nil)
    GitMaintain::checkDirectConstructor(self.class)

    @path = path
    @branch_list=nil
    @stable_branches=nil
    @suffix_list=nil
    @config_cache={}

    if path == nil
        @path = Dir.pwd()
    end
    @name = File.basename(@path)

    @valid_repo = getGitConfig("maintain.valid-repo")
    @valid_repo = @@VALID_REPO if @valid_repo == ""
    @stable_repo = getGitConfig("maintain.stable-repo")
    @stable_repo = @@STABLE_REPO if @stable_repo == ""

    @remote_valid=runGit("remote -v | egrep '^#{@valid_repo}' | grep fetch |
                        awk '{ print $2}' | sed -e 's/.*://' -e 's/\\.git//'")
    @remote_stable=runGit("remote -v | egrep '^#{@stable_repo}' | grep fetch |
                              awk '{ print $2}' | sed -e 's/.*://' -e 's/\\.git//'")

    @auto_fetch = getGitConfig("maintain.autofetch")
    case @auto_fetch
    when ""
        @auto_fetch = nil
    when "true", "yes", "on"
        @auto_fetch = true
    when "false", "no", "off"
        @auto_fetch = false
    else
        raise("Invalid value '#{@auto_fetch}' in git config for maintain.autofetch")
    end

    @branch_format_raw = getGitConfig("maintain.branch-format")
    @branch_format = Regexp.new(/#{@branch_format_raw}/)
    @stable_branch_format = getGitConfig("maintain.stable-branch-format")
    @stable_base_format = getGitConfig("maintain.stable-base-format")

    @stable_base_patterns=
        runGit("config --get-regexp   stable-base | egrep '^stable-base\.' | "+
               "sed -e 's/stable-base\.//' -e 's/---/\\//g'").split("\n").inject({}){ |m, x|
        y=x.split(" ");
        m[y[0]] = y[1]
        m
    }

    @mail_format = getGitConfig("maintain.mail-format")
    if @mail_format == "" then
        @mail_format = :imap_send
    else
        # Check that the format is valid
        case @mail_format
        when "imap_send", "send_email"
        else
            raise("Invalid mail-format #{@mail_format}")
        end

        @mail_format = @mail_format.to_sym()
    end
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



107
108
109
# File 'lib/repo.rb', line 107

def name
  @name
end

#pathObject (readonly)

Returns the value of attribute path.



107
108
109
# File 'lib/repo.rb', line 107

def path
  @path
end

#remote_stableObject (readonly)

Returns the value of attribute remote_stable.



107
108
109
# File 'lib/repo.rb', line 107

def remote_stable
  @remote_stable
end

#remote_validObject (readonly)

Returns the value of attribute remote_valid.



107
108
109
# File 'lib/repo.rb', line 107

def remote_valid
  @remote_valid
end

#stable_repoObject (readonly)

Returns the value of attribute stable_repo.



107
108
109
# File 'lib/repo.rb', line 107

def stable_repo
  @stable_repo
end

#valid_repoObject (readonly)

Returns the value of attribute valid_repo.



107
108
109
# File 'lib/repo.rb', line 107

def valid_repo
  @valid_repo
end

Class Method Details

.check_opts(opts) ⇒ Object



27
28
29
30
31
32
33
# File 'lib/repo.rb', line 27

def self.check_opts(opts)
    if opts[:action] == :submit_release then
        if opts[:br_suff] != "master" then
            raise "Action #{opts[:action]} can only be done on 'master' suffixed branches"
        end
    end
end

.execAction(opts, action) ⇒ Object



35
36
37
38
39
40
41
42
# File 'lib/repo.rb', line 35

def self.execAction(opts, action)
    repo   = Repo::load()

    if action == :submit_release then
        repo.stableUpdate()
    end
    repo.send(action, opts)
end

.load(path = ".") ⇒ Object



21
22
23
24
25
# File 'lib/repo.rb', line 21

def self.load(path=".")
    dir = Dir.pwd()
    repo_name = File.basename(dir)
    return GitMaintain::loadClass(Repo, repo_name, dir)
end

Instance Method Details

#apiObject

Github API stuff



402
403
404
# File 'lib/repo.rb', line 402

def api
 @api ||= Octokit::Client.new(:access_token => token, :auto_paginate => true)
end

#createRelease(opts, tag, github_rel = true) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/repo.rb', line 265

def createRelease(opts, tag, github_rel=true)
     log(:INFO, "Creating a release for #{tag}")
		    runGit("push #{@stable_repo} refs/tags/#{tag}")

     if github_rel == true then
    msg = runGit("tag -l -n1000 '#{tag}'") + "\n"

   # Ye ghods is is a horrific format to parse
   name, body = msg.split("\n", 2)
   name = name.gsub(/^#{tag}/, '').strip
   body = body.split("\n").map { |l| l.sub(/^    /, '') }.join("\n")
   api.create_release(@remote_stable, tag, :name => name, :body => body)
     end
end

#find_alts(commit) ⇒ Object



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/repo.rb', line 381

def find_alts(commit)
    alts=[]

    subj=runGit("log -1 --pretty='%s' #{commit}")
    return alts if $? != 0

    branches = getStableBranchList().map(){|v| @@STABLE_REPO + "/" + versionToStableBranch(v)}

    runGit("log -F --grep \"$#{subj}\" --format=\"%H\" #{branches.join(" ")}").
        split("\n").each(){|c|
        next if c == commit
        cursubj=runGit("log -1 --pretty='%s' #{c}")
        alts << c if subj == cursubj
    }

    return alts
end

#findStableBase(branch) ⇒ Object



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/repo.rb', line 289

def findStableBase(branch)
    base=nil
    if branch =~ @branch_format then
        base = branch.gsub(/^\*?\s*#{@branch_format_raw}\/.*$/, @stable_base_format)
    end

    @stable_base_patterns.each(){|pattern, b|
        if branch =~ /#{pattern}\// || branch =~ /#{pattern}$/
            base = b
            break
        end
    }
    raise("Could not a find a stable base for branch #{branch}") if base == nil
    return base
end

#genReleaseNotif(opts, new_tags) ⇒ Object



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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/repo.rb', line 211

def genReleaseNotif(opts, new_tags)
    return if @NOTIFY_RELEASE == false

    mail_path=`mktemp`.chomp()
    mail = File.open(mail_path, "w+")
    mail.puts "From " + runGit("rev-parse HEAD") + " " + `date`.chomp()
    mail.puts "From: " + getGitConfig("user.name") +
              " <" + getGitConfig("user.email") +">"
    mail.puts "To: " + getGitConfig("patch.target")
    mail.puts "Date: " + `date -R`.chomp()

    if new_tags.length > 4 then
        mail.puts "Subject: [ANNOUNCE] " + File.basename(@path) + ": new stable releases"
        mail.puts ""
        mail.puts "These version were tagged/released:\n * " +
                  new_tags.join("\n * ")
        mail.puts ""
    else
        mail.puts "Subject: [ANNOUNCE] " + File.basename(@path) + " " +
                  (new_tags.length > 1 ?
                       (new_tags[0 .. -2].join(", ") + " and " + new_tags[-1] + " have") :
                       (new_tags.join(" ") + " has")) +
                  " been tagged/released"
        mail.puts ""
    end
    mail.puts "It's available at the normal places:"
    mail.puts ""
    mail.puts "git://github.com/#{@remote_stable}"
    mail.puts "https://github.com/#{@remote_stable}/releases"
    mail.puts ""
    mail.puts "---"
    mail.puts ""
    mail.puts "Here's the information from the tags:"
    new_tags.sort().each(){|tag|
        mail.puts `git show #{tag} --no-decorate -q | awk '!p;/^-----END PGP SIGNATURE-----/{p=1}'`
        mail.puts ""
    }
    mail.close()

    case @mail_format
    when :imap_send
        puts runGitImap("< #{mail_path}")
    when :send_email
        run("cp #{mail_path} announce-release.eml")
        log(:INFO, "Generated annoucement email in #{@path}/announce-release.eml")
    end
    run("rm -f #{mail_path}")
end

#get_new_tokenObject



414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/repo.rb', line 414

def get_new_token
 puts "Requesting a new OAuth token from Github..."
 print "Github username: "
 user = $stdin.gets.chomp
 print "Github password: "
 pass = $stdin.noecho(&:gets).chomp
 puts

 api = Octokit::Client.new(:login => user, :password => pass)

 begin
  res = api.create_authorization(:scopes => [:repo], :note => "git-maintain")
 rescue Octokit::Unauthorized
  puts "Username or password incorrect.  Please try again."
  return get_new_token
       rescue Octokit::OneTimePasswordRequired
     print "Github OTP: "
     otp = $stdin.noecho(&:gets).chomp
  res = api.create_authorization(:scopes => [:repo], :note => "git-maintain",
                                          :headers => {"X-GitHub-OTP" => otp})
 end

 token = res[:token]
 runGit("config --global maintain.api-token '#{token}'")

       # Now reopen with the token so OTP does not bother us
       @api=nil
       token
end

#getBranchList(br_suff) ⇒ Object



167
168
169
170
171
172
173
174
175
176
# File 'lib/repo.rb', line 167

def getBranchList(br_suff)
    return @branch_list if @branch_list != nil

    @branch_list=runGit("branch").split("\n").map(){|x|
        x=~ /#{@branch_format_raw}\/#{br_suff}$/ ? 
            $1 : nil
    }.compact().uniq()

    return @branch_list
end

#getCommitHeadline(sha) ⇒ Object



154
155
156
# File 'lib/repo.rb', line 154

def getCommitHeadline(sha)
    return runGit("show --format=oneline --no-patch --no-decorate #{sha}")
end

#getCommitSubj(sha) ⇒ Object



157
158
159
# File 'lib/repo.rb', line 157

def getCommitSubj(sha)
    return runGit("log -1 --pretty=\"%s\" #{sha}")
end

#getGitConfig(entry) ⇒ Object



140
141
142
# File 'lib/repo.rb', line 140

def getGitConfig(entry)
    return @config_cache[entry] ||= runGit("config #{entry} 2> /dev/null").chomp()
end

#getStableBranchListObject



178
179
180
181
182
183
184
185
186
187
# File 'lib/repo.rb', line 178

def getStableBranchList()
    return @stable_branches if @stable_branches != nil

    @stable_branches=runGit("branch -a").split("\n").map(){|x|
        x=~ /remotes\/#{@@STABLE_REPO}\/#{@stable_branch_format.gsub(/\\1/, '([0-9]+)')}$/ ?
            $1 : nil
    }.compact().uniq()

    return @stable_branches
end

#getSuffixListObject



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/repo.rb', line 189

def getSuffixList()
    return @suffix_list if @suffix_list != nil

    @suffix_list = runGit("branch").split("\n").map(){|x|
        x=~ @branch_format ? 
            /^\*?\s*#{@branch_format_raw}\/([a-zA-Z0-9_-]+)\s*$/.match(x)[-1] :
            nil
    }.compact().uniq()

    return @suffix_list
end

#getUnreleasedTags(opts) ⇒ Object



201
202
203
204
205
206
207
208
209
210
# File 'lib/repo.rb', line 201

def getUnreleasedTags(opts)
    remote_tags=runGit("ls-remote --tags #{@stable_repo} |
                         egrep 'refs/tags/v[0-9.]*$'").split("\n").map(){
        |x| x.gsub(/.*refs\/tags\//, '')
    }
    local_tags =runGit("tag -l | egrep '^v[0-9.]*$'").split("\n")

    new_tags = local_tags - remote_tags
    return new_tags
end

#list_branches(opts) ⇒ Object



305
306
307
# File 'lib/repo.rb', line 305

def list_branches(opts)
    puts getBranchList(opts[:br_suff])
end

#list_suffixes(opts) ⇒ Object



308
309
310
# File 'lib/repo.rb', line 308

def list_suffixes(opts)
    puts getSuffixList()
end

#log(lvl, str) ⇒ Object



109
110
111
# File 'lib/repo.rb', line 109

def log(lvl, str)
    GitMaintain::log(lvl, str)
end

#run(cmd) ⇒ Object



113
114
115
# File 'lib/repo.rb', line 113

def run(cmd)
    return `cd #{@path} && #{cmd}`
end

#runBashObject



144
145
146
147
148
149
150
151
152
# File 'lib/repo.rb', line 144

def runBash()
    runSystem("bash")
    if $? == 0 then
        log(:INFO, "Continuing...")
    else
        log(:ERROR, "Shell exited with code #{$?}. Exiting")
        raise("Cancelled by user")
    end
end

#runGit(cmd) ⇒ Object



120
121
122
123
124
# File 'lib/repo.rb', line 120

def runGit(cmd)
    log(:DEBUG, "Called from #{caller[1]}")
    log(:DEBUG, "Running git command '#{cmd}'")
    return `git --work-tree=#{@path} #{cmd}`.chomp()
end

#runGitImap(cmd) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/repo.rb', line 131

def runGitImap(cmd)
    return `export GIT_ASKPASS=$(dirname $(dirname $(which git)))/lib/git-core/git-gui--askpass;
          if [ ! -f $GIT_ASKPASS ]; then
          	export GIT_ASKPASS=$(dirname $(which git))/git-gui--askpass;
          fi;
          if [ ! -f $GIT_ASKPASS ]; then
          	export GIT_ASKPASS=/usr/lib/ssh/ssh-askpass;
          fi; git --work-tree=#{@path} imap-send #{cmd}`
end

#runGitInteractive(cmd) ⇒ Object



125
126
127
128
129
# File 'lib/repo.rb', line 125

def runGitInteractive(cmd)
    log(:DEBUG, "Called from #{caller[1]}")
    log(:DEBUG, "Running interactive git command '#{cmd}'")
    return system("git --work-tree=#{@path} #{cmd}")
end

#runSystem(cmd) ⇒ Object



116
117
118
# File 'lib/repo.rb', line 116

def runSystem(cmd)
    return system("cd #{@path} && #{cmd}")
end

#stableUpdate(fetch = nil) ⇒ Object



161
162
163
164
165
166
# File 'lib/repo.rb', line 161

def stableUpdate(fetch=nil)
    fetch = @auto_fetch if fetch == nil
    return if fetch == false
    log(:VERBOSE, "Fetching stable updates...")
    runGit("fetch #{@stable_repo}")
end

#submit_release(opts) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/repo.rb', line 311

def submit_release(opts)
    new_tags = getUnreleasedTags(opts)
    if new_tags.empty? then
        log(:INFO,  "All tags are already submitted.")
        return
    end

    log(:WARNING, "This will officially release these tags: #{new_tags.join(", ")}")
    rep = GitMaintain::confirm(opts, "release them", true)
    if rep != 'y' then
        raise "Aborting.."
    end

    if @NOTIFY_RELEASE != false
        genReleaseNotif(opts, new_tags)
    end

    log(:WARNING, "Last chance to cancel before submitting")
    rep= GitMaintain::confirm(opts, "submit these releases", true)
    if rep != 'y' then
        raise "Aborting.."
    end
    submitReleases(opts, new_tags)
end

#submitReleases(opts, new_tags) ⇒ Object



259
260
261
262
263
# File 'lib/repo.rb', line 259

def submitReleases(opts, new_tags)
    new_tags.each(){|tag|
        createRelease(opts, tag)
    }
end

#summary(opts) ⇒ Object



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/repo.rb', line 335

def summary(opts)
      log(:INFO, "Configuration summary:")
      log(:INFO, "Stable remote: #{@stable_repo}")
      log(:INFO, "Validation remote: #{@valid_repo}")
      log(:INFO, "")
      log(:INFO, "Branch config:")
      log(:INFO, "Local branch format: /#{@branch_format_raw}/")
      log(:INFO, "Remote stable branch format: #{@stable_branch_format}")
      log(:INFO, "Remote stable base format: #{@stable_base_format}")

      if @stable_base_patterns.length > 0 then
          log(:INFO, "")
          log(:INFO, "Stable base rules:")
          @stable_base_patterns.each(){|name, base|
              log(:INFO, "\t#{name} -> #{base}")
          }
      end
      brList = getBranchList(opts[:br_suff])
      brStList = getStableBranchList()

      if brList.length > 0 then
          log(:INFO, "")
          log(:INFO, "Local branches:")
          brList.each(){|br|
              branch = Branch.load(self, br, nil, opts[:branch_suff])
              localBr = branch.local_branch
              stableBr = @@STABLE_REPO + "/" + branch.remote_branch
              stableBase = branch.stable_base
              runGit("rev-parse --verify --quiet #{stableBr}")
              stableBr = "<MISSING>" if $? != 0 
              log(:INFO, "\t#{localBr} -> #{stableBr} (#{stableBase})")
              brStList.delete(br)
          }
      end

      if brStList.length > 0 then
          log(:INFO, "")
          log(:INFO, "Upstream branches:")
          brStList.each(){|br|
              branch = Branch.load(self, br, nil, opts[:branch_suff])
              stableBr = @@STABLE_REPO + "/" + branch.remote_branch
              stableBase = branch.stable_base
              log(:INFO, "\t<MISSING> -> #{stableBr} (#{stableBase})")
          }
      end
end

#tokenObject



406
407
408
409
410
411
412
413
# File 'lib/repo.rb', line 406

def token
 @token ||= begin
             # We cannot use the 'defaults' functionality of git_config here,
             # because get_new_token would be evaluated before git_config ran
             tok = getGitConfig("maintain.api-token")
                      tok.to_s() == "" ? get_new_token : tok
            end
end

#versionToLocalBranch(version, suff) ⇒ Object



280
281
282
283
# File 'lib/repo.rb', line 280

def versionToLocalBranch(version, suff)
    return @branch_format_raw.gsub(/\\\//, '/').
        gsub(/\(.*\)/, version) + "/#{suff}"
end

#versionToStableBranch(version) ⇒ Object



285
286
287
# File 'lib/repo.rb', line 285

def versionToStableBranch(version)
    return version.gsub(/^(.*)$/, @stable_branch_format)
end