├── LICENSE
├── README.md
└── git-buildkite
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Samuel Cochran
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Git Buildkite
2 |
3 | Start [Buildkite](https://buildkite.com) builds using [Git](https://git-scm.com) with a clickable link:
4 |
5 |
6 |
7 | Walks you through first time configuration.
8 |
9 | ## Installation
10 |
11 | Install on macOS using [Homebrew](https://brew.sh/):
12 |
13 | ```
14 | brew install sj26/git-buildkite/git-buildkite
15 | ```
16 |
17 | Or just download the git-buildkite script in this repository somewhere into your `$PATH`. It works on Linux, too.
18 |
19 | Add a Buildkite [API Access Token](https://buildkite.com/user/api-access-tokens) with:
20 |
21 | ```
22 | $ git config --global buildkite.apikey my-api-key
23 | ```
24 |
25 | Or store it more securely in your [macOS Keychain](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/security.1.html):
26 |
27 | ```
28 | $ security add-generic-password -s api.buildkite.com -a "$USER" -w
29 | password data for new item:
30 | retype password for new item:
31 | ```
32 |
33 | Or in GNOME Keychain, or other secret stores which [Keyring](https://pypi.python.org/pypi/keyring) supports:
34 |
35 | ```
36 | $ pip install keyring
37 | $ keyring set api.buildkite.com "$USER"
38 | Password: *****
39 | ```
40 |
41 | ## Usage
42 |
43 | Start a build on your current branch and commit:
44 |
45 | ```
46 | $ git buildkite
47 | ```
48 |
49 | Start a build on another branch and its current commit:
50 |
51 | ```
52 | $ git buildkite my-branch-name
53 | ```
54 |
55 | Start a build on another branch at a specific commit:
56 |
57 | ```
58 | $ git builkdite my-branch-name 87cba321
59 | ```
60 |
61 | `HEAD` can be used as an alias for the current branch name when you only want to specify a commit:
62 |
63 | ```
64 | $ git buildkite HEAD 87cba321
65 | ```
66 |
67 | Any [Git revision](https://git-scm.com/docs/gitrevisions) works as a commit:
68 |
69 | ```
70 | $ git buildkite HEAD "@{1 week ago}"
71 | ```
72 |
73 | You can automatically open the build in your browser, too:
74 |
75 | ```
76 | $ git buildkite --browse
77 | ```
78 |
79 | ## To Do
80 |
81 | * Better result parsing and error handling
82 |
83 | ## License
84 |
85 | MIT, see LICENSE.
86 |
--------------------------------------------------------------------------------
/git-buildkite:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | VERSION="v0.6.0"
4 |
5 | BUILDKITE_ORGANIZATION="${BUILDKITE_ORGANIZATION:-$(git config buildkite.organization)}"
6 |
7 | if [[ -z "$BUILDKITE_ORGANIZATION" && -n "$BUILDKITE_ACCOUNT" ]]; then
8 | BUILDKITE_ORGANIZATION="$BUILDKITE_ACCOUNT"
9 |
10 | echo "Warning: \$BUILDKITE_ACCOUNT is deprecated, please use \$BUILDKITE_ORGANIZATION"
11 | echo
12 | fi
13 |
14 | if [[ -z "$BUILDKITE_ORGANIZATION" && -n "$(git config buildkite.account)" ]]; then
15 | BUILDKITE_ORGANIZATION="$(git config buildkite.account)"
16 |
17 | echo "Warning: Using buildkite.account in your Git config is deprecated, please use buildkite.organization:"
18 | echo
19 | echo " git config --local buildkite.organization $(printf "%q" "$BUILDKITE_ORGANIZATION")"
20 | echo " git config --unset buildkite.account"
21 | echo
22 | fi
23 |
24 | BUILDKITE_API_KEY="${BUILDKITE_API_KEY:-$(git config buildkite.apikey)}"
25 |
26 | # Store your API key in your macOS keychain:
27 | if [[ -z "$BUILDKITE_API_KEY" ]] && which security >/dev/null 2>&1; then
28 | # Try a per-org token first:
29 | # security add-generic-password -s api.buildkite.com/v2/organizations/YOUR-ORGANIZATION -a YOUR-USERNAME -w
30 | BUILDKITE_API_KEY="${BUILDKITE_API_KEY:-$(security find-generic-password -s "api.buildkite.com/v2/organizations/${BUILDKITE_ORGANIZATION}" -w 2>/dev/null)}"
31 |
32 | # Fall back to a global token:
33 | # security add-generic-password -s api.buildkite.com -a YOUR-USERNAME -w
34 | if [[ -z "$BUILDKITE_API_KEY" ]]; then
35 | BUILDKITE_API_KEY="${BUILDKITE_API_KEY:-$(security find-generic-password -s api.buildkite.com -w 2>/dev/null)}"
36 | fi
37 | fi
38 |
39 | # Store your API key in your keyring:
40 | # pip install keyring
41 | if [[ -z "$BUILDKITE_API_KEY" ]] && which keyring > /dev/null 2>&1; then
42 | # Try a per-org token first:
43 | # keyring set "api.buildkite.com/v2/organizations/${BUILDKITE_ORGANIZATION}" "$USER"
44 | if [[ -z "$BUILDKITE_API_KEY" ]] && which keyring > /dev/null 2>&1; then
45 | BUILDKITE_API_KEY="$(keyring get "api.buildkite.com/v2/organizations/${BUILDKITE_ORGANIZATION}" "$USER")"
46 | fi
47 |
48 | # Fall back to a global token:
49 | # keyring set api.buildkite.com "$USER"
50 | BUILDKITE_API_KEY="$(keyring get api.buildkite.com "$USER")"
51 | fi
52 |
53 | BUILDKITE_PIPELINE="${BUILDKITE_PIPELINE:-$(git config buildkite.pipeline)}"
54 |
55 | if [[ -z "$BUILDKITE_PIPELINE" && -n "$BUILDKITE_PROJECT" ]]; then
56 | BUILDKITE_PIPELINE="$BUILDKITE_PROJECT"
57 |
58 | echo "Warning: \$BUILDKITE_PROJECT is deprecated, please use \$BUILDKITE_PIPELINE"
59 | echo
60 | fi
61 |
62 | if [[ -z "$BUILDKITE_PIPELINE" && -n "$(git config buildkite.project)" ]]; then
63 | BUILDKITE_PIPELINE="$(git config buildkite.project)"
64 |
65 | echo "Warning: Using buildkite.project in your Git config is deprecated, please use buildkite.pipeline:"
66 | echo
67 | echo " git config --local buildkite.pipeline $(printf "%q" "$BUILDKITE_PIPELINE")"
68 | echo " git config --unset buildkite.project"
69 | echo
70 | fi
71 |
72 | function current_revision() {
73 | git rev-parse HEAD
74 | }
75 |
76 | function current_branch() {
77 | if [[ -f .git/rebase-merge/head-name ]]; then
78 | # We're rebasing, use the rebased head's name (without refs/heads/ prefix)
79 | echo "$(head="$(cat .git/rebase-merge/head-name)"; echo "${head#refs/heads/}")"
80 | else
81 | # Let git read the HEAD for a symbolic ref (the current branch)
82 | git symbolic-ref --short HEAD 2> /dev/null
83 | fi
84 | }
85 |
86 | function current_branch_exists() {
87 | [ "$(current_branch)" ]
88 | }
89 |
90 | function remote_upstream_branch() {
91 | git rev-parse --symbolic-full-name --abbrev-ref @{u} 2> /dev/null
92 | }
93 |
94 | function upstream_branch() {
95 | remote_and_branch=$(remote_upstream_branch)
96 | branch="${remote_and_branch#*/}"
97 | echo "$branch"
98 | }
99 |
100 | function upstream_branch_exists() {
101 | [ "$(upstream_branch)" ]
102 | }
103 |
104 | function remote_current_branch() {
105 | [ "$(current_branch)" ] && echo "origin/$(current_branch)"
106 | }
107 |
108 | function remote_current_branch_revision() {
109 | [ "$(remote_current_branch)" ] &&
110 | git rev-parse --short "$(remote_current_branch)"
111 | }
112 |
113 | function remote_current_branch_current() {
114 | [ "$(remote_current_branch)" ] &&
115 | git rev-parse --verify --quiet "$(remote_current_branch)" > /dev/null &&
116 | [ "$(git rev-parse "$(current_branch)")" == "$(git rev-parse "$(remote_current_branch)")" ]
117 | }
118 |
119 | function remote_current_branch_behind() {
120 | [ "$(remote_current_branch)" ] &&
121 | git rev-parse --verify --quiet "$(remote_current_branch)" > /dev/null &&
122 | git merge-base --is-ancestor "$(remote_current_branch)" "$(current_branch)"
123 | }
124 |
125 | function confirm() {
126 | read -p "$1 [Y/n] " -n 1 -r
127 | echo
128 | [[ "$REPLY" =~ ^[Yy]$ || "$REPLY" == "" ]]
129 | }
130 |
131 | function current_head_on_remote() {
132 | [ -z "$(git rev-list $1..$(current_revision))" ]
133 | }
134 |
135 | function current_head_dirty() {
136 | [ "$(git status --short)" ]
137 | }
138 |
139 | function build() {
140 | # XXX: There seems no nice way to make curl give us a non-zero
141 | # status code and the response body on error, so we have to check
142 | # the content for errors later.
143 | curl -# "https://api.buildkite.com/v2/organizations/${BUILDKITE_ORGANIZATION}/pipelines/${BUILDKITE_PIPELINE}/builds" \
144 | -A "git-buildkite/${VERSION}" \
145 | -H "Authorization: Bearer ${BUILDKITE_API_KEY}" \
146 | -X POST \
147 | -F "branch=$1" \
148 | -F "commit=$2" \
149 | -F "message=$(git log --format=%B -n 1 "$2")" \
150 | -F "ignore_pipeline_branch_filters=true" \
151 | -F "meta_data[personal]=true" \
152 | -F "meta_data[user_name]=$(git config user.name)" \
153 | -F "meta_data[user_email]=$(git config user.email)"
154 | }
155 |
156 | function result_error() {
157 | [[ -z "$(result_build_url "$1")" ]]
158 | }
159 |
160 | function result_api_key_error() {
161 | grep -q api_key <<< "$1"
162 | }
163 |
164 | function result_build_url() {
165 | ruby -rjson -e 'print JSON.parse(STDIN.read)["web_url"].to_s rescue ""' <<< "$1"
166 | }
167 |
168 | if [ -z "$BUILDKITE_API_KEY" ]; then
169 | echo "You need to create a new Buildkite api key with the 'Modify Builds (write_builds)' scope. You can do that here:"
170 | echo
171 | echo " https://buildkite.com/user/api-access-tokens"
172 | echo
173 | echo "Then stick it in git:"
174 | echo
175 | echo " git config --global buildkite.apikey "
176 | echo
177 |
178 | exit 1
179 | fi
180 |
181 | if [ -z "$BUILDKITE_ORGANIZATION" -o -z "$BUILDKITE_PIPELINE" ]; then
182 | echo "You need to configure your Buildkite organization and pipeline name. They're in the URL when you visit your pipeline page. For example:"
183 | echo
184 | echo " https://buildkite.com/mycompany/mypipeline"
185 | echo
186 | echo "corresponds an organization named \"mycompany\" and pipeline named \"mypipeline\". Then stick it in git:"
187 | echo
188 | echo " git config --local buildkite.organization \"mycompany\""
189 | echo " git config --local buildkite.pipeline \"mypipeline\""
190 | echo
191 |
192 | exit 1
193 | fi
194 |
195 | # Parse flags before parsing arguments
196 | let i=1
197 | while [[ $i -le $# ]]; do
198 | case "${!i}" in
199 | "--browse")
200 | browse=yes
201 | shift $i
202 | esac
203 | let i=i+1
204 | done
205 |
206 | if [[ "$#" -eq 0 ]]; then
207 | if ! current_branch_exists; then
208 | echo "Woops, you don't seem to be on a branch." >&2
209 | echo
210 | echo "Try:"
211 | echo
212 | echo " git checkout -b my_branch HEAD"
213 | echo
214 | echo "And make sure you push it first:"
215 | echo
216 | echo " git push --set-upstream origin my_branch"
217 | echo
218 |
219 | exit 1
220 | fi
221 |
222 | if upstream_branch_exists; then
223 | echo "$(current_branch) exists on origin as $(upstream_branch)"
224 | echo
225 |
226 | BUILDKITE_BRANCH="$(upstream_branch)"
227 | else
228 | if remote_current_branch_current; then
229 | echo "$(current_branch) also exists on origin at the same commit, I'll assume that's what we're building."
230 | echo
231 |
232 | BUILDKITE_BRANCH="$(current_branch)"
233 | elif remote_current_branch_behind; then
234 | echo "$(current_branch) also exists on origin, but it's at an earlier commit:"
235 | echo
236 | echo " $(git show --oneline --no-patch $(remote_current_branch_revision))"
237 | echo
238 | echo "This will only work if the commit is in another ref on origin."
239 | echo
240 |
241 | if confirm "Do you want to try anyway?"; then
242 | echo
243 | echo "Yeah, I trust you."
244 | echo
245 |
246 | BUILDKITE_BRANCH="$(current_branch)"
247 | else
248 | echo
249 | echo "Cautious player, I like that."
250 | echo
251 | echo "Try pushing your branch first:"
252 | echo
253 | echo " git push origin $(current_branch)"
254 | echo
255 | exit 1
256 | fi
257 | else
258 | echo "Woops, I can't figure out what this branch is called on origin."
259 | echo
260 | echo "Have you pushed it? Try:"
261 | echo
262 | echo " git push --set-upstream origin $(current_branch)"
263 | echo
264 | echo "Otherwise maybe you just need to set the upstream for this branch:"
265 | echo
266 | echo " git branch --set-upstream-to origin/$(current_branch)"
267 | echo
268 |
269 | exit 1
270 | fi
271 | fi
272 |
273 | if ! current_head_on_remote $BUILDKITE_BRANCH; then
274 | echo "Woops, I can't see this commit on origin."
275 | echo
276 | echo "Have you pushed it? Try:"
277 | echo
278 | echo " git push --set-upstream origin $(current_branch)"
279 | echo
280 |
281 | if confirm "Do you want to try anyway?"; then
282 | echo
283 | echo "Yeah, let's do this."
284 | echo
285 | else
286 | echo
287 | echo "Okay, let me know when you're ready!"
288 | echo
289 | exit 1
290 | fi
291 | fi
292 |
293 | if current_head_dirty; then
294 | echo "Woops, it looks like your working tree is dirty:"
295 | echo
296 | git status
297 | echo
298 | echo "If you want to build with these changes you should commit and push them first."
299 | echo
300 |
301 | if confirm "Do you want to try anyway?"; then
302 | echo
303 | echo "Yeah, let's do this."
304 | echo
305 | else
306 | echo
307 | echo "Okay, let me know when you're ready!"
308 | echo
309 | exit 1
310 | fi
311 | fi
312 |
313 | BUILDKITE_COMMIT="$(current_revision)"
314 |
315 | elif [[ "$#" -eq 1 ]]; then
316 | # git buildkite
317 |
318 | BUILDKITE_BRANCH="$1"
319 |
320 | BUILDKITE_COMMIT="$(git rev-parse "$BUILDKITE_BRANCH")" || ( echo "Woops, I can't figure out what commit to build from that branch"; exit 1 )
321 |
322 | elif [[ "$#" -eq 2 ]]; then
323 | # git buildkite
324 |
325 | if [[ "$1" == "HEAD" || "$1" == "@" ]]; then
326 | # "@" or "HEAD" are aliases for building the current branch at a specified commit.
327 | BUILDKITE_BRANCH="$(current_branch)"
328 |
329 | if ! current_branch_exists; then
330 | echo "Woops, you don't seem to be on a branch." >&2
331 | echo
332 | echo "Try:"
333 | echo
334 | echo " git checkout -b my_branch HEAD"
335 | echo
336 | echo "And make sure you push it first:"
337 | echo
338 | echo " git push --set-upstream origin my_branch"
339 | echo
340 |
341 | exit 1
342 | fi
343 | else
344 | BUILDKITE_BRANCH="$1"
345 | fi
346 |
347 | # rev-parse the commit so we can do things like HEAD~2 or :/history
348 | BUILDKITE_COMMIT="$(git rev-parse "$2")"
349 | else
350 | echo "Usage: git buildkite [branch [commit]]"
351 | fi
352 |
353 | echo "Starting a build for $BUILDKITE_BRANCH at $BUILDKITE_COMMIT"
354 |
355 | result="$(build "$BUILDKITE_BRANCH" "$BUILDKITE_COMMIT")"
356 |
357 | if result_error "$result"; then
358 | echo
359 | echo "Woops, Buildkite didn't like that:"
360 | echo
361 | echo "$result"
362 | echo
363 |
364 | if result_api_key_error "$result"; then
365 | echo "Sounds like it might be your API key Check it out:"
366 | echo
367 | echo " https://buildkite.com/user/api-access-tokens"
368 | echo
369 | echo "Here's what we've got:"
370 | echo
371 | echo " $BUILDKITE_API_KEY"
372 | echo
373 | echo "Change it with:"
374 | echo
375 | echo " git config --global buildkite.apikey "
376 | echo
377 | fi
378 |
379 | exit 1
380 | fi
381 |
382 | build_url="$(result_build_url "$result")"
383 |
384 | echo
385 | echo "Success! Watch it go:"
386 | echo
387 | echo " ${build_url}"
388 | echo
389 |
390 | # Open a browser if we can
391 | if [[ -n "$browse" ]]; then
392 | if which open >/dev/null 2>&1; then
393 | open "${build_url}"
394 | elif which xdg-open >/dev/null 2>&1; then
395 | xdg-open "${build_url}"
396 | else
397 | echo "Sorry but I don't know how to open your browser! Copy and paste the link above as you like."
398 | fi
399 | fi
400 |
--------------------------------------------------------------------------------