A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. title: GitHub Flow Like a Pro with these 13 Git Aliases
  2. url: http://haacked.com/archive/2014/07/28/github-flow-aliases/
  3. hash_url: d69a335a5162c7bf71aa35b2e7ec73a7
  4. <p><a href="http://scottchacon.com/2011/08/31/github-flow.html">GitHub Flow</a> is a Git work flow with a simple branching model. The following diagram of this flow is from Zach Holman's talk on <a href="http://zachholman.com/talk/how-github-uses-github-to-build-github/">How GitHub uses GitHub to build GitHub</a>.</p>
  5. <p><img src="https://cloud.githubusercontent.com/assets/19977/3638839/8343b14c-1063-11e4-8369-537f7d62be06.png" alt="github-branching"/></p>
  6. <p>You are now a master of GitHub flow. Drop the mic and go release some software!</p>
  7. <p>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 <code>master</code> 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 <a href="https://github.com/blog/1124-how-we-use-pull-requests-to-build-github">necessarily the completed work</a>.</p>
  8. <p>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.</p>
  9. <p>It's simple, but powerful.</p>
  10. <p>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.</p>
  11. <h2>Intro to Git Aliases</h2>
  12. <p>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:</p>
  13. <div class="highlight"><pre><code class="language-bash" data-lang="bash">git config --global alias.co checkout
  14. </code></pre></div>
  15. <p>This sets <code>co</code> as an alias for <code>checkout</code>. If you open up your <code>.gitconfig</code> file, you can see this in a section named <code>alias</code>.</p>
  16. <p>With this alias, you can checkout a branch by using <code>git co some-branch</code> instead of <code>git checkout some-branch</code>. Since I often edit aliases by hand, I have one that opens the <code>gitconfig</code> file with my default editor.</p>
  17. <p>These sort of simple aliases only begin to scratch the surface.</p>
  18. <h2>GitHub Flow Aliases</h2>
  19. <h3>Get my working directory up to date.</h3>
  20. <p>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:</p>
  21. <div class="highlight"><pre><code class="language-bash" data-lang="bash">git pull --rebase --prune
  22. git submodule update --init --recursive
  23. </code></pre></div>
  24. <p>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 <code>--prune</code> option removes remote-tracking branches that no longer exist on the remote.</p>
  25. <p>This combination is so common, I've created an alias <code>up</code> for this.</p>
  26. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">up</span> <span class="o">=</span> <span class="s">!git pull --rebase --prune $@ &amp;&amp; git submodule update --init --recursive</span>
  27. </code></pre></div>
  28. <p>Note that I'm combining two git commands together. I can use the <code>!</code> prefix to execute everything after it in the shell. This is why I needed to use the full git commands. Using the <code>!</code> prefix allows me to use <em>any</em> command and not just git commands in the alias.</p>
  29. <h3>Starting new work</h3>
  30. <p>At this point, I can start some new work. All new work starts in a branch so I would typically use <code>git checkout -b new-branch</code>. However I alias this to <code>cob</code> to build upon <code>co</code>.</p>
  31. <p>Note that this simple alias is expanded in place. So to create a branch named "emoji-completion" I simply type <code>git cob emoji-completion</code> which expands to <code>git checkout -b emoji-completion</code>.</p>
  32. <p>With this new branch, I can start writing the crazy codes. As I go along, I try and commit regularly with my <code>cm</code> alias.</p>
  33. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">cm</span> <span class="o">=</span> <span class="s">!git add -A &amp;&amp; git commit -m</span>
  34. </code></pre></div>
  35. <p>For example, <code>git cm "Making stuff work"</code>. This adds all changes including untracked files to the index and then creates a commit with the message "Making Stuff Work".</p>
  36. <p>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.</p>
  37. <p><code>git save</code> or <code>git wip</code>. 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.</p>
  38. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">save</span> <span class="o">=</span> <span class="s">!git add -A &amp;&amp; git commit -m 'SAVEPOINT'</span>
  39. <span class="s"> wip = commit -am "WIP" </span>
  40. </code></pre></div>
  41. <p>When I return to work, I'll just use <code>git undo</code> which resets the previous commit, but keeps all the changes from that commit in the working directory.</p>
  42. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">undo</span> <span class="o">=</span> <span class="s">reset HEAD~1 --mixed</span>
  43. </code></pre></div>
  44. <p>Or, if I merely need to modify the previous commit, I'll use <code>git amend</code></p>
  45. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">amend</span> <span class="o">=</span> <span class="s">commit -a --amend</span>
  46. </code></pre></div>
  47. <p>The <code>-a</code> adds any modifications and deletions of existing files to the commit but ignores brand new files. The <code>--amend</code> launches your default commit editor (Notepad in my case) and lets you change the commit message of the most recent commit.</p>
  48. <h3>A proper reset</h3>
  49. <p>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.</p>
  50. <p>In an attempt to be helpful, people might recommend: <code>git reset HEAD --hard</code>.</p>
  51. <p>Slap those people in the face. It's a bad idea. Don't do it!</p>
  52. <p>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.</p>
  53. <p>Too bad. If you reset work that you <em>never committed</em> it is gone for good. Hence, the <code>wipe</code> alias.</p>
  54. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">wipe</span> <span class="o">=</span> <span class="s">!git add -A &amp;&amp; git commit -qm 'WIPE SAVEPOINT' &amp;&amp; git reset HEAD~1 --hard</span>
  55. </code></pre></div>
  56. <p>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 <code>git reflog</code> 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.</p>
  57. <h3>Completing the pull request</h3>
  58. <p>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 <a href="https://github.com/blog/1335-tidying-up-after-pull-requests">tidy up and delete the branch via the Web UI</a>. 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, <code>git bdone</code>.</p>
  59. <p>This alias does the following.</p>
  60. <ol>
  61. <li>Switches to master (though you can specify a different default branch)</li>
  62. <li>Runs <code>git up</code> to bring master up to speed with the origin</li>
  63. <li>Deletes all branches already merged into master using another alias, <code>git bclean</code></li>
  64. </ol>
  65. <p>It's quite powerful and useful and demonstrates some advanced concepts of git aliases. But first, let me show <code>git bclean</code>. This alias is meant to be run from your master (or default) branch and does the cleanup of merged branches.</p>
  66. <div class="highlight"><pre><code class="language-ini" data-lang="ini"><span class="na">bclean</span> <span class="o">=</span> <span class="s">"!f() { git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs -r git branch -d; }; f"</span>
  67. </code></pre></div>
  68. <p>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 <code>!f() { /* git operations */; }; f</code> We define a function named <code>f</code> that encapsulates some git operations, and then we invoke the function at the very end.</p>
  69. <p>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 <code>$1</code>. 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 (<code>$0</code> returns the whole script) followed by a dash and then the default value.</p>
  70. <p>Thus when you type <code>git bclean</code> the expression <code>${1-master}</code> evaluates to <code>master</code> because no argument was provided. But if you're working on a GitHub pages repository, you'll probably want to call <code>git bclean gh-pages</code> in which case the expression <code>${1-master}</code> evaluates to <code>gh-pages</code> as that's the first argument to the the alias.</p>
  71. <p>Let's break down this alias into pieces to understand it.</p>
  72. <p><code>git branch --merged ${1-master}</code> 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 <code>grep -v "${1-master}"</code> command. <a href="http://www.gnu.org/software/grep/manual/grep.html">Grep</a> prints out lines matching the pattern. The <code>-v</code> flag inverts the match. So this will list all merged branches that are not <code>master</code> itself. Finally this gets piped into <code>xargs</code> which takes the standard input and executes the <code>git branch -d</code> line for each line in the standard input which is piped in from the previous command.</p>
  73. <p>In other words, it deletes every branch that's been merged into <code>master</code> except <code>master</code>. I love how we can compose these commands together.</p>
  74. <p>With <code>bclean</code> in place, I can compose my git aliases together and write <code>git bdone</code>.</p>
  75. <div class="highlight"><pre><code class="language-ini" data-lang="ini"> <span class="na">bdone</span> <span class="o">=</span> <span class="s">"!f() { git checkout ${1-master} &amp;&amp; git up &amp;&amp; git bclean ${1-master}; }; f"</span>
  76. </code></pre></div>
  77. <p>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.</p>
  78. <h2>The List</h2>
  79. <p>Here's a list of all the aliases together for your convenience.</p>
  80. <div class="highlight"><pre><code class="language-ini" data-lang="ini"><span class="k">[alias]</span>
  81. <span class="na">co</span> <span class="o">=</span> <span class="s">checkout</span>
  82. <span class="s"> ec = config --global -e</span>
  83. <span class="s"> up = !git pull --rebase --prune $@ &amp;&amp; git submodule update --init --recursive</span>
  84. <span class="s"> cob = checkout -b</span>
  85. <span class="s"> cm = !git add -A &amp;&amp; git commit -m</span>
  86. <span class="s"> save = !git add -A &amp;&amp; git commit -m 'SAVEPOINT'</span>
  87. <span class="s"> wip = !git add -u &amp;&amp; git commit -m "WIP" </span>
  88. <span class="s"> undo = reset HEAD~1 --mixed</span>
  89. <span class="s"> amend = commit -a --amend</span>
  90. <span class="s"> wipe = !git add -A &amp;&amp; git commit -qm 'WIPE SAVEPOINT' &amp;&amp; git reset HEAD~1 --hard</span>
  91. <span class="s"> bclean = "!f() { git branch --merged ${1-master} | grep -v " ${1-master}$" | xargs -r git branch -d; }; f"</span>
  92. <span class="s"> bdone = "!f() { git checkout ${1-master} &amp;&amp; git up &amp;&amp; git bclean ${1-master}; }; f"</span>
  93. </code></pre></div>
  94. <h2>Credits and more reading</h2>
  95. <p>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.</p>
  96. <p><em>PS: If you liked this post <a href="https://twitter.com/haacked">follow me on Twitter</a> for interesting links and my wild observations about pointless drivel</em></p>
  97. <p><em>PPS</em>: 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 <a href="https://windows.github.com/">GitHub for Windows</a> and use the GitHub Shell, these all work fine.</p>