gpack 2.0.0 → 2.0.1

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.
data/README.rst ADDED
@@ -0,0 +1,157 @@
1
+ =====
2
+ GitPack v2.0
3
+ =====
4
+
5
+ From https://github.com/GitPack/GitPackRuby
6
+
7
+ Ruby Implementation of git repository manager. Conceptually simular to a tool like bundle, gradel, ect. GitPack handles the distrubuting of Git repositories without being tied to a specific language; although it does use ruby to execute commands. GitPack specifically is intended to control multiple git repository dependancies on a project where it is required that multiple user's point to the same commit/branch/tag. GitPack simplifies the usage of Git and can be especially beneficial when working with teams where not every user knows how to manage a git repository. GitPack uses a single file "GpackRepos" to specifiy the URL and local destination of repositories that it should manage.
8
+
9
+ * Clones multiple repositories in parallel.
10
+ * Controls read-only permissions on cloned repositories.
11
+ * Pulls multiple repositoires in parallel.
12
+ * Easy clean of repositories that do not have a clean git status.
13
+ * Submodule compatible
14
+
15
+ Structure
16
+ -----
17
+ * ./gpack - The main exectuable. GitPack is self updating and downloads the latest ver. of master from this repository.
18
+ * ./GpackRepos - The main file that GitPack uses to store information about remote repositories URL, the local desitinations where the repositories should be cloned, and user configuration options like read-only, SSH keys, ect. This file is in YAML format
19
+ * ./.gpacklock - Used to store the repository read-only status.
20
+
21
+ Dependancies
22
+ -----
23
+ * Tested in Ruby 2.3
24
+
25
+ Setup
26
+ -----
27
+ Download the gpack bash script to a local directory and make the file executable:
28
+
29
+ .. code::
30
+
31
+ wget https://raw.githubusercontent.com/GitPack/GitPack/master/gpack
32
+ chmod u+x ./gpack
33
+
34
+ Or install via ruby gems
35
+
36
+ .. code::
37
+
38
+ gem install gpack
39
+
40
+ Add repos to GpackRepos file using gpack, an example is shown below:
41
+
42
+ .. code::
43
+
44
+ ./gpack add git@github.com:GitPack/GitPack.git ./GitPack
45
+
46
+ Basic Usage
47
+ -----
48
+
49
+ Installs all repos in GpackRepos file:
50
+
51
+ .. code::
52
+
53
+ ./gpack install
54
+
55
+ Update installed repos in GpackRepos file:
56
+
57
+ .. code::
58
+
59
+ ./gpack update
60
+
61
+
62
+ GpackRepos
63
+ ----------
64
+
65
+ .. code-block:: bash
66
+
67
+ test1:
68
+ url: git@github.com:GitPack/TestRepo1.git
69
+ localdir: ./repos/test1
70
+ branch: master
71
+ lock: true
72
+
73
+ test2:
74
+ url: git@github.com:GitPack/TestRepo2.git
75
+ localdir: ./repos/test2
76
+ branch: master
77
+ lock: false
78
+
79
+ test3:
80
+ url: git@github.com:GitPack/TestRepo3.git
81
+ localdir: ./repos/test3
82
+ branch: master
83
+ lock: false
84
+
85
+ test3_hash:
86
+ url: git@github.com:GitPack/TestRepo3.git
87
+ localdir: ./repos/test3_hash
88
+ branch: b41e58af7
89
+ lock: false
90
+
91
+ test1_tag:
92
+ url: git@github.com:GitPack/TestRepo1.git
93
+ localdir: ./repos/test1_tag
94
+ branch: v2.0
95
+ lock: false
96
+
97
+ # Options for Configuration
98
+ # config:
99
+ # lock: true # Option to disable read-only by default
100
+ # remote_key: http://some.valid.url # Use an external ssh key
101
+ # ssh_command: ssh -v # Custom SSH arguments passed to $GIT_SSH_COMMAND
102
+
103
+
104
+
105
+ Core Commands
106
+ -------------
107
+
108
+ **gpack cmd [-f] [-nogui] [-persist] [-s]**
109
+ * -f,--force: Force operation
110
+ * -s,--single: Single threaded, useful for debug
111
+ * -n,--nogui: Do not pop up xterm windows
112
+ * -p,--persist: Keep xterm windows open even if command is successful
113
+ * -i: Force install (applies only to update command)
114
+
115
+ **add [url] [directory] [branch]**
116
+ Adds a repo to the GpackRepos file given ssh URL and local directory
117
+ relative to current directory
118
+ **check**
119
+ Checks if all repos are clean and match GpackRepos
120
+ **status**
121
+ Runs through each repo and reports the result of git status
122
+ **help**
123
+ Displays this message
124
+ **install**
125
+ Clones repos in repo directory
126
+ -nogui doesn't open terminals when installing
127
+ **uninstall**
128
+ Removes all local repositories listed in the Repositories File
129
+ Add -f to force remove all repositories
130
+ **reinstall**
131
+ The same as running uninstall then reinstall
132
+ **list**
133
+ List all repos in GpackRepos file
134
+ **lock**
135
+ Makes repo read-only, removes from .gpacklock file
136
+ **unlock**
137
+ Allows writing to repo, appends to .gpacklock file
138
+ **update [-i] [-f]**
139
+ Updates the repositories -f will install if not already installed
140
+
141
+
142
+ Details
143
+ -----------
144
+ * Maintains a clean local repository directory by parsing GpackRepos for user-defined repositores that they wish to clone.
145
+ * By default, all cloned repositories have no write access.
146
+
147
+ Future Improvements
148
+ -----
149
+ * GitPack is not Git LFS compatible at the moment. Merge requests with this feature would be accepted.
150
+ * Add command is not implemented
151
+ * Allow GitPack commands to operate on a per-repository basis
152
+ * Lock/Unlock of individual repositores. (Python version has this)
153
+
154
+ Developers
155
+ -----
156
+ * Andrew Porter https://github.com/AndrewRPorter
157
+ * Aaron Cook https://github.com/cookacounty
@@ -0,0 +1,5 @@
1
+ $SETTINGS = { \
2
+ "core" => {"repofile" => "GpackRepos", "force" => false, "parallel" => true, "install" => false},
3
+ "gui" => {"persist" => false, "show" => true},
4
+ "ssh" => {"key_url" => false, "key" => false, "cmd" => false}
5
+ }
@@ -0,0 +1,154 @@
1
+
2
+ $RAISE_WARNING = false
3
+
4
+ class GitCollection
5
+ attr_accessor :refs
6
+
7
+ def initialize
8
+ @refs = []
9
+ end
10
+ def add_ref(ref)
11
+ @refs << ref
12
+ end
13
+ def print()
14
+ puts "="*40+"\n\tGit Reference Summary\n"+"="*40
15
+ @refs.each do |ref|
16
+ ref.print()
17
+ end
18
+ end
19
+ def archive()
20
+ puts "\nCreating archives of local Repositories....."
21
+ raise_warning = ref_loop(refs) { |ref|
22
+ ref.archive
23
+ }
24
+ if raise_warning
25
+ puts "\n"+("="*60+"\nWARNING DURING CLONING!\n\tSome repositories already existed and failed checks.\n\tReview this log or run 'gpack check' to see detailed information\n"+"="*60).color(Colors::RED)
26
+ end
27
+ end
28
+ def clone()
29
+ puts "\nCloning Repositories....."
30
+ raise_warning = ref_loop(refs) { |ref|
31
+ ref.clone
32
+ }
33
+ if raise_warning
34
+ puts "\n"+("="*60+"\nWARNING DURING CLONING!\n\tSome repositories already existed and failed checks.\n\tReview this log or run 'gpack check' to see detailed information\n"+"="*60).color(Colors::RED)
35
+ end
36
+ print()
37
+ check()
38
+ end
39
+ def rinse()
40
+ puts "\nRinsing Repositories....."
41
+ raise_warning = ref_loop(refs) { |ref|
42
+ ref.rinse
43
+ }
44
+ if raise_warning
45
+ puts ("\n"+"="*60+"\nWARNING DURING Rinse!\n"+"="*60).color(Colors::RED)
46
+ end
47
+ end
48
+ def check()
49
+ puts "\nChecking Local Repositories....."
50
+ raise_warning = ref_loop(refs,true) { |ref|
51
+ ref.check
52
+ }
53
+ if raise_warning
54
+ puts "\n"+("="*60+"\nWARNINGS FOUND DURING CHECK!\n\tReview this log to see detailed information\n" \
55
+ "\tThe following commands can be run to help debug:\n" \
56
+ "\t\tgpack status #Shows the current git status\n" \
57
+ "\t\tgpack rinse #Removes all local changes and untracked files,use with caution\n" \
58
+ +"="*60).color(Colors::RED)
59
+ else
60
+ puts "\n"+("All checks passed!").color(Colors::GREEN)
61
+ end
62
+ end
63
+ def status()
64
+ puts "\nStatus of Local Repositories....."
65
+ raise_warning = ref_loop(refs,true) { |ref|
66
+ ref.status
67
+ }
68
+ if raise_warning
69
+ puts "\n"+("="*60+"\nWARNINGS FOUND DURING CHECK!\n\tReview this log to see detailed information\n"+"="*60).color(Colors::RED)
70
+ end
71
+ end
72
+ def update()
73
+ print()
74
+ puts "\nUpdating Repositories.....\n\n"
75
+ puts "Please be patient, this can take some time if pulling large commits.....".color(Colors::GREEN)
76
+ raise_warning = ref_loop(refs) { |ref|
77
+ ref.update()
78
+ }
79
+ if raise_warning
80
+ puts "\n"+("="*60+"\nWARNING DURING UPDATE!\n\tSome repositories failed checks and were not updated.\n\tReview this log or run 'gpack check' to see detailed information\n"+"="*60).color(Colors::RED)
81
+ end
82
+ end
83
+ def remove()
84
+ puts "This will force remove repositories and repopulate. Any local data will be lost!!!\nContinue (y/n)"
85
+ if $SETTINGS["core"]["force"] == true
86
+ do_remove = true
87
+ else
88
+ cont = $stdin.gets.chomp
89
+ do_remove = cont == "y"
90
+ end
91
+
92
+ if do_remove
93
+ puts "\nRemoving Local Repositories....."
94
+
95
+ raise_warning = ref_loop(refs) { |ref|
96
+ ref.remove()
97
+ }
98
+ `rm -f .gpackunlock`
99
+ else
100
+ puts "Abort Uninstall"
101
+ end
102
+
103
+ if raise_warning
104
+ puts "\n"+("="*60+"\nWARNINGS FOUND DURING REMOVAL!\n\tReview this log to see detailed information\n"+"="*60).color(Colors::RED)
105
+ end
106
+ end
107
+ def set_writeable(tf)
108
+ ref_loop(refs) { |ref|
109
+ ref.set_writeable(tf)
110
+ }
111
+ end
112
+
113
+
114
+ def ref_loop(refs, parallel_override=false)
115
+ if $SETTINGS["core"]["parallel"] && !parallel_override
116
+ read, write = IO.pipe
117
+ Parallel.map(@refs) do |ref|
118
+
119
+ # Set up standard output as a StringIO object.
120
+ old_stdout = $stdout
121
+ foo = StringIO.new
122
+ $stdout = foo
123
+
124
+ raise_warning = yield(ref)
125
+ write.puts raise_warning
126
+
127
+ $stdout = old_stdout
128
+ puts foo.string
129
+
130
+ end
131
+ write.close
132
+ read_data = read.read
133
+ #puts read_data
134
+ if read_data.index("true")
135
+ raise_warning = true
136
+ end
137
+ else
138
+ raise_warning = false
139
+ @refs.each do |ref|
140
+
141
+ ret_warning = yield(ref)
142
+ if ret_warning
143
+ raise_warning = true
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ return raise_warning
150
+ end
151
+
152
+
153
+ end
154
+
@@ -0,0 +1,366 @@
1
+ module HasProperties
2
+ attr_accessor :props
3
+ attr_accessor :require_attrs
4
+
5
+ def has_properties *args
6
+ @props = args
7
+ instance_eval { attr_reader *args }
8
+ instance_eval { attr_writer *args }
9
+ end
10
+
11
+ def has_required *args
12
+ @require_attrs = args
13
+ end
14
+
15
+ def self.included base
16
+ base.extend self
17
+ end
18
+
19
+ def initialize(args)
20
+
21
+ # Attributes required when defining a GitReference
22
+ require_attrs = self.class.require_attrs
23
+
24
+ # Check that all the required
25
+ args.each do |k,v|
26
+ require_attrs = require_attrs - [k]
27
+ end
28
+
29
+ if require_attrs.any?
30
+ raise "Must include attributes #{require_attrs} in GitReference definition"
31
+ end
32
+
33
+ args.each {|k,v|
34
+ instance_variable_set "@#{k}", v if self.class.props.member?(k)
35
+ } if args.is_a? Hash
36
+ end
37
+ end
38
+
39
+
40
+ class GitReference
41
+ include HasProperties
42
+
43
+ has_properties :url, :localdir, :branch, :readonly
44
+ has_required :url, :localdir, :branch
45
+
46
+ def initialize args
47
+ # Non-Required defaults
48
+ @readonly = true
49
+
50
+ super
51
+ end
52
+ def clone()
53
+
54
+ #Clone the Git Repository
55
+ checks_failed = false
56
+
57
+ #check if directory already exists
58
+ if local_exists
59
+ puts "Cloning Warning - Directory #{localdir} already exists! Running checks instead"
60
+ checks_failed = self.check()
61
+ else
62
+ status = syscmd("git clone -b #{branch} #{url} #{localdir} --recursive",true,false)
63
+ self.checkout
64
+ self.set_writeable(false) if @readonly
65
+
66
+ if status != 0
67
+ checks_failed = true
68
+ end
69
+
70
+ end
71
+
72
+ return checks_failed
73
+ end
74
+
75
+ def update()
76
+ force_clone = $SETTINGS["core"]["force"] || $SETTINGS["core"]["install"]
77
+ command_failed = false
78
+ # Returns true if falure
79
+ if local_exists
80
+ checks_failed = self.check(true) # TODO, should this fail if branch is wrong?
81
+ if !checks_failed
82
+ puts "Updating local repository #{@localdir}"
83
+ self.set_writeable(true) if @readonly
84
+ syscmd("git fetch origin",true)
85
+ self.checkout
86
+ syscmd("git submodule update --init --recursive")
87
+ self.set_writeable(false) if @readonly
88
+ command_failed = false
89
+ else
90
+ command_failed = true
91
+ end
92
+ elsif force_clone
93
+ self.clone
94
+ command_failed = false
95
+ else
96
+ command_failed = true
97
+ end
98
+ return command_failed
99
+ end
100
+
101
+ def checkout()
102
+ if is_branch()
103
+ checkout_cmd = "checkout -B #{@branch} origin/#{@branch}" # Create a local branch
104
+ else
105
+ checkout_cmd = "checkout #{@branch}" # Direct checkout the tag/comit
106
+ end
107
+ syscmd("git #{checkout_cmd} && git submodule update --init --recursive")
108
+ end
109
+
110
+ def set_writeable(tf)
111
+
112
+ if tf
113
+ puts "Setting #{@localdir} to writable"
114
+ perms = "u+w"
115
+ else
116
+ puts "Setting #{@localdir} to read only"
117
+ perms = "a-w"
118
+ end
119
+
120
+ file_paths = []
121
+ ignore_paths = []
122
+ if local_exists()
123
+ Find.find(@localdir) do |path|
124
+ # Ignore .git folder
125
+ if path.match(/.*\/.git$/) || path.match(/.*\/.git\/.*/)
126
+ ignore_paths << path
127
+ else
128
+ file_paths << path
129
+ #FileUtils.chmod 'a-w', path
130
+ FileUtils.chmod(perms,path) if File.exist?(path)
131
+ end
132
+ end
133
+ end
134
+
135
+ # Useful for debug
136
+ #puts "IGNORED PATHS\n"+ignore_paths.to_s
137
+ #puts "FOUND_PATHS\n"+file_paths.to_s
138
+
139
+ end
140
+
141
+ def check(skip_branch=false)
142
+ #Check integrety
143
+ # Check that URL matches
144
+ # Check that branch matches
145
+ # Check that there are no local changes "clean" state
146
+ check_git_writable()
147
+
148
+ puts "\nRunning checks on local repository #{@localdir}"
149
+ checks_failed = false
150
+ if local_exists
151
+ if !skip_branch
152
+ if is_branch()
153
+ bname = @branch
154
+ else
155
+ bname = rev_parse(@branch)
156
+ end
157
+ branch_valid = local_branch() == bname
158
+ if !branch_valid
159
+ puts "\tFAIL - Check branch matches #{@branch} rev #{bname}".color(Colors::RED)
160
+ puts "\t\tLocal Branch abbrev : '#{rev_parse("HEAD",true)}'"
161
+ puts "\t\tLocal Branch SHA : '#{rev_parse("HEAD")}'"
162
+ puts "\t\tSpecified Branch : '#{@branch}'"
163
+ puts "\t\tSpecified Branch abbrev : '#{rev_parse(@branch)}'"
164
+ puts "\t\tSpecified Branch SHA : '#{rev_parse(@branch,true)}'"
165
+ checks_failed = true
166
+ end
167
+ end
168
+
169
+ if local_url() == @url
170
+ #puts "\tPASS - Check remote url matches #{@url}"
171
+ else
172
+ puts "\tFAIL - Check remote url matches #{@url}".color(Colors::RED)
173
+ puts "\t\tLocal URL #{local_url()}'"
174
+ puts "\t\tRemote URL #{@url}'"
175
+ checks_failed = true
176
+ end
177
+
178
+ if local_clean()
179
+ #puts "\tPASS - Check local repository clean"
180
+ else
181
+ puts "\tFAIL - Check local repository clean".color(Colors::RED)
182
+ checks_failed = true
183
+ end
184
+
185
+ if !checks_failed
186
+ puts "PASS - All checks on local repository #{@localdir}".color(Colors::GREEN)
187
+ else
188
+ puts "CHECK FAILURE on local repository #{@localdir}. See previous log for info on which check failed".color(Colors::RED)
189
+ end
190
+ else
191
+ puts "\tFAIL - Check local repository exists".color(Colors::RED)
192
+ checks_failed = true
193
+ end
194
+ return checks_failed
195
+ end
196
+
197
+ def syscmd(cmd,open_xterm=false,cd_first=true)
198
+ if cd_first
199
+ cmd = "cd #{@localdir} && #{cmd}"
200
+ end
201
+
202
+ #Pass env var to Open3
203
+ ssh_cmd = $SETTINGS["ssh"]["cmd"]
204
+ if ssh_cmd
205
+ args = {"GIT_SSH_COMMAND" => ssh_cmd}
206
+ puts "custom ssh"
207
+ else
208
+ args = {}
209
+ end
210
+
211
+ if open_xterm && $SETTINGS["gui"]["show"]
212
+ if $SETTINGS["gui"]["persist"]
213
+ hold_opt = "-hold"
214
+ end
215
+ if ssh_cmd
216
+ cmd = "echo 'GIT_SSH_COMMAND $GIT_SSH_COMMAND' ; #{cmd}"
217
+ end
218
+ cmd = "xterm #{hold_opt} -geometry 90x30 -e \"#{cmd} || echo 'Command Failed, see log above. Press CTRL+C to close window' && sleep infinity\""
219
+ end
220
+ cmd_id = Digest::SHA1.hexdigest(cmd).to_s[0..4]
221
+
222
+ stdout_str,stderr_str,status = Open3.capture3(args,cmd)
223
+
224
+ puts "="*30+"COMMAND ID #{cmd_id}"+"="*28+"\n"
225
+ puts ("#{cmd}").color(Colors::YELLOW)
226
+ if stdout_str != "" || stderr_str != ""
227
+ puts "="*30+"COMMAND #{cmd_id} LOG START"+"="*28+"\n"
228
+ puts stderr_str
229
+ puts stdout_str
230
+ puts "="*30+"COMMAND #{cmd_id} LOG END"+"="*30+"\n"
231
+ end
232
+ status
233
+ end
234
+
235
+ def localcmd(cmd_str)
236
+ return `cd #{@localdir} && #{cmd_str}`.chomp
237
+ end
238
+
239
+ def remove()
240
+ force = $SETTINGS["core"]["force"]
241
+ command_failed = false
242
+ if force || !self.check
243
+ puts "Removing local repository #{@localdir}"
244
+ self.set_writeable(true) if @readonly || force
245
+ syscmd("rm -rf #{@localdir}",false,false)
246
+ command_failed = false
247
+ else
248
+ command_failed = true
249
+ end
250
+ return command_failed
251
+ end
252
+
253
+ def rinse()
254
+ force = $SETTINGS["core"]["force"]
255
+ if !@readonly && !force
256
+ puts "Error with repository #{@localdir}\n\t Repositories can only be rinsed when in readonly mode"
257
+ command_failed = true
258
+ else
259
+ self.set_writeable(true) if @readonly
260
+ status = syscmd( \
261
+ "git fetch origin && " \
262
+ "git clean -xdff && " \
263
+ "git reset --hard && " \
264
+ "git submodule foreach --recursive git clean -xdff && " \
265
+ "git submodule foreach --recursive git reset --hard && " \
266
+ "git submodule update --init --recursive")
267
+ self.checkout
268
+ self.set_writeable(false) if @readonly
269
+ if !status
270
+ command_failed = true
271
+ puts "Rinse command failed for repo #{@localdir}, check log"
272
+ end
273
+ end
274
+
275
+
276
+ return command_failed
277
+ end
278
+
279
+ def archive()
280
+ #Archive the Git Repository
281
+
282
+ checks_failed = false
283
+
284
+ #check if directory already exists
285
+ if !self.check()
286
+ command_failed = true
287
+ else
288
+ git_ref = local_rev
289
+ dirname = @localdir.match( /\/([^\/]*)\s*$/)[1].chomp
290
+ tarname = "#{dirname}_#{git_ref}.tar.gz"
291
+ tarcmd = "tar -zcvf #{tarname} #{@localdir} > /dev/null"
292
+ syscmd(tarcmd)
293
+ end
294
+
295
+ return command_failed
296
+ end
297
+
298
+ def check_git_writable()
299
+ # Make sure .git folder is writable
300
+
301
+ gitdirs = `find #{localdir} -type d -name ".git"`
302
+ gitdirs.each_line do |dir|
303
+ dir.chomp!
304
+ if !File.writable?(dir)
305
+ puts "Warning, .git folder #{dir} was found read-only. Automatically setting it to writable"
306
+ syscmd("chmod ug+w -R #{dir}")
307
+ end
308
+ end
309
+ end
310
+ def status
311
+ self.print()
312
+ syscmd("git status && echo 'Git Branch' && git branch && echo 'Git SHA' && git rev-parse HEAD")
313
+ return false
314
+ end
315
+
316
+ def is_branch()
317
+ #check if branch ID is a branch or a tag/commit
318
+ return system("cd #{localdir} && git show-ref -q --verify refs/remotes/origin/#{@branch}")
319
+ end
320
+
321
+ def local_branch()
322
+ if is_branch()
323
+ bname = rev_parse("HEAD",true)
324
+ else
325
+ bname = rev_parse("HEAD")
326
+ end
327
+ return bname
328
+ end
329
+
330
+ def rev_parse(rev,abbrev=false)
331
+ if abbrev
332
+ rp = localcmd("git rev-parse --abbrev-ref #{rev}")
333
+ else
334
+ rp = localcmd("git rev-parse #{rev}")
335
+ end
336
+ return rp
337
+ end
338
+
339
+ def local_url()
340
+ urlname = localcmd("git config --get remote.origin.url")
341
+ return urlname
342
+ end
343
+
344
+ def local_rev()
345
+ revname = localcmd("git rev-parse --short HEAD")
346
+ return revname
347
+ end
348
+
349
+ def local_clean()
350
+ clean = localcmd("git status --porcelain")
351
+ return clean == "" # Empty string means it's clean
352
+ end
353
+
354
+ def local_exists()
355
+ if Dir.exists?(@localdir)
356
+ return true
357
+ else
358
+ return false
359
+ end
360
+ end
361
+
362
+ def print()
363
+ puts "Reference #{@url}\n\tlocaldir-#{@localdir}\n\tbranch-#{@branch}\n\treadonly-#{@readonly}"
364
+ end
365
+
366
+ end