git-thin 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +11 -0
- data/Rakefile +13 -0
- data/bin/git-thin +10 -0
- data/bin/thin +10 -0
- data/git-thin.gemspec +26 -0
- data/lib/git-thin.rb +5 -0
- data/lib/git-thin/command.rb +40 -0
- data/lib/git-thin/command/action.rb +117 -0
- data/lib/git-thin/command/checkout.rb +17 -0
- data/lib/git-thin/command/clone.rb +30 -0
- data/lib/git-thin/command/detect.rb +192 -0
- data/lib/git-thin/command/expire.rb +125 -0
- data/lib/git-thin/command/export.rb +256 -0
- data/lib/git-thin/command/fetch.rb +17 -0
- data/lib/git-thin/command/hook.rb +357 -0
- data/lib/git-thin/command/pull.rb +18 -0
- data/lib/git-thin/command/thin_config.rb +326 -0
- data/lib/git-thin/gem_version.rb +3 -0
- data/lib/git-thin/utils/utils.rb +133 -0
- data/spec/command/thin_spec.rb +12 -0
- data/spec/spec_helper.rb +50 -0
- metadata +111 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'claide'
|
2
|
+
require 'json'
|
3
|
+
require 'git-thin/command/action'
|
4
|
+
|
5
|
+
module GitThin
|
6
|
+
|
7
|
+
class Pull < Thin
|
8
|
+
include Action
|
9
|
+
self.arguments = Action.arguments
|
10
|
+
self.summary = Action.summary 'pull'
|
11
|
+
self.description = Action.description 'pull'
|
12
|
+
|
13
|
+
def initialize(argv)
|
14
|
+
super
|
15
|
+
setup(argv,'pull')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
require 'claide'
|
2
|
+
require 'json'
|
3
|
+
# require 'git-thin/utils/utils'
|
4
|
+
|
5
|
+
module GitThin
|
6
|
+
class ThinConfig
|
7
|
+
include GitThinUtils
|
8
|
+
DEFAULT = 'default'.freeze
|
9
|
+
def initialize(name,keys)
|
10
|
+
@name = name
|
11
|
+
@keys = keys
|
12
|
+
@config = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
return @name
|
17
|
+
end
|
18
|
+
def keys
|
19
|
+
return @keys
|
20
|
+
end
|
21
|
+
def setValue(value,key = nil )
|
22
|
+
if key
|
23
|
+
if @keys.include? key
|
24
|
+
@config[key] = value
|
25
|
+
end
|
26
|
+
else
|
27
|
+
for key in @keys
|
28
|
+
@config[key] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def getValue(key = nil )
|
34
|
+
if key
|
35
|
+
value = @config[key]
|
36
|
+
if value.nil? && key == 'path'
|
37
|
+
value = @config['lfs']
|
38
|
+
end
|
39
|
+
return value
|
40
|
+
else
|
41
|
+
return @config.values
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.prase_config(configs)
|
46
|
+
ret_configs = {}
|
47
|
+
for config in configs
|
48
|
+
items = config.split('=')
|
49
|
+
if items.length != 2
|
50
|
+
logE 'error thin config:'+config
|
51
|
+
next
|
52
|
+
end
|
53
|
+
name = items[0].strip.downcase
|
54
|
+
value = items[1].strip.split(',')
|
55
|
+
name_items = name.split('.')
|
56
|
+
if name_items.length <= 1
|
57
|
+
logE 'error thin config:'+config
|
58
|
+
next
|
59
|
+
end
|
60
|
+
if name_items[0] != 'thin'
|
61
|
+
# logE 'error thin config:'+config
|
62
|
+
next
|
63
|
+
end
|
64
|
+
|
65
|
+
config_name = name_items[1]
|
66
|
+
config = ret_configs[config_name]
|
67
|
+
if not config
|
68
|
+
config = ThinConfig.new config_name,['lfs','path']
|
69
|
+
ret_configs[config_name] = config
|
70
|
+
end
|
71
|
+
|
72
|
+
if name_items.length > 2
|
73
|
+
config_key = name_items[2]
|
74
|
+
config.setValue(value,config_key)
|
75
|
+
else
|
76
|
+
config.setValue value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
return ret_configs
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.prase_pwd_config(configs,source_root,pwd)
|
84
|
+
types = []
|
85
|
+
if pwd.index(source_root) == 0
|
86
|
+
# 在git 目录中,判断是否命中path路径
|
87
|
+
length = source_root.length
|
88
|
+
sub = pwd[length+1..-1]
|
89
|
+
if sub
|
90
|
+
for key in configs.keys
|
91
|
+
paths = configs[key].getValue('path')
|
92
|
+
for path in paths
|
93
|
+
if sub.index(path) == 0
|
94
|
+
types.push key.downcase
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
else
|
100
|
+
logW 'You are out of the repo'
|
101
|
+
end
|
102
|
+
return types
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.prase_type_config(argv,configs,source_root=nil ,pwd=nil )
|
106
|
+
types = []
|
107
|
+
if argv.arguments.length > 0 && argv.arguments[0].index('-')!=0
|
108
|
+
first_args = argv.arguments[0].split '|'
|
109
|
+
need_shift = false
|
110
|
+
for arg in first_args
|
111
|
+
if configs.keys.include? arg.downcase
|
112
|
+
types.push arg.downcase
|
113
|
+
need_shift = true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
if need_shift
|
117
|
+
argv.shift_argument
|
118
|
+
end
|
119
|
+
elsif pwd && source_root
|
120
|
+
types = types.concat prase_pwd_config(configs,source_root,pwd)
|
121
|
+
end
|
122
|
+
if types.length == 0
|
123
|
+
types.push(DEFAULT)
|
124
|
+
end
|
125
|
+
return types
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
class Config < Thin
|
130
|
+
include GitThinUtils
|
131
|
+
self.summary = 'Config lfs with .thinconfig'
|
132
|
+
|
133
|
+
self.arguments = [
|
134
|
+
CLAide::Argument.new('TYPE', false )
|
135
|
+
]
|
136
|
+
self.description = <<-DESC
|
137
|
+
According to .thinconfig, configure the platform directory to LFS
|
138
|
+
DESC
|
139
|
+
def self.options
|
140
|
+
[
|
141
|
+
['--source_root', 'Specify the warehouse address manually if necessary.'],
|
142
|
+
['--store', 'Store config to lfs'],
|
143
|
+
['--list', 'List the configs supported by the locally repository, (configured by.thinconfig) '],
|
144
|
+
['--current', 'Displays the current configuration'],
|
145
|
+
['--reset', 'Reset config to thin and lfs'],
|
146
|
+
['--check', 'Check the integrity of the config'],
|
147
|
+
].concat(super)
|
148
|
+
end
|
149
|
+
def initialize(argv)
|
150
|
+
super
|
151
|
+
@pwd = Dir.pwd
|
152
|
+
@types = []
|
153
|
+
@configs = {}
|
154
|
+
@source_root = argv.option('source_root')
|
155
|
+
@store = argv.flag?('store')
|
156
|
+
@list = argv.flag?('list')
|
157
|
+
@current = argv.flag?('current')
|
158
|
+
@reset = argv.flag?('reset')
|
159
|
+
@check = argv.flag?('check')
|
160
|
+
|
161
|
+
if not @source_root
|
162
|
+
run_shell 'git rev-parse --show-toplevel',false ,LOGNone do |out,err,status|
|
163
|
+
if status == 0
|
164
|
+
@source_root = out[0].strip
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
if not File.exist? @source_root+'/.git'
|
169
|
+
UI.puts "git repository not found"
|
170
|
+
exit 1
|
171
|
+
end
|
172
|
+
else
|
173
|
+
if @source_root[-1] == '/'
|
174
|
+
@source_root = @source_root[0..-2 ]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
if not Dir.exist? @source_root
|
178
|
+
@source_root = nil
|
179
|
+
return
|
180
|
+
end
|
181
|
+
if FileTest.exist? @source_root+'/.thinconfig'
|
182
|
+
run_shell "git config -lf #{@source_root}/.thinconfig",true ,true do |out,err,status|
|
183
|
+
@configs = ThinConfig.prase_config out
|
184
|
+
end
|
185
|
+
end
|
186
|
+
@types =ThinConfig.prase_type_config argv,@configs
|
187
|
+
end
|
188
|
+
|
189
|
+
def validate!
|
190
|
+
super
|
191
|
+
help! 'validate SOURCE_ROOT is required' unless @source_root
|
192
|
+
end
|
193
|
+
|
194
|
+
def git_config
|
195
|
+
if not @git_configs
|
196
|
+
run_shell 'git config -l',true ,true do |outs,errs,status|
|
197
|
+
@git_configs = ThinConfig.prase_config outs
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
def run_config
|
202
|
+
values=[]
|
203
|
+
for type in @types
|
204
|
+
config = @configs[type]
|
205
|
+
if type == ThinConfig::DEFAULT
|
206
|
+
git_config
|
207
|
+
config = @git_configs[ThinConfig::DEFAULT]
|
208
|
+
end
|
209
|
+
if config
|
210
|
+
values.push config.getValue('lfs')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
if values.length > 0
|
214
|
+
run_shell "git config thin.#{ThinConfig::DEFAULT} "+values.join(','),true ,true
|
215
|
+
if @store
|
216
|
+
run_shell "git config lfs.fetchinclude "+values.join(',')
|
217
|
+
end
|
218
|
+
end
|
219
|
+
return values
|
220
|
+
end
|
221
|
+
|
222
|
+
def list
|
223
|
+
logN 'List the configs supported by the locally repository: '
|
224
|
+
git_config
|
225
|
+
config = @git_configs[ThinConfig::DEFAULT]
|
226
|
+
if config
|
227
|
+
log = "name:#{config.name}"
|
228
|
+
for key in config.keys
|
229
|
+
log += " #{key}:#{config.getValue key}"
|
230
|
+
end
|
231
|
+
logN log
|
232
|
+
end
|
233
|
+
for config in @configs.values
|
234
|
+
log = "name:#{config.name}"
|
235
|
+
for key in config.keys
|
236
|
+
log += " #{key}:#{config.getValue key}"
|
237
|
+
end
|
238
|
+
logN log
|
239
|
+
end
|
240
|
+
end
|
241
|
+
def reset
|
242
|
+
run_shell "git config --unset thin.#{ThinConfig::DEFAULT} ",true ,true
|
243
|
+
run_shell "git config --unset lfs.fetchinclude ",true ,true
|
244
|
+
end
|
245
|
+
def current
|
246
|
+
|
247
|
+
types = ThinConfig::prase_pwd_config(@configs,@source_root,@pwd)
|
248
|
+
if types.length == 0
|
249
|
+
types.push ThinConfig::DEFAULT
|
250
|
+
end
|
251
|
+
for type in types
|
252
|
+
config = @configs[type]
|
253
|
+
if type == ThinConfig::DEFAULT
|
254
|
+
git_config
|
255
|
+
config = @git_configs[ThinConfig::DEFAULT]
|
256
|
+
end
|
257
|
+
if config
|
258
|
+
logN "name:#{type} lfs:#{config.getValue('lfs')}"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def match_config(match_file)
|
264
|
+
for config in @configs.values
|
265
|
+
lfs = config.getValue 'lfs'
|
266
|
+
if lfs
|
267
|
+
lfs = lfs.map do|file|
|
268
|
+
file.strip
|
269
|
+
end
|
270
|
+
for lf in lfs
|
271
|
+
if match_file.index(lf) == 0
|
272
|
+
return config
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
return nil
|
278
|
+
end
|
279
|
+
def check
|
280
|
+
lfs_files = []
|
281
|
+
run_shell "git lfs ls-files",false ,LOGNone do |outs,errs,status|
|
282
|
+
if status
|
283
|
+
for line in outs
|
284
|
+
line[13..-2]
|
285
|
+
lfs_files.push line[13..-2]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
unmatch_files = []
|
290
|
+
for lfs_file in lfs_files
|
291
|
+
|
292
|
+
if match_config(lfs_file).nil?
|
293
|
+
unmatch_files.push lfs_file
|
294
|
+
end
|
295
|
+
end
|
296
|
+
if unmatch_files.length > 0
|
297
|
+
logN 'The following LFS files are not configured:'
|
298
|
+
for unmatch_file in unmatch_files
|
299
|
+
logW unmatch_file
|
300
|
+
end
|
301
|
+
else
|
302
|
+
logN 'All LFS files are configured'
|
303
|
+
end
|
304
|
+
end
|
305
|
+
def run
|
306
|
+
Dir.chdir @source_root
|
307
|
+
if @list
|
308
|
+
list
|
309
|
+
elsif @reset
|
310
|
+
reset
|
311
|
+
elsif @current
|
312
|
+
current
|
313
|
+
elsif @check
|
314
|
+
check
|
315
|
+
else
|
316
|
+
values = run_config
|
317
|
+
if values.length > 0
|
318
|
+
logN "git config thin.#{ThinConfig::DEFAULT} "+values.join(",")
|
319
|
+
else
|
320
|
+
logW "not match analyze config"
|
321
|
+
end
|
322
|
+
end
|
323
|
+
Dir.chdir @pwd
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'io/console'
|
3
|
+
module GitThinUtils
|
4
|
+
|
5
|
+
LOGC= 1<<0
|
6
|
+
LOGN= 1<<1
|
7
|
+
LOGPRUNE = 1<<2
|
8
|
+
LOGNone= 0
|
9
|
+
LOGA = LOGC|LOGN
|
10
|
+
|
11
|
+
|
12
|
+
def set_progress(index, char = '*')
|
13
|
+
print (char * (index / 2.5).floor).ljust(40, " "), " #{index}%\r"
|
14
|
+
end
|
15
|
+
|
16
|
+
def run_shell(command,force = 0,log_type = LOGA,retryCount = 0 )
|
17
|
+
if log_type === false
|
18
|
+
log_type = LOGPRUNE
|
19
|
+
elsif log_type === true
|
20
|
+
log_type = LOGNone
|
21
|
+
end
|
22
|
+
if (log_type & LOGC ) == LOGC
|
23
|
+
logC command+"\n",(log_type & LOGPRUNE )!=LOGPRUNE
|
24
|
+
end
|
25
|
+
stdin, stdout, stderr,wait_thr = Open3.popen3(command)
|
26
|
+
pid = wait_thr[:pid]
|
27
|
+
out_lines = []
|
28
|
+
error_lines = []
|
29
|
+
stdout.sync = true
|
30
|
+
stderr.sync = true
|
31
|
+
out = Thread.new do
|
32
|
+
# while !stdout.eof?
|
33
|
+
# c = stdout.getc
|
34
|
+
# putc c
|
35
|
+
# stdout.flush
|
36
|
+
# end
|
37
|
+
stdout.each do |line|
|
38
|
+
out_lines.push line
|
39
|
+
if log_type && log_type > LOGC
|
40
|
+
logN line,(log_type & LOGPRUNE )!=LOGPRUNE
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
err = Thread.new do
|
45
|
+
# while !stderr.eof?
|
46
|
+
# c = stderr.getc
|
47
|
+
# putc c
|
48
|
+
# stderr.flush
|
49
|
+
# end
|
50
|
+
stderr.each do |line|
|
51
|
+
error_lines.push line
|
52
|
+
|
53
|
+
if log_type
|
54
|
+
logW line,(log_type & LOGPRUNE )!=LOGPRUNE
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
out.join
|
59
|
+
err.join
|
60
|
+
|
61
|
+
stderr.close
|
62
|
+
stdin.close
|
63
|
+
stdout.close
|
64
|
+
status = wait_thr.value
|
65
|
+
if status.exitstatus != 0
|
66
|
+
if !force
|
67
|
+
if retryCount > 0
|
68
|
+
sleep 1
|
69
|
+
run_shell command,force,log_type,retryCount-1
|
70
|
+
return
|
71
|
+
else
|
72
|
+
logE error_lines,(log_type & LOGPRUNE )
|
73
|
+
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
unless block_given?
|
79
|
+
|
80
|
+
return [out_lines,error_lines,status.exitstatus]
|
81
|
+
else
|
82
|
+
yield out_lines, error_lines,status.exitstatus
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def print_console(str)
|
87
|
+
run_shell "echo #{str}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def logInner(color_code,text)
|
91
|
+
clolr="\033[#{color_code}m"
|
92
|
+
nc="\033[0m"
|
93
|
+
puts "#{clolr}#{text}#{nc}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# 打印不同级别的 log。根据级别不同,样式(颜色)不同
|
97
|
+
def logE(text,note = true )
|
98
|
+
if note
|
99
|
+
logInner '31',"[ERROR] !!#{text}"
|
100
|
+
else
|
101
|
+
print(text)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def logP(text)
|
106
|
+
print("\r")
|
107
|
+
print(text)
|
108
|
+
STDOUT.flush
|
109
|
+
end
|
110
|
+
def logW(text,note = true )
|
111
|
+
if note
|
112
|
+
logInner '33',"[WARNING] #{text}"
|
113
|
+
else
|
114
|
+
print(text)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def logN(text,note = true)
|
119
|
+
if note
|
120
|
+
logInner '32',"[NOTE] #{text}"
|
121
|
+
else
|
122
|
+
print(text)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def logC(text,note = true)
|
127
|
+
if note
|
128
|
+
logInner '34',"[COMMAND] #{text}"
|
129
|
+
else
|
130
|
+
print(text)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|