Monthly Archives: August 2022

Some handy Git Utilities

In the course of daily coding, you gradually pile up some little personal hacks you aren’t sure are worth publishing and sharing. The world of shell scripts in particular don’t seem to be nearly as commonly packaged and shared as, say, Javascript or Python. But, here are a couple of mine, for dealing with git repos, in case anyone ever finds them handy.

FWIW, I usually stash these in my ~/.profile, as described in this post.


Sometimes, you’re working on a branch, and you just want to update trunk without much fuss and without leaving your current branch:

function tu() {
  # tu =_T_runk _U_pdate: general function for updating a trunk branch while on another
  # Defaults to master branch for basic git compatibility
  # But takes alternate trunk branch name as only argument
  trunk="master" && [[ $1 ]]  && trunk=$1
  echo "**tu**: trunk branch is defined as: $trunk"

  # 1. cache current branch & take updates from default remote
  branch=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
  git fetch --prune

  # 2. update trunk
  echo "**tu**: Switching to trunk branch, $trunk..."
  BRANCH_EXISTS=`git checkout $trunk`
  # Just in case you don't actually have a branch with name, exit gracefully:
  if [ $BRANCH_EXISTS ]; then
    git pull
  
  # 3. now that trunk is updated, return to previous branch
    echo "**tu**: $trunk updated, returning to $branch..."
    git checkout $branch
  else
    echo "**tu**: $trunk branch does not exist. Exiting."
    set -e
  fi
}

And, you can create shorthands for particular trunk branches you might be using:

function mu() {
  # Branch-Specific Shorthand form of tu() for MASTER
  # DEPENDS ON tu() !
  tu master
}

(Which, in 2022, I probably oughta’ update to ‘Main’ branch. But… I’m playing catch-up here on sharing some old code.)

More commonly, and still useful, is the case where I’m mostly wanting to update my develop branch:

function du() {
  # Branch-Specific Shorthand form of tu() for DEVELOP
  # DEPENDS ON tu() !
  tu develop
}

OK, that’s maybe handy. Occasionally. But not something I obsess about. Why do I care? Ohhhh, right… REBASING.

I often want to do a quick rebase of my current branch onto the latest version of trunk. So, let’s put those building-blocks all together into a new more useful script which lets me do just that:

function turt() {
  # turt == Trunk Update, Rebase against Trunk
  # takes a single parameter: the name of your trunk branch, which defaults to master
  localTrunk="master" && [[ $1 ]]  && localTrunk=$1
  # use first character of trunk name to customize output
  first=${localTrunk:0:1}

  echo "**${first}ur${first}**: First, update $localTrunk..."
  tu $localTrunk
  echo "**${first}ur${first}**: Now, rebase $branch onto $localTrunk..."
  git rebase $trunk
}

And then to make it more specific to particular trunk branches you might be using:

function murm() {
  # murm == Master Update, Rebase against Master
  # DEPENDS ON turt() !
  turt master
}

function durd() {
  # durd == Develop Update, Rebase against Develop
  # DEPENDS ON turt() !
  turt develop
}

Of course, as much as I think most merges of trunk into a branch ought to be rebases instead, there’s a time and a place for merging in an updated trunk as well:

function tumt() {
  # turt == Trunk Update, Merge Trunk
  # takes a single parameter: the name of your trunk branch, which defaults to master
  localTrunk="master" && [[ $1 ]]  && localTrunk=$1
  # use first character of trunk name to customize output
  first=${localTrunk:0:1}
  echo "**${first}um${first}**: First, update $localTrunk..."
  tu $localTrunk
  echo "**${first}um${first}**: Now, merge $localTrunk into $branch ..."
  git merge $trunk
}

function mumm() {
  # mumm == Master Update, Merge Master
  # DEPENDS ON tumt() !
  tumt master
}

function dumd() {
  # durd == Develop Update, Merge Develop
  # DEPENDS ON tumt() !
  tumt develop
}

Effectively Shell-ing: Decouple your environment variables from specific shells by sourcing .profile everywhere

Since this is about the shell, I’m going to make a plug for my friend Dave Kerr’s amazing ebook: Effective Shell. If you’re going to muck about in terminals/shells, you really ought to go read it.

….

No. Really. I’ll wait. You’d be stupid not to. Go read it, or at least start reading it, then come back.

OK. Now that you’re a shell ninja, and know how some strong shell-fu can make an engineer more powerful… let’s talk variables and imports.

If you’ve ever so much as setup a dev environment and installed some non-trivial tools, you’ve likely encountered installation guidance like this (example from reactnative.dev, as of 08.22):

Note how they’re already qualifying the fact that you might have to put these exports in one file if you use bash, or in another file if you use Zsh. But they’re not done yet. They go on to clarify that if you’re ever using some shell other than the one you already configured, you’ll then still need to source those variables into your current shell:

So… sure… you could do all that. And, you could wrestle with the fact that other bits of your system (such as XCode) will periodically open up their own choice of shell (often bash), in a non-interactive way, where you can’t even exercise this option. And so everything will break. And you could accept that as inevitable. (Sucker.)

OR, you could take advantage of a simple and inexplicably overlooked option which makes this whole problem go away. In addition to the shell-specific files already mentioned above…

~/.bash_profile
~/.zprofile

…there is by default another, more universal file on POSIX-compliant systems: ~/.profile. (That’s the user-specific version for your own account. In theory, you could also tinker with etc/profile, but as a general rule I prefer to stick to messing about with my user files rather than system-level ones.)

Sure, you could run around manually source-ing some other profile into your current shell… but why would you? Why not go with the even easier and more sustainable option of ensuring that you always have all the same environment variables in all your shells?

First, instead of copying them hither and yon… simply place all needed variables/exports into one place: ~/.profile instead of repeating them across all the bash/zsh-specific versions.

Then, open up your user’s shell-specific configuration files (such as ~/.bashrc and/or ~/.zshrc, and/or ~/bash_profile and/or ~/zprofile… or those for rbash, dash, tmux, etc. ) and add to them all a single simple line:

[[ -s "$HOME/.profile" ]] && source "$HOME/.profile"

# SOURCE the master sh .profile, for concerns shared across all shells, login and non-login

[[ -s "$HOME/.profile" ]] && source "$HOME/.profile" # Load the default .profile

# Everything after this should be specific to your Zsh/bash login shells

That’ll do it. You should now have all the same variables in all your shells, because they’re all source-ing from the same location.

And, if you’d maybe like to embellish that with some explanatory comments so you remember what’s happening here:

# SOURCE the master .profile, for concerns shared across all shells, login and non-login
[[ -s "$HOME/.profile" ]] && source "$HOME/.profile"
# Everything after this should then be specific to your interactive shells

I have also found it useful to provide an indicator so I can tell when these variables have been imported successfully into a given shell, so I include this at the end of ~/.profile:

###### LAST
echo ".profile is sourced"

Once that’s done, I find that my individual shell config files can be very lean and mean, with only shell-specific items. For instance, my ~/.zshrc can exclusively be about Zsh customizations like the amazing oh-my-zsh, which won’t do me any good over in ~/.bashrc. Meanwhile, it’s only in ~/.profile that I need to write universal workflow utility scripts and project aliases such as:

alias rns="cd ~/PROJECTS/react-native-scaffold && nvm use --lts"

Notes:

If you want to be more surgical, it may be useful to understand the difference between –profile files and –rc files, between interactive shells and non-interactive shells, and login vs. non-login shells. Maybe check out this article and/or this one, or go finish reading the related topics in Effective Shell. That said… it’s complicated, especially for Mac users, which is why I’ve been pretty general about which files you might need to import the ~/.profile into. YMMV.