git-smart-ng 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,304 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
5
+ <title>smart-pull.rb</title>
6
+ <link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
7
+ </head>
8
+ <body>
9
+ <div id='container'>
10
+ <div id="background"></div>
11
+ <div id="jump_to">
12
+ Jump To &hellip;
13
+ <div id="jump_wrapper">
14
+ <div id="jump_page">
15
+ <a class="source" href="smart-log.html">smart-log.rb</a>
16
+ <a class="source" href="smart-merge.html">smart-merge.rb</a>
17
+ <a class="source" href="smart-pull.html">smart-pull.rb</a>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ <table cellspacing=0 cellpadding=0>
22
+ <thead>
23
+ <tr>
24
+ <th class=docs><h1>smart-pull.rb</h1></th>
25
+ <th class=code></th>
26
+ </tr>
27
+ </thead>
28
+ <tbody>
29
+ <tr id='section-1'>
30
+ <td class=docs>
31
+ <div class="octowrap">
32
+ <a class="octothorpe" href="#section-1">#</a>
33
+ </div>
34
+ <p>Calling <code>git smart-pull</code> will fetch remote tracked changes
35
+ and reapply your work on top of it. It&rsquo;s like a much, much
36
+ smarter version of <code>git pull --rebase</code>.</p>
37
+
38
+ <p>For some background as to why this is needed, see <a href="http://notes.envato.com/developers/rebasing-merge-commits-in-git/">my blog
39
+ post about the perils of rebasing merge commits</a></p>
40
+
41
+ <p>This is how it works:</p>
42
+ </td>
43
+ <td class=code>
44
+ <div class='highlight'><pre><span class="no">GitSmart</span><span class="o">.</span><span class="n">register</span> <span class="s1">&#39;smart-pull&#39;</span> <span class="k">do</span> <span class="o">|</span><span class="n">repo</span><span class="p">,</span> <span class="n">args</span><span class="o">|</span></pre></div>
45
+ </td>
46
+ </tr>
47
+ <tr id='section-2'>
48
+ <td class=docs>
49
+ <div class="octowrap">
50
+ <a class="octothorpe" href="#section-2">#</a>
51
+ </div>
52
+ <p>Let&rsquo;s begin!</p>
53
+ </td>
54
+ <td class=code>
55
+ <div class='highlight'><pre> <span class="n">branch</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">current_branch</span>
56
+ <span class="n">start</span> <span class="s2">&quot;Starting: smart-pull on branch &#39;</span><span class="si">#{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#39;&quot;</span></pre></div>
57
+ </td>
58
+ </tr>
59
+ <tr id='section-3'>
60
+ <td class=docs>
61
+ <div class="octowrap">
62
+ <a class="octothorpe" href="#section-3">#</a>
63
+ </div>
64
+ <p>Let&rsquo;s not have any arguments, fellas.</p>
65
+ </td>
66
+ <td class=code>
67
+ <div class='highlight'><pre> <span class="nb">warn</span> <span class="s2">&quot;Ignoring arguments: </span><span class="si">#{</span><span class="n">args</span><span class="o">.</span><span class="n">inspect</span><span class="si">}</span><span class="s2">&quot;</span> <span class="k">if</span> <span class="o">!</span><span class="n">args</span><span class="o">.</span><span class="n">empty?</span></pre></div>
68
+ </td>
69
+ </tr>
70
+ <tr id='section-4'>
71
+ <td class=docs>
72
+ <div class="octowrap">
73
+ <a class="octothorpe" href="#section-4">#</a>
74
+ </div>
75
+ <p>Try grabbing the tracking remote from the config. If it doesn&rsquo;t exist,
76
+ notify the user and default to &lsquo;origin&rsquo;</p>
77
+ </td>
78
+ <td class=code>
79
+ <div class='highlight'><pre> <span class="n">tracking_remote</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">tracking_remote</span> <span class="o">||</span>
80
+ <span class="n">note</span><span class="p">(</span><span class="s2">&quot;No tracking remote configured, assuming &#39;origin&#39;&quot;</span><span class="p">)</span> <span class="o">||</span>
81
+ <span class="s1">&#39;origin&#39;</span></pre></div>
82
+ </td>
83
+ </tr>
84
+ <tr id='section-5'>
85
+ <td class=docs>
86
+ <div class="octowrap">
87
+ <a class="octothorpe" href="#section-5">#</a>
88
+ </div>
89
+ <p> Fetch the remote. This pulls down all new commits from the server, not just our branch,
90
+ but generally that&rsquo;s a good thing. This is the only communication we need to do with the server.</p>
91
+ </td>
92
+ <td class=code>
93
+ <div class='highlight'><pre> <span class="n">repo</span><span class="o">.</span><span class="n">fetch!</span><span class="p">(</span><span class="n">tracking_remote</span><span class="p">)</span></pre></div>
94
+ </td>
95
+ </tr>
96
+ <tr id='section-6'>
97
+ <td class=docs>
98
+ <div class="octowrap">
99
+ <a class="octothorpe" href="#section-6">#</a>
100
+ </div>
101
+ <p>Try grabbing the tracking branch from the config. If it doesn&rsquo;t exist,
102
+ notify the user and choose the branch of the same name</p>
103
+ </td>
104
+ <td class=code>
105
+ <div class='highlight'><pre> <span class="n">tracking_branch</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">tracking_branch</span> <span class="o">||</span>
106
+ <span class="n">note</span><span class="p">(</span><span class="s2">&quot;No tracking branch configured, assuming &#39;</span><span class="si">#{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#39;&quot;</span><span class="p">)</span> <span class="o">||</span>
107
+ <span class="n">branch</span></pre></div>
108
+ </td>
109
+ </tr>
110
+ <tr id='section-7'>
111
+ <td class=docs>
112
+ <div class="octowrap">
113
+ <a class="octothorpe" href="#section-7">#</a>
114
+ </div>
115
+ <p>Check the specified upstream branch exists. Fail if it doesn&rsquo;t.</p>
116
+ </td>
117
+ <td class=code>
118
+ <div class='highlight'><pre> <span class="n">upstream_branch</span> <span class="o">=</span> <span class="s2">&quot;</span><span class="si">#{</span><span class="n">tracking_remote</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">tracking_branch</span><span class="si">}</span><span class="s2">&quot;</span>
119
+ <span class="n">failure</span><span class="p">(</span><span class="s2">&quot;Upstream branch &#39;</span><span class="si">#{</span><span class="n">upstream_branch</span><span class="si">}</span><span class="s2">&#39; doesn&#39;t exist!&quot;</span><span class="p">)</span> <span class="k">if</span> <span class="o">!</span><span class="n">repo</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="n">upstream_branch</span><span class="p">)</span></pre></div>
120
+ </td>
121
+ </tr>
122
+ <tr id='section-8'>
123
+ <td class=docs>
124
+ <div class="octowrap">
125
+ <a class="octothorpe" href="#section-8">#</a>
126
+ </div>
127
+ <p>Grab the SHAs of the commits we&rsquo;ll be working with.</p>
128
+ </td>
129
+ <td class=code>
130
+ <div class='highlight'><pre> <span class="n">head</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">sha</span><span class="p">(</span><span class="s1">&#39;HEAD&#39;</span><span class="p">)</span>
131
+ <span class="n">remote</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">sha</span><span class="p">(</span><span class="n">upstream_branch</span><span class="p">)</span></pre></div>
132
+ </td>
133
+ </tr>
134
+ <tr id='section-9'>
135
+ <td class=docs>
136
+ <div class="octowrap">
137
+ <a class="octothorpe" href="#section-9">#</a>
138
+ </div>
139
+ <p>If both HEAD and our upstream_branch resolve to the same SHA, there&rsquo;s nothing to do!</p>
140
+ </td>
141
+ <td class=code>
142
+ <div class='highlight'><pre> <span class="k">if</span> <span class="n">head</span> <span class="o">==</span> <span class="n">remote</span>
143
+ <span class="nb">puts</span> <span class="s2">&quot;Neither your local branch &#39;</span><span class="si">#{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#39;, nor the remote branch &#39;</span><span class="si">#{</span><span class="n">upstream_branch</span><span class="si">}</span><span class="s2">&#39; have moved on.&quot;</span>
144
+ <span class="n">success</span> <span class="s2">&quot;Already up-to-date&quot;</span>
145
+ <span class="k">else</span></pre></div>
146
+ </td>
147
+ </tr>
148
+ <tr id='section-10'>
149
+ <td class=docs>
150
+ <div class="octowrap">
151
+ <a class="octothorpe" href="#section-10">#</a>
152
+ </div>
153
+ <p>Find out where the two branches diverged using merge-base. It&rsquo;s what git
154
+ uses internally.</p>
155
+ </td>
156
+ <td class=code>
157
+ <div class='highlight'><pre> <span class="n">merge_base</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">merge_base</span><span class="p">(</span><span class="n">head</span><span class="p">,</span> <span class="n">remote</span><span class="p">)</span></pre></div>
158
+ </td>
159
+ </tr>
160
+ <tr id='section-11'>
161
+ <td class=docs>
162
+ <div class="octowrap">
163
+ <a class="octothorpe" href="#section-11">#</a>
164
+ </div>
165
+ <p>Report how many commits are new locally, since that&rsquo;s useful information.</p>
166
+ </td>
167
+ <td class=code>
168
+ <div class='highlight'><pre> <span class="n">new_commits_locally</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">rev_list</span><span class="p">(</span><span class="n">merge_base</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span>
169
+ <span class="k">if</span> <span class="o">!</span><span class="n">new_commits_locally</span><span class="o">.</span><span class="n">empty?</span>
170
+ <span class="n">note</span> <span class="s2">&quot;You have </span><span class="si">#{</span><span class="n">new_commits_locally</span><span class="o">.</span><span class="n">length</span><span class="si">}</span><span class="s2"> new commit</span><span class="si">#{</span><span class="s1">&#39;s&#39;</span> <span class="k">if</span> <span class="n">new_commits_locally</span><span class="o">.</span><span class="n">length</span> <span class="o">!=</span> <span class="mi">1</span><span class="si">}</span><span class="s2"> on &#39;</span><span class="si">#{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#39;.&quot;</span>
171
+ <span class="k">end</span></pre></div>
172
+ </td>
173
+ </tr>
174
+ <tr id='section-12'>
175
+ <td class=docs>
176
+ <div class="octowrap">
177
+ <a class="octothorpe" href="#section-12">#</a>
178
+ </div>
179
+ <p>By comparing the merge_base to both HEAD and the remote, we can
180
+ determine whether both or only one have moved on.
181
+ If the remote hasn&rsquo;t changed, we&rsquo;re already up to date, so there&rsquo;s nothing
182
+ to pull.</p>
183
+ </td>
184
+ <td class=code>
185
+ <div class='highlight'><pre> <span class="k">if</span> <span class="n">merge_base</span> <span class="o">==</span> <span class="n">remote</span>
186
+ <span class="nb">puts</span> <span class="s2">&quot;Remote branch &#39;</span><span class="si">#{</span><span class="n">upstream_branch</span><span class="si">}</span><span class="s2">&#39; has not moved on.&quot;</span>
187
+ <span class="n">success</span> <span class="s2">&quot;Already up-to-date&quot;</span>
188
+ <span class="k">else</span></pre></div>
189
+ </td>
190
+ </tr>
191
+ <tr id='section-13'>
192
+ <td class=docs>
193
+ <div class="octowrap">
194
+ <a class="octothorpe" href="#section-13">#</a>
195
+ </div>
196
+ <p>If the remote <em>has</em> moved on, we actually have some work to do:</p>
197
+
198
+ <p>First, report how many commits are new on remote. Because that&rsquo;s useful information, too.</p>
199
+ </td>
200
+ <td class=code>
201
+ <div class='highlight'><pre> <span class="n">new_commits_on_remote</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">rev_list</span><span class="p">(</span><span class="n">merge_base</span><span class="p">,</span> <span class="n">remote</span><span class="p">)</span>
202
+ <span class="n">is_are</span><span class="p">,</span> <span class="n">s_or_not</span> <span class="o">=</span> <span class="p">(</span><span class="n">new_commits_on_remote</span><span class="o">.</span><span class="n">length</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">?</span> <span class="o">[</span><span class="s1">&#39;is&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="o">]</span> <span class="p">:</span> <span class="o">[</span><span class="s1">&#39;are&#39;</span><span class="p">,</span> <span class="s1">&#39;s&#39;</span><span class="o">]</span>
203
+ <span class="n">note</span> <span class="s2">&quot;There </span><span class="si">#{</span><span class="n">is_are</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">new_commits_on_remote</span><span class="o">.</span><span class="n">length</span><span class="si">}</span><span class="s2"> new commit</span><span class="si">#{</span><span class="n">s_or_not</span><span class="si">}</span><span class="s2"> on &#39;</span><span class="si">#{</span><span class="n">upstream_branch</span><span class="si">}</span><span class="s2">&#39;.&quot;</span></pre></div>
204
+ </td>
205
+ </tr>
206
+ <tr id='section-14'>
207
+ <td class=docs>
208
+ <div class="octowrap">
209
+ <a class="octothorpe" href="#section-14">#</a>
210
+ </div>
211
+ <p>Next, detect if there are local changes and stash them.</p>
212
+ </td>
213
+ <td class=code>
214
+ <div class='highlight'><pre> <span class="n">stash_required</span> <span class="o">=</span> <span class="n">repo</span><span class="o">.</span><span class="n">dirty?</span>
215
+ <span class="k">if</span> <span class="n">stash_required</span>
216
+ <span class="n">note</span> <span class="s2">&quot;Working directory dirty. Stashing...&quot;</span>
217
+ <span class="n">repo</span><span class="o">.</span><span class="n">stash!</span>
218
+ <span class="k">end</span>
219
+
220
+ <span class="n">success_messages</span> <span class="o">=</span> <span class="o">[]</span></pre></div>
221
+ </td>
222
+ </tr>
223
+ <tr id='section-15'>
224
+ <td class=docs>
225
+ <div class="octowrap">
226
+ <a class="octothorpe" href="#section-15">#</a>
227
+ </div>
228
+ <p>Then, bring the local branch up to date.</p>
229
+
230
+ <p>If our local branch hasn&rsquo;t moved on, that&rsquo;s easy &ndash; we just need to fast-forward.</p>
231
+ </td>
232
+ <td class=code>
233
+ <div class='highlight'><pre> <span class="k">if</span> <span class="n">merge_base</span> <span class="o">==</span> <span class="n">head</span>
234
+ <span class="nb">puts</span> <span class="s2">&quot;Local branch &#39;</span><span class="si">#{</span><span class="n">branch</span><span class="si">}</span><span class="s2">&#39; has not moved on. Fast-forwarding...&quot;</span>
235
+ <span class="n">repo</span><span class="o">.</span><span class="n">fast_forward!</span><span class="p">(</span><span class="n">upstream_branch</span><span class="p">)</span>
236
+ <span class="n">success_messages</span> <span class="o">&lt;&lt;</span> <span class="s2">&quot;Fast forwarded from </span><span class="si">#{</span><span class="n">head</span><span class="o">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">7</span><span class="o">]</span><span class="si">}</span><span class="s2"> to </span><span class="si">#{</span><span class="n">remote</span><span class="o">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">7</span><span class="o">]</span><span class="si">}</span><span class="s2">&quot;</span>
237
+ <span class="k">else</span></pre></div>
238
+ </td>
239
+ </tr>
240
+ <tr id='section-16'>
241
+ <td class=docs>
242
+ <div class="octowrap">
243
+ <a class="octothorpe" href="#section-16">#</a>
244
+ </div>
245
+ <p>If our local branch has new commits, we need to rebase them on top of master.</p>
246
+
247
+ <p>When we rebase, we use <code>git rebase -p</code>, which attempts to recreate merges
248
+ instead of ignoring them. For a description as to why, see my <a href="http://notes.envato.com/developers/rebasing-merge-commits-in-git/">blog post</a>.</p>
249
+ </td>
250
+ <td class=code>
251
+ <div class='highlight'><pre> <span class="n">note</span> <span class="s2">&quot;Both local and remote branches have moved on. Branch &#39;master&#39; needs to be rebased onto &#39;origin/master&#39;&quot;</span>
252
+ <span class="n">repo</span><span class="o">.</span><span class="n">rebase_preserving_merges!</span><span class="p">(</span><span class="n">upstream_branch</span><span class="p">)</span>
253
+ <span class="n">success_messages</span> <span class="o">&lt;&lt;</span> <span class="s2">&quot;HEAD moved from </span><span class="si">#{</span><span class="n">head</span><span class="o">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">7</span><span class="o">]</span><span class="si">}</span><span class="s2"> to </span><span class="si">#{</span><span class="n">repo</span><span class="o">.</span><span class="n">sha</span><span class="p">(</span><span class="s1">&#39;HEAD&#39;</span><span class="p">)</span><span class="o">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">7</span><span class="o">]</span><span class="si">}</span><span class="s2">.&quot;</span>
254
+ <span class="k">end</span></pre></div>
255
+ </td>
256
+ </tr>
257
+ <tr id='section-17'>
258
+ <td class=docs>
259
+ <div class="octowrap">
260
+ <a class="octothorpe" href="#section-17">#</a>
261
+ </div>
262
+ <p>If we stashed before, pop now.</p>
263
+ </td>
264
+ <td class=code>
265
+ <div class='highlight'><pre> <span class="k">if</span> <span class="n">stash_required</span>
266
+ <span class="n">note</span> <span class="s2">&quot;Reapplying local changes...&quot;</span>
267
+ <span class="n">repo</span><span class="o">.</span><span class="n">stash_pop!</span>
268
+ <span class="k">end</span></pre></div>
269
+ </td>
270
+ </tr>
271
+ <tr id='section-18'>
272
+ <td class=docs>
273
+ <div class="octowrap">
274
+ <a class="octothorpe" href="#section-18">#</a>
275
+ </div>
276
+ <p>Display a nice completion message in large, friendly letters.</p>
277
+ </td>
278
+ <td class=code>
279
+ <div class='highlight'><pre> <span class="n">success</span> <span class="o">[</span><span class="s2">&quot;All good.&quot;</span><span class="p">,</span> <span class="o">*</span><span class="n">success_messages</span><span class="o">].</span><span class="n">join</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="p">)</span>
280
+ <span class="k">end</span></pre></div>
281
+ </td>
282
+ </tr>
283
+ <tr id='section-19'>
284
+ <td class=docs>
285
+ <div class="octowrap">
286
+ <a class="octothorpe" href="#section-19">#</a>
287
+ </div>
288
+ <p>Still to do:</p>
289
+
290
+ <ul>
291
+ <li>Ensure ORIG_HEAD is correctly set at the end of each run.</li>
292
+ <li>If the rebase fails, and you&rsquo;ve done a stash, remind the user to unstash</li>
293
+ </ul>
294
+
295
+
296
+ </td>
297
+ <td class=code>
298
+ <div class='highlight'><pre> <span class="k">end</span>
299
+ <span class="k">end</span></pre></div>
300
+ </td>
301
+ </tr>
302
+ </table>
303
+ </div>
304
+ </body>
@@ -0,0 +1,9 @@
1
+ #This is a super simple alias for the most badass of log outputs that git
2
+ #offers. Uses git log --graph under the hood.
3
+ #
4
+ #Thanks to [@ben_h](http://twitter.com/ben_h) for this one!
5
+ GitSmart.register 'smart-log' do |repo, args|
6
+ #Super simple, passes the args through to git log, but
7
+ #ratchets up the badassness quotient.
8
+ repo.log_to_shell('--pretty=format:%C(yellow)%h%Cblue%d%Creset %s %C(white) %an, %ar%Creset', '--graph', *args)
9
+ end
@@ -0,0 +1,63 @@
1
+ #Calling `git smart-merge branchname` will, quite simply, perform a
2
+ #non-fast-forward merge wrapped in a stash push/pop, if that's required.
3
+ #With some helpful extra output.
4
+ GitSmart.register 'smart-merge' do |repo, args|
5
+ #Let's begin!
6
+ current_branch = repo.current_branch
7
+ start "Starting: smart-merge on branch '#{current_branch}'"
8
+
9
+ #Grab the merge_target the user specified
10
+ merge_target = args.shift
11
+ failure "Usage: git smart-merge ref" if !merge_target
12
+
13
+ #Make sure git can resolve the reference to the merge_target
14
+ merge_sha = repo.sha(merge_target)
15
+ failure "Branch to merge '#{merge_target}' not recognised by git!" if !merge_sha
16
+
17
+ #If the SHA of HEAD and the merge_target are the same, we're trying to merge
18
+ #the same commit with itself. Which is madness!
19
+ head = repo.sha('HEAD')
20
+ if merge_sha == head
21
+ note "Branch '#{merge_target}' has no new commits. Nothing to merge in."
22
+ success 'Already up-to-date.'
23
+ else
24
+ #Determine the merge-base of the two commits, so we can report some useful output
25
+ #about how many new commits have been added.
26
+ merge_base = repo.merge_base(head, merge_sha)
27
+
28
+ #Report the number of commits on merge_target we're about to merge in.
29
+ new_commits_on_merge_target = repo.rev_list(merge_base, merge_target)
30
+ puts "Branch '#{merge_target}' has diverged by #{new_commits_on_merge_target.length} commit#{'s' if new_commits_on_merge_target.length != 1}. Merging in."
31
+
32
+ #Determine if our branch has moved on.
33
+ if head == merge_base
34
+ #Note: Even though we _can_ fast-forward here, it's a really bad idea since
35
+ #it results in the disappearance of the branch in history. For a good discussion
36
+ #on this topic, see this [StackOverflow question](http://stackoverflow.com/questions/2850369/why-uses-git-fast-forward-merging-per-default).
37
+ note "Branch '#{current_branch}' has not moved on since '#{merge_target}' diverged. Running with --no-ff anyway, since a fast-forward is unexpected behaviour."
38
+ else
39
+ #Report how many commits on our branch since merge_target diverged.
40
+ new_commits_on_branch = repo.rev_list(merge_base, head)
41
+ puts "Branch '#{current_branch}' has #{new_commits_on_branch.length} new commit#{'s' if new_commits_on_merge_target.length != 1} since '#{merge_target}' diverged."
42
+ end
43
+
44
+ #Before we merge, detect if there are local changes and stash them.
45
+ stash_required = repo.dirty?
46
+ if stash_required
47
+ note "Working directory dirty. Stashing..."
48
+ repo.stash!
49
+ end
50
+
51
+ #Perform the merge, using --no-ff.
52
+ repo.merge_no_ff!(merge_target)
53
+
54
+ #If we stashed before, pop now.
55
+ if stash_required
56
+ note "Reapplying local changes..."
57
+ repo.stash_pop!
58
+ end
59
+
60
+ #Display a nice completion message in large, friendly letters.
61
+ success "All good. Created merge commit #{repo.sha('HEAD')[0,7]}."
62
+ end
63
+ end
@@ -0,0 +1,117 @@
1
+ #Calling `git smart-pull` will fetch remote tracked changes
2
+ #and reapply your work on top of it. It's like a much, much
3
+ #smarter version of `git pull --rebase`.
4
+ #
5
+ #For some background as to why this is needed, see [my blog
6
+ #post about the perils of rebasing merge commits](http://notes.envato.com/developers/rebasing-merge-commits-in-git/)
7
+ #
8
+ #This is how it works:
9
+
10
+ GitSmart.register 'smart-pull' do |repo, args|
11
+ #Let's begin!
12
+ branch = repo.current_branch
13
+ start "Starting: smart-pull on branch '#{branch}'"
14
+
15
+ #Let's not have any arguments, fellas.
16
+ warn "Ignoring arguments: #{args.inspect}" if !args.empty?
17
+
18
+ #Try grabbing the tracking remote from the config. If it doesn't exist,
19
+ #notify the user and default to 'origin'
20
+ tracking_remote = repo.tracking_remote ||
21
+ note("No tracking remote configured, assuming 'origin'") ||
22
+ 'origin'
23
+
24
+ # Fetch the remote. This pulls down all new commits from the server, not just our branch,
25
+ # but generally that's a good thing. This is the only communication we need to do with the server.
26
+ repo.fetch!(tracking_remote)
27
+
28
+ #Try grabbing the tracking branch from the config. If it doesn't exist,
29
+ #notify the user and choose the branch of the same name
30
+ tracking_branch = repo.tracking_branch ||
31
+ note("No tracking branch configured, assuming '#{branch}'") ||
32
+ branch
33
+
34
+ #Check the specified upstream branch exists. Fail if it doesn't.
35
+ upstream_branch = "#{tracking_remote}/#{tracking_branch}"
36
+ failure("Upstream branch '#{upstream_branch}' doesn't exist!") if !repo.exists?(upstream_branch)
37
+
38
+ #Grab the SHAs of the commits we'll be working with.
39
+ head = repo.sha('HEAD')
40
+ remote = repo.sha(upstream_branch)
41
+
42
+ #If both HEAD and our upstream_branch resolve to the same SHA, there's nothing to do!
43
+ if head == remote
44
+ puts "Neither your local branch '#{branch}', nor the remote branch '#{upstream_branch}' have moved on."
45
+ success "Already up-to-date"
46
+ else
47
+ #Find out where the two branches diverged using merge-base. It's what git
48
+ #uses internally.
49
+ merge_base = repo.merge_base(head, remote)
50
+
51
+ #Report how many commits are new locally, since that's useful information.
52
+ new_commits_locally = repo.rev_list(merge_base, head)
53
+ if !new_commits_locally.empty?
54
+ note "You have #{new_commits_locally.length} new commit#{'s' if new_commits_locally.length != 1} on '#{branch}'."
55
+ end
56
+
57
+ #By comparing the merge_base to both HEAD and the remote, we can
58
+ #determine whether both or only one have moved on.
59
+ #If the remote hasn't changed, we're already up to date, so there's nothing
60
+ #to pull.
61
+ if merge_base == remote
62
+ puts "Remote branch '#{upstream_branch}' has not moved on."
63
+ success "Already up-to-date"
64
+ else
65
+ #If the remote _has_ moved on, we actually have some work to do:
66
+ #
67
+ #First, report how many commits are new on remote. Because that's useful information, too.
68
+ new_commits_on_remote = repo.rev_list(merge_base, remote)
69
+ is_are, s_or_not = (new_commits_on_remote.length == 1) ? ['is', ''] : ['are', 's']
70
+ note "There #{is_are} #{new_commits_on_remote.length} new commit#{s_or_not} on '#{upstream_branch}'."
71
+
72
+ #Next, detect if there are local changes and stash them.
73
+ stash_required = repo.dirty?
74
+ if stash_required
75
+ note "Working directory dirty. Stashing..."
76
+ repo.stash!
77
+ end
78
+
79
+ success_messages = []
80
+
81
+ #Then, bring the local branch up to date.
82
+ #
83
+ #If our local branch hasn't moved on, that's easy - we just need to fast-forward.
84
+ if merge_base == head
85
+ puts "Local branch '#{branch}' has not moved on. Fast-forwarding..."
86
+ repo.fast_forward!(upstream_branch)
87
+ success_messages << "Fast forwarded from #{head[0,7]} to #{remote[0,7]}"
88
+ else
89
+ #If our local branch has new commits, we need to rebase them on top of master.
90
+ #
91
+ #When we rebase, we use `git rebase -p`, which attempts to recreate merges
92
+ #instead of ignoring them. For a description as to why, see my [blog post](http://notes.envato.com/developers/rebasing-merge-commits-in-git/).
93
+ note "Both local and remote branches have moved on. Branch 'master' needs to be rebased onto 'origin/master'"
94
+ repo.rebase_preserving_merges!(upstream_branch)
95
+ success_messages << "HEAD moved from #{head[0,7]} to #{repo.sha('HEAD')[0,7]}."
96
+ end
97
+
98
+ #If we stashed before, pop now.
99
+ if stash_required
100
+ note "Reapplying local changes..."
101
+ repo.stash_pop!
102
+ end
103
+
104
+ #Use smart-log to show the new commits.
105
+ GitSmart.run('smart-log', ["#{merge_base}..#{upstream_branch}"])
106
+
107
+ #Display a nice completion message in large, friendly letters.
108
+ success ["All good.", *success_messages].join(" ")
109
+ end
110
+
111
+ #Still to do:
112
+ #
113
+ #* Ensure ORIG_HEAD is correctly set at the end of each run.
114
+ #* If the rebase fails, and you've done a stash, remind the user to unstash
115
+
116
+ end
117
+ end
@@ -0,0 +1,9 @@
1
+ class Array
2
+ def group_by(&blk)
3
+ Hash.new { |h,k| h[k] = [] }.tap do |hash|
4
+ each do |element|
5
+ hash[blk.call(element)] << element
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ class Hash
2
+ def map_keys &blk
3
+ map_keys_with_values { |k,v| blk.call(k) }
4
+ end
5
+
6
+ def map_keys_with_values &blk
7
+ result = {}
8
+ each { |k,v| result[blk.call(k,v)] = v}
9
+ result
10
+ end
11
+
12
+ def map_values &blk
13
+ map_values_with_keys { |k,v| blk.call(v) }
14
+ end
15
+
16
+ def map_values_with_keys &blk
17
+ result = {}
18
+ each { |k,v| result[k] = blk.call(k,v)}
19
+ result
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ class Object
2
+ def tapp(prefix = nil, &block)
3
+ block ||= lambda {|x| x }
4
+ str = if block[self].is_a? String then block[self] else block[self].inspect end
5
+ puts [prefix, str].compact.join(": ")
6
+ self
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ class GitSmart
2
+ class Exception < ::RuntimeError
3
+ def initialize(msg = '')
4
+ super(msg)
5
+ end
6
+ end
7
+
8
+ class RunFailed < Exception; end
9
+ class UnexpectedOutput < Exception; end
10
+ end
@@ -0,0 +1,39 @@
1
+ # The context that commands get executed within. Used for defining and scoping helper methods.
2
+
3
+ class ExecutionContext
4
+ def initialize
5
+ require 'colorize'
6
+ end
7
+
8
+ def start msg
9
+ puts "- #{msg} -".green
10
+ end
11
+
12
+ def note msg
13
+ puts "* #{msg}"
14
+ end
15
+
16
+ def warn msg
17
+ puts msg.red
18
+ end
19
+
20
+ def puts_with_done msg, &blk
21
+ print "#{msg}..."
22
+ blk.call
23
+ puts "done."
24
+ end
25
+
26
+ def success msg
27
+ puts big_message(msg).green
28
+ end
29
+
30
+ def failure msg
31
+ puts big_message(msg).red
32
+ raise GitSmart::RunFailed
33
+ end
34
+
35
+ def big_message msg
36
+ spacer_line = (" " + "-" * (msg.length + 20) + " ")
37
+ [spacer_line, "|" + " " * 10 + msg + " " * 10 + "|", spacer_line].join("\n")
38
+ end
39
+ end