├── 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 | --------------------------------------------------------------------------------