Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 267 lines (222 sloc) 5.82 KB
#!/usr/bin/env ruby
=begin
"svnbr" is a Subversion utility to operate branches.
try help:
> svnbr help
=end
require 'rubygems'
require 'optparse'
require 'rexml/document'
class Branch
def repo var
@doc = REXML::Document.new(svn("info --xml")) unless @doc
repo = Hash.new
repo[:url]=@doc.elements['/info/entry/url'].text
repo[:root]=repo[:url].gsub(/(\/trunk$|\/branches\/.+$|\/tags\/.+$)/,'')
repo[var]
end
def current
name(repo(:url))
end
def list
return @branches if @branches
@branches = Array.new
root = repo(:root)
doc = REXML::Document.new(svn("list #{root}/branches --xml"))
REXML::XPath.each(doc,'//name/text()') do |name|
@branches << name.to_s
end
@branches << "trunk"
end
def tags
return @tags if @tags
@tags = Array.new
root = repo(:root)
doc = REXML::Document.new(svn("list #{root}/tags --xml"))
REXML::XPath.each(doc,'//name/text()') do |name|
@tags << name.to_s
end
@tags
end
def rm name
svn_exec("rm " << uri(name) << " -m 'remove branch #{name}'",true)
end
def mv(from,to,force = false)
raise "from: '#{from}' not found." if force ? !exists?(from) : !list.include?(from)
raise "to: '#{to}' already exists." if !force && exists?(to)
rm(to) if list.include?(to)
svn_exec("mv " << uri(from) << " " << uri(to) << " -m 'move branch #{from} to #{to}'",true)
end
def log(name)
svn_exec("log " << uri(name) << " --stop-on-copy",true)
end
def name uri
if /^\/trunk\/?/ =~ uri[repo(:root).length..-1]
return "trunk"
end
return uri.gsub(repo(:root)+"/","").gsub(/(branches|tags)\//,"")
end
def uri name
# existing url
if name == "trunk"
return repo(:root) << "/trunk"
end
if list.include?(name)
return repo(:root) << "/branches/" << name
end
if tags.include?(name)
return repo(:root) << "/tags/" << name
end
# non-existing url
repo(:root) << "/branches/" << name
end
def exists? name
list.include?(name) || tags.include?(name)
end
def rootdir
while uri(current) != repo(:url)
FileUtils.cd('..')
@doc = nil
end
end
def switch name,tag_name = nil, additional_message = nil
from = current
from_uri = uri(current)
if tag_name
raise "tag_name: '#{tag_name}' not found." unless tags.include?(tag_name)
from = tag_name
from_uri = repo(:root) << "/tags/" << tag_name
end
svn_exec("cp -m 'make #{name} branch from #{from} #{additional_message}' %s %s" %
[from_uri,uri(name)],true) unless exists?(name)
unless current == name
rootdir
svn_exec("sw --ignore-externals " << uri(name),true)
end
end
def tag tag_name, additional_message = nil
raise "tag_name: '#{tag_name}' already exists." if exists?(tag_name)
from_name = current
from_uri = uri(current)
tag_uri = repo(:root) << "/tags/" << tag_name
svn_exec("cp -m 'make #{tag_name} tag from #{from_name}. #{additional_message}' %s %s" %
[from_uri,tag_uri],true)
end
def merge_cs name,cs
rootdir
svn_exec("merge -c #{cs} " << uri(name),true)
end
def merge_rev name,rev
rootdir
svn_exec("merge -r #{rev} " << uri(name),true)
end
def diff name,file
rootdir
svn_exec("diff %s %s" % [uri(current),uri(name)],true)
end
private
def svn param
r = `svn #{param}`
raise if $?.to_i > 0
r
end
def svn_exec param,show = false
puts "svn #{param}" if show
system("svn #{param}")
end
end
opt = OptionParser.new
br = Branch.new
cmd = ARGV.shift
if cmd == "sw" or cmd == "switch"
tag = nil
opt.on('--from-tag=VAL') { |v| tag = v }
additional_message = nil
opt.on('--additional-message=VAL') { |v| additional_message = v }
opt.parse!
name = ARGV.shift
br.switch(name,tag,additional_message)
end
if cmd == "tag"
name = ARGV.shift
additional_message = nil
opt.on('--additional-message=VAL') { |v| additional_message = v }
opt.parse!
if name
br.tag(name,additional_message)
else
cmd = "tags"
end
end
if cmd == "mv"
force = false
opt.on('--force') { force = true }
opt.parse!
from = ARGV.shift
to = ARGV.shift
br.mv(from,to,force)
end
if cmd == "rm" or cmd == "del"
name = ARGV.shift
br.rm(name)
end
if cmd == "log"
name = ARGV.shift
name = br.current unless name
br.log(name)
end
if cmd == "diff"
name = ARGV.shift
file = ARGV.shift
br.diff(name,file)
end
if cmd == "tags"
br.tags.each do |i|
mark = (i == br.current) ? '* ' : ' '
puts mark << i
end
end
if cmd == "merge"
mode = nil;r=nil;
opt.on('-r=VAL') { |v| r = v ; mode = "r" }
opt.on('-c=VAL') { |v| r = v ; mode = "c" }
opt.parse!
name = ARGV.shift
br.merge_rev(name,r) if mode == "r"
br.merge_cs(name,r) if mode == "c"
end
if !cmd or cmd == "list" or cmd == "branches"
br.list.each do |i|
mark = (i == br.current) ? '* ' : ' '
puts mark << i
end
end
if cmd == "help"
i = 0
puts <<EOF.gsub(/^(\s*)%(?=\.)/m) { $1 + (i+=1).to_s }
Usage: svnbr <subcommand> [<option>] [<args>]
<branch-name> : /branches/<branch-name> or trunk
<tag-name> : /tags/<tag-name>
%. list branches
> svnbr [list | branches]
%. list tags
> svnbr tags
%. switch to branch or tag after craete /branches/<branch-name> (if missing)
> svnbr sw <branch-name> [--from-tag=<tag-name>]
> svnbr sw <tag-name>
%. create /tags/<tag-name>
> svnbr tag <tag-name>
%. delete branch or tag
> svnbr (del | rm) (<branch-name> | <tag-name>)
%. move branch or tag (if force)
> svnbr mv <old-branch-name> <new-branch-name>
> svnbr mv (--force | -f) <tag-name> <branch-name>
%. view log
> svnbr log [<branch-name> | <tag-name>]
%. merge
> svnbr merge (<branch-name> | <tag-name>) -c M[,N...]
> svnbr merge (<branch-name> | <tag-name>) -r M:N
%. diff
> svnbr diff (<branch-name> | <tag-name>) <file>
EOF
end