gistore 1.0.0.rc4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|