-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathupload.ros
executable file
·285 lines (230 loc) · 9.91 KB
/
upload.ros
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
(ros:ensure-asdf)
#+quicklisp
(ql:quickload '(log4cl
ngrok/slynk)
:silent t))
(defpackage :ros.script.upload
(:use :cl))
(in-package :ros.script.upload)
(defvar *current-dir*)
(define-condition unable-to-proceed (simple-error)
((message :initarg :message
:reader get-message))
(:report (lambda (condition stream)
(format stream (get-message condition)))))
(define-condition subprocess-error-with-output (uiop::subprocess-error)
((stdout :initarg :stdout :reader subprocess-error-stdout)
(stderr :initarg :stderr :reader subprocess-error-stderr))
(:report (lambda (condition stream)
(format stream "Subprocess ~@[~S~% ~]~@[with command ~S~% ~]exited with error~@[ code ~D ~]~@[~%STDOUT:~% ~S~]~@[~%STDERR:~% ~S~]"
(uiop:subprocess-error-process condition)
(uiop:subprocess-error-command condition)
(uiop:subprocess-error-code condition)
(subprocess-error-stdout condition)
(subprocess-error-stderr condition)))))
(defun run (command &key (raise t))
"Runs command and returns it's stdout stderr and code.
If there was an error, raises subprocess-error-with-output, but this
behaviour could be overriden by keyword argument ``:raise t``."
(multiple-value-bind (stdout stderr code)
(uiop:run-program command
:output '(:string :stripped t)
:error-output '(:string :stripped t)
:ignore-error-status t)
(when (and raise
(not (eql code 0)))
(error 'subprocess-error-with-output
:stdout stdout
:stderr stderr
:code code
:command command))
(values stdout stderr code)))
(defun gh-pages-repository-initialized-p (docs-dir)
"Checks if repository for documentation already initialized"
(uiop:directory-exists-p (uiop:merge-pathnames* #P".git/"
docs-dir)))
(defun git (&rest commands)
"Calls git command in gh-pages repository."
(uiop:with-current-directory (*current-dir*)
(let ((command (apply #'concatenate 'string
"git "
commands)))
(log:info "Running" command "in" *current-dir*)
(run command))))
(defun git-repository-was-changed-p ()
;; Here we only interested in entries which are starting from 1 (changed in porcelain v2 format).
;; And not in qlfile and qlfile.lock.
;; The "cat" at the end is to make 0 status code if there is no changed files.
;; Because we only want an output from grep.
(> (length (git "status --porcelain=v2 | grep '^1' | grep -v -e qlfile -e .github/workflows | cat"))
0))
(defun get-git-upstream ()
;; taken from http://stackoverflow.com/a/9753364/70293
(let ((upstream (run "git rev-parse --abbrev-ref --symbolic-full-name @{u}" :raise nil)))
(when (> (length upstream)
0)
(subseq upstream
0
(search "/" upstream)))))
(defun get-origin-to-push ()
(cond
;; If we are running inside github actions
((uiop:getenv "GITHUB_ACTIONS")
(unless (uiop:getenv "GITHUB_TOKEN")
(error 'unable-to-proceed
:message "Please, provide GITHUB_TOKEN environment variable."))
(format nil "https://~A:[email protected]/~A"
(uiop:getenv "GITHUB_ACTOR")
(uiop:getenv "GITHUB_TOKEN")
(uiop:getenv "GITHUB_REPOSITORY")))
;; otherwise make it from travis secret token and repo slug
((uiop:getenv "TRAVIS_REPO_SLUG")
(let ((repo-slug (uiop:getenv "TRAVIS_REPO_SLUG"))
(repo-token (uiop:getenv "GH_REPO_TOKEN")))
(unless (and repo-slug repo-token)
(error 'unable-to-proceed
:message "Current branch does not track any upstream and there is no TRAVIS_REPO_SLUG and GH_REPO_TOKEN env variables. Where to push gh-pages branch?"))
(format nil "https://[email protected]/~A"
repo-token
repo-slug)))
;; If there is already some remote upstream, then use it
(t
(let ((upstream (get-git-upstream)))
(cond
(upstream
(run (concatenate 'string "git remote get-url " upstream)))
(t
(log:error "Unable to guess correct upstream URL")
(values nil)))))))
(defun push-gh-pages (docs-dir)
(log:info "Pushing changes to gh-pages branch")
(let ((*current-dir* docs-dir))
(unless (gh-pages-repository-initialized-p docs-dir)
(git "init")
(git "remote add origin "
(get-origin-to-push)))
(git "add .")
(cond
((git-repository-was-changed-p)
(when (uiop:getenv "GITHUB_ACTIONS")
(git "config --global user.name \"github-actions[bot]\"")
(git "config --global user.email \"[email protected]\""))
(git "commit -m 'Update docs'")
(git "push --force origin master:gh-pages"))
;; or
(t (log:info "Everything is up to date."))))
(values))
(defun empty-string-to-nil (value)
(when (and value
(not (string= value "")))
value))
(defun get-head-ref ()
(or
;; This should work when we are processing a pull-request
(empty-string-to-nil
(uiop:getenv "GITHUB_HEAD_REF"))
;; And this one for pushes to a branch
(let ((full-ref (empty-string-to-nil
(uiop:getenv "GITHUB_REF")))
(prefix "refs/heads/"))
(when (and full-ref
(search prefix full-ref))
(subseq full-ref
(length prefix))))))
(defun push-local-changes ()
"Some documentation builders, like MGL-PAX or 40ANTS-DOC
can update README file as well. In this case, we need
to push the file into the current branch of the repository."
(let ((*current-dir* (probe-file #P"")))
(macrolet ((if-there-are-changes (&body body)
`(cond
((git-repository-was-changed-p)
(log:info "Pushing local changes to the repository")
,@body)
(t
(log:info "There is no local changes.")))))
(flet ((make-commit ()
(git "add -u")
;; We don't want to commit changes to qlfile,
;; because documentation builders might change them:
(git "reset -- qlfile*")
;; If a project can change github workflows
;; during the build, like https://github.com/40ants/ci
;; does, then we need to reset it, because
;; GitHub prohibits workflow changes from the action.
(git "reset -- .github/workflows")
(when (uiop:getenv "GITHUB_ACTIONS")
(git "config --global user.name \"github-actions[bot]\"")
(git "config --global user.email \"[email protected]\""))
(git "commit -m 'Update docs'")))
(let ((head-ref (get-head-ref)))
(cond
((and head-ref
;; For push events GITHUB_HEAD_REF env variable
;; can be present but set to an empty string.
(not (string= head-ref "")))
;; Inside github action we are running on
;; detached commit. Github takes last commit
;; from the "master" branch and merges
;; a branch from pull-request settings.
;;
;; To push changes back, we need to change
;; our HEAD back to the pull-request's reference:
(git "checkout " head-ref)
;; Here we need to check again if
(if-there-are-changes
(make-commit)
(git "remote add upstream "
(get-origin-to-push))
(git "push upstream HEAD:" head-ref)))
(t
(if-there-are-changes
(make-commit)
(git "push"))))))))
(values))
(defun main (&rest argv)
(let ((event-name (uiop:getenv "GITHUB_EVENT_NAME")))
(when (and event-name
(string-equal event-name "pull_request"))
;; We don't want to upload docs when they are built in pull requests.
;; Only push to master branch should lead to documentation update.
(log:info "Skipping upload because action is used on pull_request.")
(uiop:quit 0)))
(log:info "Uploading documentation")
(unless argv
(log:error "Please, specify a directory with HTML docs.")
(uiop:quit 1))
(let ((docs-dir (uiop:parse-unix-namestring (first argv)
:ensure-directory t)))
(handler-bind ((error (lambda (condition)
(uiop:print-condition-backtrace condition :stream *error-output*)
(uiop:quit 1))))
(unless (probe-file docs-dir)
(log:error "Directory \"~A not found"
docs-dir)
(uiop:quit 1))
(uiop:with-output-file (s (uiop:merge-pathnames* #P".nojekyll"
docs-dir)
:if-exists :overwrite)
(declare (ignorable s)))
(unless (string= (or (uiop:getenv "NGROK_AUTH_TOKEN")
;; If var is not given, uiop will return NIL,
;; but inside github action, not required arguments
;; are empty strings and env var will be empty string.
"")
"")
(let ((url (ngrok/slynk:start 4005)))
(when url
(log:info "Waiting for connection to ~A" url)
(log:info "do touch ~~/continue to let process continue" )
(loop
until (probe-file "~/continue")
do (sleep 5)))))
(push-gh-pages docs-dir)
(push-local-changes))))