puts line breaking in_branch in git gem? - ruby

I'm automating a bit of git workflow and have found some curious behaviour when using the git gem's in_branch method and wondered if anyone could explain why or how this issue occurs? Here's some test code that should reproduce the issue:
#!/usr/bin/env ruby
require 'git'
# git details
repo_name = 'SOME_REPO'
working_dir = "#SOME_DIR/git_test_repo"
repo_owner = 'SOME_GIT_USER'
repo_host = 'SOME_GIT_HOST'
repo_dir = "#{working_dir}/#{repo_name}"
remote_repo = "git##{repo_host}:#{repo_owner}/#{repo_name}.git"
branch_name = 'testbranch'
commit_message = 'log line breaking in_branch test'
Dir.mkdir(working_dir) unless Dir.exist?(working_dir)
Dir.chdir(working_dir)
Git.clone(remote_repo, repo_name) unless Dir.exists?(repo_dir)
repo = Git.open(repo_dir)
repo.pull(remote = 'origin', branch = 'master')
repo.branch(branch_name).in_branch(message = commit_message) do
File.write(repo_dir + '/test.txt', Time.now)
repo.add('.')
# -----this line breaks it --------------
puts 'committing changes'
# ---------------------------------------
end
When this code runs, the last puts line before the end of the in_branch block, when actually run, somehow causes the changes in the branch to be reverted, but when it's commented out, all the code behaves as expected. I've tested output lines anywhere in the block, and they all behave fine. It seems to happen across many versions of ruby (custom installs, rvm installs) and different OS's (linuxes and mac).
Is there some arcane behaviour of ruby and its terminal output I need to be aware of here?

Not really "arcane behavior of ruby". But the result of the last line in a method is what the method returns. Putting a puts in that position will usually break the method.

Just testing and I see the same behavior using print (which does not add a newline).
This is not a newline issue, moving the (now print) line above the repo.add line and all works.
Anymore thoughts?

Related

Rake task selectivly ignoring new code

In a rake task I'm writing some puts statements show changes while others don't. For instance changing
puts model+" | "+id
into
puts model+" * "+id
doesn't change in the output of the script. However in some places changing
puts "Connecting to "+site
into
puts "Connecting to ----"+site
shows the changes that where made.
In the places where any changes to the line doesn't change the output, adding a new puts statement before or after don't show up when the task is run. Commenting out lines of code around the unchanging puts statements that do the actual work cause the script to not execute those lines, just as it should, but changing or adding puts statements there do not change the output of the script.
Removing all other tasks and emacs backup files from the lib/tasks folder doesn't help. I've been bitten before by having a backup copy of a task with the same namespace and task name running instead of the one I was working on.
This is being run with Ruby 2.4.3 on OpenBSD 6.3-stable on a fx-8350. I would post the whole script but the company I'm working for won't allow it.
How about
puts "#{model} +/*/whatever #{site}"
It shouldn't matter to what sounds like a filesystem update issue (reboot), but it's probably better form to put the variables in the string like that instead of + "" them.

Have Vagrant+Chef run bail out if template files have been checked out with CRLF line endings

I keep getting bit by an annoying gotcha that happens when Windows developers check out cookbooks from my Git repo with Git's autocrlf set to true. When they run vagrant up to bring up a Linux VM, the cookbook files are mapped into the VM with CRLF line endings, which causes no end of obscure errors when the shell and other POSIX utilities try to operate on the (now invalid) template files that have been copied into the VM.
The fix for that is simple enough: re-clone the repository after changing the autocrlf setting to input or false.
My problem is that, when you have the wrong line endings, the only symptoms are errors in strange places that in no way point to there being a problem with line endings.
How can I have Chef† check for the wrong line endings in, say, the cookbook template files and throw an error if it finds one? I think a simple Ruby snippet that does an assertion on the line endings in a given file that I can put at the top of a recipe would work.
Note: in the particular case of my repo, the sequence of steps is:
Developer checks out repo
Developer runs vagrant up
Vagrant kicks off a Chef run in the VM
Finally, the repo's build script runs in the VM
† Or really anything else included in the repo
Rubocop can be used to enforce unix style line endings (among many other things).
For example (from the command line, within the guest):
gem install rubocop
rubocop --only Style/EndOfLine # only check line endings
Or it could be done from within the context of chef itself with something like the following recipe:
chef_gem 'rubocop'
ruby_block 'check line endings' do
block do
# It's probably better to call rubo cop code directly, rather than
# shelling out, but that can be an exercise for the reader ;-)
rubocop_cmd = Mixlib::ShellOut.new(
'rubocop --only Style/EndOfLine',
:cwd => 'dir_to_check'
)
rubocop_cmd.run_command
# Raise an exception if it didn't exit with 0.
rubocop_cmd.error!
end
end
The uncaught exception will cause the chef run to bail out.
Here's a Ruby snippet that you can put in any Chef recipe:
def cookbook_supporting_files(*cookbooks)
cookbooks = cookbooks.map {|name| run_context.cookbook_collection[name]}
cookbooks.flat_map do |cb|
(cb.manifest[:files] + cb.manifest[:templates]) \
.map {|f| ::File.join(cb.root_dir, f['path']) }
end
end
def dos_eol?(f)
::File.open(f, 'rb').read(4096).include? "\r\n"
end
cookbook_supporting_files(cookbook_name).each do |f|
if dos_eol? f
raise "Cookbook template '#{f}' contains CRLF line endings"
end
end
This will check the line endings of the cookbook files and templates that exist in whatever cookbook the above snippet is placed in.
If you want to have the same snippet check other cookbooks, simply replace:
cookbook_supporting_files(cookbook_name)
With the list of the cookbooks you want to check:
cookbook_supporting_files('some_cookbook', 'another_cookbook')

How to prevent capistrano replacing newlines?

I want to run some shell scripts remotely as part of my capistrano setup. To test that functionality, I use this code:
execute <<SHELL
cat <<TEST
something
TEST
SHELL
However, that is actually running /usr/bin/env cat <<TEST; something; TEST which is obviously not going to work. How do I tell capistrano to execute the heredoc as I have written it, without converting the newlines into semicolons?
I have Capistrano Version: 3.2.1 (Rake Version: 10.3.2) and do not know ruby particularly well, so there might be something obvious I missed.
I think it might work to just specify the arguments to cat as a second, er, argument to execute:
cat_args = <<SHELL
<<TEST
something
TEST
SHELL
execute "cat", cat_args
From the code #DavidGrayson posted, it looks like only the command (the first argument to execute) is sanitized.
I agree with David, though, that the simpler way might be to put the data in a file, which is what the SSHKit documentation suggests:
Upload a file from a stream
on hosts do |host|
file = File.open('/config/database.yml')
io = StringIO.new(....)
upload! file, '/opt/my_project/shared/database.yml'
upload! io, '/opt/my_project/shared/io.io.io'
end
The IO streaming is useful for uploading something rather than "cat"ing it, for example
on hosts do |host|
contents = StringIO.new('ALL ALL = (ALL) NOPASSWD: ALL')
upload! contents, '/etc/sudoers.d/yolo'
end
This spares one from having to figure out the correct escaping sequences for something like "echo(:cat, '...?...', '> /etc/sudoers.d/yolo')".
This seems like it would work perfectly for your use case.
The code responsible for this sanitization can be found in SSHKit::Command#sanitize_command!, which is called by that class's initialize method. You can see the source code here:
https://github.com/capistrano/sshkit/blob/9ac8298c6a62582455b1b55b5e742fd9e948cefe/lib/sshkit/command.rb#L216-226
You might consider monkeypatching it to do nothing by adding something like this to the top of your Rakefile:
SSHKit::Command # force the class to load so we can re-open it
class SSHKit::Command
def sanitize_command!
return if some_condition
super
end
end
This is risky and could introduce problems in other places; for example there might be parts of Capistrano that assume that the command has no newlines.
You are probably better off making a shell script that contains the heredoc or putting the heredoc in a file somewhere.
Ok, so this is the solution I figured out myself, in case it's useful for someone else:
str = %x(
base64 <<TEST
some
thing
TEST
).delete("\n")
execute "echo #{str} | base64 -d | cat -"
As you can see, I'm base64 encoding my command, sending it through, then decoding it on the server side where it can be evaluated intact. This works, but it's a real ugly hack - I hope someone can come up with a better solution.

Can't delete Dir/Files after using File.open block

When trying to delete a directory (+ contents) and after reading the files inside, FileUtils.rm_rf(path) will not delete all the folders, although it does delete all the files and some of the folders.
After some experimentation it seems to be related to a File.open block. (I actually do a regex match inside the block, but I'm just using a puts here to keep things clear)
File.open(file).each do |line|
puts line
end
From what I've read, the above should automatically close the file but when using this, FileUtils fails to complete its task.
However, if I use the following code, FileUtils works as desired.
open_file = File.open(file)
open_file.each do |line|
puts line
end
open_file.close
It's no big deal to use the code in the second example, but I do prefer the cleanliness of the first.
Is there any reason why that first example breaks FileUtils?
P.S. I'm new to both Ruby and Stack Overflow....Hi. My system is Ubuntu 11.04 (64bit), running RVM with Ruby 1.9.2-p180
You should use something like this:
File.open(file) do |f|
f.each{|line| puts line}
end
In your example the block is supplied to the each method and the version of open without a block is executed returning an IO object on which the each method is called.

Telling rspec to not load files

I'm trying to add some commit hooks to my git repo. I want to leverage Rspec and create commit message specs that will run each time I commit. I have figured out how to run rspec outside of the 'spec' command, but I now have an interesting problem.
Here is my current code:
.git/hooks/commit-msg
#!/usr/bin/env ruby
require 'rubygems'
require 'spec/autorun'
message = File.read(ARGV[0])
describe "failing" do
it "should fail" do
true.should == false
end
end
This is throwing an error when it gets to the describe call. Basically, it thinks that the commit message it receives is the file to load and run the specs against. Here is the actually error
./.git/COMMIT_EDITMSG:1: undefined local variable or method `commit-message-here' for main:Object (NameError)
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:15:in `load'
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:15:in `load_files'
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `each'
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner/example_group_runner.rb:14:in `load_files'
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner/options.rb:133:in `run_examples'
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner.rb:61:in `run'
from /Users/roykolak/.gem/ruby/1.8/gems/rspec-1.3.0/lib/spec/runner.rb:45:in `autorun'
from .git/hooks/commit-msg:12
I am looking for a way to tell rspec to not load files. I have a suspicion that I will need to create my own spec runner. I came to this conclusion after viewing these lines in rspec-1.3.0/lib/spec/runner/example_group_runner.rb
def load_files(files)
$KCODE = 'u' if RUBY_VERSION.to_f < 1.9
# It's important that loading files (or choosing not to) stays the
# responsibility of the ExampleGroupRunner. Some implementations (like)
# the one using DRb may choose *not* to load files, but instead tell
# someone else to do it over the wire.
files.each do |file|
load file
end
end
But, I would like some feedback before I do that. Any thoughts?
Do you even really need all the special stuff that RSpec provides (should and the various matchers) just to verify the contents of a single file? It really seems like overkill for the problem.
spec/autorun eventually calls Spec::Runner.autorun which parses ARGV as if it held normal arguments for a spec command line.
When you install a bare “spec” file as a Git hook,
it will get arguments that are appropriate for the whatever Git hook is being used,
not spec-style arguments (spec filenames/directories/patterns and spec options).
You might be able to hack around the problem like this:
# Save original ARGV, replace its elements with spec arguments
orig_argv = ARGV.dup
%w(--format nested).inject(ARGV.clear, :<<)
require 'rubygems'
require 'spec/autorun'
# rest of your code/spec
# NOTE: to refer to the Git hook arguments use orig_argv instead of ARGV

Resources