scrumninja-git-cli 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,375 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require "git_wrapper"
3
+
4
+ class GitWrapperUnitTest < Test::Unit::TestCase
5
+
6
+ def branch(name)
7
+ stub('Grit::Head', :name => name)
8
+ end
9
+ def remote(name)
10
+ stub('Grit::Remote', :name => name)
11
+ end
12
+
13
+ def setup
14
+ # Holler if we ever inadvertently try to run a git command
15
+ GitWrapper.expects(:run_git).never
16
+ end
17
+
18
+ def expect_git_command(command, interactive=false)
19
+ if interactive
20
+ GitWrapper.expects(:run_git).with(command, true)
21
+ else
22
+ GitWrapper.expects(:run_git).with(command)
23
+ end
24
+ end
25
+
26
+ context "#locate_git_repo_root" do
27
+ context "ENV['PWD'] is the root of a git repo" do
28
+ should 'return ENV["PWD"]' do
29
+ pwd = '/some/path/somedir'
30
+ dir = stub(:entries => %w(. .. .git foo))
31
+ Dir.expects(:new).with(pwd).returns(dir)
32
+ ENV.expects(:[]).with('PWD').returns(pwd)
33
+ assert_equal pwd, GitWrapper.locate_git_repo_root
34
+ end
35
+ end
36
+
37
+ context "ENV['PWD'] is a (grand)child directory of a git repo" do
38
+ should 'return first parent of ENV["PWD"] that contains a .git directory' do
39
+ repo_path = '/some/path/repo'
40
+ pwd = "#{repo_path}/child/grandchild"
41
+ ENV.expects(:[]).with('PWD').returns(pwd)
42
+ normal_dir = stub(:entries => %w(. .. foo))
43
+ repo_dir = stub(:entries => %w(. .. .git foo))
44
+ Dir.stubs(:new => normal_dir)
45
+ Dir.expects(:new).with(repo_path).returns(repo_dir)
46
+ assert_equal repo_path, GitWrapper.locate_git_repo_root
47
+ end
48
+ end
49
+
50
+ context "ENV['PWD'] is not part of a git repo" do
51
+ should 'return nil' do
52
+ pwd = "/some/dir/child/grandchild"
53
+ ENV.expects(:[]).with('PWD').returns(pwd)
54
+ dir = stub(:entries => %w(. .. foo))
55
+ Dir.stubs(:new => dir)
56
+ assert_nil GitWrapper.locate_git_repo_root
57
+ end
58
+ end
59
+ end
60
+
61
+ context "git_version" do
62
+ should 'use run_git to get the version and return the correct version and memoize the answer' do
63
+ GitWrapper.expects(:run_git).with('--version').returns('git version 1.7.0.2')
64
+ assert_equal [1,7,0,2], GitWrapper.git_version
65
+ assert_equal [1,7,0,2], GitWrapper.git_version
66
+ end
67
+ end
68
+
69
+ context "is_git_current_enough?" do
70
+ should "be false if version is < 1.7" do
71
+ GitWrapper.stubs(:git_version).returns([1,6,9])
72
+ deny GitWrapper.is_git_current_enough?
73
+ end
74
+
75
+ should "be true if version is = 1.7" do
76
+ GitWrapper.stubs(:git_version).returns([1,7])
77
+ assert GitWrapper.is_git_current_enough?
78
+ end
79
+
80
+ should "be true if version is = 1.7 with extra version info" do
81
+ GitWrapper.stubs(:git_version).returns([1,7,0])
82
+ assert GitWrapper.is_git_current_enough?
83
+ end
84
+
85
+ should "be true if version is > 1.7" do
86
+ GitWrapper.stubs(:git_version).returns([1,7,1])
87
+ assert GitWrapper.is_git_current_enough?
88
+ end
89
+
90
+ end
91
+
92
+
93
+ should "return current_branch_name" do
94
+ current_branch = stub('Grit::Head', :name => 'this-branch-right-here-yall')
95
+ GitWrapper.git.expects(:head).returns(current_branch)
96
+ assert_equal('this-branch-right-here-yall', GitWrapper.current_branch_name)
97
+ end
98
+
99
+ context "branches" do
100
+ should "return a list of local branches when called with no arguments" do
101
+ locals = [branch('master'), branch('hackery')]
102
+ GitWrapper.git.expects(:branches).returns(locals)
103
+ assert_equal(locals, GitWrapper.branches)
104
+ end
105
+
106
+ should "return a list of remote branches when called with a remote name as the first argument" do
107
+ remotes = [
108
+ remote('origin/master'),
109
+ remote('origin/some-topical-branch'),
110
+ remote('github/foo')
111
+ ]
112
+ remotes_on_origin = remotes[0,2]
113
+ GitWrapper.git.expects(:remotes).returns(remotes)
114
+ assert_equal(remotes_on_origin, GitWrapper.branches('origin'))
115
+ end
116
+ end
117
+
118
+ should "track_remote_branch" do
119
+ expect_git_command 'branch --set-upstream my-story origin/my-story'
120
+ GitWrapper.track_remote_branch('my-story')
121
+ end
122
+
123
+ should "publish_remote_branch" do
124
+ expect_git_command 'push origin my-story:refs/heads/my-story'
125
+ GitWrapper.publish_remote_branch('my-story')
126
+ end
127
+
128
+ context "fetch" do
129
+ should "grab a specified remote" do
130
+ expect_git_command 'fetch my_crazy_remote'
131
+ GitWrapper.fetch('my_crazy_remote')
132
+ end
133
+ should "grab 'origin' with no parameters" do
134
+ expect_git_command 'fetch origin'
135
+ GitWrapper.fetch
136
+ end
137
+ end
138
+
139
+ context "checkout" do
140
+ should 'fetch' do
141
+ expect_git_command 'fetch origin'
142
+ expect_git_command 'branch my-story'
143
+ expect_git_command 'push origin my-story:refs/heads/my-story'
144
+ expect_git_command 'branch --set-upstream my-story origin/my-story'
145
+ expect_git_command 'checkout my-story'
146
+
147
+ GitWrapper.checkout('my-story')
148
+ end
149
+
150
+ should "take an optional 'remote' parameter" do
151
+ expect_git_command 'fetch nonstandard_remote'
152
+ expect_git_command 'branch my-story'
153
+ expect_git_command 'push nonstandard_remote my-story:refs/heads/my-story'
154
+ expect_git_command 'branch --set-upstream my-story nonstandard_remote/my-story'
155
+ expect_git_command 'checkout my-story'
156
+
157
+ GitWrapper.checkout('my-story', 'nonstandard_remote')
158
+ end
159
+
160
+ context "when remote branch 'my-story' does not exist" do
161
+ setup do
162
+ @branches, @remotes = [branch('master')], []
163
+ GitWrapper.git.stubs(:branches).returns(@branches)
164
+ GitWrapper.git.stubs(:remotes).returns(@remotes)
165
+ end
166
+
167
+ should "create local 'my-story' branch, check it out, publish it, and track it" do
168
+ expect_git_command 'fetch origin'
169
+ expect_git_command 'branch local-story-branch'
170
+ expect_git_command 'push origin local-story-branch:refs/heads/local-story-branch'
171
+ expect_git_command 'branch --set-upstream local-story-branch origin/local-story-branch'
172
+ expect_git_command 'checkout local-story-branch'
173
+
174
+ GitWrapper.checkout('local-story-branch')
175
+ end
176
+
177
+ context "and local branch 'my-story' exists" do
178
+ setup do
179
+ @branches << branch('my-story')
180
+ end
181
+ should "check out the remote branch and track it" do
182
+ expect_git_command 'fetch origin'
183
+ expect_git_command 'push origin my-story:refs/heads/my-story'
184
+ expect_git_command 'branch --set-upstream my-story origin/my-story'
185
+ expect_git_command 'checkout my-story'
186
+
187
+ GitWrapper.checkout('my-story')
188
+ end
189
+ end
190
+ end
191
+
192
+ context "when remote branch 'my-story' exists" do
193
+ setup do
194
+ @branches, @remotes = [branch('master')], [remote('origin/my-story')]
195
+ GitWrapper.git.stubs(:branches).returns(@branches)
196
+ GitWrapper.git.stubs(:remotes).returns(@remotes)
197
+ end
198
+
199
+ should "create local 'my-story' branch, make it track the remote branch, and check it out" do
200
+ expect_git_command 'fetch origin'
201
+ expect_git_command 'branch my-story'
202
+ expect_git_command 'branch --set-upstream my-story origin/my-story'
203
+ expect_git_command 'checkout my-story'
204
+
205
+ GitWrapper.checkout('my-story')
206
+ end
207
+
208
+ context "when local branch 'my-story' exists" do
209
+ setup do
210
+ @branches << branch('my-story')
211
+ end
212
+ should "make it track the remote branch, and check it out" do
213
+ expect_git_command 'fetch origin'
214
+ expect_git_command 'branch --set-upstream my-story origin/my-story'
215
+ expect_git_command 'checkout my-story'
216
+
217
+ GitWrapper.checkout('my-story')
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ context "#is_state_clean?" do
224
+ should "be true when it is" do
225
+ expect_git_command('status').returns <<-EOF
226
+ # On branch master
227
+ nothing to commit (working directory clean)
228
+ EOF
229
+ assert GitWrapper.is_state_clean?
230
+ end
231
+
232
+ context "unclean" do
233
+ should 'be false when there are unstaged changes' do
234
+ expect_git_command('status').returns <<-EOF
235
+ # On branch master
236
+ # Untracked files:
237
+ # (use "git add <file>..." to include in what will be committed)
238
+ #
239
+ # newfile
240
+ nothing added to commit but untracked files present (use "git add" to track)
241
+ EOF
242
+ deny GitWrapper.is_state_clean?
243
+ end
244
+ should 'be false when there are staged changes' do
245
+ expect_git_command('status').returns <<-EOF
246
+ # On branch master
247
+ # Changes to be committed:
248
+ # (use "git reset HEAD <file>..." to unstage)
249
+ #
250
+ # new file: newfile
251
+ #
252
+ EOF
253
+ deny GitWrapper.is_state_clean?
254
+ end
255
+
256
+ should 'be false when tehre are both staged and unstaged changes' do
257
+ expect_git_command('status').returns <<-EOF
258
+ # On branch master
259
+ # Changes to be committed:
260
+ # (use "git reset HEAD <file>..." to unstage)
261
+ #
262
+ # new file: somethingelse
263
+ #
264
+ # Changed but not updated:
265
+ # (use "git add <file>..." to update what will be committed)
266
+ # (use "git checkout -- <file>..." to discard changes in working directory)
267
+ #
268
+ # modified: newfile
269
+ #
270
+ # Untracked files:
271
+ # (use "git add <file>..." to include in what will be committed)
272
+ #
273
+ # something
274
+ EOF
275
+ deny GitWrapper.is_state_clean?
276
+ end
277
+ end
278
+ end
279
+
280
+ context "require_clean_story_branch" do
281
+ should "complain if working state is not clean" do
282
+ GitWrapper.expects(:is_state_clean?).returns(false)
283
+ GitWrapper.stubs(:current_branch_name).returns('master')
284
+ assert_raise(GitWrapper::WorkingFolderDirtyException) { GitWrapper.require_clean_story_branch }
285
+ end
286
+
287
+ should "complain if already on master" do
288
+ GitWrapper.stubs(:is_state_clean?).returns(true)
289
+ GitWrapper.expects(:current_branch_name).returns('master')
290
+ assert_raise(GitWrapper::WrongBranchException) { GitWrapper.require_clean_story_branch }
291
+ end
292
+ end
293
+
294
+ context "merge_from_master" do
295
+ should 'do a checkout of master, a pull, a checkout of the local branch, and a merge' do
296
+ GitWrapper.expects(:require_clean_story_branch)
297
+ GitWrapper.stubs(:current_branch_name).returns('99-bottles')
298
+ expect_git_command 'checkout master'
299
+ expect_git_command 'pull'
300
+ expect_git_command 'checkout 99-bottles'
301
+ expect_git_command 'merge master'
302
+ GitWrapper.merge_from_master
303
+ end
304
+ end
305
+
306
+ context "push_branch" do
307
+ should "do a pull/rebase, then a push" do
308
+ GitWrapper.expects(:require_clean_story_branch)
309
+ GitWrapper.stubs(:current_branch_name).returns('321-contact')
310
+ expect_git_command 'pull --rebase'
311
+ expect_git_command 'push origin 321-contact'
312
+ GitWrapper.push_branch
313
+ end
314
+ end
315
+
316
+ context 'merge_branch_into_master' do
317
+ should "do a checkout of master, a pull, and merge the story branch in" do
318
+ GitWrapper.expects(:require_clean_story_branch)
319
+ GitWrapper.stubs(:current_branch_name).returns('99-bottles')
320
+ expect_git_command 'checkout master'
321
+ expect_git_command 'pull'
322
+ expect_git_command 'merge 99-bottles'
323
+ GitWrapper.merge_branch_into_master
324
+ end
325
+
326
+ should 'accept a branch name, if called while on master' do
327
+ GitWrapper.expects(:require_clean_master_branch)
328
+ expect_git_command 'pull'
329
+ expect_git_command 'merge 99-bottles'
330
+ GitWrapper.merge_branch_into_master('99-bottles')
331
+ end
332
+
333
+ # To consider:
334
+ # should 'prefix the commit message with "Story #{nnn}: #{story name}"'
335
+ end
336
+
337
+ context 'require_clean_master_branch' do
338
+ context "require_clean_story_branch" do
339
+ should "complain if working state is not clean" do
340
+ GitWrapper.expects(:is_state_clean?).returns(false)
341
+ GitWrapper.stubs(:current_branch_name).returns('master')
342
+ assert_raise(GitWrapper::WorkingFolderDirtyException) { GitWrapper.require_clean_master_branch }
343
+ end
344
+
345
+ should "complain if not on master" do
346
+ GitWrapper.stubs(:is_state_clean?).returns(true)
347
+ GitWrapper.expects(:current_branch_name).returns('not_master')
348
+ assert_raise(GitWrapper::WrongBranchException) { GitWrapper.require_clean_master_branch }
349
+ end
350
+ end
351
+ end
352
+
353
+ context 'push_master' do
354
+ should "do a pull/rebase, then a push" do
355
+ GitWrapper.expects(:require_clean_master_branch)
356
+ expect_git_command 'pull --rebase'
357
+ expect_git_command 'push origin master'
358
+ GitWrapper.push_master
359
+ end
360
+ end
361
+
362
+ context "#add" do
363
+ should "add the specified paths to the index" do
364
+ expect_git_command 'add -A foo bar'
365
+ GitWrapper.add('-A foo bar')
366
+ end
367
+ end
368
+
369
+ context "#commit" do
370
+ should "commit changes with story number prepended to commit message" do
371
+ expect_git_command 'commit -m "Story 12345: " -e', true
372
+ GitWrapper.commit("Story 12345: ")
373
+ end
374
+ end
375
+ end
@@ -0,0 +1,224 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'scrum_ninja_git_cli'
3
+
4
+ class TestScrumNinjaGitCli < Test::Unit::TestCase
5
+ def story(project_id, name)
6
+ ScrumNinja::Story.new("Story", project_id, name)
7
+ end
8
+
9
+ def setup
10
+ @git = mock('git')
11
+ @git.stubs(:is_git_current_enough?).returns(true)
12
+ ScrumNinjaGitCli.stubs(:git_wrapper).returns(@git)
13
+ @session = mock('session')
14
+ ScrumNinjaGitCli.stubs(:session).returns(@session)
15
+ GitWrapper.expects(:run_command).never
16
+ end
17
+
18
+ def expect_output(message_name)
19
+ message = ScrumNinjaGitCli::Messages[message_name]
20
+ ScrumNinjaGitCli.expects(:output).with(message).returns(true)
21
+ end
22
+
23
+ context "help" do
24
+ should "output ScrumNinjaGitCli::HelpText" do
25
+ ScrumNinjaGitCli.expects(:output).with(ScrumNinjaGitCli::HelpText)
26
+ ScrumNinjaGitCli.help
27
+ end
28
+
29
+ should 'be displayed when the utility is run with no args' do
30
+ ScrumNinjaGitCli.expects(:help)
31
+ ScrumNinjaGitCli.run_command
32
+ end
33
+
34
+ should 'be run when a run_command gets an ArgumentError' do
35
+ ScrumNinjaGitCli.expects(:help)
36
+ ScrumNinjaGitCli.run_command(:start)
37
+ end
38
+ end
39
+
40
+ context "info" do
41
+ should "make an API call and return a single Story object" do
42
+ the_story = story(99, "mmm... something...")
43
+ @session.expects(:get_story).with(99).returns(the_story)
44
+ ScrumNinjaGitCli.expects(:output).with(the_story)
45
+ ScrumNinjaGitCli.run_command(:info, 99)
46
+ end
47
+ end
48
+
49
+ context "xml" do
50
+ should "make an API call and return the raw XML" do
51
+ xml = "<xml>bogus XML</xml>"
52
+ @session.expects(:get_story_xml).with(99).returns(xml)
53
+ ScrumNinjaGitCli.expects(:output).with(xml)
54
+ ScrumNinjaGitCli.run_command(:xml, 99)
55
+ end
56
+ end
57
+
58
+ context 'list' do
59
+ should 'display a list of stories from ScrumNinja' do
60
+ stories = [story(999, "Do stuff"), story(42, "do something else")]
61
+ @session.expects(:get_stories).returns(stories)
62
+ ScrumNinjaGitCli.expects(:output).with(stories.map(&:to_s).join("\n"))
63
+ ScrumNinjaGitCli.run_command(:list)
64
+ end
65
+ end
66
+
67
+ context "own" do
68
+ should "update the owner of the given story" do
69
+ @session.expects(:update_ownership).with(42, nil, {})
70
+ ScrumNinjaGitCli.run_command(:own, 42)
71
+ end
72
+ end
73
+
74
+ context "start" do
75
+ should "join and own (nicely)" do
76
+ ScrumNinjaGitCli.expects(:join).with(42)
77
+ ScrumNinjaGitCli.expects(:own).with(42, nil, :abort_if_already_owned => true)
78
+ ScrumNinjaGitCli.run_command(:start, 42)
79
+ end
80
+ end
81
+
82
+ context "dev-task" do
83
+ should 'start work on a branch named "dev-hyphenated-story-name"' do
84
+ ScrumNinjaGitCli.expects(:start_work_on_branch).with('dev-hyphenated-story-name')
85
+ ScrumNinjaGitCli.run_command(:dev_task, 'hyphenated STORY _nAmE')
86
+ end
87
+ end
88
+
89
+
90
+ context "rake" do
91
+ should "run rake" do
92
+ ShellCmd.expects(:run).with('rake').returns(0)
93
+ assert ScrumNinjaGitCli.rake
94
+ end
95
+
96
+ should 'print a message and return false in the event of a rake failure' do
97
+ ShellCmd.expects(:run).with('rake').returns(256)
98
+ expect_output :rake_failure
99
+ deny ScrumNinjaGitCli.rake
100
+ end
101
+ end
102
+
103
+ context "#commit" do
104
+ should "commit pending changes and add story number to commit message" do
105
+ @git.stubs(:current_branch_name).returns('12345-some-story-name')
106
+ @git.expects(:commit).with("Story 12345: Some Story Name")
107
+ ScrumNinjaGitCli.run_command(:commit)
108
+ end
109
+ end
110
+
111
+ context "#add" do
112
+ should "add the specified paths to git" do
113
+ @git.expects(:add).with("foo bar baz")
114
+ ScrumNinjaGitCli.run_command(:add, 'foo bar baz')
115
+ end
116
+ end
117
+
118
+ context "deliver" do
119
+ actions = [
120
+ "push-branch",
121
+ "merge-from-master",
122
+ "rake",
123
+ "merge back into master",
124
+ "check out master and push it", # can't reuse GitWrapper#push_branch for this, though
125
+ ]
126
+ should actions.join('; ') do
127
+ @git.stubs(:current_branch_name).returns('not_master')
128
+ @git.expects(:push_branch)
129
+ @git.expects(:merge_from_master)
130
+ ScrumNinjaGitCli.expects(:rake).returns(true)
131
+ @git.expects(:merge_branch_into_master)
132
+ @git.expects(:push_master)
133
+ ScrumNinjaGitCli.run_command(:deliver)
134
+ end
135
+
136
+ should 'not squash nor push master if rake fails' do
137
+ @git.stubs(:current_branch_name).returns('not_master')
138
+ @git.expects(:push_branch)
139
+ @git.expects(:merge_from_master)
140
+ ScrumNinjaGitCli.expects(:rake).returns(false)
141
+ ScrumNinjaGitCli.run_command(:deliver)
142
+ end
143
+
144
+ should 'not run if the current branch is master' do
145
+ @git.expects(:current_branch_name).returns('master')
146
+ expect_output(:wrong_branch)
147
+ ScrumNinjaGitCli.run_command(:deliver)
148
+ end
149
+
150
+ should_eventually "complain if any of the commit messages to be merged in don't have a story number prefix" do
151
+ # yeah, this'll be easy to mock...
152
+ flunk
153
+ end
154
+ end
155
+
156
+ context "join" do
157
+ setup do
158
+ @story = story(99, "bottles of beer")
159
+ @session.stubs(:get_story).returns(@story)
160
+ end
161
+
162
+ should "inform the user that Git 1.7 or newer is required" do
163
+ @git.expects(:is_git_current_enough?).returns(false)
164
+ expect_output :git_version
165
+ ScrumNinjaGitCli.run_command(:join, 99)
166
+ end
167
+
168
+ should "call #own, git fetch, and git checkout" do
169
+ @git.expects(:checkout).with(@story.branch_name)
170
+ @git.stubs(:is_state_clean?).returns(true)
171
+ @git.stubs(:current_branch_name).returns('master')
172
+ ScrumNinjaGitCli.run_command(:join, 99)
173
+ end
174
+
175
+ should 'give a nice warning message and/or error if the working state is not clean' do
176
+ @session.expects(:get_story).never
177
+ @git.expects(:is_state_clean?).returns(false)
178
+ @git.stubs(:current_branch_name).returns('master')
179
+ expect_output :clean_working_state
180
+ ScrumNinjaGitCli.run_command(:join, 99)
181
+ end
182
+
183
+ should 'give a nice warning message and/or error if the current branch is not master' do
184
+ @session.expects(:get_story).never
185
+ @git.stubs(:is_state_clean?).returns(true)
186
+ @git.stubs(:current_branch_name).returns('not-at-all-master')
187
+ expect_output :must_be_on_master
188
+ ScrumNinjaGitCli.run_command(:join, 99)
189
+ end
190
+ end
191
+
192
+ context "push-branch" do
193
+ should "call GitWrapper#push_branch" do
194
+ @git.expects(:push_branch)
195
+ ScrumNinjaGitCli.run_command('push-branch')
196
+ end
197
+
198
+ should "handle raised exceptions" do
199
+ @git.expects(:push_branch).raises(GitWrapper::WorkingFolderDirtyException)
200
+ expect_output :clean_working_state
201
+ assert_nothing_raised(Exception) { ScrumNinjaGitCli.run_command('push-branch') }
202
+ end
203
+ end
204
+
205
+ context "merge-from-master" do
206
+ should "call GitWrapper#push_branch" do
207
+ @git.expects(:merge_from_master)
208
+ ScrumNinjaGitCli.run_command('merge-from-master')
209
+ end
210
+
211
+ should "handle raised exceptions" do
212
+ @git.expects(:merge_from_master).raises(GitWrapper::WrongBranchException)
213
+ expect_output :wrong_branch
214
+ assert_nothing_raised(Exception) { ScrumNinjaGitCli.run_command('merge-from-master') }
215
+ end
216
+ end
217
+
218
+ context "#git" do
219
+ should "run git command with passed-through arguments" do
220
+ ScrumNinjaGitCli.expects(:exec).with("export I_AM_A_GIT_GURU=1; git arg1 arg2 arg3").returns('some output')
221
+ ScrumNinjaGitCli.run_command('git', 'arg1', 'arg2', 'arg3')
222
+ end
223
+ end
224
+ end