GIT post-receive multiple branch deployment - ruby

I have a development and production folder on the same server and 1 repo behind them to push to both folders depending on the branch that is pushed. I would like the development folder to be deployed to when develop is pushed to the repo and the production folder when master is pushed. I have an edited ruby post-receive file I found on a different site but I am new to ruby and can't seem to figure out why it isn't pushing to either folder.
#!/usr/bin/env ruby
# post-receive
from, to, branch = ARGF.read.split " "
if (branch =~ /^master/)
puts "Received branch #{branch}, deploying to production."
deploy_to_dir = File.expand_path('/var/www/html/production')
`GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f master`
puts "DEPLOY: master(#{to}) copied to '#{deploy_to_dir}'"
exit
∂
elsif (branch =~ /^develop/)
puts "Received branch #{branch}, deploying to development."
deploy_to_dir = File.expand_path('/var/www/html/development')
`GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f develop`
puts "DEPLOY: develop(#{to}) copied to '#{deploy_to_dir}'"
exit
end
Any help on this post-receive or a replacement would be appreciated.

If you don't mind shell script instead of Ruby then these people have solved the same problem.
Git post-receive hook to checkout each branch to different folders?

Perhaps a little late to the party on this, however maybe it will help others that are trying to find a solution in Ruby. Here is a working example (modified from this source which got me sorted):
#!/usr/bin/env ruby
# post-receive
# 1. Read STDIN (Format: "from_commit to_commit branch_name")
from, to, branch = ARGF.read.split " "
# 2. Only deploy if staging or master branch was pushed
if (branch =~ /staging$/) == nil && (branch =~ /master$/) == nil
puts "Received branch #{branch}, not deploying."
exit
end
# 3. Copy files to deploy directory(Path to deploy is relative to the git bare repo: e.g. website-root/repos)
if (branch =~ /staging$/)
deploy_to_dir = File.expand_path('../path-to-staging-deploy.com')
`GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f staging`
puts "DEPLOY: staging(#{to}) copied to '#{deploy_to_dir}'"
elsif (branch =~ /master$/)
deploy_to_dir = File.expand_path('../path-to-master-deploy.com')
`GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f master`
puts "DEPLOY: master(#{to}) copied to '#{deploy_to_dir}'"
else
puts "Received branch #{branch}, not deploying."
exit
end
I'm new to Ruby so there is probably a better way to write this, but it is working as expected - as far as I can see.

Related

Implement git branch --contains with rugged library

I'm working with a ruby script that execute the following git command on a given git repository.
branches = `git branch -a --contains #{tag_name}`
This approach has some drawbacks with command output (that may change in different git versions) and is subject to git binary version on the hosting machine, so I was trying to see if it's possible to replace that command using rugged but I wasn't able to find anything similar to that.
Maybe in rugged there's no way to implement --contains flag, but I think it should be pretty easy to implement this behavior:
Given any git commit-ish (a tag, a commit sha, etc.) how to get (with rugged) the list of branches (both local and remote) that contains that commit-ish?
I need to implement something like github commit show page, i.e. tag xyz is contained in master, develop, branch_xx
Finally solved with this code:
def branches_for_tag(tag_name, repo_path = Dir.pwd)
#branches ||= begin
repo = Rugged::Repository.new(repo_path)
# Convert tag to sha1 if matching tag found
full_sha = repo.tags[tag_name] ? repo.tags[tag_name].target_id : tag_name
logger.debug "Inspecting repo at #{repo.path}, branches are #{repo.branches.map(&:name)}"
# descendant_of? does not return true for it self, i.e. repo.descendant_of?(x, x) will return false for every commit
# #see https://github.com/libgit2/libgit2/pull/4362
repo.branches.select { |branch| repo.descendant_of?(branch.target_id, full_sha) || full_sha == branch.target_id }
end
end

"git branch --merged <sha>" via Rugged libgit2 bindings?

Is there any way to get the same information as the native git command
git branch --merged <sha>
via the Rugged libgit2 bindings for Ruby?
What that git commit is doing is looking at each branch and checking whether the merge-base between the branch and the commit you've given (or HEAD if none) corresponds to the one of the branch.
If they match, it's merged; if they don't, it's not. You can do this loop in ruby fairly easily
repo.branches.each(:local) # look only at local branches
.map { |b|
tgt = b.resolve.target # look at what the branch is pointing to
# and check if the target commit is included in the history of HEAD
merged = repo.merge_base(repo.head.target, tgt) == tgt.oid
[b.name, merged]
} # this will give a list with the name and whether the branch is merged
.keep_if { |name, merged| merged } # keep only the ones which are merged
.map(&:first) # get the name
You could have an merged_list << b.name if merged in the first block and make it hang off of the each, but I like composing streams of data.
You can also change whether to use :local, :remote or both for the branches depending on your need. And you can also change repo.head.target to whatever id you want to compare against.

Enforce git branch policies

I'm trying to enforce a company policy, taking these assumptions:
There are only 3 available upstream branches: master, version/* and hotfix/*.
Master branch accepts only non-forwarded merge commits.
Version and Hotfix branches accept only fast-forward/rebased commits.
Master branch must only be merged into from Version or Hotfix branches.
Version and Hotfix branches must diverge from Master branch directly.
So far this is what I come up with:
#!/usr/bin/env ruby
# Encoding: utf-8
$oldrev, $newrev, $refname = STDIN.read.split(" ")
$real_refname = `git rev-parse --abbrev-ref #{$refname} 2> /dev/null`.strip
$merge_commits = `git rev-list --merges #{$oldrev}..#{$newrev} 2> /dev/null`.strip
$parent_commit = `git rev-parse #{$newrev}\^1`
$ancestor_branch = `git show-branch | grep '*' | grep -v '#{$real_refname}' | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'`
puts "Enforcing Policies... \n(#{$real_refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
$errors = []
def check_branch_policy()
$errors.push "Branch #{$real_refname}: Only Version, Hotfix and Master branches are allowed to be pushed upstream." if !$real_refname.match(/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/)
$errors.push "Branch #{$real_refname}: Master branch accepts only non-forwarded merge commits." if $real_refname.match('master') && (!$merge_commits.match($newrev) || !$parent_commit.match($oldrev))
$errors.push "Branch #{$real_refname}: Version and Hotfix branches accept only fast-forward/rebased commits." if !$real_refname.match('master') && !$merge_commits.empty?
$errors.push "Branch #{$real_refname}: Version and Hotfix branches must diverge from Master branch directly." if !$real_refname.match('master') && !$ancestor_branch[4,6].match('master')
false
end
check_branch_policy
unless $errors.empty?
puts '[POLICY] Invalid git branch rules.'
$errors.each { |error| puts "# #{error}" }
exit 1
end
A few issues though:
First, I'd be glad for a general code review. I'm not a rubyist whatsoever, and I just patched around things I found on the web. So the code is probably pretty bad.
Is there an easier way to enforce the "Master branch accepts only non-forwarded merge commits."?
sed and grep doesn't seem to play well with git hooks, so I basically need an alternative to the current $ancestor_branch command. Didn't come up with anything yet.
When first pushing a branch, $real_refname doesn't work - it can't seem to abbrev-ref properly.
I can't seem to find a way to enforce "Master branch must only be merged into from Version or Hotfix branches." yet. Any ideas?
EDIT #1 - 25.05.14
After tinkering around a little bit I got to this:
#!/usr/bin/env ruby
# Encoding: utf-8
oldrev, newrev, refname = STDIN.read.split(" ")
short_refname = refname[11..-1]
merge_commits = `git rev-list --merges #{oldrev}..#{newrev}`.strip
unique_revs = `git rev-list --all --not $(git rev-list --all ^#{newrev})`
missed_revs = `git rev-list #{oldrev}..#{newrev}`
puts "Enforcing Policies... \n(#{short_refname}) (#{oldrev[0,6]}) (#{newrev[0,6]})"
def check_branch_policy(oldrev,newrev,short_refname,merge_commits,unique_revs,missed_revs)
errors = []
errors << "Only Version, Hotfix and Master branches are allowed to be pushed upstream." if
!short_refname[/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/]
if short_refname['master']
# Master should have only one unique commit each time - the merge commit (newrev).
errors << "Master branch accepts only non-forwarded merge commits, one at a time." if
!merge_commits[newrev] && missed_revs.count > 2
else
# If not empty, it means there's a merge commit - whereas there shouldn't be.
errors << "Version and Hotfix branches accept only fast-forward/rebased commits." if
!merge_commits.empty?
# If not equal, it means at least one commit is reachable from another ref - meaning it was diverged.
errors << "Version and Hotfix branches must diverge from Master branch directly." if
!unique_revs[missed_revs]
end
errors
end
errors = check_branch_policy(oldrev,newrev,short_refname,unique_revs,missed_revs)
unless errors.empty?
puts '[POLICY] Invalid git branch rules.'
errors.each { |error| puts "# Branch #{short_refname}: #{error}" }
exit 1
end
More questions arose though:
Is there a way to serve the local variables without calling them in the method? Otherwise the script throws an error.
I managed to find a way to retrieve the short_refname, but it's not so elegant. I read somewhere I can use short_refname = refname.chomp("refs/heads/") but it doesn't seem to work. Help?
I found a way (clever? too complex? go figure) to find if a branch has diverged where it shouldn't have but this brings two issues - I can't get all the refs from the hook. --stdin flag doesn't seem to cut it. Further, the exclude flag (^some_ref) doesn't work inside the hook, whereas in the terminal it works fine. Ideas?
Assuming I move this script to update hook, how can I get the refnames? The web sources weren't so clear so far...
Lets first focus on the ruby part:
There is hardly ever a reason to use global variables in ruby. And in a script they are in a "global" scope anyway => get rid of the preceding $ in variable names
In this code:
$errors = []
def check_branch_policy()
$errors.push "Branch #{$real_refname}: Only Version, Hotfix and Master branches are allowed to be pushed upstream." if !$real_refname.match(/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/)
$errors.push "Branch #{$real_refname}: Master branch accepts only non-forwarded merge commits." if $real_refname.match('master') && (!$merge_commits.match($newrev) || !$parent_commit.match($oldrev))
$errors.push "Branch #{$real_refname}: Version and Hotfix branches accept only fast-forward/rebased commits." if !$real_refname.match('master') && !$merge_commits.empty?
$errors.push "Branch #{$real_refname}: Version and Hotfix branches must diverge from Master branch directly." if !$real_refname.match('master') && !$ancestor_branch[4,6].match('master')
false
end
check_branch_policy
It's bad style to write a method (or a function) which just works on a global object created only for this purpose. You might as well just remove the method definition, because it does nothing here. This is not particular "ruby style" thing but applies to programming in general. The better solution is to just create the object inside the method and return it. I also don't like these long unreadable lines. So in total would probably structure it more like this:
def check_branch_policy
errors = []
errors << "Only Version, Hotfix and Master branches are allowed to be pushed upstream." if
!real_refname[/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/]
if real_refname['master']
errors << "Master branch accepts only non-forwarded merge commits." if
!merge_commits[newrev] || !parent_commit[oldrev]
else
errors << "Version and Hotfix branches accept only fast-forward/rebased commits." if
merge_commits.empty?
errors << "Version and Hotfix branches must diverge from Master branch directly." if
!ancestor_branch[4, 6]['master']
end
errors
end
Even though the messages may be less neatly aligned here, I think it's an improvement that one can better see the conditions which should hold in each case. Note that I used the ruby idoms << instead of .push and [] instead of .match. I also left the Branch #{real_refname}: prefix out, it can be just as well in your error output loop if its always the same.
Also there is hardly a reason to rely on grep and sed when you have the power of ruby at hand.
As for the git part:
What you're trying to do is certainly possible, but I guess some try and error is needed. So I can't give you a working solution out of the hand. Some remarks though:
I think a better way to get a short symbolic ref in ruby is
`git symbolic-ref #{refname}\`[/[^\/]*$/].chomp
or even
`git symbolic-ref --short #{refname}`
you can try if that works more reliable than git rev-parse --abbrev-ref. Furthermore your variable real_refname is badly named. The 'real' ref name sounds like it would actually be the SHA1 hash. Probably short_refname would be better.
Since you're reading the refs from stdin I guess that you use a pre-receive git hook? But in this case you've clearly a bug, because there might be several branches updated in one push. You should either iterate over stdin or use the update hook
git show-branch is a porcelain command, i.e. it shouldn't be used for scripting because the output is meant for users. I think Junio did some pretty neat stuff in his pre-rebase.sample. Maybe you can get some ideas from there how to do it with plumbing commands.
I used to write even simple hooks in ruby, but I learned over the years that bash is also quite capable. So unless your hook gets really complex you might just start with bash.

Git pull multiple local repositories with script (ruby?)

I have ~30 git repositories cloned from github that I use for web/ruby/javascript development. Is it possible to bulk update all of them with a script?
I have everything pretty organized (folder structure):
- Workspace
- Android
- Chrome
- GitClones
- Bootstrap
~ etc...30 some repositories
- iPhone
- osx
- WebDev
I have a ruby script to clone repositories with octokit, but are there any suggestions on how to do git pull (overwriting/rebasing local) in all the repositories under GitClones?
Normally I would just do a pull whenever I was about to use that repo, but I am going to a place where internet connectivity is only going to be available sometimes. So I would like to update everything I can while I have internet.
Thanks! (Running osx 10.8.2)
If you must do it in Ruby, here's a quick and dirty script:
#!/usr/bin/env ruby
Dir.entries('./').select do |entry|
next if %w{. .. ,,}.include? entry
if File.directory? File.join('./', entry)
cmd = "cd #{entry} && git pull"
`#{cmd}`
end
end
Don't forget to chmod +x the file you copy this into and ensure it's in your GitClones directory.
Sure, but why use ruby when shell will suffice?
function update_all() {
for dir in GitClones/*; do
cd "$dir" && git pull
done
}
Change beginning of glob to taste. This does two useful things:
It only git pull when it contains .git subdir
It skips dot (.) dirs since no one has git repos which start with a dot.
Enjoy
# Assumes run from Workspace
Dir['GitClones/[^.]*'].select {|e| File.directory? e }.each do |e|
Dir.chdir(e) { `git pull` } if File.exist? File.join(e, '.git')
end
Revised to provider better output and be OS agnostic. This one cleans local changes, and updates code.
#!/usr/bin/env ruby
require 'pp'
# no stdout buffering
STDOUT.sync = true
# checks for windows/unix for chaining commands
OS_COMMAND_CHAIN = RUBY_PLATFORM =~ /mswin|mingw|cygwin/ ? "&" : ";"
Dir.entries('.').select do |entry|
next if %w{. .. ,,}.include? entry
if File.directory? File.join('.', entry)
if File.directory? File.join('.', entry, '.git')
full_path = "#{Dir.pwd}/#{entry}"
git_dir = "--git-dir=#{full_path}/.git --work-tree=#{full_path}"
puts "\nUPDATING '#{full_path}' \n\n"
puts `git #{git_dir} clean -f #{OS_COMMAND_CHAIN} git #{git_dir} checkout . #{OS_COMMAND_CHAIN} git #{git_dir} pull`
end
end
end

Check if a git branch exists in the local repository with ruby?

What would be the best way of checking if git branch exists in the local git repository with ruby? My ruby skills are not that good, so would like to know the best way of going about it :) Thanks.
There are ruby libraries for accessing git repositories, one being grit.
To install use [sudo] gem install grit.
$ irb
>> require 'grit'
=> true
>> repo = Grit::Repo.new('/path/to/your/repo/')
=> #<Grit::Repo "/path/to/your/repo/.git">
>> repo.branches
=> [#<Grit::Head "master">, #<Grit::Head "some-topic-branch">, ...]
I ended up coming up with this:
# Check if a Branch Exists
def branch_exists(branch)
branches = run("git branch",false)
regex = Regexp.new('[\\n\\s\\*]+' + Regexp.escape(branch.to_s) + '\\n')
result = ((branches =~ regex) ? true : false)
return result
end
Where run is a backtick expression. The reasoning is that the code is to be highly portable and is not allowed any other dependencies. Whereas git is installed in the environment.

Resources