├── web
├── index.html
└── subsplit-webhook.php
├── .gitignore
├── config.json.dist
├── composer.json
├── LICENSE
├── composer.lock
├── bin
└── subsplit-worker.php
└── README.md
/web/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | config.json
3 |
--------------------------------------------------------------------------------
/config.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "working-directory": "/home/myuser/.subsplit",
3 | "webhook-secret": "ThisTokenIsNotSoSecretChangeIt",
4 | "projects": {
5 | "project-1": {
6 | "url": "git@github.com:orga/private-repo.git",
7 | "splits": [
8 | "src/public:git@github.com:orga/public-repo.git"
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dflydev/git-subsplit-github-webhook",
3 | "description": "GitHub WebHook for Git subsplits managed by dflydev-git-subsplit",
4 | "keywords": ["github", "webhook", "subsplit"],
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Dragonfly Development Inc.",
9 | "email": "info@dflydev.com",
10 | "homepage": "http://dflydev.com"
11 | },
12 | {
13 | "name": "Beau Simensen",
14 | "email": "beau@dflydev.com",
15 | "homepage": "http://beausimensen.com"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=5.3.3",
20 | "predis/predis": "0.8.*"
21 | },
22 | "extra": {
23 | "branch-alias": {
24 | "dev-master": "1.0-dev"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Dragonfly Development Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/web/subsplit-webhook.php:
--------------------------------------------------------------------------------
1 | lpush('dflydev-git-subsplit:incoming', $body);
39 |
40 | echo "Thanks.\n";
41 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "97d992090c9e74ed5d3450ab9bc5b2ad",
8 | "packages": [
9 | {
10 | "name": "predis/predis",
11 | "version": "v0.8.7",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/nrk/predis.git",
15 | "reference": "4123fcd85d61354c6c9900db76c9597dbd129bf6"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/nrk/predis/zipball/4123fcd85d61354c6c9900db76c9597dbd129bf6",
20 | "reference": "4123fcd85d61354c6c9900db76c9597dbd129bf6",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "php": ">=5.3.2"
25 | },
26 | "require-dev": {
27 | "phpunit/phpunit": "~4.0"
28 | },
29 | "suggest": {
30 | "ext-curl": "Allows access to Webdis when paired with phpiredis",
31 | "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
32 | },
33 | "type": "library",
34 | "autoload": {
35 | "psr-0": {
36 | "Predis": "lib/"
37 | }
38 | },
39 | "notification-url": "https://packagist.org/downloads/",
40 | "license": [
41 | "MIT"
42 | ],
43 | "authors": [
44 | {
45 | "name": "Daniele Alessandri",
46 | "email": "suppakilla@gmail.com",
47 | "homepage": "http://clorophilla.net"
48 | }
49 | ],
50 | "description": "Flexible and feature-complete PHP client library for Redis",
51 | "homepage": "http://github.com/nrk/predis",
52 | "keywords": [
53 | "nosql",
54 | "predis",
55 | "redis"
56 | ],
57 | "time": "2014-08-01T09:43:10+00:00"
58 | }
59 | ],
60 | "packages-dev": [],
61 | "aliases": [],
62 | "minimum-stability": "stable",
63 | "stability-flags": [],
64 | "prefer-stable": false,
65 | "prefer-lowest": false,
66 | "platform": {
67 | "php": ">=5.3.3"
68 | },
69 | "platform-dev": []
70 | }
71 |
--------------------------------------------------------------------------------
/bin/subsplit-worker.php:
--------------------------------------------------------------------------------
1 | 0,));
14 | while ($body = $redis->brpoplpush('dflydev-git-subsplit:incoming', 'dflydev-git-subsplit:processing', 0)) {
15 | $data = json_decode($body, true);
16 | $name = null;
17 | $project = null;
18 |
19 | $data['dflydev_git_subsplit'] = array(
20 | 'processed_at' => time(),
21 | );
22 |
23 | foreach ($config['projects'] as $testName => $testProject) {
24 | if ($testProject['url'] === $data['repository']['ssh_url']) {
25 | $name = $testName;
26 | $project = $testProject;
27 | break;
28 | }
29 | }
30 |
31 | if (null === $name) {
32 | print(sprintf('Skipping request for URL %s (not configured)', $data['repository']['ssh_url'])."\n");
33 |
34 | $redis->lrem('dflydev-git-subsplit:processing', 1, $body);
35 | $redis->lpush('dflydev-git-subspilt:failures', json_encode($data));
36 | continue;
37 | }
38 |
39 | $data['dflydev_git_subsplit']['name'] = $name;
40 | $data['dflydev_git_subsplit']['project'] = $project;
41 |
42 | $ref = $data['ref'];
43 |
44 | $publishCommand = array(
45 | 'git subsplit publish',
46 | escapeshellarg(implode(' ', $project['splits'])),
47 | );
48 |
49 | if (preg_match('/refs\/tags\/(.+)$/', $ref, $matches)) {
50 | $publishCommand[] = escapeshellarg('--rebuild-tags');
51 | $publishCommand[] = escapeshellarg('--no-heads');
52 | $publishCommand[] = escapeshellarg(sprintf('--tags=%s', $matches[1]));
53 | } elseif (preg_match('/refs\/heads\/(.+)$/', $ref, $matches)) {
54 | $publishCommand[] = escapeshellarg('--no-tags');
55 | $publishCommand[] = escapeshellarg(sprintf('--heads=%s', $matches[1]));
56 | } else {
57 | print sprintf('Skipping request for URL %s (unexpected reference detected: %s)', $data['repository']['ssh_url'], $ref)."\n";
58 |
59 | $redis->lrem('dflydev-git-subsplit:processing', 1, $body);
60 | $redis->lpush('dflydev-git-subspilt:failures', json_encode($data));
61 | continue;
62 | }
63 |
64 | $repositoryUrl = isset($project['repository-url'])
65 | ? $project['repository-url']
66 | : $project['url'];
67 |
68 | print sprintf('Processing subsplit for %s (%s)', $name, $ref)."\n";
69 |
70 | $projectWorkingDirectory = $config['working-directory'].'/'.$name;
71 | if (!file_exists($projectWorkingDirectory)) {
72 | print sprintf('Creating working directory for project %s (%s)', $name, $projectWorkingDirectory)."\n";
73 | mkdir($projectWorkingDirectory, 0750, true);
74 | }
75 |
76 | $command = implode(' && ', array(
77 | sprintf('cd %s', $projectWorkingDirectory),
78 | sprintf('( git subsplit init %s || true )', $repositoryUrl),
79 | 'git subsplit update',
80 | implode(' ', $publishCommand)
81 | ));
82 |
83 | passthru($command, $exitCode);
84 |
85 | if (0 !== $exitCode) {
86 | print sprintf('Command %s had a problem, exit code %s', $command, $exitCode)."\n";
87 |
88 | $redis->lrem('dflydev-git-subsplit:processing', 1, $body);
89 | $redis->lpush('dflydev-git-subspilt:failures', json_encode($data));
90 | continue;
91 | }
92 |
93 | $redis->lrem('dflydev-git-subsplit:processing', 1, $body);
94 | $redis->rpush('dflydev-git-subsplit:processed', json_encode($data));
95 | }
96 |
97 | $seconds = time() - $start;
98 | throw new \RuntimeException(sprintf('Something strange happened after %s seconds', $seconds));
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Git Subsplit GitHub WebHook
2 | ===========================
3 |
4 | [GitHub][1] WebHook for Git subsplits managed by [git-subsplit][2].
5 |
6 | Automates the process of keeping one-way read-only subtree splits up to date
7 | with the source repository.
8 |
9 | The WebHook works in two parts, a web listener and a worker. The web listener
10 | adds requests to Redis and the worker processes the requests.
11 |
12 | The worker will interact with the system's git as the user running the worker.
13 | **This means that the user running the worker should have its key added to
14 | the appropriate GitHub accounts.**
15 |
16 | During testing it would make sense to run the worker manually. For production
17 | deployments it would probably make more sense to runt he worker using something
18 | along the lines of [upstart][6] or [supervisor][7].
19 |
20 |
21 | Usage
22 | -----
23 |
24 | ### git-subsplit
25 |
26 | Ensure that [git-subsplit][2] is installed correctly. If is not available
27 | in your version of git (likely true for versions older than 1.7.11)
28 | please install it manually from [here][5].
29 |
30 | You should initialize subsplit with a git repository:
31 |
32 | cd /home/myuser
33 | git subsplit init git@github.com:orga/repo.git
34 |
35 | It will create a `.subsplit` working directory that you will use later.
36 |
37 | ### Installation
38 |
39 | git clone git@github.com:dflydev/dflydev-git-subsplit-github-webhook.git
40 | mv dflydev-git-subsplit-github-webhook/ webhook/
41 | cd webhook
42 | composer install
43 |
44 | N.B. If you need composer : [https://getcomposer.org/download/][8]
45 |
46 | ### Redis
47 |
48 | Ensure that the Redis server is running.
49 |
50 | ### Configure
51 |
52 | Copy `config.json.dist` to `config.json` and edit it accordingly. Please make sure
53 | to pay special attention to setting `working-directory` correctly.
54 | Don't forget to change the `webhook-secret` to secure your webhook.
55 |
56 | ### Web Server
57 |
58 | Setup a virtual host pointing to `web/` as its docroot. Assuming the virtual host
59 | is **webhook.example.com**, test the WebHook by visiting the following URL:
60 | **http://webhook.example.com/subsplit-webhook.php**
61 |
62 | ### Worker
63 |
64 | Start the worker by running `php bin/subsplit-worker.php`.
65 |
66 | ### GitHub
67 |
68 | From your repository go to **Settings** / **Service Hooks** / **WebHook URLs**.
69 | Enter the URL to your WebHook and your secret. Then click **Update Settings**.
70 |
71 | Click **WebHook URLs** again and click **Test Hook**.
72 |
73 | If everything is setup correctly the Worker should give you some sort of feedback.
74 |
75 |
76 | Configuration
77 | -------------
78 |
79 | ### Example
80 |
81 | ```
82 | {
83 | "working-directory": "/home/myuser/.subsplit",
84 | "webhook-secret": "ThisTokenIsNotSoSecretChangeIt",
85 | "projects": {
86 | "project-1": {
87 | "url": "git@github.com:orga/private-repo.git",
88 | "splits": [
89 | "src/public:git@github.com:orga/public-repo.git"
90 | ]
91 | }
92 | }
93 | }
94 | ```
95 |
96 | ### Schema
97 |
98 | #### working-directory
99 |
100 | *String. Default: None. **Required.***
101 |
102 | The directory in which the subsplits will be processed. This is more or less
103 | a temporary directory in which all projects will have their subsplit initialized.
104 |
105 | #### webhook-secret
106 |
107 | *String. Default: "ThisTokenIsNotSoSecretChangeIt". **Required.***
108 |
109 | This is a secret string that should be unique to your webhook. It's used to secure communication between the webhook and github.
110 | You can use a *secret generator* if you want a strong string.
111 |
112 | #### projects
113 |
114 | *Object. **Required**.*
115 |
116 | An object whose keys are project names and values are a project description
117 | object.
118 |
119 | Project names should only contain a-z, A-Z, 0-9, `.`, `_`, and `-`.
120 |
121 | Each project description object can have the following properties:
122 |
123 | * **url**:
124 | The URL for the project. The WebHook will check each incoming request's
125 | `url` property against each project's listed `url` property to determine
126 | which project the request is for.
127 |
128 | This URL should be like: **git@github.com:orga/repo.git**
129 |
130 | * **repository-url**:
131 | The URL that `git` will use to check out the project. This setting is
132 | optional. If it is not defined the repository URL will be read from the
133 | incoming request.
134 | * **splits**:
135 | An array of subsplit definitions as defined by [git-subsplit][2].
136 | The pattern for the splits is `${subPath}:${url}`.
137 |
138 |
139 | License
140 | -------
141 |
142 | MIT, see LICENSE.
143 |
144 |
145 | Community
146 | ---------
147 |
148 | If you have questions or want to help out, join us in the
149 | **#dflydev** channel on irc.freenode.net.
150 |
151 |
152 | Not Invented Here
153 | -----------------
154 |
155 | This project is based heavily on work originally done by [igorw][4].
156 | Thanks Igor. :)
157 |
158 |
159 | [1]: https://github.com
160 | [2]: https://github.com/dflydev/git-subsplit
161 | [3]: http://getcomposer.org
162 | [4]: https://igor.io
163 | [5]: https://github.com/apenwarr/git-subtree
164 | [6]: http://upstart.ubuntu.com
165 | [7]: http://supervisord.org
166 | [8]: https://getcomposer.org/download/
167 |
--------------------------------------------------------------------------------