redmine_github_hook 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/LICENSE +1 -1
- data/README.md +46 -15
- data/app/controllers/github_hook_controller.rb +4 -101
- data/app/services/github_hook/updater.rb +152 -0
- data/init.rb +2 -0
- data/lib/redmine_github_hook/version.rb +1 -1
- data/test/functional/github_hook_controller_test.rb +7 -101
- data/test/unit/github_hook/updater_test.rb +174 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80d3b1fb7dfd0ba831f64fa058e41c143c2a3d85
|
4
|
+
data.tar.gz: c1437776bd70f444d30d5c26c4928aa3fef21908
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d44d1a781d5a714324fe7ec938d8df9f6607783efe6004ebb418a13cad9d56cd1df6f178ad342f276c01bdab51cd8e9928654447d298f657e90780b8f21daf9b
|
7
|
+
data.tar.gz: 81d3e3cf4e3aaa09e0837196158d4aa32aec72e620b65b5bf6f909b8aad3a6314631e15d4eff42c927ab564f80b34a8107576e299da1f31a17ba1107f9b46fd9
|
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -11,24 +11,46 @@ If your shared repository is on a remote machine - for example on GitHub - this
|
|
11
11
|
That approach works perfectly fine, but is a bit heavy-handed and cumbersome. The Redmine GitHub Hook plugin allows GitHub to notify your Redmine installation when changes have been pushed to a repository, triggering an update of your local repository and Redmine data only when it is actually necessary.
|
12
12
|
|
13
13
|
|
14
|
-
##
|
14
|
+
## Getting started
|
15
15
|
|
16
|
-
1.
|
17
|
-
1. Install the [json gem](http://json.rubyforge.org/) on the machine where Redmine is running.
|
18
|
-
2. Follow the plugin installation procedure outlined in the [Redmine wiki](http://www.redmine.org/wiki/redmine/Plugins).
|
19
|
-
* Make sure that Redmine GitHub Hook is installed in a directory named `redmine_github_hook`
|
20
|
-
3. Restart your Redmine.
|
21
|
-
4. If you already have a local Git repository set up and working from Redmine go to step 3, otherwise continue at step 2.
|
16
|
+
### 1. Install the plugin
|
22
17
|
|
23
|
-
|
18
|
+
You have two options for installing the plugin:
|
24
19
|
|
25
|
-
|
26
|
-
1. Go to the repository Settings interface on GitHub.
|
27
|
-
2. Under "Webhooks & Services" add a new "WebHook". The "Payload URL" needs to be of the format: `[redmine_url]/github_hook` (for example `http://redmine.example.com/github_hook`).
|
28
|
-
1. By default, GitHub Hook assumes your GitHub repository name is the same as the project identifier in your Redmine installation. If this is not the case, you can specify the actual Redmine project identifier in the Post-Receive URL by using the format `[redmine_url]/github_hook?project_id=[identifier]` (for example `http://redmine.example.com/github_hook?project_id=my_project`).
|
29
|
-
2. In most cases, just having the "push" event trigger the webhook should suffice, but you are free to customize the events as you desire.
|
20
|
+
#### A: As a RubyGem
|
30
21
|
|
31
|
-
|
22
|
+
1. Add the gem to your Gemfile.local:
|
23
|
+
`gem "redmine_github_hook"`
|
24
|
+
2. `bundle`
|
25
|
+
3. Restart your Redmine
|
26
|
+
|
27
|
+
#### B: As a plugin
|
28
|
+
|
29
|
+
1. Follow the plugin installation procedure outlined in the [Redmine wiki](http://www.redmine.org/wiki/redmine/Plugins).
|
30
|
+
* Make sure that Redmine GitHub Hook is installed in a directory named `redmine_github_hook`
|
31
|
+
* Easiest way: change in your plugins directory and pull with git: `git clone git://github.com/koppen/redmine_github_hook.git`
|
32
|
+
2. Restart your Redmine.
|
33
|
+
|
34
|
+
### 2. Add the repository to Redmine
|
35
|
+
|
36
|
+
Adding a Git repository to a project (note, this should work whether you want to use Redmine GitHub Hook or not).
|
37
|
+
|
38
|
+
1. Simply follow the instructions for [keeping your git repository in sync](http://www.redmine.org/wiki/redmine/HowTo_keep_in_sync_your_git_repository_for_redmine).
|
39
|
+
* You don't need to set up a cron task as described in the Redmine instructions.
|
40
|
+
|
41
|
+
### 3. Connecting GitHub to Redmine
|
42
|
+
|
43
|
+
1. Go to the repository Settings interface on GitHub.
|
44
|
+
2. Under "Webhooks & Services" add a new "WebHook". The "Payload URL" needs to be of the format: `[redmine_url]/github_hook` (for example `http://redmine.example.com/github_hook`).
|
45
|
+
* By default, GitHub Hook assumes your GitHub repository name is the same as the *project identifier* in your Redmine installation.
|
46
|
+
* If this is not the case, you can specify the actual Redmine project identifier in the Post-Receive URL by using the format `[redmine_url]/github_hook?project_id=[identifier]` (for example `http://redmine.example.com/github_hook?project_id=my_project`).
|
47
|
+
* GitHub Hook will then update **all repositories** in the specified project. *Be aware, that this process may take a while if you have many repositories in your project.*
|
48
|
+
* If you want GitHub Hook to **only update the current repository** you can specify it with an additional parameter in the Post-Receive URL by using the format `[redmine_url]/github_hook?project_id=[identifier]&repository_id=[repository]` (for example `http://redmine.example.com/github_hook?project_id=my_project&repository_id=my_repo`).
|
49
|
+
* If you want GitHub Hook to **only update the current repository** you can specify it with an additional parameter in the Post-Receive URL by using the format `[redmine_url]/github_hook?project_id=[identifier]&repository_id=[repository]` (for example `http://redmine.example.com/github_hook?project_id=my_project&repository_id=my_repo`).
|
50
|
+
* In most cases, just having the "push" event trigger the webhook should suffice, but you are free to customize the events as you desire.
|
51
|
+
* *Note: Make sure you're adding a Webhook - which is what Redmine Github Hook expects. GitHub has some builtin Redmine integration; that's not what you're looking for.*
|
52
|
+
|
53
|
+
That's it. GitHub will now send a HTTP POST to the Redmine GitHub Hook plugin whenever changes are pushed to GitHub. The plugin then takes care of pulling the changes to the local repositories and updating the Redmine database with them.
|
32
54
|
|
33
55
|
|
34
56
|
## Assumptions
|
@@ -66,9 +88,18 @@ This means you need to add its SSH keys on GitHub. If the user doesn't already h
|
|
66
88
|
The user running Redmine needs permissions to read and write to the local repository on the server.
|
67
89
|
|
68
90
|
|
91
|
+
## What happens
|
92
|
+
|
93
|
+
The interactions between the different parts of the process is outlined in the following sequence diagram:
|
94
|
+
|
95
|
+
![sequence](https://cloud.githubusercontent.com/assets/6480/3311503/3a789390-f6c5-11e3-804d-d5ca2562799f.png)
|
96
|
+
|
97
|
+
(Diagram made with [http://bramp.github.io/js-sequence-diagrams/](js-sequence-diagrams)).
|
98
|
+
|
99
|
+
|
69
100
|
## License
|
70
101
|
|
71
|
-
Copyright (c) 2009-
|
102
|
+
Copyright (c) 2009-2014 Jakob Skjerning
|
72
103
|
|
73
104
|
Permission is hereby granted, free of charge, to any person
|
74
105
|
obtaining a copy of this software and associated documentation
|
@@ -1,21 +1,14 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
class GithubHookController < ApplicationController
|
4
|
-
|
5
|
-
GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
|
6
4
|
skip_before_filter :verify_authenticity_token, :check_if_login_required
|
7
5
|
|
8
6
|
def index
|
9
7
|
if request.post?
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
update_repository(repository)
|
15
|
-
|
16
|
-
# Fetch the new changesets into Redmine
|
17
|
-
repository.fetch_changesets
|
18
|
-
end
|
8
|
+
payload = JSON.parse(params[:payload] || '{}')
|
9
|
+
updater = GithubHook::Updater.new(payload, params)
|
10
|
+
updater.logger = logger
|
11
|
+
updater.call
|
19
12
|
end
|
20
13
|
|
21
14
|
render(:text => 'OK')
|
@@ -24,94 +17,4 @@ class GithubHookController < ApplicationController
|
|
24
17
|
def welcome
|
25
18
|
# Render the default layout
|
26
19
|
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def system(command)
|
31
|
-
Kernel.system(command)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Executes shell command. Returns true if the shell command exits with a
|
35
|
-
# success status code.
|
36
|
-
#
|
37
|
-
# If directory is given the current directory will be changed to that
|
38
|
-
# directory before executing command.
|
39
|
-
def exec(command, directory)
|
40
|
-
logger.debug { "GithubHook: Executing command: '#{command}'" }
|
41
|
-
|
42
|
-
# Get a path to a temp file
|
43
|
-
logfile = Tempfile.new('github_hook_exec')
|
44
|
-
logfile.close
|
45
|
-
|
46
|
-
full_command = "#{command} > #{logfile.path} 2>&1"
|
47
|
-
success = if directory.present?
|
48
|
-
Dir.chdir(directory) do
|
49
|
-
system(full_command)
|
50
|
-
end
|
51
|
-
else
|
52
|
-
system(full_command)
|
53
|
-
end
|
54
|
-
|
55
|
-
output_from_command = File.readlines(logfile.path)
|
56
|
-
if success
|
57
|
-
logger.debug { "GithubHook: Command output: #{output_from_command.inspect}"}
|
58
|
-
else
|
59
|
-
logger.error { "GithubHook: Command '#{command}' didn't exit properly. Full output: #{output_from_command.inspect}"}
|
60
|
-
end
|
61
|
-
|
62
|
-
return success
|
63
|
-
ensure
|
64
|
-
logfile.unlink
|
65
|
-
end
|
66
|
-
|
67
|
-
def git_command(command)
|
68
|
-
GIT_BIN + " #{command}"
|
69
|
-
end
|
70
|
-
|
71
|
-
# Fetches updates from the remote repository
|
72
|
-
def update_repository(repository)
|
73
|
-
command = git_command('fetch origin')
|
74
|
-
if exec(command, repository.url)
|
75
|
-
command = git_command("fetch origin \"+refs/heads/*:refs/heads/*\"")
|
76
|
-
exec(command, repository.url)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Gets the project identifier from the querystring parameters and if that's not supplied, assume
|
81
|
-
# the Github repository name is the same as the project identifier.
|
82
|
-
def get_identifier
|
83
|
-
identifier = get_project_name
|
84
|
-
raise ActiveRecord::RecordNotFound, "Project identifier not specified" if identifier.nil?
|
85
|
-
return identifier
|
86
|
-
end
|
87
|
-
|
88
|
-
# Attempts to find the project name. It first looks in the params, then in the
|
89
|
-
# payload if params[:project_id] isn't given.
|
90
|
-
def get_project_name
|
91
|
-
payload = JSON.parse(params[:payload] || '{}')
|
92
|
-
params[:project_id] || (payload['repository'] ? payload['repository']['name'] : nil)
|
93
|
-
end
|
94
|
-
|
95
|
-
# Finds the Redmine project in the database based on the given project identifier
|
96
|
-
def find_project
|
97
|
-
identifier = get_identifier
|
98
|
-
project = Project.find_by_identifier(identifier.downcase)
|
99
|
-
raise ActiveRecord::RecordNotFound, "No project found with identifier '#{identifier}'" if project.nil?
|
100
|
-
return project
|
101
|
-
end
|
102
|
-
|
103
|
-
# Returns the Redmine Repository object we are trying to update
|
104
|
-
def find_repositories
|
105
|
-
project = find_project
|
106
|
-
repositories = project.repositories.select do |repo|
|
107
|
-
repo.is_a?(Repository::Git)
|
108
|
-
end
|
109
|
-
|
110
|
-
if repositories.nil? or repositories.length == 0
|
111
|
-
raise TypeError, "Project '#{project.to_s}' ('#{project.identifier}') has no repository"
|
112
|
-
end
|
113
|
-
|
114
|
-
return repositories
|
115
|
-
end
|
116
|
-
|
117
20
|
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module GithubHook
|
2
|
+
class Updater
|
3
|
+
GIT_BIN = Redmine::Configuration['scm_git_command'] || "git"
|
4
|
+
|
5
|
+
attr_writer :logger
|
6
|
+
|
7
|
+
def initialize(payload, params = {})
|
8
|
+
@payload = payload
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
repositories = find_repositories
|
14
|
+
|
15
|
+
repositories.each do |repository|
|
16
|
+
tg1 = Time.now
|
17
|
+
# Fetch the changes from Github
|
18
|
+
update_repository(repository)
|
19
|
+
tg2 = Time.now
|
20
|
+
|
21
|
+
tr1 = Time.now
|
22
|
+
# Fetch the new changesets into Redmine
|
23
|
+
repository.fetch_changesets
|
24
|
+
tr2 = Time.now
|
25
|
+
|
26
|
+
logger.info { " GithubHook: Redmine repository updated: #{repository.identifier} (Git: #{time_diff_milli(tg1,tg2)}ms, Redmine: #{time_diff_milli(tr1,tr2)}ms)" }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
class NullLogger
|
33
|
+
def debug(*_); end
|
34
|
+
def info(*_); end
|
35
|
+
def warn(*_); end
|
36
|
+
def error(*_); end
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :params, :payload
|
40
|
+
|
41
|
+
# Executes shell command. Returns true if the shell command exits with a
|
42
|
+
# success status code.
|
43
|
+
#
|
44
|
+
# If directory is given the current directory will be changed to that
|
45
|
+
# directory before executing command.
|
46
|
+
def exec(command, directory)
|
47
|
+
logger.debug { " GithubHook: Executing command: '#{command}'" }
|
48
|
+
|
49
|
+
# Get a path to a temp file
|
50
|
+
logfile = Tempfile.new('github_hook_exec')
|
51
|
+
logfile.close
|
52
|
+
|
53
|
+
full_command = "#{command} > #{logfile.path} 2>&1"
|
54
|
+
success = if directory.present?
|
55
|
+
Dir.chdir(directory) do
|
56
|
+
system(full_command)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
system(full_command)
|
60
|
+
end
|
61
|
+
|
62
|
+
output_from_command = File.readlines(logfile.path)
|
63
|
+
if success
|
64
|
+
logger.debug { " GithubHook: Command output: #{output_from_command.inspect}"}
|
65
|
+
else
|
66
|
+
logger.error { " GithubHook: Command '#{command}' didn't exit properly. Full output: #{output_from_command.inspect}"}
|
67
|
+
end
|
68
|
+
|
69
|
+
return success
|
70
|
+
ensure
|
71
|
+
logfile.unlink if logfile && logfile.respond_to?(:unlink)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Finds the Redmine project in the database based on the given project identifier
|
75
|
+
def find_project
|
76
|
+
identifier = get_identifier
|
77
|
+
project = Project.find_by_identifier(identifier.downcase)
|
78
|
+
raise ActiveRecord::RecordNotFound, "No project found with identifier '#{identifier}'" if project.nil?
|
79
|
+
return project
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the Redmine Repository object we are trying to update
|
83
|
+
def find_repositories
|
84
|
+
project = find_project
|
85
|
+
repositories = project.repositories.select do |repo|
|
86
|
+
repo.is_a?(Repository::Git)
|
87
|
+
end
|
88
|
+
|
89
|
+
if repositories.nil? or repositories.length == 0
|
90
|
+
raise TypeError, "Project '#{project.to_s}' ('#{project.identifier}') has no repository"
|
91
|
+
end
|
92
|
+
|
93
|
+
# if a specific repository id is passed in url parameter "repository_id", then try to find it in
|
94
|
+
# the list of current project repositories and use only this and not all to pull changes from
|
95
|
+
# (issue #54)
|
96
|
+
if params.has_key?(:repository_id)
|
97
|
+
param_repo = repositories.select do |repo|
|
98
|
+
repo.identifier == params[:repository_id]
|
99
|
+
end
|
100
|
+
|
101
|
+
if param_repo.nil? or param_repo.length == 0
|
102
|
+
logger.info { " GithubHook: The repository '#{params[:repository_id]}' isn't in the list of projects repos. Updating all repos instead." }
|
103
|
+
|
104
|
+
else
|
105
|
+
repositories = param_repo
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
return repositories
|
110
|
+
end
|
111
|
+
|
112
|
+
# Gets the project identifier from the querystring parameters and if that's not supplied, assume
|
113
|
+
# the Github repository name is the same as the project identifier.
|
114
|
+
def get_identifier
|
115
|
+
identifier = get_project_name
|
116
|
+
raise ActiveRecord::RecordNotFound, "Project identifier not specified" if identifier.nil?
|
117
|
+
return identifier
|
118
|
+
end
|
119
|
+
|
120
|
+
# Attempts to find the project name. It first looks in the params, then in the
|
121
|
+
# payload if params[:project_id] isn't given.
|
122
|
+
def get_project_name
|
123
|
+
params[:project_id] || (payload['repository'] ? payload['repository']['name'] : nil)
|
124
|
+
end
|
125
|
+
|
126
|
+
def git_command(command)
|
127
|
+
GIT_BIN + " #{command}"
|
128
|
+
end
|
129
|
+
|
130
|
+
def logger
|
131
|
+
@logger || NullLogger.new
|
132
|
+
end
|
133
|
+
|
134
|
+
def system(command)
|
135
|
+
Kernel.system(command)
|
136
|
+
end
|
137
|
+
|
138
|
+
def time_diff_milli(start, finish)
|
139
|
+
((finish - start) * 1000.0).round(1)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Fetches updates from the remote repository
|
143
|
+
def update_repository(repository)
|
144
|
+
command = git_command('fetch origin')
|
145
|
+
if exec(command, repository.url)
|
146
|
+
command = git_command("fetch origin \"+refs/heads/*:refs/heads/*\"")
|
147
|
+
exec(command, repository.url)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
data/init.rb
CHANGED
@@ -4,5 +4,7 @@ Redmine::Plugin.register :redmine_github_hook do
|
|
4
4
|
name 'Redmine Github Hook plugin'
|
5
5
|
author 'Jakob Skjerning'
|
6
6
|
description 'This plugin allows your Redmine installation to receive Github post-receive notifications'
|
7
|
+
url 'https://github.com/koppen/redmine_github_hook'
|
8
|
+
author_url 'http://mentalized.net'
|
7
9
|
version RedmineGithubHook::VERSION
|
8
10
|
end
|
@@ -68,129 +68,35 @@ class GithubHookControllerTest < ActionController::TestCase
|
|
68
68
|
Project.stubs(:find_by_identifier).with('github').returns(project)
|
69
69
|
|
70
70
|
# Make sure we don't run actual commands in test
|
71
|
-
|
71
|
+
GithubHook::Updater.any_instance.expects(:system).never
|
72
72
|
Repository.expects(:fetch_changesets).never
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
76
|
-
|
77
|
-
descriptor.expects(:readlines).returns(contents)
|
78
|
-
descriptor
|
79
|
-
end
|
80
|
-
|
81
|
-
def do_post(payload = nil)
|
82
|
-
payload = json if payload.nil?
|
83
|
-
payload = payload.to_json if payload.is_a?(Hash)
|
84
|
-
post :index, :payload => payload
|
85
|
-
end
|
86
|
-
|
87
|
-
def test_should_use_the_repository_name_as_project_identifier
|
88
|
-
Project.expects(:find_by_identifier).with('github').returns(project)
|
89
|
-
@controller.stubs(:exec).returns(true)
|
90
|
-
do_post
|
91
|
-
end
|
92
|
-
|
93
|
-
def test_should_fetch_changes_from_origin
|
94
|
-
Project.expects(:find_by_identifier).with('github').returns(project)
|
95
|
-
@controller.expects(:exec).with("git fetch origin", repository.url)
|
96
|
-
do_post
|
97
|
-
end
|
98
|
-
|
99
|
-
def test_should_reset_repository_when_fetch_origin_succeeds
|
100
|
-
Project.expects(:find_by_identifier).with('github').returns(project)
|
101
|
-
@controller.expects(:exec).with("git fetch origin", repository.url).returns(true)
|
102
|
-
@controller.expects(:exec).with("git fetch origin \"+refs/heads/*:refs/heads/*\"", repository.url)
|
103
|
-
do_post
|
104
|
-
end
|
105
|
-
|
106
|
-
def test_should_not_reset_repository_when_fetch_origin_fails
|
107
|
-
Project.expects(:find_by_identifier).with('github').returns(project)
|
108
|
-
@controller.expects(:exec).with("git fetch origin", repository.url).returns(false)
|
109
|
-
@controller.expects(:exec).with("git reset --soft refs\/remotes\/origin\/master", repository.url).never
|
110
|
-
do_post
|
111
|
-
end
|
112
|
-
|
113
|
-
def test_should_use_project_identifier_from_request
|
114
|
-
Project.expects(:find_by_identifier).with('redmine').returns(project)
|
115
|
-
@controller.stubs(:exec).returns(true)
|
116
|
-
post :index, :project_id => 'redmine', :payload => json
|
117
|
-
end
|
118
|
-
|
119
|
-
def test_should_return_404_if_project_identifier_not_found
|
120
|
-
assert_raises ActiveRecord::RecordNotFound do
|
121
|
-
post :index
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def test_should_downcase_identifier
|
126
|
-
# Redmine project identifiers are always downcase
|
127
|
-
Project.expects(:find_by_identifier).with('redmine').returns(project)
|
128
|
-
@controller.stubs(:exec).returns(true)
|
129
|
-
post :index, :project_id => 'ReDmInE', :payload => json
|
75
|
+
def do_post
|
76
|
+
post :index, :payload => json
|
130
77
|
end
|
131
78
|
|
132
79
|
def test_should_render_ok_when_done
|
133
|
-
|
134
|
-
do_post
|
135
|
-
assert_response :success
|
136
|
-
assert_equal 'OK', @response.body
|
137
|
-
end
|
138
|
-
|
139
|
-
def test_should_fetch_changesets_into_the_repository
|
140
|
-
@controller.expects(:update_repository).returns(true)
|
141
|
-
repository.expects(:fetch_changesets).returns(true)
|
142
|
-
|
80
|
+
GithubHook::Updater.any_instance.expects(:update_repository).returns(true)
|
143
81
|
do_post
|
144
82
|
assert_response :success
|
145
83
|
assert_equal 'OK', @response.body
|
146
84
|
end
|
147
85
|
|
148
|
-
def test_should_return_404_if_project_identifier_not_given
|
149
|
-
assert_raises ActiveRecord::RecordNotFound do
|
150
|
-
do_post :repository => {}
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def test_should_return_404_if_project_not_found
|
155
|
-
assert_raises ActiveRecord::RecordNotFound do
|
156
|
-
Project.expects(:find_by_identifier).with('foobar').returns(nil)
|
157
|
-
do_post :repository => {:name => 'foobar'}
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def test_should_return_500_if_project_has_no_repository
|
162
|
-
assert_raises TypeError do
|
163
|
-
project = mock('project', :to_s => 'My Project', :identifier => 'github')
|
164
|
-
project.expects(:repositories).returns([])
|
165
|
-
Project.expects(:find_by_identifier).with('github').returns(project)
|
166
|
-
do_post :repository => {:name => 'github'}
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def test_should_return_500_if_repository_is_not_git
|
171
|
-
assert_raises TypeError do
|
172
|
-
project = mock('project', :to_s => 'My Project', :identifier => 'github')
|
173
|
-
repository = Repository::Subversion.new
|
174
|
-
project.expects(:repositories).at_least(1).returns([repository])
|
175
|
-
Project.expects(:find_by_identifier).with('github').returns(project)
|
176
|
-
do_post :repository => {:name => 'github'}
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
86
|
def test_should_not_require_login
|
181
|
-
|
87
|
+
GithubHook::Updater.any_instance.expects(:update_repository).returns(true)
|
182
88
|
@controller.expects(:check_if_login_required).never
|
183
89
|
do_post
|
184
90
|
end
|
185
91
|
|
186
92
|
def test_exec_should_log_output_from_git_as_debug_when_things_go_well
|
187
|
-
|
93
|
+
GithubHook::Updater.any_instance.expects(:system).at_least(1).returns(true)
|
188
94
|
@controller.logger.expects(:debug).at_least(1)
|
189
95
|
do_post
|
190
96
|
end
|
191
97
|
|
192
98
|
def test_exec_should_log_output_from_git_as_error_when_things_go_sour
|
193
|
-
|
99
|
+
GithubHook::Updater.any_instance.expects(:system).at_least(1).returns(false)
|
194
100
|
@controller.logger.expects(:error).at_least(1)
|
195
101
|
do_post
|
196
102
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
class GithubHookUpdaterTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def project
|
9
|
+
return @project if @project
|
10
|
+
|
11
|
+
@project ||= Project.new
|
12
|
+
@project.repositories << repository
|
13
|
+
@project
|
14
|
+
end
|
15
|
+
|
16
|
+
def repository
|
17
|
+
return @repository if @repository
|
18
|
+
|
19
|
+
@repository ||= Repository::Git.new(:identifier => "redmine")
|
20
|
+
@repository.stubs(:fetch_changesets).returns(true)
|
21
|
+
@repository
|
22
|
+
end
|
23
|
+
|
24
|
+
def payload
|
25
|
+
# Ruby hash with the parsed data from the JSON payload
|
26
|
+
{"before"=>"5aef35982fb2d34e9d9d4502f6ede1072793222d", "repository"=>{"url"=>"http://github.com/defunkt/github", "name"=>"github", "description"=>"You're lookin' at it.", "watchers"=>5, "forks"=>2, "private"=>1, "owner"=>{"email"=>"chris@ozmm.org", "name"=>"defunkt"}}, "commits"=>[{"id"=>"41a212ee83ca127e3c8cf465891ab7216a705f59", "url"=>"http://github.com/defunkt/github/commit/41a212ee83ca127e3c8cf465891ab7216a705f59", "author"=>{"email"=>"chris@ozmm.org", "name"=>"Chris Wanstrath"}, "message"=>"okay i give in", "timestamp"=>"2008-02-15T14:57:17-08:00", "added"=>["filepath.rb"]}, {"id"=>"de8251ff97ee194a289832576287d6f8ad74e3d0", "url"=>"http://github.com/defunkt/github/commit/de8251ff97ee194a289832576287d6f8ad74e3d0", "author"=>{"email"=>"chris@ozmm.org", "name"=>"Chris Wanstrath"}, "message"=>"update pricing a tad", "timestamp"=>"2008-02-15T14:36:34-08:00"}], "after"=>"de8251ff97ee194a289832576287d6f8ad74e3d0", "ref"=>"refs/heads/master"}
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_updater(payload, options = {})
|
30
|
+
updater = GithubHook::Updater.new(payload, options)
|
31
|
+
updater.stubs(:exec).returns(true)
|
32
|
+
updater
|
33
|
+
end
|
34
|
+
|
35
|
+
def updater
|
36
|
+
return @memoized_updater if @memoized_updater
|
37
|
+
@memoized_updater = build_updater(payload)
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup
|
41
|
+
Project.stubs(:find_by_identifier).with('github').returns(project)
|
42
|
+
|
43
|
+
# Make sure we don't run actual commands in test
|
44
|
+
GithubHook::Updater.any_instance.expects(:system).never
|
45
|
+
Repository.expects(:fetch_changesets).never
|
46
|
+
end
|
47
|
+
|
48
|
+
def teardown
|
49
|
+
@memoized_updater = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_uses_repository_name_as_project_identifier
|
53
|
+
Project.expects(:find_by_identifier).with('github').returns(project)
|
54
|
+
updater.call
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_fetches_changes_from_origin
|
58
|
+
updater.expects(:exec).with("git fetch origin", repository.url)
|
59
|
+
updater.call
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_resets_repository_when_fetch_origin_succeeds
|
63
|
+
updater.expects(:exec).with("git fetch origin", repository.url).returns(true)
|
64
|
+
updater.expects(:exec).with("git fetch origin \"+refs/heads/*:refs/heads/*\"", repository.url)
|
65
|
+
updater.call
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_resets_repository_when_fetch_origin_fails
|
69
|
+
updater.expects(:exec).with("git fetch origin", repository.url).returns(false)
|
70
|
+
updater.expects(:exec).with("git reset --soft refs\/remotes\/origin\/master", repository.url).never
|
71
|
+
updater.call
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_uses_project_identifier_from_request
|
75
|
+
Project.expects(:find_by_identifier).with('redmine').returns(project)
|
76
|
+
updater = build_updater(payload, {:project_id => 'redmine'})
|
77
|
+
updater.call
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_updates_all_repositories_by_default
|
81
|
+
another_repository = Repository::Git.new
|
82
|
+
another_repository.expects(:fetch_changesets).returns(true)
|
83
|
+
project.repositories << another_repository
|
84
|
+
|
85
|
+
updater = build_updater(payload)
|
86
|
+
updater.expects(:exec).with("git fetch origin", repository.url)
|
87
|
+
updater.call
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_updates_only_the_specified_repository
|
91
|
+
another_repository = Repository::Git.new
|
92
|
+
another_repository.expects(:fetch_changesets).never
|
93
|
+
project.repositories << another_repository
|
94
|
+
|
95
|
+
updater = build_updater(payload, {:repository_id => 'redmine'})
|
96
|
+
updater.expects(:exec).with("git fetch origin", repository.url)
|
97
|
+
updater.call
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_updates_all_repositories_if_specific_repository_is_not_found
|
101
|
+
another_repository = Repository::Git.new
|
102
|
+
another_repository.expects(:fetch_changesets).returns(true)
|
103
|
+
project.repositories << another_repository
|
104
|
+
|
105
|
+
updater = build_updater(payload, {:repository_id => 'redmine or something'})
|
106
|
+
updater.expects(:exec).with("git fetch origin", repository.url)
|
107
|
+
updater.call
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_raises_record_not_found_if_project_identifier_not_found
|
111
|
+
assert_raises ActiveRecord::RecordNotFound do
|
112
|
+
updater = build_updater({})
|
113
|
+
updater.call
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_raises_record_not_found_if_project_identifier_not_given
|
118
|
+
assert_raises ActiveRecord::RecordNotFound do
|
119
|
+
updater = build_updater(payload.merge({"repository" => {}}))
|
120
|
+
updater.call
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_raises_record_not_found_if_project_not_found
|
125
|
+
assert_raises ActiveRecord::RecordNotFound do
|
126
|
+
Project.expects(:find_by_identifier).with('foobar').returns(nil)
|
127
|
+
updater = build_updater(payload, {:project_id => "foobar"})
|
128
|
+
updater.call
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_downcases_identifier
|
133
|
+
# Redmine project identifiers are always downcase
|
134
|
+
Project.expects(:find_by_identifier).with('redmine').returns(project)
|
135
|
+
updater = build_updater(payload, {:project_id => 'ReDmInE'})
|
136
|
+
updater.call
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_fetches_changesets_into_the_repository
|
140
|
+
updater.expects(:update_repository).returns(true)
|
141
|
+
repository.expects(:fetch_changesets).returns(true)
|
142
|
+
updater.call
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_raises_type_error_if_project_has_no_repository
|
146
|
+
assert_raises TypeError do
|
147
|
+
project = mock('project', :to_s => 'My Project', :identifier => 'github')
|
148
|
+
project.expects(:repositories).returns([])
|
149
|
+
Project.expects(:find_by_identifier).with('github').returns(project)
|
150
|
+
updater.call
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_raises_type_error_if_repository_is_not_git
|
155
|
+
assert_raises TypeError do
|
156
|
+
project = mock('project', :to_s => 'My Project', :identifier => 'github')
|
157
|
+
repository = Repository::Subversion.new
|
158
|
+
project.expects(:repositories).at_least(1).returns([repository])
|
159
|
+
Project.expects(:find_by_identifier).with('github').returns(project)
|
160
|
+
updater.call
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_logs_if_a_logger_is_given
|
165
|
+
updater = GithubHook::Updater.new(payload)
|
166
|
+
updater.stubs(:exec).returns(true)
|
167
|
+
|
168
|
+
logger = stub('Logger')
|
169
|
+
logger.expects(:info).at_least_once
|
170
|
+
updater.logger = logger
|
171
|
+
|
172
|
+
updater.call
|
173
|
+
end
|
174
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redmine_github_hook
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakob Skjerning
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,7 @@ files:
|
|
52
52
|
- Rakefile
|
53
53
|
- app/controllers/github_hook_controller.rb
|
54
54
|
- app/helpers/git_hook_helper.rb
|
55
|
+
- app/services/github_hook/updater.rb
|
55
56
|
- app/views/github_hook/welcome.html.erb
|
56
57
|
- config/routes.rb
|
57
58
|
- init.rb
|
@@ -61,6 +62,7 @@ files:
|
|
61
62
|
- redmine_github_hook.gemspec
|
62
63
|
- test/functional/github_hook_controller_test.rb
|
63
64
|
- test/test_helper.rb
|
65
|
+
- test/unit/github_hook/updater_test.rb
|
64
66
|
homepage: ''
|
65
67
|
licenses:
|
66
68
|
- MIT
|
@@ -89,4 +91,4 @@ summary: Allow your Redmine installation to be notified when changes have been p
|
|
89
91
|
test_files:
|
90
92
|
- test/functional/github_hook_controller_test.rb
|
91
93
|
- test/test_helper.rb
|
92
|
-
|
94
|
+
- test/unit/github_hook/updater_test.rb
|