Module: GitTopic
- Defined in:
- lib/git_topic.rb,
lib/git_topic/cli.rb,
lib/git_topic/git.rb,
lib/git_topic/logger.rb,
lib/git_topic/naming.rb,
lib/git_topic/comment.rb
Defined Under Namespace
Modules: Comment, Git, Logger, Naming
Constant Summary collapse
- GlobalOptKeys =
[ :verbose, :help, :verbose_given, :version, :completion_help, :completion_help_given, :no_log ]
- SubCommands =
%w( work-on done abandon status review comment comments accept reject install-aliases )
- Version =
lambda { h = YAML::load_file( "#{File.dirname( __FILE__ )}/../../VERSION.yml" ) if h.is_a? Hash [h[:major], h[:minor], h[:patch], h[:build]].compact.join( "." ) end }.call
Class Attribute Summary collapse
-
.global_opts ⇒ Object
Returns the value of attribute global_opts.
Class Method Summary collapse
-
.abandon(topic = nil, opts = {}) ⇒ Object
Delete
topic
locally and remotely. -
.accept(topic = nil, opts = {}) ⇒ Object
Accept the branch currently being reviewed.
- .comment(opts = {}) ⇒ Object
- .comments(spec = nil, opts = {}) ⇒ Object
-
.done(topic = nil, opts = {}) ⇒ Object
Done with the given topic.
- .install_aliases(opts = {}) ⇒ Object
-
.reject(topic_or_opts = nil, opts = {}) ⇒ Object
Reject the branch currently being reviewed.
-
.review(ref = nil, opts = {}) ⇒ Object
Switch to a review branch to check somebody else’s code.
- .run ⇒ Object
-
.setup(opts = {}) ⇒ Object
Setup .git/config.
-
.status(opts = {}) ⇒ Object
Produce status like.
-
.work_on(topic, opts = {}) ⇒ Object
Switch to a branch for the given topic.
Methods included from Comment
Methods included from Naming
Methods included from Git
Class Attribute Details
.global_opts ⇒ Object
Returns the value of attribute global_opts.
3 4 5 |
# File 'lib/git_topic/git.rb', line 3 def global_opts @global_opts end |
Class Method Details
.abandon(topic = nil, opts = {}) ⇒ Object
Delete topic
locally and remotely. Defaults to current topic if unspecified.
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 |
# File 'lib/git_topic.rb', line 78 def abandon topic=nil, opts={} local_branch = if topic.nil? parts = topic_parts current_branch if parts && parts[:namespace] == "wip" && parts[:user] == user topic = current_topic current_branch else raise "Cannot abandon #{current_branch}." end else wip_branch topic end unless rb = remote_branch( topic, :strip_remote => true ) raise "No such topic #{topic}." end git [ ( "checkout master" if current_branch == local_branch ), ( "branch -D #{local_branch}" if branches.include? local_branch ), "push origin :#{rb}", ].compact report "Topic #{topic} abandoned." end |
.accept(topic = nil, opts = {}) ⇒ Object
Accept the branch currently being reviewed.
252 253 254 255 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 |
# File 'lib/git_topic.rb', line 252 def accept topic=nil, opts={} raise "Must be on a review branch." unless on_review_branch? raise "Working tree must be clean" unless working_tree_clean? # switch to master # merge review branch, assuming FF # push master, destroy remote # destroy local user, topic = user_topic_name( current_branch ) local_review_branch = current_branch ff_merge = git [ "checkout master", "merge --ff-only #{local_review_branch}", ] unless ff_merge git "checkout #{local_review_branch}" raise " review branch is not up to date: merge not a fast-forward. Either rebase or reject this branch. ".unindent end rem_review_branch = find_remote_review_branch( topic ).gsub( %r{^origin/}, '' ) git [ "push origin master :refs/heads/#{rem_review_branch}", "branch -d #{local_review_branch}" ] report "Accepted topic #{user}/#{topic}." end |
.comment(opts = {}) ⇒ Object
286 287 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 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/git_topic.rb', line 286 def comment opts={} diff_legal = git( "diff --diff-filter=ACDRTUXB --quiet" ) && git( "diff --cached --diff-filter=ACDRTUXB --quiet" ) raise " Diffs are not comments. Files have been added, deleted or had their modes changed. See git diff --diff-filter=ACDRTUXB for a list of changes preventing git-topic comment from saving your comments. ".unindent unless diff_legal diff_empty = git( "diff --diff-filter=M --quiet" ) added_comments = case current_namespace when "wip" if existing_comments? raise " diff → comments not allowed when replying. Please make sure your working tree is completely clean and then invoke git-topic comment again. ".oneline unless diff_empty notes_from_reply_to_comments else puts "No comments to reply to. See git-topic comment --help for usage." return end when "review" if existing_comments? if opts[ :force_update ] notes_from_initial_comments( "edit" ) else raise " diff → comments not allowed when replying. Please make sure your working tree is completely clean and then invoke git-topic comment again. ".oneline unless diff_empty notes_from_reply_to_comments end else notes_from_initial_comments end else raise "Inappropriate namespace for comments: [#{namespace}]" end if added_comments report "Your comments have been saved." else report "You did not write any comments. Nothing to save." end end |
.comments(spec = nil, opts = {}) ⇒ Object
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 |
# File 'lib/git_topic.rb', line 344 def comments spec=nil, opts={} args = [ spec ].compact if args.empty? && current_branch.nil? if guess = guess_branch args << guess_branch puts " You are not on a branch and no topic branch was specified. Using alternate name for HEAD, #{guess}. ".oneline else puts " You are not on a branch and no topic branch was specified. I could not find an appropriate name for HEAD to guess. " return end end unless existing_comments? *args puts "There are no comments on this branch." return end range = "origin/master..#{remote_branch *args}" git "log #{range} --show-notes=#{notes_ref *args} --no-standard-notes", :show => true end |
.done(topic = nil, opts = {}) ⇒ Object
Done with the given topic. If none is specified, then topic is assumed to be the current branch (if it’s a topic branch).
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 |
# File 'lib/git_topic.rb', line 107 def done topic=nil, opts={} if topic.nil? raise " Current branch is not a topic branch. Switch to a topic branch or supply an argument. ".oneline if current_topic.nil? topic = current_topic else raise " Specified topic #{topic} does not refer to a topic branch. " unless branches.include? wip_branch( topic ) end raise "Working tree must be clean" unless working_tree_clean? wb = wip_branch( topic ) rb = review_branch( topic ) refspecs = [ "refs/heads/#{wb}:refs/heads/#{rb}", ":refs/heads/#{wb}", "refs/notes/reviews/*:refs/notes/reviews/*" ].join( " " ) git [ "push origin #{refspecs}", ("checkout master" if strip_namespace( topic ) == current_topic), "branch -D #{wip_branch( topic )}" ].compact report "Completed topic #{topic}. It has been pushed for review." end |
.install_aliases(opts = {}) ⇒ Object
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 |
# File 'lib/git_topic.rb', line 435 def install_aliases opts={} opts.assert_valid_keys :local, :local_given, *GlobalOptKeys flags = "--global" unless opts[:local] git [ "config #{flags} alias.work-on 'topic work-on'", "config #{flags} alias.done 'topic done'", "config #{flags} alias.review 'topic review'", "config #{flags} alias.accept 'topic accept'", "config #{flags} alias.reject 'topic reject'", "config #{flags} alias.comment 'topic comment'", "config #{flags} alias.comments 'topic comments'", ] report "Aliases installed Successfully.", " Error installing aliases. re-run with --verbose flag for details. ".oneline end |
.reject(topic_or_opts = nil, opts = {}) ⇒ Object
Reject the branch currently being reviewed.
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/git_topic.rb', line 375 def reject topic_or_opts=nil, opts={} if topic_or_opts.is_a? Hash topic = nil opts = topic_or_opts else topic = topic_or_opts end raise "Must be on a review branch." unless on_review_branch? unless working_tree_clean? if opts[:save_comments] comment else raise "Working tree must be clean without --save-comments." end end # switch to master # push to rejected, destroy remote # destroy local user, topic = user_topic_name( current_branch ) rem_review_branch = find_remote_review_branch( topic ).gsub( %r{^origin/}, '' ) rem_rej_branch = remote_rejected_branch( topic, user ) refspecs = [ "refs/heads/#{current_branch}:refs/heads/#{rem_rej_branch}", ":refs/heads/#{rem_review_branch}", "refs/notes/reviews/*:refs/notes/reviews/*" ].join( " " ) git [ "checkout master", "push origin #{refspecs}", "branch -D #{current_branch}" ] report "Rejected topic #{user}/#{topic}" end |
.review(ref = nil, opts = {}) ⇒ Object
Switch to a review branch to check somebody else’s code.
203 204 205 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 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/git_topic.rb', line 203 def review ref=nil, opts={} rb = remote_branches_organized review_branches = rb[:review] if ref.nil? # select the oldest (by HEAD) topic, if any exist if review_branches.empty? puts "nothing to review." return else user, topic = oldest_review_user_topic end else p = topic_parts( ref ) user, topic = p[:user], p[:topic] end if remote_topic_branch = find_remote_review_branch( topic ) # Get the actual user/topic, e.g. to get the user if ref only specifies # the topic. real_user, real_topic = user_topic_name( remote_topic_branch ) git [ switch_to_branch( review_branch( real_topic, real_user ), remote_topic_branch )] else raise "No review topic found matching ‘#{ref}’" end report "Reviewing topic #{user}/#{topic}." unless rebased_to_master? git "rebase --quiet master" report( " #{user}/#{topic} was not rebased to master, but has successfully been automatically rebased. ".unindent, " #{user}/#{topic} was not rebased to master, and I could not automatically rebase it. You will not be able to accept this topic. Either 1) rebase the topic manually or 2) reject the topic and ask the author to rebase. ".unindent ) end end |
.run ⇒ Object
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 205 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 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 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/git_topic/cli.rb', line 25 def run global_opts = self.global_opts = Trollop:: do " git-topic #{Version} Manage a topic/review workflow. see <http://github.com/hjdivad/git-topic> Commands are: #{SubCommands.join( " " )} Global Options: ".unindent version Version opt :verbose, "Verbose output, including complete traces on errors." opt :completion_help, "View instructions for setting up autocompletion." opt :no_log, "Disable logging." stop_on SubCommands end if global_opts[:completion_help] root = File.( "#{File.dirname( __FILE__ )}/../.." ) completion_bash_file = "#{root}/share/completion.bash" puts %Q{ To make use of bash autocompletion, you must do the following: 1. Make sure you source #{completion_bash_file} before you source git's completion. 2. Optionally, install git-topic with the --no-wrappers option. This is to sidestep ruby issue 3465 which makes loading gems through the generated wrapper far too slow for autocompletion. For more information, see: http://redmine.ruby-lang.org/issues/show/3465 }.unindent exit 0 end info '' info ARGV.join( " " ) cmd = ARGV.shift cmd_opts = Trollop:: do case cmd when "work-on" " git[-topic] work-on <topic> [<upstream> | --continue] Switches to a local work-in-progress (wip) branch for <topic>. The branch (and a matching remote branch) is created if necessary. If this is a rejected topic, work will continue from the state of the rejected topic branch. Similarly, if this is a review topic, the review will be pulled and work will continue on that topic. <topic>'s branch's HEAD will point to <upstream>, if supplied. If --continue is supplied instead, HEAD will point to the most recent review (i.e. submitted) of your topic branches. If you have just submitted a topic with git done, git work-on next-topic --continue would begin the next topic starting from where you had left off. If both <upstream> and --continue are omitted, <topic>'s branch's HEAD will default to the current HEAD. Options: ".unindent opt :continue, "Use latest review branch as <upstream>", :default => false when "abandon" " git[-topic] abandon [<topic>] Deletes <topic> locally and remotely. Defaults to current topic if unspecified. ".unindent when /done(-with)?/ " git[-topic] done Indicate that this topic branch is ready for review. Push to a remote review branch and switch back to master. Options: ".unindent when "status" " git st git-topic status Print a status, showing rejected branches to work on and branches that can be reviewed. Options: ".unindent opt :prepended, " Prepend status to git status output (for a complete view of status). ".oneline, :default => false when "review" " git[-topic] review [<topic>] Review <topic>. If <topic> is unspecified, review the oldest (by HEAD) topic. Options: ".unindent when "comment" " git[-topic] comment Add your comments to the current topic. If this is the first time you are reviwing <topic> you can set initial comments (see INITIAL_COMMENTS below). Otherwise, your GIT_EDITOR will open to let you enter your replies to the comments. Similarly, if you are working on a rejected branch, git-topic comment will open your GIT_EDITOR so you can reply to the reviewer's comments. INITIAL_COMMENTS For the initial set of comments, you can edit the files in your working tree to include any file specific comments. Simply ensure that all such comments are prefixed with a ‘#’. git-topic comment will convert your changes to a list of file-specific comments. In order to use this feature, there are several requirements about the output of git diff. 1. It must only have file modifications. i.e., no deletions, additions or mode changes. 2. Those modifications must only have line additions. i.e. no line deletions. 3. Those line additions must all begin with any amount of whitespace followed by a ‘#’ character. i.e. they should be comments. Options: ".unindent opt :force_update, " If you are commenting on the initial review and you wish to edit your comments, you can pass this flag to do so. ".oneline when "comments" " git[-topic] comments [<topic>] View the comments for <topic>, which defaults to the current topic. If your branch was rejected, you should read these comments so you know what to do to appease the reviewer. Options: ".unindent when "accept" " git[-topic] accept Accept the current in-review topic, merging it to master and cleaning up the remote branch. This will fail if the branch does not merge as a fast-forward in master. If that happens, the topic should either be rejected, or you can manually rebase. Options: ".unindent when "reject" " git[-topic] reject Reject the current in-review topic. Options: ".unindent opt :save_comments, " If the current diff includes your comments (see git-topic comment --help), this flag will autosave those comments before rejecting the branch. ".oneline when "install-aliases" " git-topic install-aliases Install aliases to make git topic nicer to work with. The aliases are as follows: w[ork-on] topic work-on done topic done r[eview] topic review accept topic accept reject topic reject st topic status --prepended Options: ".unindent opt :local, " Install aliases non-globally (i.e. in .git/config instead of $HOME/.gitconfig ".oneline, :default => false end end check_for_setup unless cmd == "setup" opts = global_opts.merge( cmd_opts ) display_git_output! if opts[:verbose] case cmd when "work-on" topic = ARGV.shift upstream = ARGV.shift opts.merge!({ :upstream => upstream }) work_on topic, opts when "abandon" topic = ARGV.shift abandon topic when /done(-with)?/ topic = ARGV.shift done topic, opts when "status" status opts when "review" spec = ARGV.shift review spec, opts when "comment" comment opts when "comments" spec = ARGV.shift comments spec, opts when "accept" topic = ARGV.shift accept topic, opts when "reject" topic = ARGV.shift reject topic, opts when "install-aliases" install_aliases opts when "setup" setup opts end rescue => error puts "Error: #{error.}" puts error.backtrace.join( "\n" ) if opts[:verbose] end |
.setup(opts = {}) ⇒ Object
Setup .git/config.
This means setting up:
1. refspecs for origin fetching for review comments.
2. notes.rewriteRef for copying review comments on rebase.
421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'lib/git_topic.rb', line 421 def setup opts={} cmds = [] cmds <<( "config --add remote.origin.fetch +refs/notes/reviews/*:refs/notes/reviews/*" ) unless has_setup_refspec? cmds <<( "config --add notes.rewriteRef refs/notes/reviews/*" ) unless has_setup_notes_rewrite? git cmds.compact end |
.status(opts = {}) ⇒ Object
Produce status like
# There are 2 topics you can review.
#
# from davidjh:
# zombies
# pirates
# from king-julian:
# fish
# whales
#
# 2 of your topics were rejected.
# dragons
# liches
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 |
# File 'lib/git_topic.rb', line 154 def status opts={} opts.assert_valid_keys :prepended, :prepended_given, *GlobalOptKeys sb = '' rb = remote_branches_organized # Only others' branche should appear as ‘topics you can review’ review_ut = rb[:review].select{ |u, t| u != user } rejected_ut = rb[:rejected] unless review_ut.empty? topic_count = review_ut.inject( 0 ){ |a, uts| a + uts.size } prep = topic_count == 1 ? "is 1" : "are #{topic_count}" sb << "# There #{prep} #{'topic'.pluralize( topic_count )} you can review.\n\n" sb << review_ut.map do |user, topics| sb2 = " from #{user}:\n" sb2 << topics.map do |t| age = ref_age( review_branch( t, user, :remote => true )) age_str = " (#{age} days)" if age && age > 0 " #{t}#{age_str}" end.join( "\n" ) sb2 end.join( "\n" ) end rejected_topics = (rejected_ut[ user ] || []).dup rejected_topics.map! do |topic| suffix = " (reviewer comments) " "#{topic}#{suffix if existing_comments?( "#{user}/#{topic}" )}" end unless rejected_topics.empty? sb << "\n" unless review_ut.empty? verb = rejected_topics.size == 1 ? 'is' : 'are' sb << "\n#{rejected_topics.size} of your topics #{verb} rejected.\n " sb << rejected_topics.join( "\n " ) end sb.gsub! "\n", "\n# " sb << "\n" unless sb.empty? print sb if opts[ :prepended ] print "#\n" unless sb.empty? git "status", :show => true end end |
.work_on(topic, opts = {}) ⇒ Object
Switch to a branch for the given topic.
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 |
# File 'lib/git_topic.rb', line 30 def work_on topic, opts={} opts.assert_valid_keys :continue, :continue_given, :upstream, *GlobalOptKeys raise "Topic must be specified" if topic.nil? upstream = if opts[:upstream] && opts[:continue] raise "upstream and continue options mutually exclusive." elsif opts[:upstream] opts[:upstream] elsif opts[:continue] newest_pending_branch end # setup a remote branch, if necessary wb = wip_branch( topic ) git( "push origin HEAD:refs/heads/#{wb}" ) unless remote_branches.include? "origin/#{wb}" # switch to the new branch git [ switch_to_branch( wb, "origin/#{wb}" )] # Check for rejected or review branch [ rejected_branch( topic ), review_branch( topic ) ].each do |b| if remote_branches.include? "origin/#{b}" git [ "reset --hard origin/#{b}", "push origin :refs/heads/#{b} +HEAD:refs/heads/#{wb}", ] end end # Reset upstream, if specified if upstream git "reset --hard #{upstream}" end report "Switching branches to work on #{topic}." if existing_comments? report "You have reviewer comments on this topic." end end |