mass_git_clone 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cb092e9b18bec254cd426940d49b95600ffbd23ff549f5baee27e3c41cdd8e40
4
+ data.tar.gz: 5e73ba35f65106cda87d2f78679fef93d9fe4badcf141f9ef38f9fc4d54a0626
5
+ SHA512:
6
+ metadata.gz: fcf74cf540f0af2fb833a57ee6161c6879e575e2fdaf33ef989f159a8393af900a2bc0261f40d80237a5c2c5d28e3644c9ebf01f96efbc18f4e8c22bfc637c11
7
+ data.tar.gz: feceed95021732b251ff1b411b4e03787e1801b1db9acda9fdcfda8aa9c1491aefe2e493da196dade95678b491ba663b97b5de4427e9703a6c8fb5e8849e6d0b
@@ -0,0 +1,48 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ ruby:
16
+ - "2.7"
17
+ - "3.0"
18
+ - "3.1"
19
+
20
+ steps:
21
+ - uses: actions/checkout@v3
22
+
23
+ - name: Setup ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+
29
+ - name: Rubocop
30
+ run: bundle exec rubocop
31
+
32
+ - name: Syntax Tree
33
+ run: bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
34
+
35
+ publish:
36
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+
40
+ steps:
41
+ - uses: actions/checkout@v3
42
+
43
+ - name: Release Gem
44
+ uses: discourse/publish-rubygems-action@v2
45
+ env:
46
+ RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
47
+ GIT_EMAIL: team@discourse.org
48
+ GIT_NAME: discoursebot
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ pkg
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_gem:
2
+ rubocop-discourse: stree-compat.yml
3
+
4
+ Discourse/NoChdir:
5
+ Enabled: false
data/.streerc ADDED
@@ -0,0 +1 @@
1
+ --print-width=100
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Civilized Discourse Construction Kit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # mass_git_clone
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/mass_git_clone'
5
+
6
+
7
+ if ARGV[0].nil? || ARGV.size > 1 || STDIN.tty?
8
+ STDERR.puts "Usage `mass_git_clone <repo_base_directory>`. Pass newline-separated repo list via STDIN."
9
+ exit 1
10
+ end
11
+
12
+ MassGitClone.mass_clone(repo_base_dir: ARGV[0], repo_list: STDIN.each_line.to_a)
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open3"
5
+ require "set"
6
+ require "parallel"
7
+
8
+ def run(*args)
9
+ out, err, status = Open3.capture3(*args)
10
+ raise <<~ERROR if !status.success?
11
+ Status #{status.exitstatus} running #{args.inspect}
12
+
13
+ stdout:
14
+ #{out}
15
+
16
+ stderr:
17
+ #{err}
18
+ ERROR
19
+ out
20
+ end
21
+
22
+ def prefixed_puts(str)
23
+ prefix = "[reset-all-repos]"
24
+ if n = Parallel.worker_number
25
+ prefix += "[t#{n}]"
26
+ end
27
+ print "#{prefix} #{str}\n"
28
+ end
29
+
30
+ # Clone or update a repo with the given URL
31
+ # On failure, return the repo url
32
+ # On success, return nil
33
+ def update_repo(repo_url)
34
+ dir_name = File.basename(repo_url, ".git")
35
+ if Dir.exist?(dir_name)
36
+ prefixed_puts "Updating #{dir_name}..."
37
+
38
+ run "git", "-C", dir_name, "remote", "set-url", "origin", repo_url
39
+
40
+ default_branch =
41
+ File.basename(
42
+ run("git", "-C", dir_name, "symbolic-ref", "--short", "refs/remotes/origin/HEAD").strip
43
+ )
44
+
45
+ run "git", "-C", dir_name, "checkout", "-f", default_branch
46
+ run "git", "-C", dir_name, "fetch", "origin", default_branch
47
+ run "git", "-C", dir_name, "reset", "--hard", "origin/#{default_branch}"
48
+ run "git", "-C", dir_name, "clean", "-f", "-d"
49
+ else
50
+ prefixed_puts "Cloning #{dir_name}..."
51
+ run "git", "clone", "--quiet", repo_url, dir_name
52
+ end
53
+ nil
54
+ rescue => e
55
+ prefixed_puts "Error while working on #{repo_url}\n\n#{e.message}"
56
+ repo_url
57
+ end
58
+
59
+ module MassGitClone
60
+ def self.mass_clone(repo_base_dir:, repo_list:)
61
+ use_ssh =
62
+ begin
63
+ run "git", "ls-remote", "git@github.com:discourse/discourse", "main"
64
+ prefixed_puts "Using SSH for GitHub..."
65
+ true
66
+ rescue => e
67
+ prefixed_puts "SSH failed. Using https for GitHub..."
68
+ false
69
+ end
70
+
71
+ all_repos = repo_list.map(&:strip).filter { |l| l.length > 0 }
72
+ if all_repos.size === 0
73
+ prefixed_puts "No plugin URLs supplied"
74
+ exit 1
75
+ end
76
+
77
+ all_repos =
78
+ all_repos.map do |repo|
79
+ if !repo.match?(%r{\A[\w-]+/[\w-]+\z})
80
+ repo # Full URL, leave untouched
81
+ elsif use_ssh
82
+ "git@github.com:#{repo}"
83
+ else
84
+ "https://github.com/#{repo}"
85
+ end
86
+ end
87
+
88
+ Dir.mkdir(repo_base_dir) if !Dir.exist?(repo_base_dir)
89
+
90
+ Dir.chdir(repo_base_dir) do
91
+ failures =
92
+ Parallel
93
+ .map(all_repos, in_threads: ENV["PARALLEL"]&.to_i || 10) do |repo_url|
94
+ update_repo(repo_url)
95
+ end
96
+ .reject(&:nil?)
97
+
98
+ final_dirs = Dir.glob("*")
99
+ expected_dirs = Set.new(all_repos.map { |r| File.basename(r, ".git") })
100
+ to_delete = final_dirs.reject { |dir| expected_dirs.include?(dir) }
101
+
102
+ if to_delete.any?
103
+ puts
104
+ prefixed_puts "Deleting #{to_delete.size} stale directories"
105
+ to_delete.each { |dir| FileUtils.rm_rf(dir) }
106
+ end
107
+
108
+ puts
109
+ if failures.any?
110
+ prefixed_puts "#{failures.count} repo(s) failed to update"
111
+ prefixed_puts failures.map { |url| " #{url}" }.join("\n")
112
+ exit 1
113
+ else
114
+ prefixed_puts "Done - all repositories are up-to-date 🚀"
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mass_git_clone"
8
+ spec.version = "0.1.0"
9
+ spec.authors = ["Discourse Team"]
10
+
11
+ spec.summary = %q{Tool for maintaining clones of a large number of git repositories}
12
+ spec.description = %q{Tool for maintaining clones of a large number of git repositories}
13
+ spec.homepage = "https://github.com/discourse/mass_git_clone"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+
20
+ spec.bindir = "bin"
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.required_ruby_version = '>= 2.7.0'
25
+
26
+ spec.add_dependency "parallel", "~> 1.0"
27
+
28
+ spec.add_development_dependency "bundler", "~> 2.0"
29
+ spec.add_development_dependency "rake", "~> 13.0"
30
+ spec.add_development_dependency "rubocop"
31
+ spec.add_development_dependency "rubocop-discourse"
32
+ spec.add_development_dependency "syntax_tree"
33
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mass_git_clone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Discourse Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parallel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-discourse
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: syntax_tree
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Tool for maintaining clones of a large number of git repositories
98
+ email:
99
+ executables:
100
+ - mass_git_clone
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".github/workflows/ci.yml"
105
+ - ".gitignore"
106
+ - ".rubocop.yml"
107
+ - ".streerc"
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - bin/mass_git_clone
113
+ - lib/mass_git_clone.rb
114
+ - mass_git_clone.gemspec
115
+ homepage: https://github.com/discourse/mass_git_clone
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 2.7.0
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubygems_version: 3.3.26
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Tool for maintaining clones of a large number of git repositories
138
+ test_files: []