git_pr 0.0.2 → 0.0.3.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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