git add --patch

Git is so powerful and with such a rich feature set it can take a tremendous amount of time to learn. Over time, I’ve been trying to add to my git alias’ with useful commands that have improved my work flow. This is my first post on a series of commands I’d like to share.

First up is git add. When I pair with a lot of other developers, I see the same work flow - git add .. Readers likely know that this stages everything into the current tree/index. To see this in action, I’ve run a rails new advanced-git command to give us some files to play with.

That generated this:

~/projects/advanced-git
▶ ls -la
total 56
drwxr-xr-x   18 anthonyross  staff   612 Jul 21 11:21 .
drwxr-xr-x  147 anthonyross  staff  4998 Jul 21 11:22 ..
-rw-r--r--    1 anthonyross  staff   543 Jul 21 11:21 .gitignore
-rw-r--r--    1 anthonyross  staff  1715 Jul 21 11:21 Gemfile
-rw-r--r--    1 anthonyross  staff  4267 Jul 21 11:21 Gemfile.lock
-rw-r--r--    1 anthonyross  staff   374 Jul 21 11:21 README.md
-rw-r--r--    1 anthonyross  staff   227 Jul 21 11:21 Rakefile
drwxr-xr-x   10 anthonyross  staff   340 Jul 21 11:21 app
drwxr-xr-x    8 anthonyross  staff   272 Jul 21 11:21 bin
drwxr-xr-x   14 anthonyross  staff   476 Jul 21 11:21 config
-rw-r--r--    1 anthonyross  staff   130 Jul 21 11:21 config.ru
drwxr-xr-x    3 anthonyross  staff   102 Jul 21 11:21 db
drwxr-xr-x    4 anthonyross  staff   136 Jul 21 11:21 lib
drwxr-xr-x    3 anthonyross  staff   102 Jul 21 11:21 log
drwxr-xr-x    9 anthonyross  staff   306 Jul 21 11:21 public
drwxr-xr-x    9 anthonyross  staff   306 Jul 21 11:21 test
drwxr-xr-x    4 anthonyross  staff   136 Jul 21 11:21 tmp
drwxr-xr-x    3 anthonyross  staff   102 Jul 21 11:21 vendor

Let’s pretend this is an existing project that has a few commits:

git init
git add .
git commit -m "Initial Commit"

Now let’s edit the README.md file and explore a useful git feature. Edit the file to look like this:

# README

## Project Overview

This is a new project to showcase some useful git features



This is a footer where I might put license information.

Normally if you ran git add . you’d know that this file was staged with all the changes. However, with git add -p you can see your changes in ‘hunks’.

▶ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

~/projects/advanced-git  master ✗
▶ git add -p
diff --git a/README.md b/README.md
index 7db80e4..fcae1bc 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,8 @@
 # README

-This README would normally document whatever steps are necessary to get the
-application up and running.
+## Project Overview

-Things you may want to cover:
+This is a new project to showcase some useful git features

-* Ruby version

-* System dependencies
-
-* Configuration
-
-* Database creation
-
-* Database initialization
-
-* How to run the test suite
-
-* Services (job queues, cache servers, search engines, etc.)
-
-* Deployment instructions
-
-* ...
+This is a footer where I might put license information.
Stage this hunk [y,n,q,a,d,/,s,e,?]?

The -p stands for patch and if you run git add --help you can see that is defined as:

-p, –patch Interactively choose hunks of patch between the index and the work tree and add them to the index. This gives the user a chance to review the difference before adding modified contents to the index.

This effectively runs add –interactive, but bypasses the initial command menu and directly jumps to the patch subcommand. See “Interactive mode” for details.

If you read the first line after git add -p you’ll see git is actually running a diff with diff --git a/README.md b/README.md. In addition to that, you’re left a prompt thanks to git running this in --interactive mode:

Stage this hunk [y,n,q,a,d,/,s,e,?]?

This prompt is the basis for “Interactive Mode” and the commands are fairly intuitive:

y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
g - select a hunk to go to
/ - search for a hunk matching the given regex
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
K - leave this hunk undecided, see previous hunk
s - split the current hunk into smaller hunks
e - manually edit the current hunk
? - print help

When starting out, I recommended going through your changes with git add -p. I’ve found debug statements, extra prints and badly described test cases by stepping through code changes with -p. Ok, let’s say we’re still at our interactive prompt and we’re happy with our changes, enter y at the prompt.

You should be back at your shell and see that the file has been staged:

▶ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   README.md

From my experience I generally use y, n, q and s the most. Play around with those and enjoy the interactive git commands!

I’ve aliased this to g ap in ~/.gitconfig.

Upgrade from ActiveRecord 3 to ActiveRecord 4 with a little help from sed

I’ve been working on a project that is fairly large, around 300k lines of ruby code. We were asked to upgrade to Rails 4 early on and that work has yielded many subtle syntax changes, especially for ActiveRecord queries.

Some of these issues I was able to solve with a gem ArelConverter which has been pretty good at locating 60-70% of deprecated syntax.

One issue we learned when we had to convert scopes to respond to #call was that some of the developers used lambda and others use the -> rocket syntax. Well, we needed to standardize that and I was hoping to strengthen my sed skills at the same time.

If you’re not familiar with sed, $ man sed right now, but it’s basically a command line search and replace tool that I find to be predictable and a pleasure to work with. Combined with ag and xargs it can be really powerful.

Again, I’m looking where we use lambda’s across the repository. Ag is really good for this:

$ ag 'lambda'
app/models/user.rb
163:  scope :with_program, lambda { |program| where("user_programs.program_id IN (?)", program).joins(:user_programs).group("users.id") }
164:  scope :with_company, lambda { |company| where("users.company_id in (?)", company)}

So for example, that first line needs to actually become:

scope :with_program, ->(program) { where("user_programs.program_id IN (?)", program).joins(:user_programs).group("users.id") }

Sed is not destructive by default, meaning you are able to run your substitutions almost ‘interactively’ and confirm what you’re trying to do.

Let’s give that a shot and start small by just replacing the word lambda with the rocket ->

$ sed -n 's/lambda/->/p' app/models/user.rb

  scope :with_program, -> { |program| where("user_programs.program_id IN (?)", program).joins(:user_programs).group("users.id") }

If you did git status, you’ll see we didn’t actually edit that file, instead sed is just printing to STDOUT the changes it made. This is nice if you want to pipe it somewhere else. The -n flag limits the output to only the lines that contained a substitution (I wish I learned about that flag sooner!).

Ok, you probably didn’t come here to learn sed so let me show a way to do this across all your ruby files and replace it so -> is used, with and without arguments.

$ ag -l 'lambda' -G '.rb' | xargs sed -n -E 's:lambda(.*)\|(.*)\|:->(\2)\1:p'

Breaking that down:

ag
 -l # only give back file names
 'lambda' # search for any lines with the word lambda
 -G # limit file names to *.rb

xargs # send each file name to sed

sed
 -n # limit output to only lines touched
 -E # use perl regex

Take a look at that output, did everything change correctly? If it did, great let’s actually edit those files.

$ ag -l 'lambda' -G '.rb' | xargs sed -i .bak -E 's:lambda(.*)\|(.*)\|:->(\2)\1:p'

If you run git status you’ll likely see a lot of changes. Go ahead and confirm everything looks good. The .bak files you see are copies of the original files so if you royally messed up, you can use those files or I usually just git reset.

One of my 2016 goals has been to get better at the command line with tools like sed, xargs, ag, grep, awk and more. I hope this helps some others who are trying to do something similar!

Sharing your system clipboard

Today I ran into an issue that had me spinning my wheels for a little too long. I’m making it a habit to write down these moments so a.) maybe someone out there might waste less time than I did and b.) I’m sure I’ll have to come back to this at some point!

Today, I was ssh’d into a remote server and tried to yank something out of vim and into another vim running on my local machine. To clarify:

On the remote(Ubuntu): vim -> yank

On my local(OSX): vim -> paste

I quickly realized that yanking on a remote machine does not mean it will end up on my system(OSX) clipboard. That would be too easy. After some endless googling and bugging people in #vim #tmux and #ubuntu I got no where. That was until I found this post.

There’s some additional set up beyond that post so I hope this to be all encompassing, here we go:

OSX Set up

First you need to be using MacVim on OSX, I wasn’t. MacVim comes with nearly all the extra packages, but the most important one here is +clipboard . You can check if you have that already by running:

$ vim --version
$ mvim --version
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Aug 27 2015 16:21:48)
MacOS X (unix) version
Included patches: 1-769
Compiled by Homebrew
Huge version with MacVim GUI.  Features included (+) or not (-):
+acl             +file_in_path    +mouse_sgr       +tag_binary
+arabic          +find_in_path    -mouse_sysmouse  +tag_old_static
+autocmd         +float           +mouse_urxvt     -tag_any_white
+balloon_eval    +folding         +mouse_xterm     +tcl
+browse          -footer          +multi_byte      +terminfo
++builtin_terms  +fork()          +multi_lang      +termresponse
+byte_offset     +fullscreen      -mzscheme        +textobjects
+cindent         -gettext         +netbeans_intg   +title
+clientserver    -hangul_input    +odbeditor       +toolbar
+clipboard       +iconv           +path_extra      +transparency
+cmdline_compl   +insert_expand   +perl            +user_commands
+cmdline_hist    +jumplist        +persistent_undo +vertsplit
+cmdline_info    +keymap          +postscript      +virtualedit
+comments        +langmap         +printer         +visual
+conceal         +libcall         +profile         +visualextra
+cryptv          +linebreak       +python          +viminfo
+cscope          +lispindent      -python3         +vreplace
+cursorbind      +listcmds        +quickfix        +wildignore
+cursorshape     +localmap        +reltime         +wildmenu
+dialog_con_gui  -lua             +rightleft       +windows
+diff            +menu            +ruby            +writebackup
+digraphs        +mksession       +scrollbind      -X11
+dnd             +modify_fname    +signs           -xfontset
-ebcdic          +mouse           +smartindent     +xim
+emacs_tags      +mouseshape      -sniff           -xsmp
+eval            +mouse_dec       +startuptime     -xterm_clipboard
+ex_extra        -mouse_gpm       +statusline      -xterm_save
+extra_search    -mouse_jsbterm   -sun_workshop    -xpm
+farsi           +mouse_netterm   +syntax
...

From here forward, I’ll assume you’re using MacVim. I was a little hesitant at first too but you can run MacVim in a terminal by using:

$ mvim -v Filename

Here’s what I have in my ~/.zshrc for helpers:

export EDITOR=/usr/local/bin/mvim
mac_vim="$EDITOR -v"
alias vi=$mac_vim
alias vim=$mac_vim
alias v=$mac_vim

Ok, now following garyjohn’s instructions, let’s open up ~/.ssh/config in vim:

$ v ~/.ssh/config

Let’s copy in:

ForwardX11 yes
SendEnv WINDOWID

That’s it for OSX.

Remote (ie Ubuntu, Linux, *Nix, Debian, etc)

You’ll need appropriate permissions, so either run as root or have sudo access.

First a lot of distro’s come with a pre-installed version of VIM, unfortunately like OSX they usually don’t have the write packages installed which is 50% of our issue(s). I recommend the following:

$ sudo apt-get purge vim && sudo apt-get install vim-gtk

If you run vim –version you should see the gtk package ships with most of the extra options for vim, including +clipboard and +xterm_clipboard.

Let’s head into /etc/ssh/sshd_config

$ vim /etc/ssh/sshd_config

In there put:

AcceptEnv WINDOWID

We’re ready to yank. Let’s try a line in some file

"*yy

Open vim locally and paste, with p. There you have it, we just copy & pasted from a remote ssh session to your local vim.