Want to see the full-length video right now for free?
In this video, we'll be talking about using Git with remotes like GitHub and Heroku. While Git is perfectly serviceable when used alone, its real power comes from working with others. Git is designed to work well in a distributed environment, and with platforms like GitHub and Heroku we can can really take advantage of that.
The first topic we want to cover is a command line utility called Hub. Hub is maintained by the folks at GitHub, and allows us to introduce Git and GitHub to each other. With Hub we can run a whole range of commands from the command line, just as we would any other Git command, and have them interact with the GitHub API for us.
The hub readme has the full listing of commands that hub
provides, but
we'll just be looking at a few in this video.
We'll start with Hub's browse
command, which opens the GitHub remote repo for
the current local repo. Even better, it opens the GitHub URL for the
current branch.
Similar to browse
, the compare
command in hub
will open the GitHub repo
associated with the current local repo, but on the GitHub Compare
Page. The compare page displays the range of commits specified (you can
compare branches, specific commit ranges, or even time) and will
show the complete diff.
Likewise, from the compare view we can create a new pull request or navigate to the PR if one already exists.
The pull-request
command allows us to compose our pull request title and
description locally, using our favorite editor (I hear good things about
Vim), just like we would with a commit message. Once we finish writing, hub
will take our newly composed document and use the GitHub API to build a pull
request using the provided text.
If we're using continuous integration (CI) with our pull project, we can use
the ci-status
command to see the current status for our CI build. This will
mirror the pull request status on the PR page, aggregating any checks from
services like TravisCI, CircleCI, Hound, Code Climate, etc.
The command will provide both a text output ("success", "failure", or "pending"), as well as providing a relevant exit code for use in scripts.
While we certainly can use Hub directly with the hub
command, the recommended
way to use Hub is to alias Hub as Git. It provides a simple code
snippet that you can put into your zshrc
file. This will then load on startup,
and commands will be intelligently handled by Hub, or passed through to Git.
eval "$(hub alias -s)"
If you'd prefer not to fully wrap hub
around git
, but still want the ease
of use and continuity of using git
for all your Git- related fun, an
alternative approach is to create specific aliases in your ~/.gitconfig
file.
[alias]
browse = !hub browse
compare = !hub compare
issues = !hub browse --issues
GitHub does a wonderful job of hosting our code and making it possible to share links to specific files and directories. Even better, if we click on a line number (or shift-click to select a range of lines), then those lines will be highlighted and the URL will be updated to include a reference to those lines. We can then share that URL to highlight specific lines in specific files, which is pretty great.
Unfortunately, by default these URLs will include the current branch that
we're looking at, typically master
. If we share this URL and then make
changes to that file on the master branch, our line numbers and the code we
reference will likely be incorrect.
Instead, we want to provide a URL for the code exactly as it looks right now, which we can do by specifying a commit that will always point at the same version of the code, rather than a branch which will change over time.
Thankfully, GitHub has thought this through and provides canonical URLs for
all content in our repos. If we press y
, GitHub will reload the page and
replace the branch reference with the relevant commit, making the link robust
and safe to share.
pr
is a custom subcommand that allows us to easily open the pull request
associated with the current local branch. As we iterate on a PR, respond to
feedback, and refactor our branch, we often find ourselves needing to return to
the PR to review comments or CI build status. Automating and streamlining this
iteration is a big win.
Check out the full implementation of the git-pr script to set this up, but here's a brief explanation. The script is implemented as a sub command that looks at the branch name, then builds and opens the relevant URL for us when the command is run. GitHub then redirects this request to either the PR (if one exists), or to the compare URL to open the PR.
The final tool we'll discuss for working with remotes is Parity. Parity
wraps the heroku
command, providing distinct executables for:
development
staging
production
Parity works under the assumption that you have the Git remotes staging
and
production
, which point at the relevant Heroku instances. This allows
the slightly wordy Heroku command:
$ heroku run console -r production
to be replaced with the more concise
$ production console
In this example, not only does Parity provide the production
command to
manage the -r production
flag, but it also allows us to use the direct
console
as an alias for run console
.
Parity also exposes a set of subcomamnds such as backup
, restore
,
migrate
, deploy
, etc. that make working with our Heroku apps much easier.
Parity isn't specifically a Git utility, so we won't go into too much detail in this video, but do check out the Parity usage guide as well as the Heroku episode of The Weekly Information for more info.
Tracking branches, also known as "upstream branches", are branches that have
a direct relationship to a branch on a remote such as origin
. When working
with these tracking branches, we can run the bare git pull
or git push
,
and Git will automatically use the configured remote branch as the target.
If we check out our local branch based on a remote branch, then the upstream tracking will automatically be configured.
$ git branch --remote origin/remote-branch-name
$ git checkout remote-branch-name
$ git push
Everything up to date.
However, if you have a newly created local branch that does not yet exist on your remote server then you cannot simply push:
$ git co -b upstream-test
$ git push
fatal: The current branch upstream-test has no upstream branch.
Instead (as Git kindly points out in the error message for the above
push
command), you need to explicitly specify where to push, and instruct
Git to set up tracking:
$ git push -u origin local-branch
The -u
flag here is short for --set-upstream
.
Lastly, if we want to push our local branch with an alternate name on our remote, we can do this using a slightly altered form:
$ git push -u origin local-branch-name:upstream-name
This is, of course, a prime candidate for automation, and my git-publish subcommand does just that.
One way that we can see the data about our local and remote branches, as well
as the relationships between them, is by using a specific form of the
branch
command:
$ git branch -vv
Running this causes Git to list out each of the local branches and the current commit they point at, as well as listing the upstream remote branch (if there is one).
Focusing on the current branch, we can ask Git specifically for the remote tracking branch. Unfortunately, the command needed to do this is a bit verbose, but luckily we have aliases to clean it up:
# in the [alias] section of the ~/.gitconfig file
upstream = rev-parse --abbrev-ref --symbolic-full-name @{upstream}
With this alias in place, we can run:
$ git checkout master
$ git upstream
origin/master
and Git will kindly display the name of the remote branch we are tracking.
In some cases, we'll want to deploy a feature branch to our Heroku staging
instance without merging it into master. The complication here is that Heroku
will only deploy the master
branch. In order to deploy our non-master
branch, we have to essentially push it up to staging, setting staging's
master branch to point at our feature branch:
$ git push --force staging local-branch:master
Note: The use of --force
pushing onto master
here can be destructive.
It should only be used on a throwaway environment like staging
, and never
with origin
or production
.
A typical application will have at least three remotes: origin (GitHub), staging (Heroku), and production (Heroku). If we're working with a team, we will quickly fall behind and need to fetch these remotes. The following alias automates fetching all of the remotes in one single command:
# in the [alias] section of the ~/.gitconfig file
fall = !for remote in $(git remote); do echo Fetching $remote; git fetch $remote; done
This uses a "bang alias" to loop over each of the remotes, running git fetch
in turn.
Note git fetch
actually accepts an --all
argument that provides
identical behavior to the above alias. That said, the above is still a useful
example of a complex alias.