scrumninja-git-cli 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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