| #!/usr/bin/env ruby |
| |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with this |
| # work for additional information regarding copyright ownership. The ASF |
| # licenses this file to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| |
| # This file has been shamelessly copied and tweaked from the one written by |
| # Victor Hugo Borja from Apache Buildr. Thanks Victor! |
| |
| # This script helps ODE developers to obtain their own git clone from |
| # github, having a set of pre-defined aliases to work with Apache's SVN. |
| |
| require 'yaml' |
| require 'optparse' |
| require 'ostruct' |
| |
| # Pager from http://nex-3.com/posts/73-git-style-automatic-paging-in-ruby |
| def run_pager |
| return if PLATFORM =~ /win32/ |
| return unless STDOUT.tty? |
| |
| read, write = IO.pipe |
| |
| unless Kernel.fork # Child process |
| STDOUT.reopen(write) |
| STDERR.reopen(write) if STDERR.tty? |
| read.close |
| write.close |
| return |
| end |
| |
| # Parent process, become pager |
| STDIN.reopen(read) |
| read.close |
| write.close |
| |
| ENV['LESS'] = 'FSRX' # Don't page if the input is short enough |
| |
| Kernel.select [STDIN] # Wait until we have input before we start the pager |
| pager = ENV['PAGER'] || 'less' |
| exec pager rescue exec "/bin/sh", "-c", pager |
| end |
| |
| def header |
| <<HEADER |
| |
| ODE official commit channel is Apache's svn repository, however some |
| developers may prefer to use git while working on several features and |
| merging other's changes. |
| |
| This script will configure a ode-git copy on so you can commit to svn. |
| |
| Enter <-h> to see options, <-H> to see notes about configured aliases |
| and recommended workflow, or any other option. |
| |
| Ctrl+D or an invalid option to abort |
| HEADER |
| end |
| |
| def notice |
| <<NOTICE |
| ALIASES: |
| |
| Some git aliases have been created for developer convenience: |
| |
| git apache-fetch # get changes from apache/trunk without merging them |
| # you can inspect what's happening on trunk without |
| # having to worry about merging conflicts. |
| # Inspect the remote branch with `git log apache/trunk` |
| # Or if you have a git-ui like `tig` you can use that. |
| |
| git apache-merge # Merge already fetched changes on the current branch |
| # Use this command to get up to date with trunk changes |
| # you can always cherry-pick from the apache/trunk |
| # branch. |
| |
| git apache-pull # get apache-fetch && git apache-merge |
| |
| git apache-push # Push to Apache's SVN. Only staged changes (those |
| # recorded using `git commit`) will be sent to SVN. |
| # You need not to be on the master branch. |
| # Actually you can work on a tiny-feature branch and |
| # commit directly from it. |
| # |
| # VERY IMPORTANT: |
| # |
| # Missing commits on Apache's SVN will be sent using |
| # your apache svn account. This means that you can |
| # make some commits on behalf of others (like patches |
| # comming from JIRA issues or casual contributors) |
| # Review the apache-push alias on .git/config if you |
| # want to change login-name used for commit to SVN. |
| # |
| # See the recommended workflow to avoid commiting |
| # other developers' changes and the following section. |
| |
| THE GITHUB MIRROR: |
| |
| ODE has an unofficial git mirror on github, maintained by Matthieu: |
| |
| http://github.com/matthieu/apache-ode |
| |
| Actually it's not Matthieu who manually updates it, he has a cron-job on his |
| server, that only runs |
| |
| git synchronize |
| |
| A command you also have configured on your .git/config file. |
| |
| However there are some limitations due to the fact that git-svn cannot |
| commit as multiple authors (unless all of them provided their passwords |
| to Matthieu, yet he doesn't want to take over the world.) |
| This means that if a commit is pushed to matthieu/apache-ode/master and has not |
| already been pushed by YOU to Apache's SVN by using `git apache-push` |
| your change will be commited to apache/trunk having Matthieu as the author |
| (that's how he gains meritocratic points at Apache :P). |
| |
| So, it's very important - if you care about meritocracy - to follow or at |
| least that you get an idea of the recommended workflow. |
| |
| RECOMMENDED WORKFLOW: |
| |
| So now that you have your local ODE copy you can create topic branches |
| to work on independent features, and still merge easily with head changes. |
| |
| They may seem lots of things to consider, but it's all for ODE's healt. |
| As all things git, you can always follow your own workflow and even create |
| aliases on you .git/config file to avoid typing much. So, here they are: |
| |
| 1) get your ode-git configured |
| (you have already do so, this was the most difficult part) |
| |
| 2) create a topic branch to work on, say.. you want to add cool-feature: |
| |
| git checkout -b cool-feature master |
| # now on branch cool-feature |
| |
| 3) hack hack hack.. use the source luke. |
| every time you feel you have something important like added failing |
| spec, added part of feature, or resolved some conflict from merges, |
| you can commit your current progress. If you want to be selective, use: |
| |
| git commit --interactive |
| |
| 3) review your changes, get ALL specs passing, repeat step 3 as needed |
| |
| 4) let's see what are they doing on trunk |
| |
| git apache-fetch |
| # You can inspect the upstream changes without having to merge them |
| git log apache/trunk # what are they doing!! |
| |
| 5) integrate mainstream changes to your cool-feature branch, you can always |
| use `git cherry-pick` to select only some commits. |
| |
| git merge apache/trunk cool-feature |
| |
| 6) Go to 3 unless ALL specs are passing. |
| |
| 7.a) (Skip to 7.b you have commit bit on Apache's SVN) |
| Create a patch using `git format-patch` |
| Promote your changes, create a JIRA issue and upload it granting Apache |
| license to include your code: |
| |
| https://issues.apache.org/jira/browse/ODE |
| dev@ode.apache.org |
| |
| 7.b) Now you have everyhing on staging area and merged important changes |
| from apache/trunk, it's time to commit them to Apache's SVN. |
| |
| git apache-push |
| |
| 8) Optional. If you can push to matthieu/apache-ode/master mirror, you can just |
| synchronize the mirror helping others to get changes without having to |
| wait on Matthieu's cronjob to run every hour (useful for urgent changes). |
| |
| git synchronize |
| |
| 9) Pull changes from origin frequently. |
| |
| git fetch origin |
| git rebase --onto origin/master master master |
| |
| 10) Unconditionally, Go to step 2 ;) |
| Share your ode-git workflow, git tips, etc. |
| |
| RESOURCES: |
| |
| http://github.com/matthieu/apache-ode/tree/master |
| http://git.or.cz/gitwiki/GitCheatSheet |
| http://groups.google.com/group/git-users/web/git-references |
| |
| NOTICE |
| end # notice method |
| |
| def optparse(options = OpenStruct.new, argv = ARGV) |
| opt = OptionParser.new do |opt| |
| |
| if `git status 2>/dev/null`.chomp.empty? |
| options.local = File.expand_path('ode', Dir.pwd) |
| else |
| puts "Current directory is a git repo: #{Dir.pwd}" |
| options.local = Dir.pwd |
| end |
| |
| options.svn_branch = "apache/trunk" |
| options.origin = "git://github.com/matthieu/apache-ode.git" |
| options.member = false |
| |
| opt.banner = "Usage: ode-git.rb [options]" |
| opt.separator "" |
| opt.separator "OPTIONS:" |
| |
| opt.on('-a', "--anon", "Use git://github.com/matthieu/apache-ode.git as origin") do |
| options.origin = "git://github.com/matthieu/apache-ode.git" |
| end |
| opt.on('-A', "--auth", "Use git@github.com:matthieu/apache-ode.git as origin") do |
| options.origin = "git@github.com:matthieu/apache-ode.git" |
| end |
| opt.on("-o", "--origin GIT_URL", "Clone from GIT_URL origin") do |value| |
| options.origin = value |
| end |
| opt.on('-l', "--local DIRECTORY", "Create local git clone on DIRECTORY") do |value| |
| options.local = value |
| end |
| opt.on('-b', "--branch GIT_SVN_BRANCH", |
| "Set the name for svn branch instead of apache/trunk") do |value| |
| options.svn_branch = value |
| end |
| opt.on('-e', "--email EMAIL", |
| "Configure git to use EMAIL for commits") do |value| |
| options.email = value |
| end |
| opt.on('-n', "--name USER_NAME", |
| "Configure your USER_NAME for git commits") do |value| |
| options.user_name = value |
| end |
| opt.on('-h', "--help", "Show ode-git help") do |
| puts opt |
| throw :exit |
| end |
| opt.on('-H', "--notice", "Show notice about aliases and git workflow") do |
| run_pager |
| puts notice |
| throw :exit |
| end |
| end |
| opt.parse!(argv) |
| options |
| end # optparse method |
| |
| |
| def main |
| catch :exit do |
| options = optparse |
| puts header |
| puts |
| print '> ' |
| if input = gets |
| options = optparse(options, input.split) |
| end |
| if input.nil? || (input != "\n" && input.empty?) |
| puts "Aborting." |
| return |
| end |
| perform(options) |
| end |
| end |
| |
| def perform(options) |
| origin = options.origin |
| member = origin =~ /@/ |
| local = options.local |
| svn_branch = options.svn_branch |
| user_email = options.email |
| user_name = options.user_name |
| |
| `git clone #{origin} #{local} 1>&2` unless File.directory?(File.join('.git', origin)) |
| |
| puts "Entering #{local} directory" |
| puts |
| Dir.chdir(local) do |
| |
| # Load the list of git-svn committers |
| svn_authors_file = File.expand_path('git-svn-authors', Dir.pwd) |
| svn_authors = File.read(svn_authors_file) |
| svn_authors.gsub!(/\s+=\s+/, ': ') |
| svn_authors = YAML.load(svn_authors) |
| |
| # set the git-svn-authors file |
| `git config svn.authorsfile "#{svn_authors_file}"` |
| |
| # Check that git is configured for the developer |
| user_email ||= `git config --get user.email`.chomp |
| if user_email.empty? |
| if member |
| puts "Enter your email as listed on #{svn_authors_file}" |
| print "> " |
| user_email = gets.chomp |
| else |
| puts "Provide an email address for git usage:" |
| user_email = gets.chomp |
| end |
| end |
| |
| # Check user is listed |
| unless user_email.empty? |
| svn_user, git_contact = *svn_authors.find { |entry| /#{user_email}/ === entry.join(' ') } |
| end |
| |
| if member |
| if git_contact.nil? # if using the authenticated git, he must be listed |
| puts <<-NOTICE |
| You need to be a ODE commmitter listed on #{svn_authors_file}. |
| Perhaps you need to add yourself to it, commit it using SVN, and |
| and run this script again. |
| Also checks your git global values for user.email and user.name |
| NOTICE |
| return |
| elsif /\s*(.*?)\s+\<(.+)\>\s*/ === git_contact |
| user_name, user_email = $1, $2 |
| else |
| puts "Invalid contact string for #{svn_user}: #{git_contact.inspect}" |
| return |
| end |
| elsif user_email =~ /^\s*$/ |
| puts "User email shall not include spaces: #{user_email.inspect}" |
| return |
| end |
| |
| user_name ||= `git config --get user.name`.chomp |
| if git_contact.nil? && user_name.empty? |
| puts "Provide a developer name for git usage:" |
| user_name = gets.chomp |
| end |
| |
| # Start the pager |
| run_pager |
| puts |
| |
| |
| old_origin = `git config --get remote.origin.url`.chomp |
| if member && old_origin !~ /@/ |
| puts "Switching to authenticated origin #{origin}", "" |
| `git config remote.origin.url "#{origin}"` |
| elsif !member && old_origin =~ /@/ |
| puts "Switching to anonymous origin #{origin}", "" |
| `git config remote.origin.url "#{origin}"` |
| end |
| |
| # Configure user name and email for git sake (and github's gravatar) |
| puts "You claim to be #{user_name.inspect} <#{user_email}> " |
| puts "with apache-svn user: #{svn_user}" if svn_user |
| puts |
| `git config user.name "#{user_name}"` |
| `git config user.email "#{user_email}"` |
| |
| # Ok, now obtain the last svn commit from history |
| last_svn_log = `git log -n 10 | grep git-svn-id | head -n 1` |
| fail "No svn metadata on last 10 commits" unless last_svn_log =~ /git-svn-id/ |
| svn_repo, svn_prev = last_svn_log.split[1].split("@") |
| |
| # Tell git where the svn repository is. |
| `git config svn-remote.#{svn_branch}.url #{svn_repo}` |
| `git config svn-remote.#{svn_branch}.fetch :refs/remotes/#{svn_branch}` |
| |
| # Create the svn branch, do this instead of pulling the full svn history |
| `git push --force . refs/remotes/origin/master:refs/remotes/#{svn_branch}` |
| |
| # Create apache aliases for developers git-workflow. |
| `git config alias.apache-fetch "!git-svn fetch #{svn_branch}"` |
| `git config alias.apache-merge "!git merge #{svn_branch}"` |
| `git config alias.apache-pull "!git apache-fetch && git apache-merge"` |
| if svn_user |
| `git config alias.apache-push "!git-svn dcommit --username #{svn_user}"` |
| else |
| `git config alias.apache-push "!git-svn dcommit"` |
| end |
| |
| # Create github origin aliases |
| `git config alias.get "!git apache-fetch && git fetch origin"` |
| `git config alias.mrg "!git apache-merge && git merge origin"` |
| `git config alias.rbs "!git rebase --onto #{svn_branch} origin/master master"` |
| `git config alias.put "!git apache-push && git push origin"` |
| |
| # This is Matthieu's cronjob |
| `git config alias.synchronize "!git get && git rbs && git put"` |
| |
| # Final notices. |
| puts <<-NOTICE + notice |
| |
| Your git repo #{local} has been configured, please review the |
| #{File.join(local, '.git/config')} file. |
| |
| NOTICE |
| end |
| end # perform method |
| |
| main |