Mange-git-remote-monitor 0.1.1 → 0.1.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,38 @@
1
+ # The MIT License
2
+ # Copyright © 2009 Magnus Bergmark
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+
23
+ class GitRemoteMonitor::Notifiers::Base
24
+ class << self
25
+ def available?
26
+ raise NoMethodError, "available? has not been implemented in #{self.class} yet!"
27
+ end
28
+ end
29
+
30
+ def initialize
31
+ raise NoMethodError, "initialize has not been implemented in #{self.class} yet!"
32
+ end
33
+
34
+ def send_notification!(status)
35
+ raise NoMethodError, "send_notification! has not been implemented in #{self.class} yet!"
36
+ end
37
+
38
+ end
@@ -0,0 +1,72 @@
1
+ # The MIT License
2
+ # Copyright © 2009 Magnus Bergmark
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ class GitRemoteMonitor::Notifiers::Meow < GitRemoteMonitor::Notifiers::Base
23
+ class << self
24
+ def available?
25
+ # Try to load the meow gem
26
+ require 'meow'
27
+ return true
28
+ rescue LoadError
29
+ return false
30
+ end
31
+ end
32
+
33
+ def initialize
34
+ @meow = ::Meow.new('git-remote-monitor')
35
+ end
36
+
37
+ def send_notification!(status)
38
+ message = get_message(status)
39
+ if message
40
+ @meow.notify(notification_title(status), message, :icon => notification_icon(status), :note_type => notification_type(status))
41
+ end
42
+ end
43
+
44
+ protected
45
+ def notification_title(status)
46
+ "#{status.name} (#{status.branch_name})"
47
+ end
48
+
49
+ def notification_icon(status)
50
+ # TODO: Different icons for each status type
51
+ "#{GitRemoteMonitor.root}/images/git-logo.png"
52
+ end
53
+
54
+ def notification_type(status)
55
+ status.type.to_s.capitalize
56
+ end
57
+
58
+ def get_message(status)
59
+ case status.type
60
+ when :local
61
+ # Don't bother notify on local changes only
62
+ nil
63
+ when :diverged
64
+ "Diverged! Remote has #{status.remote_commits} new commit(s), and you have #{status.local_commits} commit(s) to push."
65
+ when :remote
66
+ "Remote got updated! Now #{status.remote_commits} commit(s) in front of you."
67
+ else
68
+ raise "Unknown case. This is a bug! Please report it with the stack trace. Thank you."
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,58 @@
1
+ # The MIT License
2
+ # Copyright © 2009 Magnus Bergmark
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ #
23
+ # Helps notifiers to build their messages by hinding the logic used to get certain information,
24
+ # either from the repository and/or from the monitor itself. It will get constructed by Notifier and
25
+ # sent to the approriate notifier plugin.
26
+ #
27
+ class GitRemoteMonitor::Status
28
+
29
+ attr_reader :local_commits, :remote_commits, :branch_name
30
+
31
+ def initialize(monitor, branch_name, local_commits, remote_commits)
32
+ @monitor = monitor
33
+ @local_commits = local_commits
34
+ @remote_commits = remote_commits
35
+ @branch_name = branch_name
36
+ end
37
+
38
+ # Returns either <tt>:local</tt>, <tt>:remote</tt> or <tt>:diverged</tt> depending
39
+ # on where the commits occured
40
+ def type
41
+ if remote_commits == 0
42
+ :local
43
+ elsif local_commits == 0
44
+ :remote
45
+ else
46
+ :diverged
47
+ end
48
+ end
49
+
50
+ def directory
51
+ @monitor.directory
52
+ end
53
+
54
+ def name
55
+ @monitor.name
56
+ end
57
+
58
+ end
@@ -19,7 +19,7 @@
19
19
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
20
  # THE SOFTWARE.
21
21
 
22
- require File.dirname(__FILE__) + '/../../lib/git_remote_monitor'
22
+ require File.dirname(__FILE__) + '/../spec_helper'
23
23
 
24
24
  describe GitRemoteMonitor::GitWrapper do
25
25
 
@@ -27,8 +27,96 @@ describe GitRemoteMonitor::GitWrapper do
27
27
  # Shortcut :-D
28
28
  @wrapper = GitRemoteMonitor::GitWrapper
29
29
  end
30
-
30
+
31
+ describe "system calls" do
32
+
33
+ describe "to fetch" do
34
+ it "should raise error if git gets an error" do
35
+ lambda {
36
+ $?.stub!(:success? => false)
37
+ @wrapper.stub!(:`)
38
+ @wrapper.send(:call_fetch)
39
+ }.should raise_error
40
+ end
41
+
42
+ it "should return output" do
43
+ $?.stub!(:success? => true)
44
+ @wrapper.should_receive(:`).with(/git fetch/).and_return("Downloading stuff...")
45
+ @wrapper.send(:call_fetch).should == "Downloading stuff..."
46
+ end
47
+ end
48
+
49
+ it "should call git branch correctly" do
50
+ @wrapper.should_receive(:`).with(/git branch/).and_return("Output")
51
+ @wrapper.send(:call_get_local_branch).should == "Output"
52
+ end
53
+
54
+ it "should call remote listing correctly" do
55
+ @wrapper.should_receive(:`).with(/git branch -r/).and_return("output")
56
+ @wrapper.send(:call_remotes_list).should == "output"
57
+ end
58
+
59
+ it "should call command to discover git repo correctly and return exit status" do
60
+ # We test a "impossible" value here as a shortcut. If the method returns this, we know the status is returned unchanged
61
+ status = mock("Exit status")
62
+ $?.stub!(:success? => status)
63
+
64
+ @wrapper.should_receive(:`).with(/git show-ref/).and_return("")
65
+
66
+ @wrapper.send(:call_repo_discovery).should == status
67
+ end
68
+
69
+ it "should call git config correctly to get remote branch" do
70
+ @wrapper.should_receive(:`).with(/git config branch\.foobar\.remote/).and_return("Output")
71
+ @wrapper.send(:call_get_remote, "foobar").should == "Output"
72
+ end
73
+
74
+ it "should call git rev-list correctly to get tracking information" do
75
+ # This is a complex one... :-(
76
+ # Hard to test this without a copy-paste
77
+ @wrapper.should_receive(:`).
78
+ with("git rev-list --left-right foo...bar | cut -c 1 | sort | uniq -c").
79
+ and_return("Output")
80
+ @wrapper.send(:call_get_tracking_info, "foo", "bar").should == "Output"
81
+ end
82
+
83
+ end
84
+
85
+ describe "discovering remote branches" do
86
+ it "should return number of remotes when some are defined" do
87
+ @wrapper.should_receive(:call_remotes_list).and_return(" origin/master\n friend/cool-branch\n")
88
+ @wrapper.has_remotes?.should == 2
89
+ end
90
+
91
+ it "should return nil when no remotes are defined" do
92
+ @wrapper.should_receive(:call_remotes_list).and_return("\n")
93
+ @wrapper.has_remotes?.should be_nil
94
+ end
95
+ end
96
+
97
+ describe "discovering git repo" do
98
+ it "should return true if inside repository" do
99
+ @wrapper.stub!(:call_repo_discovery => true)
100
+ @wrapper.in_repo?.should be_true
101
+ end
102
+ it "should return false if outside repository" do
103
+ @wrapper.stub!(:call_repo_discovery => false)
104
+ @wrapper.in_repo?.should be_false
105
+ end
106
+ end
107
+
31
108
  describe "fetch" do
109
+ before(:each) do
110
+ @wrapper.stub!(:has_remotes? => 1, :call_fetch => "")
111
+ end
112
+
113
+ it "should raise error if no remote repositories are defined" do
114
+ @wrapper.should_receive(:has_remotes?).and_return(nil)
115
+ lambda {
116
+ @wrapper.fetch!
117
+ }.should raise_error(/no remote/)
118
+ end
119
+
32
120
  it "should know if a fetch didn't occur" do
33
121
  @wrapper.should_receive(:call_fetch).and_return "\n"
34
122
  @wrapper.fetch!.should == false
@@ -55,7 +143,7 @@ describe GitRemoteMonitor::GitWrapper do
55
143
  describe "branch discovery" do
56
144
  before(:each) do
57
145
  @wrapper.stub!(:call_get_remote).and_return ""
58
-
146
+
59
147
  @wrapper.stub!(:call_get_local_branch).and_return " * master"
60
148
  @wrapper.stub!(:call_get_remote).with("master").and_return "upstream\n"
61
149
  end
@@ -78,32 +166,46 @@ describe GitRemoteMonitor::GitWrapper do
78
166
  @wrapper.remote_branch.should be_nil
79
167
  end
80
168
  end
81
-
82
-
169
+
83
170
  describe "getting tracking information" do
84
171
 
172
+ def tracking_info_should_be(local_changes, remote_changes, options = {})
173
+ @wrapper.should_receive(:call_get_tracking_info).and_return(options[:for])
174
+ @wrapper.get_tracking_info("master", "origin/master").should == [local_changes, remote_changes]
175
+ end
176
+
85
177
  before(:each) do
86
- @wrapper.stub!(:call_get_tracking_info).and_return " 1 "
178
+ # Stub away the system command to be safe
179
+ @wrapper.stub!(:call_get_tracking_info => "")
180
+ end
181
+
182
+ it "should raise error on unexpected output from command" do
183
+ lambda {
184
+ @wrapper.stub!(:call_get_tracking_info => nil)
185
+ @wrapper.get_tracking_info("master", "origin/master")
186
+ }.should raise_error
187
+
188
+ lambda {
189
+ @wrapper.stub!(:call_get_tracking_info => " 1\n 2\n 3\n")
190
+ @wrapper.get_tracking_info("master", "origin/master")
191
+ }.should raise_error
87
192
  end
88
193
 
89
194
  it "should be able to get the correct number of commits when nothing has changed" do
90
- @wrapper.get_tracking_info("me", "you").should == [0, 0]
195
+ tracking_info_should_be 0, 0, :for => " 1 "
91
196
  end
92
197
 
93
198
  it "should be able to get the correct number of commits when diverged" do
94
- @wrapper.should_receive(:call_get_tracking_info).and_return(" 1 <\n 7 >")
95
- @wrapper.get_tracking_info("me", "you").should == [1, 7]
199
+ tracking_info_should_be 1, 7, :for => " 1 <\n 7 >"
96
200
  end
97
201
 
98
202
  it "should be able to get the correct number of commits when remote is ahead" do
99
- @wrapper.should_receive(:call_get_tracking_info).and_return(" 7 >")
100
- @wrapper.get_tracking_info("me", "you").should == [0, 7]
203
+ tracking_info_should_be 0, 7, :for => " 7 >"
101
204
  end
102
205
 
103
206
  it "should be able to get the correct number of commits when remote is behind" do
104
- @wrapper.should_receive(:call_get_tracking_info).and_return(" 1 <")
105
- @wrapper.get_tracking_info("me", "you").should == [1, 0]
207
+ tracking_info_should_be 132, 0, :for => " 132 <"
106
208
  end
107
- end
209
+ end
108
210
 
109
211
  end
@@ -0,0 +1,138 @@
1
+ # The MIT License
2
+ # Copyright © 2009 Magnus Bergmark
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+
22
+ require File.dirname(__FILE__) + '/../spec_helper'
23
+
24
+ describe GitRemoteMonitor::Monitor do
25
+ before(:each) do
26
+ Dir.stub!(:chdir)
27
+ File.stub!(:exist? => true, :directory? => true)
28
+ end
29
+
30
+ describe "initializing" do
31
+ before(:each) do
32
+ GitRemoteMonitor::Monitor.stub!(:move_to_directory!)
33
+ end
34
+
35
+ it "should move to directory specified" do
36
+ GitRemoteMonitor::Monitor.should_receive(:move_to_directory!).with("foo")
37
+ GitRemoteMonitor::Monitor.new("foo", 120, mock("Notifier"), "Name")
38
+ end
39
+
40
+ it "should auto-discover the name from the directory when no name given" do
41
+ instance = GitRemoteMonitor::Monitor.new("/complex/path/with_name", 120, mock("Notifier"))
42
+ instance.name.should == "with_name"
43
+ end
44
+
45
+ end
46
+
47
+ it "should notify the user with the correct information" do
48
+ GitRemoteMonitor::GitWrapper.stub!(:local_branch => 'local', :remote_branch => 'remote')
49
+ GitRemoteMonitor::GitWrapper.should_receive(:get_tracking_info).with('local', 'remote').and_return([4, 5])
50
+
51
+ notifier = mock("Notifier")
52
+ instance = GitRemoteMonitor::Monitor.new("/", 120, notifier)
53
+ notifier.should_receive(:notify_commits).with(instance, 'local', 4, 5)
54
+
55
+ instance.send(:notify!)
56
+ end
57
+
58
+ describe "moving to a directory" do
59
+ def stub_io(options = {})
60
+ options = {:exist => false, :dir => false, :repo => false}.merge(options)
61
+ File.stub!(:exist?).with('foo').and_return(options[:exist])
62
+ File.stub!(:directory?).with('foo').and_return(options[:dir])
63
+ GitRemoteMonitor::GitWrapper.stub!(:in_repo?).and_return(options[:repo])
64
+ Dir.stub!(:chdir)
65
+ end
66
+
67
+ it "should raise error if path does not exist" do
68
+ stub_io :exist => false
69
+ Dir.should_not_receive(:chdir)
70
+ lambda {
71
+ GitRemoteMonitor::Monitor.move_to_directory!('foo')
72
+ }.should raise_error
73
+ end
74
+
75
+ it "should raise error if path exists but is not a directory" do
76
+ stub_io :exist => true, :dir => false
77
+ Dir.should_not_receive(:chdir)
78
+ lambda {
79
+ GitRemoteMonitor::Monitor.move_to_directory!('foo')
80
+ }.should raise_error
81
+ end
82
+
83
+ it "should raise error if path is not part of a git repository" do
84
+ stub_io :exist => true, :dir => true, :repo => false
85
+ Dir.should_receive(:chdir).with('foo') # We still chdir, though. Raises come after
86
+ lambda {
87
+ GitRemoteMonitor::Monitor.move_to_directory!('foo')
88
+ }.should raise_error
89
+ end
90
+
91
+ it "should try to move if directory exists" do
92
+ stub_io :exist => true, :dir => true, :repo => true
93
+ Dir.should_receive(:chdir).with('foo')
94
+ lambda {
95
+ GitRemoteMonitor::Monitor.move_to_directory!('foo')
96
+ }.should_not raise_error
97
+ end
98
+
99
+ end
100
+
101
+ it "should monitor forever" do
102
+ instance = GitRemoteMonitor::Monitor.new('dir', 180, mock("Notifier"))
103
+ instance.send(:should_loop_monitoring?).should be_true
104
+ end
105
+
106
+ describe "monitoring" do
107
+ before(:each) do
108
+ GitRemoteMonitor::GitWrapper.stub!(:fetch!)
109
+ @instance = GitRemoteMonitor::Monitor.new('dir', 180, mock("Notifier"))
110
+ @instance.stub!(:sleep => true, :notify! => true, :should_loop_monitoring? => false)
111
+ end
112
+
113
+ it "should loop" do
114
+ # Loop once and then abort it by changing th return of the method from true to false
115
+ @instance.should_receive(:should_loop_monitoring?).twice.and_return(true, false)
116
+ @instance.start_monitoring!
117
+ end
118
+
119
+ it "should notify when changes are detected" do
120
+ GitRemoteMonitor::GitWrapper.should_receive(:fetch!).and_return(true)
121
+ @instance.should_receive(:notify!)
122
+ @instance.start_monitoring!
123
+ end
124
+
125
+ it "should not notify when no changes are detected" do
126
+ GitRemoteMonitor::GitWrapper.should_receive(:fetch!).and_return(false)
127
+ @instance.should_not_receive(:notify!)
128
+ @instance.start_monitoring!
129
+ end
130
+
131
+ it "should sleep the specified interval" do
132
+ @instance.should_receive(:sleep).with(180)
133
+ @instance.start_monitoring!
134
+ end
135
+
136
+ end
137
+
138
+ end