gistore 1.0.0.rc4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +30 -0
- data/COPYING +340 -0
- data/README.md +98 -0
- data/exe/gistore +15 -0
- data/lib/gistore.rb +20 -0
- data/lib/gistore/cmd/add.rb +15 -0
- data/lib/gistore/cmd/checkout.rb +49 -0
- data/lib/gistore/cmd/commit.rb +171 -0
- data/lib/gistore/cmd/config.rb +23 -0
- data/lib/gistore/cmd/export-to-backups.rb +79 -0
- data/lib/gistore/cmd/gc.rb +15 -0
- data/lib/gistore/cmd/git-version.rb +14 -0
- data/lib/gistore/cmd/init.rb +36 -0
- data/lib/gistore/cmd/restore-from-backups.rb +91 -0
- data/lib/gistore/cmd/rm.rb +15 -0
- data/lib/gistore/cmd/safe-commands.rb +53 -0
- data/lib/gistore/cmd/status.rb +40 -0
- data/lib/gistore/cmd/task.rb +85 -0
- data/lib/gistore/cmd/version.rb +27 -0
- data/lib/gistore/config.rb +13 -0
- data/lib/gistore/config/gistore.yml +1 -0
- data/lib/gistore/error.rb +6 -0
- data/lib/gistore/repo.rb +683 -0
- data/lib/gistore/runner.rb +43 -0
- data/lib/gistore/templates/description +1 -0
- data/lib/gistore/templates/hooks/applypatch-msg.sample +15 -0
- data/lib/gistore/templates/hooks/commit-msg.sample +24 -0
- data/lib/gistore/templates/hooks/post-update.sample +8 -0
- data/lib/gistore/templates/hooks/pre-applypatch.sample +14 -0
- data/lib/gistore/templates/hooks/pre-commit.sample +49 -0
- data/lib/gistore/templates/hooks/pre-push.sample +54 -0
- data/lib/gistore/templates/hooks/pre-rebase.sample +169 -0
- data/lib/gistore/templates/hooks/prepare-commit-msg.sample +36 -0
- data/lib/gistore/templates/hooks/update.sample +128 -0
- data/lib/gistore/templates/info/exclude +6 -0
- data/lib/gistore/utils.rb +382 -0
- data/lib/gistore/version.rb +4 -0
- data/t/Makefile +80 -0
- data/t/README +745 -0
- data/t/aggregate-results.sh +46 -0
- data/t/lib-worktree.sh +76 -0
- data/t/t0000-init.sh +75 -0
- data/t/t0010-config.sh +75 -0
- data/t/t0020-version.sh +32 -0
- data/t/t1000-add-remove.sh +89 -0
- data/t/t1010-status.sh +87 -0
- data/t/t1020-commit.sh +134 -0
- data/t/t1030-commit-and-rotate.sh +266 -0
- data/t/t2000-task-and-commit-all.sh +132 -0
- data/t/t3000-checkout.sh +115 -0
- data/t/t3010-export-and-restore.sh +141 -0
- data/t/test-binary-1.png +0 -0
- data/t/test-binary-2.png +0 -0
- data/t/test-lib-functions.sh +722 -0
- data/t/test-lib.sh +684 -0
- metadata +161 -0
@@ -0,0 +1,382 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'gistore/error'
|
3
|
+
|
4
|
+
if RUBY_VERSION < '1.9'
|
5
|
+
require 'open4'
|
6
|
+
else
|
7
|
+
require 'open3'
|
8
|
+
end
|
9
|
+
|
10
|
+
module Gistore
|
11
|
+
|
12
|
+
class <<self
|
13
|
+
def git_cmd
|
14
|
+
@git_cmd ||= begin
|
15
|
+
git_path = which "git"
|
16
|
+
unless git_path
|
17
|
+
abort "Please install git first."
|
18
|
+
else
|
19
|
+
git_path.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def git_version
|
25
|
+
@git_version ||= begin
|
26
|
+
shellout(git_cmd, "--version",
|
27
|
+
:without_locale => true) do |io|
|
28
|
+
if io.read.strip =~ /^git version (.*)/
|
29
|
+
$1.split('.').map(&:to_i)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def git_version_compare(v1, v2=nil)
|
36
|
+
if v2
|
37
|
+
current_version = v1.is_a?(Array) ? v1.dup : v1.split('.').map(&:to_i)
|
38
|
+
check_version = v2.is_a?(Array) ? v2.dup : v2.split('.').map(&:to_i)
|
39
|
+
else
|
40
|
+
current_version = git_version.dup
|
41
|
+
check_version = v1.is_a?(Array) ? v1.dup : v1.split('.').map(&:to_i)
|
42
|
+
end
|
43
|
+
current_version.each do |current|
|
44
|
+
check = check_version.shift.to_i
|
45
|
+
result = current <=> check
|
46
|
+
return result if result != 0
|
47
|
+
end
|
48
|
+
check_version.shift ? -1 : 0
|
49
|
+
end
|
50
|
+
|
51
|
+
def which cmd, path=ENV['PATH']
|
52
|
+
dir = path.split(File::PATH_SEPARATOR).find {|p| File.executable? File.join(p, cmd)}
|
53
|
+
Pathname.new(File.join(dir, cmd)) unless dir.nil?
|
54
|
+
end
|
55
|
+
|
56
|
+
def shellout(*args, &block)
|
57
|
+
if Hash === args.last
|
58
|
+
options = args.pop.dup
|
59
|
+
else
|
60
|
+
options = {}
|
61
|
+
end
|
62
|
+
options[:stdout_only] = true
|
63
|
+
args << options
|
64
|
+
self.popen3(*args, &block)
|
65
|
+
end
|
66
|
+
|
67
|
+
def shellpipe(*args, &block)
|
68
|
+
if Hash === args.last
|
69
|
+
options = args.pop.dup
|
70
|
+
else
|
71
|
+
options = {}
|
72
|
+
end
|
73
|
+
args << options unless options.empty?
|
74
|
+
self.popen3(*args, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
def system(*args, &block)
|
78
|
+
fork do
|
79
|
+
block.call if block_given?
|
80
|
+
args.map!{|arg| arg.to_s}
|
81
|
+
exec(*args) rescue nil
|
82
|
+
# never gets here unless raise some error (exec failed)
|
83
|
+
exit! 1
|
84
|
+
end
|
85
|
+
Process.wait
|
86
|
+
$?.success?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Same like system but with exceptions
|
90
|
+
def safe_system(*args, &block)
|
91
|
+
unless Gistore.system(*args, &block)
|
92
|
+
args = args.map{ |arg| arg.to_s.gsub " ", "\\ " } * " "
|
93
|
+
raise CommandReturnError, "Failure while executing: #{args}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# prints no output
|
98
|
+
def quiet_system(*args)
|
99
|
+
Gistore.system(*args) do
|
100
|
+
# Redirect output streams to `/dev/null` instead of closing as some programs
|
101
|
+
# will fail to execute if they can't write to an open stream.
|
102
|
+
$stdout.reopen('/dev/null')
|
103
|
+
$stderr.reopen('/dev/null')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
if RUBY_VERSION < '1.9'
|
108
|
+
|
109
|
+
def popen3(*cmd, &block)
|
110
|
+
if Hash === cmd.last
|
111
|
+
options = cmd.pop.dup
|
112
|
+
else
|
113
|
+
options = {}
|
114
|
+
end
|
115
|
+
result = nil
|
116
|
+
pid, stdin, stdout, stderr = nil
|
117
|
+
begin
|
118
|
+
pid, stdin, stdout, stderr = Open4.popen4(*cmd)
|
119
|
+
if options[:stdout_only]
|
120
|
+
stdin.close
|
121
|
+
result = block.call(stdout) if block_given?
|
122
|
+
else
|
123
|
+
result = block.call(stdin, stdout, stderr) if block_given?
|
124
|
+
end
|
125
|
+
ignored, status = Process::waitpid2 pid
|
126
|
+
if options[:check_return] and status and status.exitstatus != 0
|
127
|
+
raise CommandReturnError.new("Command failed (return #{status.exitstatus}).")
|
128
|
+
end
|
129
|
+
rescue Exception => e
|
130
|
+
msg = strip_credential(e.message)
|
131
|
+
# The command failed, log it and re-raise
|
132
|
+
logmsg = "Command failed: #{msg}"
|
133
|
+
logmsg << "\n"
|
134
|
+
logmsg << " >> #{strip_credential(cmd)}"
|
135
|
+
if e.is_a? CommandReturnError
|
136
|
+
raise CommandReturnError.new(logmsg)
|
137
|
+
else
|
138
|
+
raise CommandExceptionError.new(logmsg)
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
[stdin, stdout, stderr].each {|io| io.close unless io.closed?}
|
142
|
+
end
|
143
|
+
result
|
144
|
+
end
|
145
|
+
|
146
|
+
else
|
147
|
+
|
148
|
+
def popen3(*cmd, &block)
|
149
|
+
if Hash === cmd.last
|
150
|
+
options = cmd.pop.dup
|
151
|
+
else
|
152
|
+
options = {}
|
153
|
+
end
|
154
|
+
|
155
|
+
result = nil
|
156
|
+
stdin, stdout, stderr, wait_thr = nil
|
157
|
+
begin
|
158
|
+
if options[:merge_stderr]
|
159
|
+
stdin, stdout, wait_thr = Open3.popen2e(*cmd)
|
160
|
+
else
|
161
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(*cmd)
|
162
|
+
end
|
163
|
+
if options[:stdout_only]
|
164
|
+
stdin.close
|
165
|
+
result = block.call(stdout) if block_given?
|
166
|
+
elsif options[:merge_stderr]
|
167
|
+
result = block.call(stdin, stdout) if block_given?
|
168
|
+
else
|
169
|
+
result = block.call(stdin, stdout, stderr) if block_given?
|
170
|
+
end
|
171
|
+
wait_thr.join
|
172
|
+
if (options[:check_return] and
|
173
|
+
wait_thr and wait_thr.value and
|
174
|
+
wait_thr.value.exitstatus != 0)
|
175
|
+
raise CommandReturnError.new("Command failed (return #{wait_thr.value.exitstatus}).")
|
176
|
+
end
|
177
|
+
rescue Exception => e
|
178
|
+
msg = strip_credential(e.message)
|
179
|
+
# The command failed, log it and re-raise
|
180
|
+
logmsg = "Command failed: #{msg}"
|
181
|
+
logmsg << "\n"
|
182
|
+
logmsg << " >> #{strip_credential(cmd)}"
|
183
|
+
if e.is_a? CommandReturnError
|
184
|
+
raise CommandReturnError.new(logmsg)
|
185
|
+
else
|
186
|
+
raise CommandExceptionError.new(logmsg)
|
187
|
+
end
|
188
|
+
ensure
|
189
|
+
[stdin, stdout, stderr].each {|io| io.close if io and not io.closed?}
|
190
|
+
end
|
191
|
+
result
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def strip_credential(message)
|
196
|
+
message
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_gistore_tasks(options={})
|
200
|
+
cmds = [git_cmd, "config"]
|
201
|
+
unless ENV["GISTORE_TEST_GIT_CONFIG"]
|
202
|
+
if options[:system]
|
203
|
+
cmds << "--system"
|
204
|
+
elsif options[:global]
|
205
|
+
cmds << "--global"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
cmds << "--get-regexp"
|
209
|
+
cmds << "gistore.task."
|
210
|
+
cmds << {:with_git_config => true} if ENV["GISTORE_TEST_GIT_CONFIG"]
|
211
|
+
tasks = {}
|
212
|
+
begin
|
213
|
+
Gistore::shellout(*cmds) do |stdout|
|
214
|
+
stdout.readlines.each do |line|
|
215
|
+
if line =~ /^gistore.task.([\S]+) (.*)$/
|
216
|
+
tasks[Regexp.last_match(1)] = Regexp.last_match(2)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
tasks
|
221
|
+
rescue
|
222
|
+
{}
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def is_git_repo?(name)
|
227
|
+
File.directory?("#{name}/objects") &&
|
228
|
+
File.directory?("#{name}/refs") &&
|
229
|
+
File.exist?("#{name}/config")
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def git_cmd; self.class.git_cmd; end
|
234
|
+
|
235
|
+
def git_version; self.class.git_version; end
|
236
|
+
|
237
|
+
def git_version_compare(version)
|
238
|
+
self.class.git_version_compare(version)
|
239
|
+
end
|
240
|
+
|
241
|
+
class Tty
|
242
|
+
class << self
|
243
|
+
def options
|
244
|
+
@options ||= {}
|
245
|
+
end
|
246
|
+
def blue; bold 34; end
|
247
|
+
def white; bold 39; end
|
248
|
+
def red; underline 31; end
|
249
|
+
def yellow; underline 33 ; end
|
250
|
+
def reset; escape 0; end
|
251
|
+
def em; underline 39; end
|
252
|
+
def green; color 92 end
|
253
|
+
def gray; bold 30 end
|
254
|
+
|
255
|
+
def width
|
256
|
+
@width = begin
|
257
|
+
w = %x{stty size 2>/dev/null}.chomp.split.last.to_i.nonzero?
|
258
|
+
w ||= %x{tput cols 2>/dev/null}.to_i
|
259
|
+
w < 1 ? 80 : w
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def truncate(str)
|
264
|
+
str.to_s[0, width - 4]
|
265
|
+
end
|
266
|
+
|
267
|
+
def die(message)
|
268
|
+
error message
|
269
|
+
exit 1
|
270
|
+
end
|
271
|
+
|
272
|
+
def error(message)
|
273
|
+
lines = message.to_s.split("\n")
|
274
|
+
if STDERR.tty?
|
275
|
+
STDERR.puts "#{Tty.red}Error#{Tty.reset}: #{lines.shift}"
|
276
|
+
else
|
277
|
+
STDERR.puts "Error: #{lines.shift}"
|
278
|
+
end
|
279
|
+
STDERR.puts lines unless lines.empty?
|
280
|
+
end
|
281
|
+
|
282
|
+
def warning(message)
|
283
|
+
if STDERR.tty?
|
284
|
+
STDERR.puts "#{Tty.red}Warning#{Tty.reset}: #{message}"
|
285
|
+
else
|
286
|
+
STDERR.puts "Warning: #{message}"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def info(message)
|
291
|
+
unless quiet?
|
292
|
+
if STDERR.tty?
|
293
|
+
STDERR.puts "#{Tty.blue}Info#{Tty.reset}: #{message}"
|
294
|
+
else
|
295
|
+
STDERR.puts "Info: #{message}"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def debug(message)
|
301
|
+
if verbose?
|
302
|
+
if STDERR.tty?
|
303
|
+
STDERR.puts "#{Tty.yellow}Debug#{Tty.reset}: #{message}"
|
304
|
+
else
|
305
|
+
STDERR.puts "Debug: #{message}"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
private
|
311
|
+
|
312
|
+
def verbose?
|
313
|
+
options[:verbose]
|
314
|
+
end
|
315
|
+
|
316
|
+
def quiet?
|
317
|
+
options[:quiet]
|
318
|
+
end
|
319
|
+
|
320
|
+
def color n
|
321
|
+
escape "0;#{n}"
|
322
|
+
end
|
323
|
+
def bold n
|
324
|
+
escape "1;#{n}"
|
325
|
+
end
|
326
|
+
def underline n
|
327
|
+
escape "4;#{n}"
|
328
|
+
end
|
329
|
+
def escape n
|
330
|
+
"\033[#{n}m" if $stdout.tty?
|
331
|
+
end
|
332
|
+
|
333
|
+
public
|
334
|
+
|
335
|
+
def show_columns(args)
|
336
|
+
if Hash === args.last
|
337
|
+
options = args.last.dup
|
338
|
+
else
|
339
|
+
options = {}
|
340
|
+
end
|
341
|
+
options[:padding] ||= 4
|
342
|
+
options[:indent] ||= 4
|
343
|
+
output = ''
|
344
|
+
|
345
|
+
if $stdout.tty?
|
346
|
+
# determine the best width to display for different console sizes
|
347
|
+
console_width = width
|
348
|
+
longest = args.sort_by { |arg| arg.length }.last.length rescue 0
|
349
|
+
optimal_col_width = ((console_width - options[:indent] + options[:padding]).to_f /
|
350
|
+
(longest + options[:padding]).to_f).floor rescue 0
|
351
|
+
cols = optimal_col_width > 1 ? optimal_col_width : 1
|
352
|
+
|
353
|
+
Gistore::shellpipe("/usr/bin/pr",
|
354
|
+
"-#{cols}",
|
355
|
+
"-o#{options[:indent]}",
|
356
|
+
"-t",
|
357
|
+
"-w#{console_width}") do |stdin, stdout, stderr|
|
358
|
+
stdin.puts args
|
359
|
+
stdin.close
|
360
|
+
output << stdout.read
|
361
|
+
end
|
362
|
+
output.rstrip!
|
363
|
+
output << "\n" unless output.empty?
|
364
|
+
else
|
365
|
+
output << args.map{|e| " " * options[:indent] + e.to_s} * "\n"
|
366
|
+
end
|
367
|
+
output
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def git_cmd
|
374
|
+
Gistore.git_cmd
|
375
|
+
end
|
376
|
+
|
377
|
+
class String
|
378
|
+
def charat(n)
|
379
|
+
result = self.send "[]", n
|
380
|
+
RUBY_VERSION < "1.9" ? result.chr : result
|
381
|
+
end
|
382
|
+
end
|
data/t/Makefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Run tests
|
2
|
+
#
|
3
|
+
# Copyright (c) 2005 Junio C Hamano
|
4
|
+
#
|
5
|
+
|
6
|
+
#GIT_TEST_OPTS = --verbose --debug
|
7
|
+
SHELL_PATH ?= $(SHELL)
|
8
|
+
PERL_PATH ?= /usr/bin/perl
|
9
|
+
TAR ?= $(TAR)
|
10
|
+
RM ?= rm -f
|
11
|
+
PROVE ?= prove
|
12
|
+
DEFAULT_TEST_TARGET ?= test
|
13
|
+
TEST_LINT ?= test-lint-duplicates test-lint-executable
|
14
|
+
|
15
|
+
ifdef TEST_OUTPUT_DIRECTORY
|
16
|
+
TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results
|
17
|
+
else
|
18
|
+
TEST_RESULTS_DIRECTORY = test-results
|
19
|
+
endif
|
20
|
+
|
21
|
+
# Shell quote;
|
22
|
+
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
23
|
+
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
|
24
|
+
TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY))
|
25
|
+
|
26
|
+
T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
|
27
|
+
|
28
|
+
all: $(DEFAULT_TEST_TARGET)
|
29
|
+
|
30
|
+
test: pre-clean $(TEST_LINT)
|
31
|
+
$(MAKE) aggregate-results-and-cleanup
|
32
|
+
|
33
|
+
prove: pre-clean $(TEST_LINT)
|
34
|
+
@echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
|
35
|
+
$(MAKE) clean-except-prove-cache
|
36
|
+
|
37
|
+
$(T):
|
38
|
+
@echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
|
39
|
+
|
40
|
+
pre-clean:
|
41
|
+
$(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
|
42
|
+
|
43
|
+
clean-except-prove-cache:
|
44
|
+
$(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)'
|
45
|
+
$(RM) -r valgrind/bin
|
46
|
+
|
47
|
+
clean: clean-except-prove-cache
|
48
|
+
$(RM) .prove
|
49
|
+
|
50
|
+
test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax
|
51
|
+
|
52
|
+
test-lint-duplicates:
|
53
|
+
@dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \
|
54
|
+
test -z "$$dups" || { \
|
55
|
+
echo >&2 "duplicate test numbers:" $$dups; exit 1; }
|
56
|
+
|
57
|
+
test-lint-executable:
|
58
|
+
@bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \
|
59
|
+
test -z "$$bad" || { \
|
60
|
+
echo >&2 "non-executable tests:" $$bad; exit 1; }
|
61
|
+
|
62
|
+
test-lint-shell-syntax:
|
63
|
+
@'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T)
|
64
|
+
|
65
|
+
aggregate-results-and-cleanup: $(T)
|
66
|
+
$(MAKE) aggregate-results
|
67
|
+
$(MAKE) clean
|
68
|
+
|
69
|
+
aggregate-results:
|
70
|
+
for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \
|
71
|
+
echo "$$f"; \
|
72
|
+
done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh
|
73
|
+
|
74
|
+
valgrind:
|
75
|
+
$(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind"
|
76
|
+
|
77
|
+
perf:
|
78
|
+
$(MAKE) -C perf/ all
|
79
|
+
|
80
|
+
.PHONY: pre-clean $(T) aggregate-results clean valgrind perf
|