forked from BrewPi/brewpi-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
updater.py
executable file
·429 lines (372 loc) · 17.7 KB
/
updater.py
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
#!/usr/bin/python
# Copyright 2013 BrewPi
# This file is part of BrewPi.
# BrewPi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# BrewPi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with BrewPi. If not, see <http://www.gnu.org/licenses/>.
### Geo Van O, v0.9, Sep 2013
### Elco Jacobs, sept 2013
import subprocess
from time import localtime, strftime
import sys
import os
import urllib2
import getopt
try:
import git
except ImportError:
print "This update script requires gitpython, please install it with 'sudo pip install gitpython"
sys.exit(1)
# Read in command line arguments
try:
opts, args = getopt.getopt(sys.argv[1:], "a", ['ask'])
except getopt.GetoptError:
print ("Unknown parameter, available options: \n" +
" updater.py --ask\t\t do not use default options, but ask me which branches to check out")
sys.exit()
userInput = False
for o, a in opts:
# print help message for command line options
if o in ('-a', '--ask'):
print "\nUsing interactive (advanced) update with user input \n\n"
userInput = True
### Quits all running instances of BrewPi
def quitBrewPi(webPath):
print "\nStopping running instances of BrewPi"
try:
import BrewPiProcess
allProcesses = BrewPiProcess.BrewPiProcesses()
allProcesses.stopAll(webPath+"/do_not_run_brewpi")
except:
pass # if we cannot stop running instances of the script, just continue. Might be a very old version
# remove do_not_run file
def startBrewPi(webPath):
filePath = webPath+"/do_not_run_brewpi"
if os.path.isfile(filePath):
os.remove(filePath)
### calls update-tools-repo, which returns 0 if the brewpi-tools repo is up-to-date
def checkForUpdates():
if os.path.exists(os.path.dirname(os.path.realpath(__file__)) + "/update-tools-repo.sh"):
try:
print "Checking whether the update script is up to date"
subprocess.check_call(["sudo", "bash", os.path.dirname(os.path.realpath(__file__)) + "/update-tools-repo.sh"],
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
print "This script was not up-to-date and has been automatically updated. Please re-run updater.py."
sys.exit(1)
else:
print "The required file update-this-repo.sh was not found. This is likely to occur if you manually copied updater.py here.\n"+ \
"Please run this from the original location you installed the brewpi-tools git repo and try again."
sys.exit(1)
### call installDependencies.sh, so commands are only defined in one place.
def runAfterUpdate(scriptDir):
try:
print "Installing dependencies, updating CRON and fixing file permissions..."
subprocess.check_call(["sudo", "bash", scriptDir + "/utils/runAfterUpdate.sh"], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
print "I tried to execute the runAfterUpdate.sh bash script, but an error occurred. " + \
"Try running it from the command line in your <brewpi-script>/utils dir"
### Stash any local repo changes
def stashChanges(repo):
print "\nYou have local changes in this repository, that are prevent a successful merge."
print "These changes can be stashed to bring your repository back to its original state so we can merge."
print "Your changes are not lost, but saved on the stash." +\
"You can (optionally) get them back later with 'git stash pop'."
choice = raw_input("Would you like to stash local changes? (Required to continue) [Y/n]: ")
if any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
print "Attempting to stash any changes...\n"
try:
repo.git.config('--get', 'user.name')
except git.GitCommandError, e:
print "Warning: No user name set for git, which is necessary to stash."
userName = raw_input("--> Please enter a global username for git on this system (just make something up): ")
repo.git.config('--global', 'user.name', userName)
try:
repo.git.config('--get', 'user.email')
except git.GitCommandError, e:
print "Warning: No user e-mail address set for git, which is necessary to stash."
userEmail = raw_input("--> Please enter a global user e-mail address for git on this system: ")
repo.git.config('--global', 'user.email', userEmail)
try:
resp = repo.git.stash()
print "\n" + resp + "\n"
print "Stash successful"
print "##################################################################"
print "#Your local changes were in conflict with the last update of code.#"
print "##################################################################"
print "The conflict was:\n"
print "-------------------------------------------------------"
print repo.git.stash("show", "--full-diff", "stash@{0}")
print "-------------------------------------------------------"
print ""
print ("To make merging possible, these changes were stashed." +
"To merge the changes back in, you can use 'git stash pop'."
"Only do this if you really know what you are doing!" +
"Your changes might be incompatible with the update or could cause a new merge conflict.")
return True
except git.GitCommandError, e:
print e
print "Unable to stash, don't want to overwrite your stuff, aborting this branch update"
return False
else:
print "Changes are not stashed, cannot continue without stashing. Aborting update"
return False
### Function used to stash local changes and update a branch passed to it
def update_repo(repo, remote, branch):
stashed = False
repo.git.fetch(remote, branch)
try:
print repo.git.merge(remote + '/' + branch)
except git.GitCommandError, e:
print e
if "Your local changes to the following files would be overwritten by merge" in str(e):
stashed = stashChanges(repo)
if not stashed:
return False
print "Trying to merge again..."
try:
print repo.git.merge(remote + '/' + branch)
except git.GitCommandError, e:
print e
print "Sorry, cannot automatically stash/discard local changes. Aborting"
return False
print branch + " updated!"
return True
### Function to be used to check most recent commit date on the repo passed to it
def check_repo(repo):
updated = False
localBranch = repo.active_branch.name
newBranch = localBranch
remoteRef = None
print "You are on branch " + localBranch
if not localBranch in ["master", "legacy"] and not userInput:
print "Your checked out branch is not master, our stable release branch."
choice = raw_input("It is highly recommended that you switch to the stable master branch."
" Would you like to do that? [Y/n]:")
if any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
print "Switching branch to master"
newBranch = "master"
### Get available remotes
remote = repo.remotes[0] # default to first found remote
if userInput and len(repo.remotes) > 1:
print "Multiple remotes found in " + repo.working_tree_dir
for i, rem in enumerate(repo.remotes):
print "[%d] %s" % (i, rem.name)
print "[" + str(len(repo.remotes)) + "] Skip updating this repository"
while 1:
try:
choice = raw_input("From which remote do you want to update? [%s]:" % remote)
if choice == "":
print "Updating from default remote %s" % remote
break
else:
selection = int(choice)
except ValueError:
print "Use the number!"
continue
if selection == len(repo.remotes):
return False # choice = skip updating
try:
remote = repo.remotes[selection]
except IndexError:
print "Not a valid selection. Try again"
continue
break
repo.git.fetch(remote.name, "--prune")
### Get available branches on the remote
try:
remoteBranches = remote.refs
except AssertionError as e:
print "Failed to get references from remote: " + repr(e)
print "Aborting update of " + repo.working_tree_dir
return False
if userInput:
print "\nAvailable branches on the remote '%s' for %s:" % (remote.name, repo.working_tree_dir)
for i, ref in enumerate(remoteBranches):
remoteRefName = "%s" % ref
if "/HEAD" in remoteRefName:
remoteBranches.pop(i) # remove HEAD from list
for i, ref in enumerate(remoteBranches):
remoteRefName = "%s" % ref
remoteBranchName = remoteRefName.replace(remote.name + "/", "")
if remoteBranchName == newBranch:
remoteRef = ref
if userInput:
print "[%d] %s" % (i, remoteBranchName)
if userInput:
print "[" + str(len(remoteBranches)) + "] Skip updating this repository"
while 1:
try:
choice = raw_input("Enter the number of the branch you wish to update [%s]:" % localBranch)
if choice == "":
print "Keeping current branch %s" % localBranch
break
else:
selection = int(choice)
except ValueError:
print "Use the number!"
continue
if selection == len(remoteBranches):
return False # choice = skip updating
try:
remoteRef = remoteBranches[selection]
except IndexError:
print "Not a valid selection. Try again"
continue
break
if remoteRef is None:
print "Could not find branch selected branch on remote! Aborting"
return False
remoteBranch = ("%s" % remoteRef).replace(remote.name + "/", "")
checkedOutDifferentBranch = False
if localBranch != remoteBranch:
choice = raw_input("The " + remoteBranch + " branch is not your currently active branch - " +
"would you like me to check it out for you now? (Required to continue) [Y/n]: ")
if any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
stashedForCheckout = False
while True:
try:
if remoteBranch in repo.branches:
print repo.git.checkout(remoteBranch)
else:
print repo.git.checkout(remoteRef, b=remoteBranch)
print "Successfully switched to " + remoteBranch
checkedOutDifferentBranch = True
break
except git.GitCommandError, e:
if not stashedForCheckout:
if "Your local changes to the following files would be overwritten by checkout" in str(e):
print "Local changes exist in your current files that need to be stashed to continue"
if not stashChanges(repo):
return
print "Trying to checkout again..."
stashedForCheckout = True # keep track of stashing, so it is only tried once
continue # retry after stash
else:
print e
print "I was unable to checkout. Please try it manually from the command line and re-run this tool"
return False
else:
print "Skipping this branch"
return False
if remoteRef is None:
print "Error: Could not determine which remote reference to use, aborting"
return False
localDate = repo.head.commit.committed_date
localDateString = strftime("%a, %d %b %Y %H:%M:%S", localtime(localDate))
localSha = repo.head.commit.hexsha
localName = repo.working_tree_dir
remoteDate = remoteRef.commit.committed_date
remoteDateString = strftime("%a, %d %b %Y %H:%M:%S", localtime(remoteDate))
remoteSha = remoteRef.commit.hexsha
remoteName = remoteRef.name
alignLength = max(len(localName), len(remoteName))
print "The latest commit in " + localName.ljust(alignLength) + " is " + localSha + " on " + localDateString
print "The latest commit on " + remoteName.ljust(alignLength) + " is " + remoteSha + " on " + remoteDateString
if localDate < remoteDate:
print "*** Updates are available! ****"
choice = raw_input("Would you like to update " + localName + " from " + remoteName + " [Y/n]: ")
if any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
updated = update_repo(repo, remote.name, remoteBranch)
else:
print "Your local version of " + localName + " is up to date!"
return updated or checkedOutDifferentBranch
print "######################################################"
print "#### ####"
print "#### Welcome to the BrewPi Updater! ####"
print "#### ####"
print "######################################################"
print ""
if os.geteuid() != 0:
print "This update script should be run as root."
print "Try running it gain with sudo, exiting..."
exit(1)
checkForUpdates()
print ""
changed = False
scriptPath = '/home/brewpi'
webPath = '/var/www'
print "\n\n*** Updating BrewPi script repository ***"
for i in range(3):
correctRepo = False
try:
scriptRepo = git.Repo(scriptPath)
gitConfig = open(scriptPath + '/.git/config', 'r')
for line in gitConfig:
if "url =" in line and "brewpi-script" in line:
correctRepo = True
break
gitConfig.close()
except git.NoSuchPathError:
print "The path '%s' does not exist" % scriptPath
scriptPath = raw_input("What path did you install the BrewPi python scripts to?")
continue
except (git.InvalidGitRepositoryError, IOError):
print "The path '%s' does not seem to be a valid git repository" % scriptPath
scriptPath = raw_input("What path did you install the BrewPi python scripts to?")
continue
if not correctRepo:
print "The path '%s' does not seem to be the BrewPi python script git repository" % scriptPath
scriptPath = raw_input("What path did you install the BrewPi python scripts to?")
continue
### Add BrewPi repo into the sys path, so we can import those modules as needed later
sys.path.insert(0, scriptPath)
quitBrewPi(webPath) # exit running instances of BrewPi
changed = check_repo(scriptRepo) or changed
break
else:
print "Maximum number of tries reached, updating BrewPi scripts aborted"
print "\n\n*** Updating BrewPi web interface repository ***"
for i in range(3):
correctRepo = False
try:
webRepo = git.Repo(webPath)
gitConfig = open(webPath + '/.git/config', 'r')
for line in gitConfig:
if "url =" in line and "brewpi-www" in line:
correctRepo = True
break
gitConfig.close()
except git.NoSuchPathError:
print "The path '%s' does not exist" % webPath
webPath = raw_input("What path did you install the BrewPi web interface scripts to? ")
continue
except (git.InvalidGitRepositoryError, IOError):
print "The path '%s' does not seem to be a valid git repository" % webPath
webPath = raw_input("What path did you install the BrewPi web interface scripts to? ")
continue
if not correctRepo:
print "The path '%s' does not seem to be the BrewPi web interface git repository" % webPath
webPath = raw_input("What path did you install the BrewPi web interface scripts to? ")
continue
changed = check_repo(webRepo) or changed
break
else:
print "Maximum number of tries reached, updating BrewPi web interface aborted"
if changed:
print "\nOne our more repositories were updated, running runAfterUpdate.sh from %s/utils..." % scriptPath
runAfterUpdate(scriptPath)
else:
print "\nNo changes were made, skipping runAfterUpdate.sh."
print "If you encounter problems, you can start it manually with:"
print "sudo %s/utils/runAfterUpdate.sh" % scriptPath
choice = raw_input("\nThe update script can automatically check your controller firmware version and " +
"program it with the latest release on GitHub, would you like to do this now? [Y/n]:")
if any(choice == x for x in ["", "yes", "Yes", "YES", "yes", "y", "Y"]):
# start as a separate python process, or it will not use the updated modules
p = subprocess.Popen("python {0} --silent".format(os.path.join(scriptPath, 'utils', 'updateFirmware.py')), shell=True)
p.wait()
result = p.returncode
if(result == 0):
print "Firmware update done"
else:
print "Skipping controller update"
startBrewPi(webPath)
print "\n\n*** Done updating BrewPi! ***\n"