#!/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 script helps buildr developers to obtain their own git clone from # github, and also provides GitFlow commands to keep the git mirror in sync # with Apache SVN. # # If you already have a buildr clone, just do the following: # # git config alias.apache '!'"ruby $PWD/doc/scripts/buildr-git.rb" # # After this, you have a 'git apache' command and you can try (be sure to read the help) # # git apache help # git apache setup svn --help # git apache sync --help # # To configure your local repo for svn synchronization, # # git apache update-authors # git remote add upstream git@github.com:buildr/buildr.git # git apache setup svn --username apacheLogin --apache-git upstream # git apache sync # # This script can also be run without having a local buildr clone: # # ruby -ropen-uri -e 'eval(open("http://svn.apache.org/viewvc/buildr/trunk/doc/scripts/buildr-git.rb?view=co").read)' help require 'yaml' require 'open-uri' if $0 == '-e' # invoked from open-uri gitflow = "http://svn.apache.org/viewvc/buildr/trunk/doc/scripts/gitflow.rb?view=co" eval(open(gitflow).read) else require File.expand_path('gitflow', File.dirname(__FILE__)) end GitFlow.program = 'buildr-git' module BuildrGit class UpdateUsersCommand < GitFlow/'update-users' @help = "Update list of Apache SVN committers from Jukka's list." @@url = 'http://people.apache.org/~jukka/authors.txt' def self.authors_file File.expand_path('.git/authors.txt', Dir.pwd) end def self.user_email(apache_login, authors_file = nil) authors_file ||= self.authors_file authors = YAML.load(File.read(authors_file).gsub!(/\s+=\s+/, ': ')) contact = authors[apache_login] fail "You are not listed as apache commiter on #{authors_file}" unless contact fail "Not a valid contact line: #{contact}" unless contact =~ /\s+<(.*)>/ [$`, $1] end def options(opts) opts.url = @@url opts.file = self.class.authors_file [['-u', '--url URL', "From URL. defaults to: #{opts.url}", lambda { |url| opts.url = url }], ['-f', '--file FILE', "Write to FILE, defaults to #{opts.file}", lambda { |path| opts.file = path }] ] end def execute(opts, argv) FileUtils.mkdir_p(File.dirname(opts.file)) content = open(opts.url).read File.open(opts.file, "w") { |f| f.print content } end end class CloneCommand < GitFlow/:clone @help = "Create a clone from github.com/buildr repository." def options(opts) opts.origin = 'git://github.com/buildr/buildr.git' opts.svn_prefix = 'apache' opts.project = 'buildr' opts.local = expand_path(opts.project) [['--prefix SVN_PREFIX', opts.svn_prefix, lambda { |p| opts.svn_prefix = p }], ['--origin GIT_ORIGIN', opts.origin, lambda { |o| opts.origin = o }], ['-d', '--dir DIR', opts.local, lambda { |d| opts.local = d }] ] end def execute(opts, argv) git 'clone', opts.origin, opts.local Dir.chdir(opts.local) do run 'update-users' run 'setup' end end end class SetupCommand < GitFlow/:setup @help = "Setup your buildr clone to be used with git mirror." def options(opt) [] end def execute(opt, argv) run 'setup', 'alias' run 'setup', 'svn' end end class SetupAliasCommand < SetupCommand/:alias def execute(opt, argv) me = expand_path('doc/scripts/buildr-git.rb') git 'config', 'alias.apache', "!ruby #{me}" end end class SetupSvnCommand < SetupCommand/:svn @help = "Setup for getting changes from Apache SVN." def options(opt) opt.svn_prefix = 'apache' opt.svn_path = 'buildr' opt.townhall = 'origin' [['--username SVN_USER', 'Use Apache svn username for this svn remote', lambda { |e| opt.apache_login = e }], ['--svn-prefix PREFIX', 'The name of svn remote to use for project.', "Defaults to #{opt.svn_prefix}", lambda{|p| opt.svn_prefix = p }], ['--svn-uri URI', lambda {|p| opt.svn_uri = p }], ['--svn-rev REVISION', lambda {|p| opt.svn_rev = p }], ['--svn-path PATH', 'The path to append to svn-uri.', "Defaults to #{opt.svn_path}", lambda {|p| opt.svn_path = p }], ['--apache-git REMOTE', 'The name of remote you are using as town-hall git repo.', "Defaults to #{opt.townhall}", lambda {|p| opt.townhall = p }] ] end def execute(opt, argv) authors_file = UpdateUsersCommand.authors_file git 'config', 'svn.authorsfile', authors_file git 'config', 'apache.svn', opt.svn_prefix git 'config', 'apache.git', opt.townhall if opt.apache_login user, email = UpdateUsersCommand.user_email(opt.apache_login, authors_file) puts "You claim to be #{user} <#{email}> with apache login: #{opt.apache_login}" git('config', 'user.name', user) git('config', 'user.email', email) end if opt.svn_rev revision = opt.svn_rev else location, revision = svn_loc_rev revision = opt.svn_rev || revision end if opt.svn_uri repo = opt.svn_uri else fail "No #{opt.svn_path} directory on #{location}" unless location =~ /\/#{opt.svn_path}/ repo = $` end # Tell git where the svn repository is git('config', "svn-remote.#{opt.svn_prefix}.url", repo) git('config', "svn-remote.#{opt.svn_prefix}.fetch", "#{opt.svn_path}/trunk:refs/remotes/#{opt.svn_prefix}/trunk") git('config', "svn-remote.#{opt.svn_prefix}.branches", "#{opt.svn_path}/branches/*:refs/remotes/#{opt.svn_prefix}/*") git('config', "svn-remote.#{opt.svn_prefix}.tags", "#{opt.svn_path}/tags/*:refs/remotes/#{opt.svn_prefix}/tags/*") # Store the user for svn dcommit if opt.apache_login git('config', "svn-remote.#{opt.svn_prefix}.username", opt.apache_login) end # Create the svn branch, do this instead of pulling the full svn history git('update-ref', "refs/remotes/#{opt.svn_prefix}/trunk", 'refs/remotes/origin/master') # create tags from git git('tag').split.each do |tag| git('update-ref', "refs/remotes/#{opt.svn_prefix}/tags/#{tag}", "refs/tags/#{tag}") end # update svn metadata mkdir_p(expand_path('.git/svn')) svn_meta = expand_path('.git/svn/.metadata') git('config', '--file', svn_meta, "svn-remote.#{opt.svn_prefix}.branches-maxRev", revision) git('config', '--file', svn_meta, "svn-remote.#{opt.svn_prefix}.tags-maxRev", revision) end def svn_loc_rev meta = sh('git log -n 10 | grep git-svn-id | head -n 1').chomp fail "No svn metadata on last 10 commits" if meta.empty? meta.split[1].split('@') end end class FetchCommand < GitFlow/:fetch @help = "Get changes from svn, creating tags, branches on townhall" @documentation = <<-DOC This command can be used to fetch changes from Apache\'s SVN repo. GIT CONFIG VALUES: apache.svn - The svn remote using to get changes from Apache SVN. Set by setup-svn --svn-prefix. DOC def options(opt) opt.apache_svn = git('config', '--get', 'apache.svn').chomp rescue nil [['--apache-svn SVN_REMOTE', 'The SVN remote used to get changes from Apache', "Current value: #{opt.apache_svn}", lambda { |r| opt.apache_svn = r }] ] end def execute(opt, argv) fail "Missing apache.svn config value" unless opt.apache_svn git('svn', 'fetch', opt.apache_svn) end end class SyncCommand < GitFlow/:sync @help = "Synchronizes between Apache svn and git townhall." @documentation = <<-DOC This command will perform the following actions: * fetch changes from apache svn. * rebase them on the current branch or on the one specified with --onto * dcommit (this will push your changes to Apache trunk) GIT CONFIG VALUES: apache.svn The svn remote using to get changes from Apache SVN. Set by setup-svn --svn-prefix. apache.git The git remote used as townhall repository. Set by setup-svn --townhall. svn-remote.APACHE_GIT.username If configured, sync will use this svn username while dcommiting. DOC def options(opt) git('branch').split.tap { |n| opt.current = n[n.index('*')+1] } opt.branch = opt.current opt.svn_branch = 'trunk' opt.git_branch = 'master' opt.apache_git = git('config', '--get', 'apache.git').chomp rescue nil opt.apache_svn = git('config', '--get', 'apache.svn').chomp rescue nil opt.svn_username = git('config', '--get', "svn-remote.#{opt.apache_svn}.username").chomp rescue nil [['--apache-svn SVN_REMOTE', 'The SVN remote used to get changes from Apache', "Current value: #{opt.apache_svn}", lambda { |r| opt.apache_svn = r }], ['--apache-git REMOTE', 'The git remote used as town-hall repository.', "Current value: #{opt.apache_git}", lambda { |r| opt.apache_git = r }], ['--username SVN_USER', 'Specify the SVN username for dcommit', "Defaults to: #{opt.svn_username}", lambda { |b| opt.svn_username = b }], ['--svn-branch SVN_BRANCH', 'Specify the SVN branch to rebase changes from, and where to dcommit', "Defaults to: #{opt.svn_branch}", lambda { |b| opt.svn_branch = b }], ['--git-branch REMOTE_BRANCH', 'Specify the remote town-hall branch (on apache.git) to update', "Defaults to: #{opt.git_branch}", lambda { |b| opt.git_branch = b }], ['--branch BRANCH', 'Specify the local branch to take changes from', "Current branch: #{opt.branch}", lambda { |b| opt.branch = b }] ] end def execute(opt, argv) # obtain the svn url url = git('config', '--get', "svn-remote.#{opt.apache_svn}.url").chomp # obtain the path for project path = git('config', '--get', "svn-remote.#{opt.apache_svn}.branches"). chomp.split('/branches').first commit_url = "#{url}/#{path}/#{opt.svn_branch}" # obtain latest changes from svn git('svn', 'fetch', '--svn-remote', opt.apache_svn) # obtain latest changes from git git('fetch', opt.apache_git, "#{opt.git_branch}:refs/remotes/#{opt.apache_git}/#{opt.git_branch}") # rebase svn changes in the desired branch git('rebase', "#{opt.apache_svn}/#{opt.svn_branch}", opt.branch) git('rebase', "#{opt.apache_git}/#{opt.git_branch}", opt.branch) # dcommit to the specific svn branch ['svn', 'dcommit', '--svn-remote', opt.apache_svn, '--commit-url', commit_url].tap do |cmd| if opt.svn_username cmd << '--username' << opt.svn_username end git(*cmd) end # update townhall remote ref git('update-ref', "refs/remotes/#{opt.apache_git}/#{opt.git_branch}", "refs/remotes/#{opt.apache_svn}/#{opt.svn_branch}") # forward the remote townhall/master to apache/trunk git('push', opt.apache_git, "refs/remotes/#{opt.apache_git}/#{opt.git_branch}:#{opt.git_branch}") # get back to the original branch git('checkout', opt.current) end end # This one is displayed when the user executes this script using # open-uri -e HEADER = <
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 # When fork is completed, we display the following notice on a # pager, giving the user a brief overview of git aliases used # to keep the mirror in sync. NOTICE = <git to keep trunk upto date. You need to be an Apache committer and have permissions on the SVN repo. It's VERY IMPORTANT for Buildr committers to remember that contributions from external entities wanting to be accepted will require them to sign the Apache ICLA. We provide the git mirror to make it easier for people to experiment and contribute back to Buildr, before merging their code in, please remember they have to create create a JIRA issue granting ASF permission to include their code, just like any other contribution following Apache's guidelines. 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 buildr 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 Buildr'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 gitflow 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/BUILDR dev@buildr.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 are a buildr committer you may want to synchronize the github mirror for helping others to get changes without having to wait on Victor'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 gitflow workflow, git tips, etc. RESOURCES: http://github.com/buildr/buildr/tree/master http://git.or.cz/gitwiki/GitCheatSheet http://groups.google.com/group/git-users/web/git-references NOTICE #' for emacs end