GitHub Flow Like a Pro with these 13 Git Aliases

git github 71 comments suggest edit

BONUS! I’ve added a useful 14th Git Alias: git migrate and now a 15th useful alias to open the repository in the browser

GitHub Flow is a Git work flow with a simple branching model. The following diagram of this flow is from Zach Holman’s talk on How GitHub uses GitHub to build GitHub.

github-branching

You are now a master of GitHub flow. Drop the mic and go release some software!

Ok, there’s probably a few more details than that diagram to understand. The basic idea is that new work (such as a bug fix or new feature) is done in a “topic” branch off of the master branch. At any time, you should feel free to push the topic branch and create a pull request (PR). A Pull Request is a discussion around some code and not necessarily the completed work.

At some point, the PR is complete and ready for review. After a few rounds of review (as needed), either the PR gets closed or someone merges the branch into master and the cycle continues. If the reviews have been respectful, you may even still continue to like your colleagues.

It’s simple, but powerful.

Over time, my laziness spurred me to write a set of Git aliases that streamline this flow for me. In this post, I share these aliases and some tips on writing your own. These aliases start off simple, but they get more advanced near the end. The advanced ones demonstrate some techniques for building your own very useful aliases.

Intro to Git Aliases

An alias is simply a way to add a shorthand for a common Git command or set of Git commands. Some are quite simple. For example, here’s a common one:

git config --global alias.co checkout

This sets co as an alias for checkout. If you open up your .gitconfig file, you can see this in a section named alias.

[alias]
co = checkout

With this alias, you can checkout a branch by using git co some-branch instead of git checkout some-branch. Since I often edit aliases by hand, I have one that opens the gitconfig file with my default editor.

ec = config --global -e

These sort of simple aliases only begin to scratch the surface.

GitHub Flow Aliases

Get my working directory up to date.

When I’m ready to start some work, I always do the work in a new branch. But first, I make sure that my working directory is up to date with the origin before I create that branch. Typically, I’ll want to run the following commands:

git pull --rebase --prune
git submodule update --init --recursive

The first command pulls changes from the remote. If I have any local commits, it’ll rebase them to come after the commits I pulled down. The --prune option removes remote-tracking branches that no longer exist on the remote.

This combination is so common, I’ve created an alias up for this.

up = !git pull --rebase --prune $@ && git submodule update --init --recursive

Note that I’m combining two git commands together. I can use the ! prefix to execute everything after it in the shell. This is why I needed to use the full git commands. Using the ! prefix allows me to use any command and not just git commands in the alias.

Starting new work

At this point, I can start some new work. All new work starts in a branch so I would typically use git checkout -b new-branch. However I alias this to cob to build upon co.

cob = checkout -b

Note that this simple alias is expanded in place. So to create a branch named “emoji-completion” I simply type git cob emoji-completion which expands to git checkout -b emoji-completion.

With this new branch, I can start writing the crazy codes. As I go along, I try and commit regularly with my cm alias.

cm = !git add -A && git commit -m

For example, git cm "Making stuff work". This adds all changes including untracked files to the index and then creates a commit with the message “Making Stuff Work”.

Sometimes, I just want to save my work in a commit without having to think of a commit message. I could stash it, but I prefer to write a proper commit which I will change later.

git save or git wip. The first one adds all changes including untracked files and creates a commit. The second one only commits tracked changes. I generally use the first one.

save = !git add -A && git commit -m 'SAVEPOINT'
wip = commit -am "WIP"

When I return to work, I’ll just use git undo which resets the previous commit, but keeps all the changes from that commit in the working directory.

undo = reset HEAD~1 --mixed

Or, if I merely need to modify the previous commit, I’ll use git amend

amend = commit -a --amend

The -a adds any modifications and deletions of existing files to the commit but ignores brand new files. The --amend launches your default commit editor (Notepad in my case) and lets you change the commit message of the most recent commit.

A proper reset

There will be times when you explore a promising idea in code and it turns out to be crap. You just want to throw your hands up in disgust and burn all the work in your working directory to the ground and start over.

In an attempt to be helpful, people might recommend: git reset HEAD --hard.

Slap those people in the face. It’s a bad idea. Don’t do it!

That’s basically a delete of your current changes without any undo. As soon as you run that command, Murphy’s Law dictates you’ll suddenly remember there was that one gem among the refuse you don’t want to rewrite.

Too bad. If you reset work that you never committed it is gone for good. Hence, the wipe alias.

wipe = !git add -A && git commit -qm 'WIPE SAVEPOINT' && git reset HEAD~1 --hard

This commits everything in my working directory and then does a hard reset to remove that commit. The nice thing is, the commit is still there, but it’s just unreachable. Unreachable commits are a bit inconvenient to restore, but at least they are still there. You can run the git reflog command and find the SHA of the commit if you realize later that you made a mistake with the reset. The commit message will be “WIPE SAVEPOINT” in this case.

Completing the pull request

While working on a branch, I regularly push my changes to GitHub. At some point, I’ll go to github.com and create a pull request, people will review it, and then it’ll get merged. Once it’s merged, I like to tidy up and delete the branch via the Web UI. At this point, I’m done with this topic branch and I want to clean everything up on my local machine. Here’s where I use one of my more powerful aliases, git bdone.

This alias does the following.

  1. Switches to master (though you can specify a different default branch)
  2. Runs git up to bring master up to speed with the origin
  3. Deletes all branches already merged into master using another alias, git bclean

It’s quite powerful and useful and demonstrates some advanced concepts of git aliases. But first, let me show git bclean. This alias is meant to be run from your master (or default) branch and does the cleanup of merged branches.

bclean = "!f() { git checkout ${1-master} && git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs git branch -d; }; f"

If you’re not used to shell scripts, this looks a bit odd. What it’s doing is defining a function and then calling that function. The general format is !f() { /* git operations */; }; f We define a function named f that encapsulates some git operations, and then we invoke the function at the very end.

What’s cool about this is we can take advantage of arguments to this alias. In fact, we can have optional parameters. For example, the first argument to this alias can be accessed via $1. But suppose you want a default value for this argument if none is provided. That’s where the curly braces come in. Inside the braces you specify the argument index ($0 returns the whole script) followed by a dash and then the default value.

Thus when you type git bclean the expression ${1-master} evaluates to master because no argument was provided. But if you’re working on a GitHub pages repository, you’ll probably want to call git bclean gh-pages in which case the expression ${1-master} evaluates to gh-pages as that’s the first argument to the alias.

Let’s break down this alias into pieces to understand it.

git branch --merged ${1-master} lists all the branches that have been merged into the specify branch (or master if none is specified). This list is then piped into the grep -v "${1-master}" command. Grep prints out lines matching the pattern. The -v flag inverts the match. So this will list all merged branches that are not master itself. Finally this gets piped into xargs which takes the standard input and executes the git branch -d line for each line in the standard input which is piped in from the previous command.

In other words, it deletes every branch that’s been merged into master except master. I love how we can compose these commands together.

With bclean in place, I can compose my git aliases together and write git bdone.

bdone = "!f() { git checkout ${1-master} && git up && git bclean ${1-master}; }; f"

I use this one all the time when I’m deep in the GitHub flow. And now, you too can be a GitHub flow master.

The List

Here’s a list of all the aliases together for your convenience.

[alias]
  co = checkout
  ec = config --global -e
  up = !git pull --rebase --prune $@ && git submodule update --init --recursive
  cob = checkout -b
  cm = !git add -A && git commit -m
  save = !git add -A && git commit -m 'SAVEPOINT'
  wip = !git add -u && git commit -m "WIP"
  undo = reset HEAD~1 --mixed
  amend = commit -a --amend
  wipe = !git add -A && git commit -qm 'WIPE SAVEPOINT' && git reset HEAD~1 --hard
  bclean = "!f() { git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs git branch -d; }; f"
  bdone = "!f() { git checkout ${1-master} && git up && git bclean ${1-master}; }; f"

Credits and more reading

It would be impossible to source every git alias I use as many of these are pretty common and I’ve adapted them for my own needs. However, here are a few blog posts that provided helpful information about git aliases that served as my inspiration. I also added a couple posts about how GitHub uses pull requests.

PS: If you liked this post follow me on Twitter for interesting links and my wild observations about pointless drivel

PPS: For Windows users, these aliases don’t require using Git Bash. They work in PowerShell and CMD when msysgit is in your path. For example, if you install GitHub for Windows and use the GitHub Shell, these all work fine.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

71 responses

  1. Avatar for kevdog
    kevdog July 28th, 2014

    Great post, thanks! Extremely helpful.

  2. Avatar for Scott Koon
    Scott Koon July 28th, 2014

    aa = add -A

    I think I got this one from Paul. I use it all the time

    pu=!git fetch origin;git fetch upstream;git merge upstream/master

    This one is my all-in-one update my clone to the cloned repo command.

    I still use
    fu = reset --hard

    Because I'm often using git-svn, which means my repo is often screwed up through no fault of my own and I just need to reset and get rid of all my merge conflicts.

  3. Avatar for Max Battcher
    Max Battcher July 28th, 2014

    Some useful suggestions. Think I may rewrite bclean to PowerShell, a) as useful practice, and b) because yeah, I am one of those developers that hates GIT bash on Windows.

  4. Avatar for haacked
    haacked July 28th, 2014

    All of my aliases work in PowerShell when you have MsysGit installed and in the path or when you use the "GitHub Shell" that ships with GitHub for Windows.

  5. Avatar for Latish
    Latish July 28th, 2014

    Great stuff! If I have a project with binaries checked in, I use the this for ignoring them in diffs

    dt = !git difftool -- $(git difftool --name-only | grep -Ev "*dll")

  6. Avatar for Dave Shaw
    Dave Shaw July 28th, 2014

    Awesome, just tried one or two on Windows with MsysGit installed and they seem fine.

  7. Avatar for ecksun
    ecksun July 28th, 2014

    I like the idea of letting simple aliases doing so much work.

    However, you are not actually using git grep, but ordinary grep:
    "Git Grep prints out lines matching the pattern."

    bclean = ... | grep -v " ${1-master}$" | ...

  8. Avatar for haacked
    haacked July 28th, 2014

    Ah, thanks! I better fix that.

  9. Avatar for liquid-electron
    liquid-electron July 28th, 2014

    Shame on you for not making your alias list a Gist! Now I have to fork via copy/paste 😁

  10. Avatar for Brian Vallelunga
    Brian Vallelunga July 29th, 2014

    When you merge a pull request from a topic branch, do you squash your branch's commits down, or just leave the entire history there? If so, wouldn't you end up with a lot of SAVEPOINT commits on master?

    Similarly, after you merge into master, delete your branch and push, is it up to all other developers that collaborated with you on that topic branch to clean up their own copies? Their topic branches don't get automatically closed on the next pull, right?

  11. Avatar for haacked
    haacked July 29th, 2014

    If I've run git save, usually I'm leaving to do something else. When I get back, I run `git undo` and continue work. Or I'll do more work and amend the SAVEPOINT commit. I don't leave those in there.

    When I'm ready to push a topic branch, I don't squash the commits, but I often do an interactive rebase to clean up any turds I've left in. :)

    > Their topic branches don't get automatically closed on the next pull, right?

    True, but the next time they run `git bdone` it'll clean up their local branches as long as they don't have any commits in there that haven't been merged.

  12. Avatar for Brian Vallelunga
    Brian Vallelunga July 29th, 2014

    OK, thanks for the clarification. The save/undo works well on a single machine, but I'm constantly moving between my laptop and desktop. I'm not sure of a great solution to that problem.

  13. Avatar for haacked
    haacked July 30th, 2014

    Ah, before I switch I push to a "private" branch. It's public on GitHub.com, but "private" by convention. I prefix my branches with my username and a slash.

    For example, `git push -u origin haacked/branch-name`

  14. Avatar for Nick Janssen
    Nick Janssen July 30th, 2014

    Amazing to see how some aliases I've made over the years are exactly the same as yours! Now I can add a few new ones to my collection, thanks!

  15. Avatar for Shahbaz Khan
    Shahbaz Khan July 31st, 2014

    Excellent

  16. Avatar for Ingvij
    Ingvij August 2nd, 2014

    Why don't you stash instead of commiting WIP?

  17. Avatar for thejspr
    thejspr August 6th, 2014

    I tweaked the `bclean` alias a bit to also not remove a production branch is it's been merged:

    bclean = "!f() { git branch --merged ${1-master} | grep -vE '(master|production)' | xargs git branch -d; }; f"

    I also got `xargs: illegal option -- r` on osx

  18. Avatar for Max Battcher
    Max Battcher August 6th, 2014

    "Production branches" aren't github flow, and are a bit of a pet peeve of mine when I see then in git repos. Your deployment tooling should either be able to tell you the last commit hash it deployed and/or create a git tag for the deployed release. No reason to keep merged branch pointers around to "mirror" Production. (It also sounds dangerous to me that you imply there may be times that your "production" branch isn't fully merged to master. Seems to me to indicate that you might be on a hybrid flow that you should reconsider.)

  19. Avatar for thejspr
    thejspr August 6th, 2014

    I agree completely. It was more directed at sharing how to exclude multiple branches and the xargs issue.

  20. Avatar for Max Battcher
    Max Battcher August 6th, 2014

    Ah, fair enough. I will admit to using a "hotfix/" branch from time to time, although I try to encourage other approaches to that, but hot fixes are an old habit hard to break for a lot of businesses, when it "absolutely has to get fixed" before the next "scheduled release", because most of us live in hybrid agile/waterfall worlds...

  21. Avatar for Niko
    Niko August 7th, 2014

    Great post! :D

    However the first push I do in a new branch is asking me to do "git push --set-upstream origin my-new-branch" every time... is there a need to create a new alias for pushing or am I doing something wrong?

  22. Avatar for haacked
    haacked August 7th, 2014

    You can use -u instead of --set-upstream.

    git push -u origin branch-name

    It wouldn't be too hard to write an alias that assumes origin. Something like (untested):

    pushit = push -u origin $1

    usage:

    git pushit branch-name

  23. Avatar for Niko
    Niko August 8th, 2014

    Thanks for the answer! :)

    I ended up creating this alias:
    pushit = "!f() { git rev-parse --abbrev-ref HEAD ; }; git push --set-upstream origin `f`"

    And using simple pushes:
    [push]
    default = simple

  24. Avatar for blueyed
    blueyed August 14th, 2014

    The `wipe` alias is great. I have made a function out of it (using your pattern), which allows to reset to something else than `HEAD`:

    wipe = "!f() { rev=$(git rev-parse ${1-HEAD}); \
    git add -A && git commit --allow-empty -qm 'WIPE SAVEPOINT' \
    && git reset $rev --hard; }; f"

    It gets the rev before committing, and uses `--allow-empty` for the commit, to make it work with an unchanged worktree, where nothing would be committed, but the `reset` should get done always.

  25. Avatar for haacked
    haacked August 14th, 2014

    Nice! I may have to steal that. :)

  26. Avatar for qmmr
    qmmr October 2nd, 2014

    Thanks for sharing!

    After upgrading to git 2.1 I'm having weird errors when running my script, saying "error: branch 'nameofthebranch' not found."
    git branch --merged | grep -v | xargs git branch -d

    Anyone have the same issue?

  27. Avatar for Alex D
    Alex D November 19th, 2014

    Can we use some aliases in our aliases? :)
    For example: save = cm SAVEPOINT or !git cm SAVEPOINT

  28. Avatar for magikid
    magikid December 3rd, 2014

    Exactly what I was coming to comment.
    git stash will save changes since your last commit then git pop will put them back.

  29. Avatar for Chowdhury M Rahman
    Chowdhury M Rahman December 4th, 2014

    A bit late to the party but thanks for sharing.
    Here are a couple I use quite often...
    lg = log --graph --oneline --decorate --all
    and a slight variation of it
    lgs = log --graph --oneline --decorate --all --name-status
    and ofcourse >
    l1 = log --oneline
    I am lazy, I know. :)

  30. Avatar for Christian Crowhurst
    Christian Crowhurst December 5th, 2014

    Inspired me to write my own set of aliases that will help enforce a commit history that look like a set of release notes: http://codingmonster.co.uk/...

  31. Avatar for Stephen James
    Stephen James December 11th, 2014

    Great post! Enabled me to write an alias "git lucky" which checks out the first matching branch of a partial expression. http://stephenjamescode.blo...

  32. Avatar for Avi Haiat
    Avi Haiat December 23rd, 2014

    Great aliases, i use them all the time, and the workflow is a breeze.
    But the git bclean command is giving an error (illegal --r options) on macos. Do you have a compatible alias, it is a pain to delete manually the local branches after a PR merge....

  33. Avatar for haacked
    haacked December 23rd, 2014

    --r? That should be -r.

  34. Avatar for Avi Haiat
    Avi Haiat December 23rd, 2014

    i meant -r , it is not a valid option of args in Mac OS

  35. Avatar for haacked
    haacked December 24th, 2014

    -r is a shorthand for --no-run-if-empty. Try that instead?

  36. Avatar for Avi Haiat
    Avi Haiat December 25th, 2014

    not working either, this option is a GNU extension that does not exist on Mac, i removed it from the bclean alias, and that seems a good enough workaround, let me know what you think.

  37. Avatar for SleepyBobos
    SleepyBobos January 13th, 2015

    Awesome. Has streamlined my git-flow. Just got to be careful to git undo a git wip\save before pushing :-)

  38. Avatar for Brandon Johnson
    Brandon Johnson January 15th, 2015

    Very helpful! Thanks!

  39. Avatar for kevin
    kevin March 8th, 2015

    Great post. I'm glad to see you're happy there and wish that more companies would put a tiny bit of effort into employee happiness.

  40. Avatar for werchter
    werchter March 20th, 2015

    Yes, same question here

  41. Avatar for Will Thames
    Will Thames March 23rd, 2015

    Couldn't

    wip = !git add -u && git commit -m "WIP"

    be rewritten
    wip = commit -am "WIP" ?

  42. Avatar for Guest
    Guest March 23rd, 2015

    I have a couple that I use all the time,

  43. Avatar for Evgeny Zislis
    Evgeny Zislis March 23rd, 2015

    I have a couple that I use all the time,

    logg = log --oneline --graph --color --decorate # short log
    st = status -s # short status

  44. Avatar for gwillem
    gwillem March 23rd, 2015

    po = !git push -u origin $(git describe --contains --all HEAD)

  45. Avatar for haacked
    haacked March 23rd, 2015

    Yes, it could be! I think the reason I have it split up like that is because I actually want "wip" to be git add -A so it adds all my files and not just the ones already in the index.

  46. Avatar for haacked
    haacked March 23rd, 2015

    Actually, scratch that. That's what git save is for. I've been refining my workflow and confused myself.

  47. Avatar for haacked
    haacked March 23rd, 2015

    The short answer is that git stash is global. I tend to work on a lot of branches in parallel and what I usually want is a per-branch stash.

    I use stash when I want to bring a set of changes across to another branch.

    Also, I sometimes push my work in progress to a one-off branch as a backup of my local work. The git stash is local.

  48. Avatar for glandium
    glandium March 23rd, 2015

    For `up`, --rebase and --prune can both be set globally or per remote/branch with pull.rebase, fetch.prune, branch.$branch.rebase and remote.$remote.prune

    `cob` seems a bit of a stretch, since it's the same as `co -b`, which is only 2 more characters (compared to `checkout -b` which is much longer).

    `save` and `wip` could be replaced with proper use of stash (`save` is essentially `stash save -u`)

    `stash save -u -k` could also be used in place of `wipe`

  49. Avatar for haacked
    haacked March 23rd, 2015

    co -b is three more characters. There's the space. It's not much, but I type on dvorak and the - character is under my pinky, whereas cob are under stronger fingers. It's a micro-optimization, but when you've dealt with serious pain while typing, http://haacked.com/archive/..., every bit counts. :)

    Save and WIP aren't exactly equivalent to stash. I wrote up why I have wip and save in this comment:
    http://haacked.com/archive/...

  50. Avatar for Marco Antonino
    Marco Antonino April 2nd, 2015

    First of all, great post!
    I love the bclean command but I wanted to tag the branches before deleting them and I failed so I reverted to powershell:

    function gitclean($branch = "master")
    {
    $branchesToRemove = git branch --merged $branch | ?{-not $_.Contains($branch)} | %{$_.trim()}
    $branchesToRemove | %{git tag -f archive/$_ $_}
    $branchesToRemove | %{git branch -D $_}
    }

    Is there a way to do this in a git alias?

  51. Avatar for Bryan Johns
    Bryan Johns June 5th, 2015

    Great post. By using some of these I've got my git commands from the command line down to 15 chars or less as long as I don't need to type a branch name.

  52. Avatar for sideshowcoder
    sideshowcoder August 5th, 2015

    This will actually fail if there are no branches to clean, a small additional tweak:

    ` bclean = "!f() { branches=$(git branch --merged ${1-master} | grep -v " ${1-master}$"); [ -z \"$branches\" ] || git branch -d $branches; }; f"`

  53. Avatar for haacked
    haacked August 5th, 2015

    Yeah, I might want to add gh-pages to that, for example. :)

  54. Avatar for Rafeh Qazi
    Rafeh Qazi August 21st, 2015

    alias pushit='git add . && echo "Please enter github commit message: " && read input_variable && git commit -m "$input_variable" && git push'

    This lets you add everything, commit it, and push it all at once. One neat thing about this is that it let's you input your own commit message without you going through any hassle!

  55. Avatar for Michal
    Michal October 1st, 2015

    saved
    Thanks :)

  56. Avatar for Cristian Llanos
    Cristian Llanos October 18th, 2015

    Really useful, thanks :)

  57. Avatar for Wolfgang
    Wolfgang December 28th, 2016

    A simple alias that helps me to keep track of the aliases:
    aliases = config --get-regexp '^alias\\.'

  58. Avatar for Malay Ladu
    Malay Ladu January 17th, 2017

    Thanks for the great post. It's really handy. :)

  59. Avatar for berkus
    berkus March 2nd, 2017

    I'd call `git undo` actually `git restore` (an opposite of save, see GLsave/GLrestore for example).

  60. Avatar for Jordan Bradford
    Jordan Bradford July 25th, 2017

    I've been using your aliases and have created some of my own.

    I use these to list local branches only, remote branches only, or both:

    b = branch
    br = branch -r
    ba = branch -a

    This one lists changed files (I got the command from some other site) -- it will be empty if there aren't any. For scripts this is easier to handle than trying to parse the output from "git status":

    lsch = ls-files -dmo --exclude-standard

  61. Avatar for Nikolaj Jørgensen
    Nikolaj Jørgensen September 5th, 2017

    Are you aware of Git Worktree, which was added in v2.5? (released 2 years ago)

    Might be a more clean way, than the current alternative, when stash doesn't suits you 😄

    As for rebase, I do hope you're fully aware what it does and what side-effects it actually can have 🤔

  62. Avatar for Daniel Burnett
    Daniel Burnett September 6th, 2017

    I've found it's nice to let branches have descriptions longer then their names (say, commit-message-length), and having to manually edit a file for that, push it, etc. every time someone makes a new branch can be annoying.

    Enter these two aliases:

    git new-branch branch-name "Branch description here."

    That creates and checks out the new branch, writes the description to a BranchDesc.md file, then adds, commits, and pushes the file. As a bonus, it also sets the upstream for pushing while it's doing so.

    Then, to get the description of a branch:

    git desc branch-name

    This checks out the branch, cats out the BranchDesc.md file, then checks out the previous branch so you don't lose your work flow.

    Together, these are super useful when you're working on multiple branches and are like, "wait, is banner-button the branch for the banner in the header, or the footer? And what was supposed to be the color scheme again?" Without having to go pull up your notes or switch to the branch, of course.

  63. Avatar for Bryan Johns
    Bryan Johns September 6th, 2017

    Those sound like really useful aliases. Are they documented somewhere?

  64. Avatar for Daniel Burnett
    Daniel Burnett September 6th, 2017

    Now that I'm back at my computer, I can document them here :)

    Keep in mind I use Windows PowerShell, so these need to be changed a little if you're using Bash or another terminal (mostly replacing null with /dev/null and changing the parameter numbers, and perhaps Bash may be more lenient about command grouping so you don't need to use sh first), but they are as follows:

    desc = !sh -c 'git checkout $1 > null 2>&1 && echo && cat BranchDesc.md && echo && echo && git checkout - > null 2>&1' -

    new-branch = !sh -c 'git checkout -b $0 && echo $1 > BranchDesc.md && git add -A && git commit -m \"Initial BranchDesc commit.\" && git push -u origin $0'

    And of course the echos don't need to be there; I just like having some new line padding around my output, for aesthetics :)

  65. Avatar for Bryan Johns
    Bryan Johns September 6th, 2017

    Thanks... Now that I see what's happening under the hood, I'm a little curious as to how you handle the almost inevitable merge conflicts as topic/feature branches get merged in.

  66. Avatar for Daniel Burnett
    Daniel Burnett September 6th, 2017

    Honestly, I haven't been using this system very long and that hasn't come up yet XD

    My recommendation would be simply that all BranchDesc merge conflicts be resolved by keeping the description of the branch you're merging into. If there's a way to automate that... It would be useful, but not necessary xD

  67. Avatar for Douglas Pagani
    Douglas Pagani October 26th, 2017

    Very well-written, considered, and formatted post. A clean set of aliases/functions/personal utilities makes for a clean mind & cleaner workflow. In making these aliases, I'm sure you clarified your personal workflow substantially.

    A small-point: for xargs, you used the '-r' flag; I'm not entirely sure why you did this, and would suggest an alternative.

    There are a few minor problems with inclusion of -r:

    1. -r is a GNU-extension to xargs, which means it will not work for many macos users unless they import that specific binary. I'm not sure which system you're on, but either way, around half of users with this invocation will have a fatal & confusing error for this alias.
    2. Even if present on the system, -r swallows the error case for 'git branch -d', that being branches aren't provided. With -r, no output is given, and so people are likely to expect it to have been successful, including yourself even having written the alias. It may be slightly confusing, because one would think "oh but yes I did, I said `git bclean master`, but it at least shows an error when it should. (why are you running a cmd to clean merged branches if there aren't any?)
    3. There could be a problem with one of the branches, but that shouldn't make git branch -d choke. Instead, for more flexibiliity, -I{} and then {} for the `git branch -d {}` invocation would create a branch -d instance for each run, as by default, it implies xargs -L 1; run, per each line of input from grep -v, the utility following.
    4. Using the form: `xargs -I{} git branch -d {}` has a second advantage, and that's improved clarity. It's easier to more immediately tell what xargs is doing, both at first glance for those familiar, and for novices completely unfamiliar.
    5. -I is compatible across nearly all systems/flavors of xargs, so you won't run into issues with macos, linux, etc.

  68. Avatar for haacked
    haacked October 27th, 2017

    I'll be honest, I found the `xargs` part from some other alias and repurposed it here without digging into it too much. It looks like I could just remove the -r part entirely though, don't you think?

  69. Avatar for Douglas Pagani
    Douglas Pagani October 28th, 2017

    Yea, it's useless here for the reason I detailed. In fact, worse than useless, it's probably circumstantially confusing. Also, it doesn't even work on macOS flavor, so you must use Linux or at least import updated GNU-utils, but maybe no one else tested that alias. (it is specific to a select group of workflows, how often do people delete branches to where they need to automate that?)

    It's still a cool idea, though.

    Two suggestions:

    1. take out -r of xargs, replace it with -I{} ... <util> {}. This is a typical xargs construct, works across all flavors, and is adaptable to other aliases, should people get the idea to do something similar.
    2. Put in another clause for bdone, to `co` the master branch first. This is to avoid the error where git tries to parse the "* topic" output of the branch list. This would never come-up (and probably hasnt for you) if you are always checked out on master at that point, and so I'm guessing you always use bclean by way of bdone, rarely using it singularly. If you're on master, the line "* master" will get removed anyways.

    My new invocation is this:

    ` bclean = "!f() { git checkout ${1-master} && git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs -I{} git branch -d {}; }; f" `

    And so you can trust the above-written is verified: (the changes I wrote about)
    https://imgur.com/a/AxriN

  70. Avatar for haacked
    haacked November 2nd, 2017

    Thanks for the suggestions!

    What's the advantage of doing `xargs -I{} git branch -d {}` over just `xargs git branch -d`?

  71. Avatar for Douglas Pagani
    Douglas Pagani November 2nd, 2017

    Nothing, functionally. But if you're writing a blog-post for the purpose of sharing information & helping people to improve their workflows, quite a bit : )

    xargs can be weird for people unfamiliar with it; it has a strange, let alone platform-inconsistent, invocation-pattern. -I{} is the cleanest way to show what xargs is doing under the head, since one can "see" where it will get substituted, implied by the last {}, and then more-quickly understand "oh, it's running that command by passing in its input as arguments."

    -I{} is also the most likely first-extension you'd make beyond xargs' basic capability. You can insert args wherever you please by just shifting {}, and as well, you can "swallow" the input, yet run the command still. (There are a couple cases where I've wanted to do this)

    -r is pretty terrible, though. You're basically placing a try-catch in for other people who don't know it's there, and swallowing perhaps-valid errors. Wouldn't you want to know if you invoked bclean, yet there were no merged-branches?

    By default, xargs, without -I{} will just put args after -d. But -I{} allows you to place it anywhere.

    And actually, there is one functional difference that I'm 99% aware of. xargs -I{} will actually run 3 instances of the command, one "line" per argument. Without it, it will just dump all args after git branch -d. However, for git branch -d this is completely irrelevant, since multiple invocations of itself with single-args, and a single invocation with multiple-args, work the same. But for other commands, this might not be the case.

    Again, I'd argue for -I{} promoting a more clear understanding of a very murky, yet very powerful, utility.

    PS: tested, yes that is the case. BUT, only if the inputs are one-per-line.
    So:
    echo '
    one
    two
    three ' | xargs -I{} echo x. {}
    VS.
    echo one two three | xargs -I{} echo x {}

    Whether or not "words" of input that xargs received are on lines DOESNT matter for xargs echo x. (both produce x. one two three), but for xargs -I{}, new "echo"s will be run for each line. That's because for -I, -L1 is implied.

    Again, getting into the bones of it, but the takeaway is that -I{} provides clarity in an area where it is much-needed.