Class: Publicity::Main

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

Instance Method Summary collapse

Constructor Details

#initializeMain

Returns a new instance of Main.



10
11
12
13
14
15
16
17
# File 'lib/publicity/prompt.rb', line 10

def initialize
  @credential_helper = `git config --global credential.helper`.delete("\n")
  @github = nil
  @username = nil
  @auth_type = nil

  prompt
end

Instance Method Details

#create_readme!(repo, data, remote_repo_name) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/publicity/prompt.rb', line 288

def create_readme!(repo, data, remote_repo_name)
  text = <<EOS
##{remote_repo_name}

This is a copy of the work I did on a private repo, originally a project from
the [Hack Reactor](http://hackreactor.com) curriculum. This project was worked
on with a pair, and as such is representative of the kind of problems that I've
tackled, but not of my solo work.

For a better perspective on my own work, please see [#{data[:name]}](#{data[:url]}).
EOS

  oid = repo.write(text, :blob)
  index = repo.index
  # Read the current tree into the index. This prevents us from overwriting
  # the current index (and thus replacing the entire current tree)
  index.read_tree(repo.lookup(repo.head.target).tree)
  index.add(:path => "README.md", :oid => oid, :mode => 0100644)

  author = { :email => "[email protected]", :name => 'Hack Reactor', :time => Time.now }

  Rugged::Commit.create(repo,
    :tree       => repo.lookup(index.write_tree),
    :author     => author,
    :committer  => author,
    :message    => "Add a readme (automated commit)",
    :parents    => repo.empty? ? [] : [repo.head.target].compact,
    :update_ref => 'HEAD')
end

#get_github_credentialsObject



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
# File 'lib/publicity/prompt.rb', line 206

def get_github_credentials
  # TODO: Set a flag guarding Keychain access. If we try to access GitHub and the
  # user's credentials fail, this will prevent us from looping over invalid
  # keychain credentials over and over.

  # If we're on OS X and can retrieve GitHub credentials from the keychain,
  # use those. If the keychain is unavailable, or if we weren't able to
  # retrieve GitHub credentials from it, we'll prompt the user for their
  # credentials.
  if Publicity::HOST_IS_OSX
    item = Keychain.items.find { |i| i.label =~ /github\.com/ }
  end

  # Create an instance of the GitHub API. We'll use this to make all GitHub
  # requests
  @github = Github.new do |config|
    if item
      @auth_type = 'keychain'
      config. = item.
      config.password = item.password
    else
      @auth_type = 'basic'
      config. = ask("Enter your GitHub username: ") { |q| q.echo = true }
      config.password = ask("And your password: ") { |q| q.echo = "*" }
    end
  end
end

#get_github_credentials!Object



234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/publicity/prompt.rb', line 234

def get_github_credentials!
  begin
    get_github_credentials
    # Try to access something that requires credentials to test if these
    # credentials are valid
    @github.authorizations.list
  rescue
    puts '', "Sorry, login to GitHub failed. Please re-enter your credentials."
    retry
  end
  @username = @github.current_options[:login]
  @github
end

#get_private_reposObject



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/publicity/prompt.rb', line 256

def get_private_repos
  result = Array.new

  @github.repos.list do |repo|
    # If the repo isn't a fork, we need to skip it. This prevents us from
    # inadvertently trying to access a repo on which a user has been
    # granted collaborator status, but hasn't forked.
    unless repo.fork
      next
    end

    # The version of repo that comes with github.repos.list only has a
    # limited subset of the repo's metadata--get the full version
    #
    # TODO: This is jank. Jesus, please fix this. Rescue any errors and
    # just skip to the next repo.
    begin
      repo = @github.repos.get(@username, repo[:name])
    rescue
      next
    end

    # If the repo is still private and it's a fork of a Hack Reactor repo,
    # it qualifies for liberation
    if repo[:private] and repo[:parent].andand[:owner].andand[:login] === 'hackreactor'
      result.push repo
    end
  end

  result
end

#get_project_infoObject



248
249
250
251
252
253
254
# File 'lib/publicity/prompt.rb', line 248

def get_project_info
  project = Hash.new
  project[:name] = ask("Please enter the name of that project: ")
  project[:url] = ask("Please enter the URL to that project's GitHub repository, or to where it's hosted:")
  puts ''
  project
end

#promptObject



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
# File 'lib/publicity/prompt.rb', line 19

def prompt
  puts <<EOS
PUBLICITY
=========

Hello! I'm a script that will publicize all of your forked repositories.

EOS
  ask("Press enter when you're ready.") { |q| q.echo = '' }

  # Clear the screen
  print "\e[2J\e[f"

  puts "I'll need your GitHub credentials to get started.", ''
  get_github_credentials!

  puts <<EOS

Thanks.

One more thing--this script will create a new readme for each of your projects.
The readme will point to a project you think shows off your skills; you should
pick a project you're proud of, and you should avoid picking a group project.

EOS
  project = get_project_info

  # Gather a list of the user's repos which are forked from the hackreactor
  # organization and are private. Ask the user which of those repos they'd like
  # to publicize.
  remote_repos = Array.new
  private_repos = get_private_repos
  if private_repos.length === 0
    puts "Sorry, doesn't look like you have any private forks. Exiting..."
    exit 0
  end

  private_repos.each do |private_repo|
    response = ask("Would you like to publicize the #{private_repo[:name]} repository? (y/n): ") do |q|
      q.validate = /^(y|n)$/i
      q.responses[:not_valid] = "Sorry, that's an invalid option. Please enter either y or n: "
    end
    remote_repos.push(private_repo) if response === 'y'
  end

  # Create a scratch directory where we'll clone all our repos. This repo
  # will get cleaned up after the begin block.
  tmpdir = Dir.mktmpdir(nil, '/tmp')
  begin
    # Determine the credential helper we'll be using
    if @auth_type === 'basic'
      # If the user is using basic auth, we'll need to put their credentials
      # somewhere Git can access.
      `git config --global credential.helper store`
      tmpcreds = File.open("#{Dir.home}/.git-credentials", 'w', 0664) do |f|
        f.write("https://#{@username}:#{@github.current_options[:password]}@github.com")
        f
      end
    elsif @auth_type === 'keychain'
      `git config --global credential.helper osxkeychain`
    else
      raise StandardError, "Unknown credential helper"
    end

    remote_repos.each do |remote_repo|
      puts "Cloning down #{remote_repo[:name]}..."

      dir = "#{tmpdir}/#{remote_repo[:name]}.git"

      # Clone the original repo
      `git clone --bare #{remote_repo.clone_url} #{dir} 1> /dev/null`

      puts "Finished cloning."

      # Wrap the cloned git directory in a libgit wrapper
      local_repo = Rugged::Repository.new(dir)

      # Get a list of all the branches, and filter out irrelevant branches
      branches = []
      regex = /(#{Regexp.quote(@username)}|master|.*2013)/
      Rugged::Branch.each(local_repo) do |branch|
        branches.push(branch) if branch.name =~ regex
      end

      # Ask the user which branch they want to keep; we'll end up deleting
      # all other branches. Skip over this prompt if the repo only has a
      # :master branch.
      puts <<EOS

I'll now take a branch of your choosing and make it your master branch. This
way, anyone who looks at your repository will see your code right away, rather
than the repository's boilerplate code.

EOS
      if branches.any? { |b| b.name == 'master' } and branches.length === 1
        branch = branches[0]
        puts "Only found branch `#{branch.name}`. Using that one."
      else
        branch = choose do |menu|
          menu.index = :number
          menu.select_by = :index
          menu.prompt = "Please enter the number of the branch you'd like to keep: "
          menu.choices(*branches.map { |item| item.name }) do |selection|
            branches.detect { |b| b.name == selection }
          end
        end
      end

      # Replace the master branch with the user's branch. This ensures that
      # anyone viewing the repo on GitHub is first presented with the user's
      # code, not some empty boilerplate project.
      if not branch.name =~ /^master$/
        branch.move('master')
      end

      # Delete all remote branches
      Rugged::Branch.each_name(local_repo, :remote).sort.each do |r|
        if not r =~ /^master$/
          Rugged::Branch.lookup(local_repo, r).delete! if Rugged::Branch.lookup(local_repo, r)
        end
      end

      # Generate a readme and commit it to the repository.
      create_readme!(local_repo, project, remote_repo[:name])

      # Rename the source repo on GitHub to make way for the (nicenamed) new
      # repository.
      @github.repos.edit @username, "#{remote_repo[:name]}",
        :name => "#{remote_repo[:name]}.old",
        :private => true

      # Create an endpoint for the new repository.
      options = {
        :name => remote_repo[:name],
        :has_issues => false,
        :has_wiki => false
      }
      @github.repos.create options

      # Push the processed repository back up
      puts '', 'Pushing up your processed repository...'
      Dir.chdir(dir)
      # Write to ~/.netrc and ensure delete afterward. Move mktmpdir into
      # that block.
      `git push -u #{remote_repo[:clone_url]} master 1> /dev/null`

      # Display a different message if this is the last item
      if remote_repos.index(remote_repo) === remote_repos.length - 1
        puts "Finished with #{remote_repo[:name]}. Moving on to the next repository...", ''
      else
        puts "Finished with #{remote_repo[:name]}. All repos processed.", ''
      end
    end
    # Each user currently has two versions of each repo up on GitHub--a
    # private one called reponame.old, and a public one without the .old
    # suffix. Offer to delete all the old, private repos.
    puts <<EOS
All your repos have been pushed up to GitHub. Please review all your newly
pushed repos to make sure they look okay.

EOS
    delete_branches = ask("Would you like me to delete the old, private versions of your repos? (y/n) ") do |q|
      q.validate = /^(y|n)$/i
      q.responses[:not_valid] = "Sorry, that's an invalid option. Please enter either y or n: "
    end

    if delete_branches === 'y'
      remote_repos.each do |private_repo|
        @github.repos.delete(@username, "#{private_repo[:name]}.old")
      end
    end
  ensure
    # Make sure we tidy up after ourselves
    `git config --global credential.helper '#{@credential_helper}'`
    FileUtils.remove_entry(tmpdir) if tmpdir
    FileUtils.remove_entry("#{Dir.home}/.git-credentials") if tmpcreds
  end
  puts <<EOS

All your repos are now public. Please note that any of the repos this script
processed are now out of sync with your local versions; before making any
changes to local copies of these repos, please refresh from GitHub.

Enjoy!
EOS
end