-
Notifications
You must be signed in to change notification settings - Fork 33
/
Rakefile
362 lines (329 loc) · 10.9 KB
/
Rakefile
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
require 'rake'
require 'erb'
$my_verbose = 1
$my_verbose = false if $my_verbose == 0
desc "Update the dotfiles in the user's home directory"
task :update => [:pull, :sync_submodules, :update_submodules] do
end
desc "Pull new changes, via `git pull`"
task :pull do
puts "Pulling.." if $my_verbose
system %Q{git pull} or raise "Git pull failed."
end
desc "Sync submodules, via `git submodule sync`"
task :sync_submodules do
puts "Syncing submodules.." if $my_verbose
system %Q{git submodule --quiet sync 2>&1} or raise "Git submodule sync failed."
end
desc "Update all submodules"
task :update_submodules do
submodules = get_submodule_status
puts "Updating submodules.." if $my_verbose
git_sm_has_recursive = true # until proven otherwise
# Get submodule summary and remove any submodules with _new_ commits from
# the list to be updated.
sm_summary = %x[git submodule summary]
if not $?.success?
raise sm_summary
end
sm_path = nil
sm_summary.split("\n").each do |line|
if line =~ /^\* (.*?) \w+\.\.\.\w+/
sm_path = $1
elsif sm_path and line =~ /^ >/
puts "Skipping submodule #{sm_path}, which is ahead locally (new commits)."
submodules.delete(sm_path)
sm_path = nil
end
end
i = 0
n = submodules.length
begin
while true
break if i == n
path = submodules.keys[i]
sm = submodules[path]
i+=1
if $my_verbose
cols = `stty size`.scan(/\d+/).map { |s| s.to_i }[1]
s = "[#{i}/#{n}] Updating #{path}.."
print "\r" + s.ljust(cols, ' ')
end
if git_sm_has_recursive
sm_update = %x[git submodule update --init --recursive #{path} 2>&1]
if not $?.success?
if sm_update.start_with?("Usage: ")
git_sm_has_recursive = false
end
end
end
if not git_sm_has_recursive
sm_update = %x[git submodule update --init #{path} 2>&1]
end
puts sm_update if $my_verbose and sm_update != ""
output = sm_update.split("\n")
if sm_update =~ /^Unable to checkout '(\w+)' in submodule path '(.*?)'$/
if output.index('Please, commit your changes or stash them before you can switch branches.')
puts "Stashing changes in #{path}"
if submodules[path]['stashed']
raise "Already stashed #{path}!"
end
stash_output = %x[ cd '#{path}' && git stash save 'Stashed for `rake update` at #{Time.new.strftime("%Y-%m-%d %H:%M:%S")}.' ]
if not $?.success?
raise "ERROR when stashing:\n" + stash_output
end
submodules[path]['stashed'] = true
i -= 1
next
end
github_user = get_github_repo_user
if github_user == ""
puts "No GitHub repo user found to add a remote for/from. Skipping."
next
end
# check for github_user's remote, and maybe add it and then retry
remotes = %x[git --git-dir '#{path}/.git' remote]
if remotes =~ /^#{github_user}$/
puts "Remote '#{github_user}' exists already." if $my_verbose
else
puts "Adding remote '#{github_user}'." if $my_verbose
output = %x[cd #{path} && hub remote add #{github_user} 2>&1]
if not $?.success?
puts "Failed to add submodule:\n" + output
next
end
end
puts "Fetching remote '#{github_user}'." if $my_verbose
output = %x[cd #{path} && git fetch #{github_user} -v 2>&1]
if not $?.success?
puts "Failed to fetch submodule:\n" + output
next
end
output = output.split("\n")
if output.index(' = [up to date] master -> blueyed/master')
puts "ERROR: blueyed/master already up to date. Something wrong. Skipping.\n\t" + output.join("\n\t")
next
end
puts "Retrying.." if verbose
i -= 1
end
end
rescue Exception => exc
puts "Exception: " + exc.message
puts exc.backtrace.join("\n")
ensure
submodules.each do |sm_path,sm|
if sm['stashed'] == true
puts "Unstashing #{sm_path}" if verbose
stash_output = %x[cd '#{sm_path}' && git stash pop 2>&1]
if not $?.success?
puts "ERROR when popping stash for #{sm_path}:\n" + stash_output
end
sm['stashed'] = false
end
end
end
# TODO: update/add new symlinks
end
desc "Generate diff files for repo and any modified submodules"
task :diff do
puts "Writing dotfiles.diff"
%x[git diff --ignore-submodules=all > dotfiles.diff]
get_submodule_status.each do |path,sm|
puts "Diffing #{path}.." if verbose
diff = %x[ cd #{path} && git diff ]
if diff.length > 0
diffname = "#{File.basename(path)}.diff"
puts "Writing #{diffname}"
File.open(diffname, "w").write(diff)
end
end
end
desc "Upgrade submodules to current master"
task :upgrade => [:update_submodules] do
ignore_modified = true
system %Q{ git diff --cached --exit-code > /dev/null } or raise "The git index is not clean."
submodules = {}
get_submodule_status.each do |path, sm|
if ignore_modified && sm["state"] == "+"
puts "Skipping modified submodule #{path}."
next
end
if sm["state"] == "-"
puts "Skipping uninitialized submodule #{path}."
next
end
submodules[path] = sm
end
begin
submodules.each do |path,sm|
puts "Upgrading #{path}.." if $my_verbose
# puts "Fetching all remotes" if $my_verbose
output = %x[cd #{path} && git fetch --all]
sm_url = %x[git config --get submodule.#{path}.url].chomp
puts "Fetching #{path} from #{sm_url}" if $my_verbose
output = %x[cd #{path} && git fetch #{sm_url} 2>&1]
puts output if $my_verbose and $my_verbose > 1
if not $?.success?
raise "Fetching failed: " + output
end
# Check that current commit is ancestor of FETCH_HEAD
# sm_commit = %x[cd #{path} && git rev-parse #{sm["commit"]}].chomp
sm_commit = sm['commit']
merge_base = %x[cd #{path} && git merge-base #{sm_commit} FETCH_HEAD].chomp
if sm_commit != merge_base
# puts "Skipping #{path}: Current commit does not appear to be ancestor of FETCH_HEAD."
# puts "Info: sm_commit: #{sm_commit}, merge_base: #{merge_base}"
# next
output = %x[cd #{path} && git merge FETCH_HEAD]
puts "Merged FETCH_HEAD:\n" + output
end
output = %x[cd #{path} && git merge --ff --ff-only FETCH_HEAD 2>&1]
if ! output.split("\n")[-1] =~ /^Already up-to-date.( Yeeah!)?/
puts output
else
puts output if $my_verbose and $my_verbose > 1
# TODO: pull result
end
if not $?.success?
puts $?
if output =~ /^error: Your local changes to the following files would be overwritten by merge:/
output = %x[cd #{path} && git stash save "Stashed by rake upgrade" 2>&1 && git merge --ff --ff-only FETCH_HEAD 2>&1 && git stash pop 2>&1]
puts $?
puts output if $my_verbose and $my_verbose > 1
end
if not $?.success?
submodules.delete(path)
raise "Merging FETCH_HEAD failed: " + output
end
end
next
# 1. get available remotes
# 2. find newest one, via
# git --describe always
# git branch (-a) --contains $DESC
# get available master branches via gb -r
# remotes = %x[git --git-dir '#{path}/.git' remote]
# if not $?.success?
# raise "Pulling failed: " + output
# end
#
output = output.split("\n")
# Output important lines
puts output.select{|x| x=~/^Your branch is/}
end
rescue Exception => exc
puts "Exception: " + exc.message
puts exc.backtrace.join("\n")
ensure
Rake::Task[:commitsubmodules].invoke(submodules)
end
end
desc "Commit modified submodules"
task :commitsubmodules, :submodules do |t, args|
# Commit any updated modules
if not args.submodules
# only call the method on demand; there might be a ruby way to do this anyway..
args.with_defaults(:submodules => get_modified_submodules)
end
get_submodule_status( args.submodules.keys.join(" ") ).each do |path, sm|
next if sm["state"] != "+"
status = %x[ cd '#{path}' && git status -b -s ]
if status =~ /^## master...origin\/master \[ahead/
puts "WARNING: #{path} appears to be ahead of origin. You might need to push it."
puts "Skipping commit.."
next
end
output = %x[ zsh -i -c 'gsmc #{path}' ]
puts output
end
# %x[ git submodule foreach "git pull origin master && git co master" ]
# Commit any new modules
end
desc "install the dot files into user's home directory"
task :install do
base = ENV['HOME']
if not base
puts "Fatal error: no base path given.\nPlease make sure that HOME is set in your environment.\nAborting."
exit 1
end
$replace_all = false
# TODO: do not install all files, see INSTALL_FILES in Makefile
Dir['*'].each do |file|
next if %w[Rakefile README.rdoc LICENSE].include? file
# Install files in "config" separately
if 'config' == file
Dir[File.join(file, '*')].each do |file|
clean_name = file.sub(/\.erb$/, '')
install_file(file, File.join(base, "."+clean_name))
end
else
clean_name = file.sub(/\.erb$/, '')
install_file(file, File.join(base, "."+clean_name))
end
end
end
def install_file(file, target)
nice_target = target.sub(/#{ENV['HOME']}/, '~') # for display: collapse "~"
if File.exist?(target)
if File.identical? file, target
puts "identical #{nice_target}"
elsif $replace_all
replace_file(file, target)
else
print "overwrite #{nice_target}? [ynaq] "
case $stdin.gets.chomp
when 'a'
$replace_all = true
replace_file(file, target)
when 'y'
replace_file(file, target)
when 'q'
exit
else
puts "skipping #{nice_target}"
end
end
else
link_file(file, target)
end
end
def replace_file(file, target)
system %Q{rm -rf "#{target}"}
link_file(file, target)
end
def link_file(file, target)
nice_target = target.sub(/#{ENV['HOME']}/, '~') # for display: collapse "~"
if file =~ /.erb$/
puts "generating #{nice_target}"
File.open(target, 'w') do |new_file|
new_file.write ERB.new(File.read(file)).result(binding)
end
else
puts "linking #{nice_target}"
system %Q{ln -sfn "$PWD/#{file}" "#{target}"}
end
end
def get_github_repo_user
return "blueyed"
# XXX: parse from "git remote" output, e.g. origin git://github.com/blueyed/dotfiles.git
# irrelevant: return %x[git config --get github.user].chomp
end
def get_submodule_status(sm_args='')
# return { 'vim/bundle/solarized' => {'state'=>' '} }
puts "Getting submodules status.." if $my_verbose
status = %x[ git submodule status #{sm_args} ] or raise "Getting submodule status failed"
r = {}
status.split("\n").each do |line|
if not line =~ /^([ +-])(\w{40}) (.*?)(?: \(.*\))?$/
raise "Found unexpected submodule line: #{line}"
end
path = $3
next if ! path
r[path] = {"state" => $1, "commit" => $2}
end
return r
end
def get_modified_submodules()
get_submodule_status.delete_if {|path, sm| sm["state"] != "+"}
end