Thursday, March 31, 2011

Authorized_keys and SCP

I have SSH access to a machine, and I want to add my public key to the `authorized_keys` file so I don't have to type a password to log in. A friend taught me a neat command for doing this:

% cat ~/.ssh/id_rsa.pub | ssh user@server "cat >> .ssh/authorized_keys"

Meaning:

  • Output the contents of my public key
  • Send that as standard input to this ssh command
  • The ssh command runs `cat` on the server
  • Since `cat` on the server is called without arguments, it uses standard input - in this case, the contents of my public key file
  • My public key gets added to `authorized_keys`

Added my key to `authorized_keys` has another benefit, besides not having to type the password when I SSH in: since I'm automatically recognized as a user, I can use tab completion for scp commmands. (Note: I'm using the zsh shell; remote tab-completion doesn't work for me in bash.)

In other words, if there's a file called "foo.txt" in my home directory on that server, and I want to copy it to my local machine, I can start type this:

% scp user@server:fo

... and hit tab, and it will auto-complete "foo.txt". That makes using scp a lot less tedious.

Using git cherry-pick

One Git command that I've found to be really useful lately is "git cherry-pick". It lets you take a single commit from one branch and apply it to another.

For example, say you're working on your "add_blinking_lights" feature branch, and you find a bug in the "rotate_missile_chamber" method. You fix it and commit to the branch between a couple of other feature commits.

It's going to be a while before you merge this feature back into your master branch, but you do want that bugfix to go live on the next deploy. So, on the feature branch, you do this:

cyborg[add_blinking_lights]% git log -3
e9dd159 Added tests for blink rate
a8e358c Fixed bug in rotate_missile_chamber
a7d3302 Added tests for lights turning on

OK, so you know the hash for the bugfix. Now you do "git checkout master."

On the master branch you run "git cherry-pick a8e358c". Here's what that command does:

  • Look at that commit on the feature branch
  • See what changes it introduced
  • Create a new commit on the master branch that introduces the same changes

Notice that it creates a new commit on the master branch. If, on master, you run "git log", you'll see a different hash for the same commit message. Why?

This is because of how Git models what a commit is. A commit is a complete snapshot of the whole repository, and the hash for a given commit reflects the state of every file in the whole directory - it is a hash of all their hashes.

So clearly, since master branch doesn't have all of the commits from the feature branch, a complete snapshot of it at the time the bugfix is applied will generate a different hash than a complete snapshot of the feature branch at the time the bugfix is applied there. Thus, different hashes.

But when you do merge the feature branch into master, that won't matter; the hashes for the individual file where you made the bugfix will be the same, because their contents will be the same, so there will be nothing to update on master for that file.

Of course, another way of approaching this problem would have been to switch to master before committing the bugfix in the first place, then merge master into the feature branch, or cherry-pick from master to the feature, or rebase the feature on master. But the method above is simple, and if you did already commit the bugfix on your branch, is the best way to get that commit into production.

Thursday, March 10, 2011

Where cliche is a mandate

A recruiting company that spams me has the slogan "Where Commitment is a Passion."

What does that even mean?

"We're always looking for stuff to commit to. I just committed to eating a whole jar of pickles! Jimmy over there committed to sing everything he says! Linda committed to paying the mortgage for the zoo! We're c-c-crazy for commitment!"

Monday, March 7, 2011

Short Rubyzip tutorial

Rubyzip is a handy tool for creating zip files with a Ruby script. But to me, the documentation doesn't make it immediately obvious how to use the thing. This quick tutorial should get you started.

Say you're submitting some pictures of kitchen gadgets to Strange Cooks Weekly. You need to zip up your images before sending them, and you're contractually obligated to use Ruby to do it. Here's how.

require 'rubygems'
require 'zip/zip'

puts "Zipping files!"

file_path = "Users/me/Pictures/kitchen_implements"
file_list = ['toast_mitten.png', 'gravy_jug.png', 'biscuit_juicer.png']

zipfile_name = "/Users/me/Desktop/someFile.zip"

Zip::ZipFile.open(zipfile_name, Zip::ZipFile::CREATE) do |zipfile|
  file_list.each do |filename|
    # Two arguments:
    # - The name of the file as it will appear in the archive
    # - The original file, including the path to find it
    zipfile.add(filename, file_path + '/' + filename)
  end
end

Note that if the zip file already exists, this script will try to add stuff to it. If you try to add a file that's already in the archive, it will complain.

Friday, March 4, 2011

Using Ruby for SCP file transfers

Today I needed to write a Ruby script to fetch a file using SCP. I thought it would be nice if it also displayed a "percent complete" counter as it went. This is what I came up with.

require 'rubygems'
require 'net/scp'
puts "Fetching file"

# Establish the SSH session
ssh = Net::SSH.start("IP Address", "username on server", :password => "user's password on server", :port => 12345)

# Use that session to generate an SCP object
scp = ssh.scp

# Download the file and run the code block each time a new chuck of data is received
scp.download!("path/to/file/on/server/fileName", "/Users/me/Desktop/") do |ch, name, received, total|

  # Calculate percentage complete and format as a two-digit percentage
  percentage = format('%.2f', received.to_f / total.to_f * 100) + '%'

  # Print on top of (replace) the same line in the terminal
  # - Pad with spaces to make sure nothing remains from the previous output
  # - Add a carriage return without a line feed so the line doesn't move down
  print "Saving to #{name}: Received #{received} of #{total} bytes" + " (#{percentage})               \r"

  # Print the output immediately - don't wait until the buffer fills up
  STDOUT.flush
end
puts "Fetch complete!"