git_pr 0.0.2 → 0.0.3.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e177f1696787c1c00a2f32b1434b622513690951
4
- data.tar.gz: e8fd9ce384e2c11d7db089a587b73ecc657ada12
3
+ metadata.gz: 658d65e69d1e82754cf3a52a1dbc2acb6f7e6c7f
4
+ data.tar.gz: e4755905156ddc6de7dde59f8cdc244daeec8258
5
5
  SHA512:
6
- metadata.gz: 12174ea2522aee16ccaf1e7941dea8395996558a878409c670661c57c72d96f466a9de8590ea7e969142b87f5b0668879c6417a158fb70a084da697ca15e6b65
7
- data.tar.gz: d7ccc87a980d5e36af33f799a52d188231b8035b05a9427963392e05342a4f421673cd8b7c888dda34767b8c5282b42610bfc54d4006d407bc4568e98f8f3003
6
+ metadata.gz: 076c502311bb4c1cd68c82a22dadd5cbffa3ee7df1f5cafb1e638f64293707281b58f7e8532aefc57fde4a96f8971c8439717339464931e94b463ab87b3d1a3c
7
+ data.tar.gz: 78e4a335f11d88519db214dfeaabf1a824fbd97a4070b4242bf3edda6131346f7b067d765d7e11f94c3c8ec1cad27b4ce015553694d790744ca311bd6a83f056
data/bin/git-pr CHANGED
@@ -9,80 +9,98 @@
9
9
 
10
10
  require 'colorize'
11
11
  require 'git'
12
+ require 'git_pr'
12
13
  require 'highline/import'
13
14
  require 'io/console'
14
15
  require 'octokit'
15
16
  require 'optparse'
16
- require 'pp'
17
- require 'git_pr'
18
17
  require 'ostruct'
18
+ require 'pp'
19
19
 
20
+ $verbose = false
20
21
  options = OpenStruct.new(:help => false,
21
22
  :verbose => false,
22
- :auth => OpenStruct.new(),
23
+ :project => "origin",
24
+ :pull_request => nil,
25
+ :diff => OpenStruct.new(),
26
+ :difftool => OpenStruct.new(),
23
27
  :list => OpenStruct.new(),
24
- :merge => OpenStruct.new(:remote => "origin"),
28
+ :merge => OpenStruct.new(),
25
29
  :open => OpenStruct.new())
26
30
 
27
31
  global_options = OptionParser.new do |opts|
28
- opts.banner = "Usage: git pr [options] subcommand [options]"
32
+ opts.banner = <<eos
33
+ git_pr version #{GitPr::VERSION}
34
+
35
+ Usage: git pr [options] subcommand [options]
36
+ eos
29
37
 
30
38
  opts.separator "\nGlobal options"
31
39
 
40
+ opts.on("-p",
41
+ "--project [REMOTE|PROJECT]",
42
+ "The GitHub project to access. Can be a named remote, or a GitHub project in",
43
+ "<user>/<project> form. Defaults to the GitHub project that the \"origin\" remote",
44
+ "points to.") do |project|
45
+ options.project = project
46
+ end
32
47
  opts.on("-h", "--help", "Show help") do
33
48
  options.help = true
34
49
  end
35
50
  opts.on("-v", "--verbose", "Verbose output") do
36
- options.verbose = true
51
+ $verbose = true
37
52
  end
38
53
  opts.on("-V", "--version", "Print version") do
39
54
  puts GitPr::VERSION
40
55
  exit
41
56
  end
42
57
 
43
- # auth: Check GitHub auth credentials, and prompt to update them if necessary
44
- # list: List open pull requests
45
- # open: Open the webpage for a pull request
46
58
  opts.separator <<eos
47
59
 
48
60
  Valid subcommands:
49
61
  merge: Merge and close a pull request
62
+ list: List open pull requests
63
+
64
+ Run "git pr <subcommand> -h" for help with subcommands.
50
65
 
51
66
  eos
52
67
  end
53
68
 
54
- subcommands = {
55
- # 'auth' => OptionParser.new do |opts|
56
- # opts.banner = "Usage: git pr auth [options]"
69
+ def make_diff_argument_parser command_name
70
+ OptionParser.new do |opts|
71
+ opts.banner = <<eos
72
+ Usage: git pr #{command_name} [PR number] [-- [additional options]]
57
73
 
58
- # opts.separator "\nAuth command options"
74
+ Fetch the latest changes for the specified PR, and then run "git
75
+ #{command_name}". Additional options are passed to the "git #{command_name}" command.
76
+ eos
59
77
 
60
- # opts.on("-r", "--readonly", "Check without prompting") do |v|
61
- # options.auth.readonly = true
62
- # end
78
+ opts.separator ""
79
+ end
80
+ end
63
81
 
64
- # opts.separator ""
65
- # end,
66
- # 'list' => OptionParser.new do |opts|
67
- # opts.banner = "Usage: git pr list [options]"
82
+ subcommands = {
83
+ 'diff' => make_diff_argument_parser("diff"),
84
+ 'difftool' => make_diff_argument_parser("difftool"),
85
+ 'list' => OptionParser.new do |opts|
86
+ opts.banner = "Usage: git pr list [options]"
68
87
 
69
- # opts.separator "\nList command options"
88
+ opts.separator "\nList command options"
70
89
 
71
- # opts.on("-r", "--readonly", "Check without prompting") do |v|
72
- # options.list.readonly = true
73
- # end
90
+ opts.on("-u", "--user [USERNAME]", "Only list PRs for the named GitHub user") do |user|
91
+ options.list.user = user
92
+ end
74
93
 
75
- # opts.separator ""
76
- # end,
94
+ opts.separator ""
95
+ end,
77
96
  'merge' => OptionParser.new do |opts|
78
- opts.banner = "Usage: git pr merge [options] [PR number]"
97
+ opts.banner = <<eos
98
+ Usage: git pr merge [PR number]
79
99
 
80
- opts.separator "\nMerge command options"
100
+ If a PR number isn't passed, a menu of open PRs will be displayed.
101
+ eos
81
102
 
82
- opts.on("-r", "--remote REMOTE", "Remote that PR will be merged into. Default: origin") do |remote|
83
- puts remote
84
- options.merge.remote = remote
85
- end
103
+ # opts.separator "\nMerge command options"
86
104
 
87
105
  opts.separator ""
88
106
  end,
@@ -121,42 +139,6 @@ rescue OptionParser::InvalidOption => e
121
139
  exit
122
140
  end
123
141
 
124
- # TODO: clean up subcommand parsing
125
- if command == "merge" and !options.merge.additional_arguments.empty?
126
- options.merge.pr = options.merge.additional_arguments.shift.to_i
127
- end
128
-
129
- def run(cmd, args = { :failure => lambda {} })
130
- puts cmd.green
131
- puts `#{cmd}`
132
- puts ''
133
- if $?.exitstatus != 0
134
- args[:failure].call()
135
- exit -1
136
- end
137
- end
138
-
139
- def run_test_output_empty(cmd)
140
- "" == `#{cmd}`
141
- end
142
-
143
- def pull_summary(pull)
144
- return "##{pull[:number]} from #{pull[:user][:login]}: \"#{pull[:title]}\""
145
- end
146
-
147
- def query_for_pull_to_merge(pulls)
148
- puts
149
- pull_to_merge = nil
150
- choose do |menu|
151
- menu.prompt = "Select PR to merge: "
152
- pulls.each do |pull|
153
- menu.choice(pull_summary(pull)) { pull_to_merge = pull }
154
- end
155
- menu.choice(:Quit, "Exit program.") { exit }
156
- end
157
- return pull_to_merge
158
- end
159
-
160
142
  if not GitPr::GitHub.test_credentials and not GitPr::GitHub.prompt_for_credentials
161
143
  exit -1
162
144
  end
@@ -166,156 +148,85 @@ GitPr::GitHub.initialize_octokit
166
148
  # Get local Git object pointed at our repo root
167
149
  git = Git.open `git rev-parse --show-toplevel`.chomp!
168
150
 
169
- # Find the target repository that we'll merge the pull request into
170
- target_remote = git.remotes.find { |x| x.name == options.merge.remote }
171
- url_match = target_remote.url.match "^git@github.com:(.*)/(.*).git"
172
- organization = url_match[1]
173
- repository = url_match[2]
151
+ def pull_summary(pull)
152
+ return "##{pull[:number]} from #{pull[:user][:login]}: \"#{pull[:title]}\""
153
+ end
174
154
 
175
- github_repo = Octokit.repo "#{organization}/#{repository}"
176
- # pp github_repo
155
+ # Figure out what GitHub project we're dealing with.
156
+ github_project = GitPr::GitHub.determine_project_name_from_command_line git, options.project
177
157
 
178
- pulls = Octokit.pulls "#{organization}/#{repository}/pulls"
179
- unless pulls.length > 0
180
- puts "No open pull requests found for #{organization}/#{repository}.".yellow
181
- exit
182
- end
158
+ case command
183
159
 
184
- if options.merge.pr
185
- pull = pulls.find { |p| p[:number] == options.merge.pr }
186
- unless pull
187
- puts "Pull request #{options.merge.pr} not found!".red
188
- exit
160
+ when "merge"
161
+ unless options.merge.additional_arguments.empty?
162
+ pull_request = options.merge.additional_arguments.shift.to_i
163
+ end
164
+ # Load a pull request
165
+ pull = GitPr::GitHub.find_or_prompt_for_pull_request github_project, pull_request
166
+ GitPr::merge_pull_cleanly git, pull
167
+
168
+ when "list"
169
+ pulls = Octokit.pulls "#{github_project}/pulls"
170
+ if options.list.user
171
+ pulls = pulls.select { |p| p[:user][:login] == options.list.user }
189
172
  end
190
- else
191
- pull = query_for_pull_to_merge pulls
192
- end
193
-
194
173
 
195
- pull_number = pull[:number]
196
- source_branch = pull[:head][:ref]
197
- source_repo_ssh_url = pull[:head][:repo][:git_url]
198
- source_repo_clone_url = pull[:head][:repo][:clone_url]
174
+ if pulls.any?
175
+ pulls.each { |p| puts pull_summary(p) }
176
+ else
177
+ puts "No open pull requests found.".yellow
178
+ end
199
179
 
200
- target_branch = pull[:base][:ref]
201
- target_repo_ssh_url = pull[:base][:repo][:git_url]
202
- target_repo_clone_url = pull[:base][:repo][:clone_url]
203
180
 
204
- puts
205
- puts "Merging #{pull_summary(pull)}".cyan
206
- puts "#{target_repo_ssh_url}/#{target_branch} <= #{source_repo_ssh_url}/#{source_branch}".cyan
181
+ when "diff", "difftool"
182
+ pull_request = nil
207
183
 
208
- # find or add a remote for the PR
209
- # pp git.remotes.last
210
- source_remote = git.remotes.find { |x| [pull[:head][:repo][:git_url],
211
- pull[:head][:repo][:ssh_url]].include? x.url }
212
- unless source_remote
213
- puts "Adding remote: #{pull[:user][:login]} from #{pull[:head][:repo][:ssh_url]}"
214
- source_remote = git.add_remote pull[:user][:login], pull[:head][:repo][:ssh_url]
215
- end
184
+ unless options[command].additional_arguments.empty?
185
+ pull_request = options[command].additional_arguments.shift.to_i
186
+ end
216
187
 
217
- # Fetch latest changes from source & target remotes. Useful in case one of source or target
218
- # branches doesn't exist locally yet, or if we've never pulled from one of the remotes.
219
- puts "Fetching latest changes from #{source_remote}"
220
- source_remote.fetch
221
- puts "Fetching latest changes from #{target_remote}"
222
- target_remote.fetch
223
-
224
- # Get the target branch up to date
225
- run "git checkout #{target_branch}"
226
- run "git pull --no-rebase --ff-only", :failure => lambda {
227
- "Unable to update local target branch (#{target_branch}). Please repair manually before continuing.".red
228
- }
188
+ unless pull_request
189
+ puts "Must specify a pull request to diff.\n".red
190
+ puts subcommands[command]
191
+ exit -1
192
+ end
229
193
 
230
- # If the local target branch differs from the remote target branch, they
231
- # must be reconciled manually.
232
- remote_target_branch = "#{target_remote}/#{target_branch}"
233
- if (not run_test_output_empty "git diff #{target_branch} #{remote_target_branch}")
234
- puts "Local branch (#{target_branch}) differs from remote branch (#{remote_target_branch}). Please reconcile before continuing.".red
235
- exit -1
236
- end
194
+ puts "Getting pull request information..."
195
+ begin
196
+ pull = Octokit.pull github_project, pull_request
197
+ rescue Octokit::NotFound
198
+ puts "Pull request #{pull_request} not found in #{github_project}.\n".red
199
+ puts subcommands[command]
200
+ exit -1
201
+ end
237
202
 
238
- # If a local branch exists with the name source_branch, check that it has the
239
- # same contents as the remote source branch. If not, it must be reconciled
240
- # manually.
241
- remote_source_branch = "#{source_remote}/#{source_branch}"
242
- if (not run_test_output_empty "git branch --list #{source_branch}" and not run_test_output_empty "git diff #{remote_source_branch} #{source_branch}")
243
- puts "Local branch (#{source_branch}) differs from remote branch (#{remote_source_branch}). Please reconcile before continuing.".red
244
- exit -1
245
- end
203
+ # Make sure we have the source and destination remotes
204
+ source_remote, target_remote = GitPr.ensure_remotes_for_pull_request git, pull
246
205
 
247
- # Check out the remote source branch using a temporary branch name,
248
- # failing if the temporary name already exists.
249
- rebase_branch = "#{source_branch}-rebase"
250
- if not run_test_output_empty "git branch --list #{rebase_branch}"
251
- puts "Local rebase branch (#{rebase_branch}) already exists. Please remove before continuing.".red
252
- exit -1
253
- end
254
- run "git checkout -b #{rebase_branch} #{remote_source_branch}"
255
-
256
- # Rebase the rebase branch on top of the target branch
257
- run "git rebase #{target_branch}", :failure => lambda {
258
- puts "Conflict detected in rebase. Please rebase manually, update PR, and re-run.".red
259
- run "git rebase --abort"
260
- run "git checkout #{target_branch}"
261
- run "git branch -D #{rebase_branch}"
262
- }
206
+ # Figure out if we need to fetch; skip it if we can
207
+ def have_commit_locally git, sha
208
+ begin
209
+ git.object sha
210
+ rescue Git::GitExecuteError
211
+ return false
212
+ end
213
+ true
214
+ end
215
+ source_remote.fetch unless have_commit_locally git, pull[:head][:sha]
216
+ target_remote.fetch unless have_commit_locally git, pull[:base][:sha]
217
+
218
+ source_branch = pull[:head][:ref]
219
+ target_branch = pull[:base][:ref]
220
+ merge_base = `git merge-base #{source_remote}/#{source_branch} #{target_remote}/#{target_branch}`.strip!
221
+ diff_command = "git #{command} #{options[command].additional_arguments.join " "} #{merge_base} #{source_remote}/#{source_branch}".gsub /\s* /, " "
222
+ puts "Executing #{diff_command}"
223
+
224
+ # Fork a child so that it gets access to the terminal. This makes "git diff" work with paging.
225
+ child = Kernel.fork do ||
226
+ Kernel.exec "#{diff_command} ;"
227
+ end
263
228
 
264
- # Force push the rebased branch to the source remote.
265
- run "git push -f #{source_remote.name} HEAD:#{source_branch}"
266
-
267
- # Merge the source branch into the target. Use --no-ff so that an explicit
268
- # merge commit is created.
269
- run "git checkout #{target_branch}"
270
- run "git merge --no-ff #{rebase_branch} -m 'Merge #{pull_summary(pull)}'"
271
-
272
- # Now that the rebase branch is merged, we can safely delete it.
273
- run "git branch -D #{rebase_branch}"
274
-
275
- # Print a log of the merge with branch structure visible. Jump through hoops to
276
- # get the right branch to start the log revision range with. If origin/develop
277
- # is a merge commit, we need the right parent of the merge.
278
- #
279
- # The goal is to get output like this:
280
- #
281
- # * 5be2a77 (HEAD, develop) PR #1269. Merge branch floatplane/feature/categories into develop.
282
- # |\
283
- # | * 2242141 (floatplane/feature/categories, feature/categories) Process CR feedback. Remove StaticCreatorListDataSource, will just rework Streamed* version to meet needs instead.
284
- # | * d7cf231 Implement StaticCreatorListDataSource for categories, rename CreatorListDataSource => StreamedCreatorListDataSource
285
- # | * ef034d0 Don't animate profile pic transitions when we're re-using a cell and needing to replace someone else's picture. Only animate from the blank thumbnail to an actual picture.
286
- # | * 25cda8b Refactor CreatorListViewController.
287
- # | * 682b7ba Adjust search dialog size and default position. Remove temp close button. Stub categories into search dialog.
288
- # | * e8ba0b1 Rename CollaboratorsListViewController => CreatorListViewController. Add CollaboratorListViewController as a subclass of CreatorListViewController, will refactor behavior into it in future commits.
289
- # | * e901256 Make dismissWithBackgroundTouch work for all CustomModalDialogs, even those that don't set useCustomPopover. Fix latent bug in ApplicationInfoNavigationController's implementation of the same.
290
- # |/
291
- # * 8d5ecbc (origin/develop, origin/HEAD) Merge branch 'feature/schemaUpgradeUtils' into develop
292
- #
293
- # where the log stops at origin/develop, no matter whether it's a merge commit or not.
294
- #
295
- origin_parent = `git rev-list --abbrev-commit --parents -n 1 origin/#{target_branch}`.split().last
296
- run "git log --graph --decorate --pretty=oneline --abbrev-commit --color #{target_branch} #{origin_parent}..#{target_branch}"
297
-
298
- def get_char
299
- state = `stty -g`
300
- `stty raw -echo -icanon isig`
301
-
302
- STDIN.getc.chr
303
- ensure
304
- `stty #{state}`
305
- puts ""
306
- end
229
+ # Wait for the child
230
+ Process.wait child
307
231
 
308
- print "Do you want to proceed with the merge (y/n)? ".cyan
309
- if get_char.downcase == 'y'
310
- puts "Pushing changes to #{target_remote}"
311
- run "git push #{target_remote} #{target_branch}"
312
- print "Do you want to delete the feature branch (y/n)? ".cyan
313
- if get_char.downcase == 'y'
314
- run "git push #{source_remote} :#{source_branch}"
315
- run "git branch -d #{source_branch}"
316
- end
317
- puts "Merge complete!".cyan
318
- else
319
- puts "Undoing local merge"
320
- run "git reset --hard #{target_remote}/#{target_branch}"
321
232
  end
@@ -0,0 +1,7 @@
1
+ module GitPr
2
+
3
+ def self.diff
4
+
5
+ end
6
+
7
+ end
data/lib/git_pr/github.rb CHANGED
@@ -15,8 +15,6 @@ module GitPr
15
15
  begin
16
16
  client.user
17
17
  rescue
18
- n.delete NETRC_KEY
19
- n.save
20
18
  return false
21
19
  end
22
20
  return true
@@ -31,7 +29,7 @@ module GitPr
31
29
  unless user
32
30
  print "Enter your github username: "
33
31
  user = STDIN.gets.chomp!
34
- print "Password: "
32
+ print "Enter github password for #{user} (never stored): "
35
33
  pass = STDIN.noecho(&:gets).chomp!
36
34
  puts "\n"
37
35
  end
@@ -80,5 +78,62 @@ module GitPr
80
78
  end
81
79
  end
82
80
 
81
+ def self.determine_project_name_from_command_line git, project_name
82
+ # Figure out what GitHub project we're dealing with. First, did they pass us a name of
83
+ # an existing remote, or did they pass a GitHub project?
84
+ project_remote = git.remotes.find { |x| x.name == project_name }
85
+ if project_remote
86
+ url_match = project_remote.url.match "^git@github.com:(.*).git"
87
+ unless url_match
88
+ puts "Specified project '#{options.project}' is not a GitHub remote.".red
89
+ exit -1
90
+ end
91
+ github_project = url_match[1]
92
+ else
93
+ github_project = project_name
94
+ end
95
+
96
+ begin
97
+ github_repo = Octokit.repo "#{github_project}"
98
+ rescue
99
+ puts "Project `#{github_project}` is not a valid GitHub project.".red
100
+ exit -1
101
+ end
102
+
103
+ github_project
104
+ end
105
+
106
+ def self.query_for_pull_to_merge(pulls)
107
+ puts
108
+ pull_to_merge = nil
109
+ choose do |menu|
110
+ menu.prompt = "Select PR to merge: "
111
+ pulls.each do |pull|
112
+ menu.choice(pull_summary(pull)) { pull_to_merge = pull }
113
+ end
114
+ menu.choice(:Quit, "Exit program.") { exit }
115
+ end
116
+ pull_to_merge
117
+ end
118
+
119
+ def self.find_or_prompt_for_pull_request github_project, pull_request
120
+ pulls = Octokit.pulls "#{github_project}/pulls"
121
+ unless pulls.length > 0
122
+ puts "No open pull requests found for '#{github_project}'.".yellow
123
+ exit
124
+ end
125
+ if pull_request
126
+ pull_request = pull_request
127
+ pull = pulls.find { |p| p[:number] == pull_request }
128
+ unless pull
129
+ puts "Pull request #{pull_request} not found in project '#{github_project}'!".red
130
+ exit -1
131
+ end
132
+ else
133
+ pull = self.query_for_pull_to_merge pulls
134
+ end
135
+ pull
136
+ end
137
+
83
138
  end
84
139
  end
@@ -0,0 +1,151 @@
1
+ module GitPr
2
+
3
+ def self.ensure_remotes_for_pull_request git, pull
4
+ source_remote = GitPr.ensure_remote_for_project(git,
5
+ pull[:head][:user][:login],
6
+ pull[:head][:repo][:git_url],
7
+ pull[:head][:repo][:ssh_url])
8
+
9
+ target_remote = GitPr.ensure_remote_for_project(git,
10
+ pull[:base][:user][:login],
11
+ pull[:base][:repo][:git_url],
12
+ pull[:base][:repo][:ssh_url])
13
+
14
+ [source_remote, target_remote]
15
+ end
16
+
17
+ def self.merge_pull_cleanly git, pull
18
+
19
+ pull_number = pull[:number]
20
+ source_branch = pull[:head][:ref]
21
+ source_repo_ssh_url = pull[:head][:repo][:git_url]
22
+ source_repo_clone_url = pull[:head][:repo][:clone_url]
23
+
24
+ target_branch = pull[:base][:ref]
25
+ target_repo_ssh_url = pull[:base][:repo][:git_url]
26
+ target_repo_clone_url = pull[:base][:repo][:clone_url]
27
+
28
+ puts "Merging #{pull_summary(pull)}".cyan
29
+ puts "#{target_repo_ssh_url}/#{target_branch} <= #{source_repo_ssh_url}/#{source_branch}\n".cyan
30
+
31
+ # find or add a remote for the PR
32
+ source_remote, target_remote = self.ensure_remotes_for_pull_request git, pull
33
+
34
+ # Fetch latest changes from source & target remotes. Useful in case one of source or target
35
+ # branches doesn't exist locally yet, or if we've never pulled from one of the remotes.
36
+ puts "Fetching latest changes from '#{source_remote}'"
37
+ source_remote.fetch
38
+ unless target_remote.name == source_remote.name
39
+ puts "Fetching latest changes from '#{target_remote}'"
40
+ target_remote.fetch
41
+ end
42
+
43
+ # Get the target branch up to date
44
+ puts "Update branch '#{target_branch}' from remote"
45
+ GitPr.run_command "git checkout -q #{target_branch}"
46
+ GitPr.run_command "git pull --no-rebase --ff-only", :failure => lambda {
47
+ "Unable to update local target branch '#{target_branch}'. Please repair manually before continuing.".red
48
+ }
49
+
50
+ # If the local target branch differs from the remote target branch, they
51
+ # must be reconciled manually.
52
+ remote_target_branch = "#{target_remote}/#{target_branch}"
53
+ if git.diff("remotes/#{remote_target_branch}", target_branch).any?
54
+ puts "Local branch '#{target_branch}' differs from remote branch '#{remote_target_branch}'. Please reconcile before continuing.".red
55
+ exit -1
56
+ end
57
+
58
+ # If a local branch exists with the name source_branch, check that it has the
59
+ # same contents as the remote source branch. If not, it must be reconciled
60
+ # manually.
61
+ remote_source_branch = "#{source_remote}/#{source_branch}"
62
+ if git.is_branch? source_branch and
63
+ git.diff("remotes/#{remote_source_branch}", source_branch).any?
64
+ puts "Local branch '#{source_branch}' differs from remote branch '#{remote_source_branch}'. Please reconcile before continuing.".red
65
+ exit -1
66
+ end
67
+
68
+ # Check out the remote source branch using a temporary branch name,
69
+ # failing if the temporary name already exists.
70
+ rebase_branch = "#{source_branch}-rebase"
71
+ puts "Create temporary branch '#{rebase_branch}'"
72
+ if git.is_branch? rebase_branch
73
+ puts "Local rebase branch '#{rebase_branch}' already exists. Please remove before continuing.".red
74
+ exit -1
75
+ end
76
+ GitPr.run_command "git checkout -q -b #{rebase_branch} #{remote_source_branch}"
77
+
78
+ # Add an at_exit handler to blow away the temp branch when we exit
79
+ at_exit do
80
+ if git.is_branch? rebase_branch
81
+ puts "Removing temporary branch #{rebase_branch}" if $verbose
82
+ GitPr.run_command "git checkout -q #{target_branch}"
83
+ GitPr.run_command "git branch -D #{rebase_branch}"
84
+ end
85
+ end
86
+
87
+ # Rebase the rebase branch on top of the target branch
88
+ puts "Rebasing '#{rebase_branch}' on top of '#{target_branch}'"
89
+ GitPr.run_command "git rebase #{target_branch} 2>&1", :failure => lambda {
90
+ GitPr.run_command "git rebase --abort"
91
+
92
+ puts "Unable to automatically rebase #{remote_source_branch} on top of #{target_branch}. Rebase manually and push before trying again.".red
93
+ puts "Run: " + "git checkout #{source_branch}".yellow
94
+ puts " " + "git rebase #{target_branch}".yellow + " and fix up any conflicts."
95
+ puts " " + "git push -f".yellow
96
+ }
97
+
98
+ # Force push the rebased branch to the source remote.
99
+ puts "Pushing changes from '#{rebase_branch}' to '#{source_remote.name}/#{source_branch}'"
100
+ GitPr.run_command "git push -f #{source_remote.name} HEAD:#{source_branch} 2>&1"
101
+
102
+ # Merge the source branch into the target. Use --no-ff so that an explicit
103
+ # merge commit is created.
104
+ puts "Merging changes from '#{rebase_branch}' to '#{target_branch}'"
105
+ GitPr.run_command "git checkout -q #{target_branch}"
106
+ GitPr.run_command "git merge --no-ff #{rebase_branch} -m 'Merge #{pull_summary(pull)}'"
107
+
108
+ # Print a log of the merge with branch structure visible. Jump through hoops to
109
+ # get the right branch to start the log revision range with. If origin/develop
110
+ # is a merge commit, we need the right parent of the merge.
111
+ #
112
+ # The goal is to get output like this:
113
+ #
114
+ # * 5be2a77 (HEAD, develop) PR #1269. Merge branch floatplane/feature/categories into develop.
115
+ # |\
116
+ # | * 2242141 (floatplane/feature/categories, feature/categories) Process CR feedback. Remove StaticCreatorListDataSource, will just rework Streamed* version to meet needs instead.
117
+ # | * d7cf231 Implement StaticCreatorListDataSource for categories, rename CreatorListDataSource => StreamedCreatorListDataSource
118
+ # | * ef034d0 Don't animate profile pic transitions when we're re-using a cell and needing to replace someone else's picture. Only animate from the blank thumbnail to an actual picture.
119
+ # | * 25cda8b Refactor CreatorListViewController.
120
+ # | * 682b7ba Adjust search dialog size and default position. Remove temp close button. Stub categories into search dialog.
121
+ # | * e8ba0b1 Rename CollaboratorsListViewController => CreatorListViewController. Add CollaboratorListViewController as a subclass of CreatorListViewController, will refactor behavior into it in future commits.
122
+ # | * e901256 Make dismissWithBackgroundTouch work for all CustomModalDialogs, even those that don't set useCustomPopover. Fix latent bug in ApplicationInfoNavigationController's implementation of the same.
123
+ # |/
124
+ # * 8d5ecbc (origin/develop, origin/HEAD) Merge branch 'feature/schemaUpgradeUtils' into develop
125
+ #
126
+ # where the log stops at origin/develop, no matter whether it's a merge commit or not.
127
+ #
128
+ puts "\nVerify that the merge looks clean:\n".cyan
129
+ origin_parent = `git rev-list --abbrev-commit --parents -n 1 #{target_remote}/#{target_branch}`.split().last
130
+ GitPr.run_command "git log --graph --decorate --pretty=oneline --abbrev-commit --color #{target_branch} #{origin_parent}..#{target_branch}", :force_print_output => true
131
+
132
+ if GitPr.prompt "\nDo you want to proceed with the merge (y/n)? ".cyan
133
+ puts "Pushing changes to '#{target_remote}'"
134
+ GitPr.run_command "git push #{target_remote} #{target_branch} 2>&1"
135
+ if GitPr.prompt "\nDo you want to delete the feature branch (y/n)? ".cyan
136
+ source_branch_sha = git.branches["#{source_remote}/#{source_branch}"].gcommit.sha[0..6]
137
+ GitPr.run_command "git push #{source_remote} :#{source_branch} 2>&1"
138
+ if git.is_branch? source_branch
139
+ source_branch_sha = git.branches[source_branch].gcommit.sha[0..6]
140
+ GitPr.run_command "git branch -D #{source_branch}"
141
+ end
142
+ puts "Feature branch '#{source_branch}' deleted. To restore it, run: " + "git branch #{source_branch} #{source_branch_sha}".green
143
+ end
144
+ puts "\nMerge complete!".cyan
145
+ else
146
+ puts "\nUndoing local merge"
147
+ GitPr.run_command "git reset --hard #{target_remote}/#{target_branch}"
148
+ end
149
+ end
150
+
151
+ end
@@ -1,3 +1,3 @@
1
1
  module GitPr
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3.beta1"
3
3
  end
data/lib/git_pr.rb CHANGED
@@ -1,7 +1,49 @@
1
1
  require "git_pr/github"
2
2
  require "git_pr/pull_request"
3
3
  require "git_pr/version"
4
+ require "git_pr/merge"
5
+ require "git_pr/diff"
4
6
 
5
7
  module GitPr
6
8
  # Your code goes here...
9
+
10
+ def self.run_command(cmd,
11
+ args = {
12
+ :failure => lambda {},
13
+ :force_print_output => false
14
+ })
15
+ puts cmd.green if $verbose
16
+ result = `#{cmd}`
17
+ puts result if $verbose || args[:force_print_output]
18
+ puts '' if $verbose
19
+ if $?.exitstatus != 0
20
+ args[:failure].call()
21
+ exit -1
22
+ end
23
+ end
24
+
25
+ def self.get_char
26
+ state = `stty -g`
27
+ `stty raw -echo -icanon isig`
28
+
29
+ STDIN.getc.chr
30
+ ensure
31
+ `stty #{state}`
32
+ puts ""
33
+ end
34
+
35
+ def self.prompt prompt
36
+ print prompt
37
+ return GitPr.get_char.downcase == 'y'
38
+ end
39
+
40
+ def self.ensure_remote_for_project git, username, ssh_url, git_url
41
+ # find or add a remote for the PR
42
+ remote = git.remotes.find { |r| [git_url, ssh_url].include? r.url }
43
+ unless remote
44
+ puts "Adding remote '#{username}' from #{ssh_url}"
45
+ remote = git.add_remote username, ssh_url
46
+ end
47
+ remote
48
+ end
7
49
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git_pr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Sharon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-03 00:00:00.000000000 Z
11
+ date: 2014-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -138,7 +138,9 @@ files:
138
138
  - bin/git-pr
139
139
  - git_pr.gemspec
140
140
  - lib/git_pr.rb
141
+ - lib/git_pr/diff.rb
141
142
  - lib/git_pr/github.rb
143
+ - lib/git_pr/merge.rb
142
144
  - lib/git_pr/pull_request.rb
143
145
  - lib/git_pr/version.rb
144
146
  homepage: ''
@@ -156,9 +158,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
156
158
  version: '0'
157
159
  required_rubygems_version: !ruby/object:Gem::Requirement
158
160
  requirements:
159
- - - '>='
161
+ - - '>'
160
162
  - !ruby/object:Gem::Version
161
- version: '0'
163
+ version: 1.3.1
162
164
  requirements: []
163
165
  rubyforge_project:
164
166
  rubygems_version: 2.0.14