gitomator 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +57 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/Rakefile +9 -0
- data/bin/gitomator-clone-repos +21 -0
- data/bin/gitomator-console +19 -0
- data/bin/gitomator-disable-ci +24 -0
- data/bin/gitomator-enable-ci +24 -0
- data/bin/gitomator-make-access-permissions +72 -0
- data/bin/gitomator-make-repos +45 -0
- data/bin/gitomator-make-teams +20 -0
- data/bin/setup +8 -0
- data/gitomator.gemspec +28 -0
- data/lib/gitomator/console.rb +54 -0
- data/lib/gitomator/context.rb +132 -0
- data/lib/gitomator/exceptions.rb +21 -0
- data/lib/gitomator/service/ci.rb +35 -0
- data/lib/gitomator/service/git.rb +48 -0
- data/lib/gitomator/service/hosting.rb +204 -0
- data/lib/gitomator/service/tagging.rb +99 -0
- data/lib/gitomator/service.rb +64 -0
- data/lib/gitomator/service_provider/git_shell.rb +68 -0
- data/lib/gitomator/service_provider/hosting_local.rb +103 -0
- data/lib/gitomator/task/base_repos_task.rb +88 -0
- data/lib/gitomator/task/clone_repos.rb +61 -0
- data/lib/gitomator/task/config/repos_config.rb +117 -0
- data/lib/gitomator/task/config/team_config.rb +57 -0
- data/lib/gitomator/task/enable_disable_ci.rb +56 -0
- data/lib/gitomator/task/make_repos.rb +86 -0
- data/lib/gitomator/task/setup_team.rb +63 -0
- data/lib/gitomator/task/update_repo_access_permissions.rb +42 -0
- data/lib/gitomator/task.rb +48 -0
- data/lib/gitomator/util/repo/name_resolver.rb +68 -0
- data/lib/gitomator/util/script_util.rb +69 -0
- data/lib/gitomator/version.rb +3 -0
- data/lib/gitomator.rb +61 -0
- metadata +173 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
|
4
|
+
module Gitomator
|
5
|
+
module ServiceProvider
|
6
|
+
# A hosting provider that manages repos in a directory on the local
|
7
|
+
# file-system.
|
8
|
+
class HostingLocal
|
9
|
+
|
10
|
+
|
11
|
+
#-------------------------------------------------------------------------
|
12
|
+
|
13
|
+
#
|
14
|
+
# A small wrapper that takes a hash, and create an attr_accessor for
|
15
|
+
# each hash key.
|
16
|
+
# This is a temporary implementation, until we create proper model
|
17
|
+
# objects (e.g. HostedRepo, Team, PullRequest, etc.)
|
18
|
+
#
|
19
|
+
class ModelObject
|
20
|
+
def initialize(hash)
|
21
|
+
hash.each do |key, value|
|
22
|
+
setter = "#{key}="
|
23
|
+
self.class.send(:attr_accessor, key) if !respond_to?(setter)
|
24
|
+
send setter, value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#-------------------------------------------------------------------------
|
30
|
+
|
31
|
+
|
32
|
+
attr_reader :local_dir, :local_repos_dir
|
33
|
+
|
34
|
+
def initialize(git_service, local_dir, opts = {})
|
35
|
+
@git = git_service
|
36
|
+
|
37
|
+
raise "Local directory doesn't exist, #{local_dir}" unless Dir.exist? local_dir
|
38
|
+
@local_dir = local_dir
|
39
|
+
|
40
|
+
@local_repos_dir = File.join(@local_dir, opts[:repos_dir] || 'repos')
|
41
|
+
Dir.mkdir @local_repos_dir unless Dir.exist? @local_repos_dir
|
42
|
+
end
|
43
|
+
|
44
|
+
#---------------------------------------------------------------------
|
45
|
+
|
46
|
+
def name
|
47
|
+
:local
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def repo_root(name)
|
52
|
+
File.join(local_repos_dir, name)
|
53
|
+
end
|
54
|
+
|
55
|
+
#---------------------------------------------------------------------
|
56
|
+
|
57
|
+
def create_repo(name, opts)
|
58
|
+
raise "Directory exists, #{repo_root(name)}" if Dir.exist? repo_root(name)
|
59
|
+
@git.init(repo_root(name), opts)
|
60
|
+
return ModelObject.new({
|
61
|
+
:name => name, :full_name => name, :url => "#{repo_root(name)}/.git"
|
62
|
+
})
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def read_repo(name)
|
67
|
+
if Dir.exist? repo_root(name)
|
68
|
+
return ModelObject.new({
|
69
|
+
:name => name, :full_name => name, :url => "#{repo_root(name)}/.git"
|
70
|
+
})
|
71
|
+
else
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def update_repo(name, opts={})
|
78
|
+
if opts[:name]
|
79
|
+
_rename_repo(name, opts[:name])
|
80
|
+
end
|
81
|
+
return read_repo(name)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def delete_repo(name)
|
86
|
+
if Dir.exist? repo_root(name)
|
87
|
+
FileUtils.rm_rf repo_root(name)
|
88
|
+
else
|
89
|
+
raise "No such repo, '#{name}'"
|
90
|
+
end
|
91
|
+
return nil
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def _rename_repo(old_name, new_name)
|
96
|
+
raise "No such repo '#{old_name}'" unless Dir.exist? repo_root(old_name)
|
97
|
+
FileUtils.mv repo_root(old_name), repo_root(new_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'gitomator/task'
|
2
|
+
|
3
|
+
module Gitomator
|
4
|
+
module Task
|
5
|
+
class BaseReposTask < Gitomator::BaseTask
|
6
|
+
|
7
|
+
attr_reader :local_dir # String or nil
|
8
|
+
attr_reader :repos # Array<String>
|
9
|
+
|
10
|
+
#
|
11
|
+
# @param context [Gitomator::Context]
|
12
|
+
# @param auto_marker_config [Gitomator::Classroom::Config::AutoMarker] Parsed configuration object (TODO: Implement it as a subclass of Gitomator::Classroom::Assignment)
|
13
|
+
# @param local_dir [String] A local directory where the repos will be (or have been) cloned.
|
14
|
+
#
|
15
|
+
def initialize(context, repos, local_dir=nil)
|
16
|
+
super(context)
|
17
|
+
unless local_dir.nil?
|
18
|
+
raise "No such directory #{local_dir}" unless Dir.exists? local_dir
|
19
|
+
end
|
20
|
+
@local_dir = local_dir
|
21
|
+
@repos = repos
|
22
|
+
@blocks = { :before => [], :after => [] }
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def run()
|
28
|
+
@blocks[:before].each {|b| self.instance_exec(&b) }
|
29
|
+
|
30
|
+
repo2result, repo2error = {}, {}
|
31
|
+
|
32
|
+
repos.each_with_index do |repo, index|
|
33
|
+
logger.debug "#{repo} (#{index + 1} out of #{repos.length})"
|
34
|
+
begin
|
35
|
+
repo2result[repo] = process_repo(repo, index)
|
36
|
+
rescue => e
|
37
|
+
process_repo_error(repo, index, e)
|
38
|
+
repo2error[repo] = e
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@blocks[:after].each {|b| self.instance_exec(repo2result, repo2error, &b) }
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
#
|
48
|
+
# You need to override this method!
|
49
|
+
#
|
50
|
+
# @param repo [String] The name of the repo
|
51
|
+
# @return Object (Optionally) return a result that can be used after processing all repos
|
52
|
+
#
|
53
|
+
def process_repo(repo, index)
|
54
|
+
raise "Unimplemented"
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Override this to provide custom error handling
|
59
|
+
#
|
60
|
+
def process_repo_error(repo, index, error)
|
61
|
+
logger.error "#{repo} : #{error}\n#{error.backtrace.join("\n\t")}"
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
#
|
66
|
+
# Inject a block that will run before processing any repos.
|
67
|
+
# The blocks takes no arguments, and doesn't (need to) return any specific value.
|
68
|
+
#
|
69
|
+
def before_processing_any_repos(&block)
|
70
|
+
@blocks[:before].push block
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Inject a block that will run after all repos have been processed.
|
75
|
+
#
|
76
|
+
# @yield [result2mark, repo2error]
|
77
|
+
# @yieldparam [Hash<String,Object>] repo2result
|
78
|
+
# @yieldparam [Hash<String,Error>] repo2error
|
79
|
+
#
|
80
|
+
def after_processing_all_repos(&block)
|
81
|
+
@blocks[:after].push block
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'gitomator/task/base_repos_task'
|
2
|
+
|
3
|
+
module Gitomator
|
4
|
+
module Task
|
5
|
+
class CloneRepos < Gitomator::Task::BaseReposTask
|
6
|
+
|
7
|
+
|
8
|
+
#
|
9
|
+
# @param context [Gitomator::Context]
|
10
|
+
# @param repos [Array<String>] The repos to clone
|
11
|
+
# @param local_dir [String] A local directory where the repos will be cloned.
|
12
|
+
# @param opts [Hash]
|
13
|
+
#
|
14
|
+
def initialize(context, repos, local_dir, opts={})
|
15
|
+
super(context, repos, local_dir)
|
16
|
+
@opts = opts
|
17
|
+
|
18
|
+
before_processing_any_repos do
|
19
|
+
logger.debug "Clonning #{repos.length} repo(s) into #{local_dir} ..."
|
20
|
+
end
|
21
|
+
|
22
|
+
after_processing_all_repos do |repo2result, repo2error|
|
23
|
+
cloned = repo2result.select {|_, result| result}.length
|
24
|
+
skipped = repo2result.reject {|_, result| result}.length
|
25
|
+
errored = repo2error.length
|
26
|
+
logger.info "Done (#{cloned} cloned, #{skipped} skipped, #{errored} errors)"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# override
|
32
|
+
def process_repo(source, index)
|
33
|
+
namespace = hosting.resolve_namespace(source)
|
34
|
+
repo_name = hosting.resolve_repo_name(source)
|
35
|
+
branch = hosting.resolve_branch(source)
|
36
|
+
|
37
|
+
local_repo_root = File.join(@local_dir, repo_name)
|
38
|
+
if Dir.exist? local_repo_root
|
39
|
+
logger.info "Local clone exists, #{local_repo_root}"
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
repo = hosting.read_repo(repo_name)
|
44
|
+
raise "No such remote repo, #{repo_name}" if repo.nil?
|
45
|
+
|
46
|
+
logger.info "git clone #{repo.url}"
|
47
|
+
git.clone(repo.url, local_repo_root)
|
48
|
+
|
49
|
+
unless branch.nil?
|
50
|
+
logger.debug("Switching to remote branch #{branch}")
|
51
|
+
git.checkout(local_repo_root, branch, {:is_new => true, :is_remote => true})
|
52
|
+
end
|
53
|
+
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
|
2
|
+
module Gitomator
|
3
|
+
module Task
|
4
|
+
module Config
|
5
|
+
|
6
|
+
|
7
|
+
class ReposConfig
|
8
|
+
|
9
|
+
attr_reader :default_access_permission
|
10
|
+
attr_reader :repo_properties
|
11
|
+
attr_reader :source_repo
|
12
|
+
|
13
|
+
#
|
14
|
+
# @param config_obj [Hash] Configuration data (commonly loaded from a YAML file)
|
15
|
+
#
|
16
|
+
def initialize(config_obj)
|
17
|
+
raise "Missing required key, repos" unless config_obj.has_key? 'repos'
|
18
|
+
|
19
|
+
@default_access_permission = (config_obj['default_access_permission'] || :read).to_sym
|
20
|
+
@source_repo = config_obj['source_repo']
|
21
|
+
@repo_properties = config_obj['repo_properties'] || {}
|
22
|
+
|
23
|
+
@repo2permissions = parse_repo2permissions(config_obj['repos'])
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
#
|
28
|
+
# @return [Enumerable<Strings>] The names of the repos.
|
29
|
+
#
|
30
|
+
def repos
|
31
|
+
@repo2permissions.keys
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# @param repo [String] The name of a repository
|
36
|
+
# @return [Hash<String,Symbol>] Map name (user or team) to permission (:read/:write/:admin)
|
37
|
+
#
|
38
|
+
def permissions(repo)
|
39
|
+
@repo2permissions[repo]
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# =================== Protected Helper Methods =========================
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
|
48
|
+
#
|
49
|
+
# Helper function.
|
50
|
+
# @param repos_config [Array] Array of repo config items (various formats are supported)
|
51
|
+
#
|
52
|
+
def parse_repo2permissions(repos_config)
|
53
|
+
repo2permissions = {}
|
54
|
+
|
55
|
+
repos_config.each do |repo_config|
|
56
|
+
repo_name = nil
|
57
|
+
permissions = {}
|
58
|
+
|
59
|
+
# 1. Parse it ...
|
60
|
+
if repo_config.is_a? String
|
61
|
+
repo_name = repo_config
|
62
|
+
elsif (repo_config.is_a? Hash) and (repo_config.length == 1)
|
63
|
+
repo_name = repo_config.keys.first.to_s
|
64
|
+
permissions = parse_permissions(repo_config[repo_name])
|
65
|
+
else
|
66
|
+
raise Gitomator::Classroom::Exception::InvalidConfig.new(
|
67
|
+
"Cannot parse #{repo_config} (expected String or a Hash with one entry)")
|
68
|
+
end
|
69
|
+
|
70
|
+
# 2. Check if there was an error ...
|
71
|
+
if repo2permissions.has_key? repo_config
|
72
|
+
raise Gitomator::Classroom::Exception::InvalidConfig.new("Duplicate property, #{repo_config}")
|
73
|
+
end
|
74
|
+
|
75
|
+
# 3. If no error, store the information
|
76
|
+
repo2permissions[repo_name] = permissions
|
77
|
+
end
|
78
|
+
|
79
|
+
return repo2permissions
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
#
|
84
|
+
# Parse the permissions configuration of a single repo into a hash that
|
85
|
+
# maps user/team names (Strings) to access-permissions (Symbols).
|
86
|
+
#
|
87
|
+
# Various configuration formats are supported:
|
88
|
+
# * String - Single name, with default permission
|
89
|
+
# * Hash - Maps names to permissions
|
90
|
+
# * Array<String/Hash> - An array mixing both of the previous options.
|
91
|
+
#
|
92
|
+
# @param permissions_config [String/Hash/Array]
|
93
|
+
# @return [Hash<String,Symbol>]
|
94
|
+
#
|
95
|
+
def parse_permissions(permissions_config)
|
96
|
+
if permissions_config.is_a? String
|
97
|
+
return { permissions_config => default_access_permission }
|
98
|
+
|
99
|
+
elsif permissions_config.is_a? Hash
|
100
|
+
return permissions_config.map {|name,perm| [name, perm.to_sym] } .to_h
|
101
|
+
|
102
|
+
elsif permissions_config.is_a? Array
|
103
|
+
return permissions_config.map {|x| parse_permissions(x) } .reduce(:merge)
|
104
|
+
|
105
|
+
else
|
106
|
+
raise "Invalid permission configuration, #{permissions_config}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Gitomator
|
2
|
+
module Task
|
3
|
+
module Config
|
4
|
+
|
5
|
+
class TeamConfig
|
6
|
+
|
7
|
+
|
8
|
+
#=========================================================================
|
9
|
+
# Static factory methods
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param config [Hash] - Configuration data (e.g. parsed from a YAML file)
|
13
|
+
# @return [Enumerable<Gitomator::Task::Config::TeamConfig>]
|
14
|
+
#
|
15
|
+
def self.from_hash(config)
|
16
|
+
return config.map {|name, members| new(name, members) }
|
17
|
+
end
|
18
|
+
|
19
|
+
#=========================================================================
|
20
|
+
|
21
|
+
|
22
|
+
attr_reader :name, :members # Hash[String -> String], username to role
|
23
|
+
|
24
|
+
#
|
25
|
+
# @param name [String]
|
26
|
+
# @param members_config [Array] Each item is either a string (username) or Hash with one entry (username -> role)
|
27
|
+
#
|
28
|
+
def initialize(name, members_config)
|
29
|
+
@name = name
|
30
|
+
@members = parse_members_config(members_config)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def parse_members_config(members_config)
|
35
|
+
result = {}
|
36
|
+
|
37
|
+
members_config.each do |entry|
|
38
|
+
if entry.is_a? String
|
39
|
+
result[entry] = 'member' # Default role is 'member'
|
40
|
+
elsif entry.is_a?(Hash) && entry.length == 1
|
41
|
+
result[entry.keys.first] = entry.values.first
|
42
|
+
else
|
43
|
+
raise "Invalid team-member config, #{entry}."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
return result
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'gitomator/task/base_repos_task'
|
2
|
+
|
3
|
+
module Gitomator
|
4
|
+
module Task
|
5
|
+
|
6
|
+
#
|
7
|
+
# Abstract parent class
|
8
|
+
#
|
9
|
+
class EnableDisableCI < Gitomator::Task::BaseReposTask
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param context - Has a `ci` method that returns a Gitomator::Service::CI
|
13
|
+
# @param repos [Array<String>] - Names of the repos to enable/disable CI on.
|
14
|
+
# @param opts [Hash<Symbol,Object>] - Task options
|
15
|
+
# @option opts [Boolean] :sync - Indicate whether we should start by sync'ing the CI service.
|
16
|
+
#
|
17
|
+
def initialize(context, repos, opts={})
|
18
|
+
super(context, repos)
|
19
|
+
|
20
|
+
if opts[:sync]
|
21
|
+
before_processing_any_repos do
|
22
|
+
logger.info "Syncing CI service (this may take a little while) ..."
|
23
|
+
ci.sync()
|
24
|
+
while ci.syncing?
|
25
|
+
print "."
|
26
|
+
sleep 1
|
27
|
+
end
|
28
|
+
puts ""
|
29
|
+
logger.info "CI service synchronized"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
class EnableCI < EnableDisableCI
|
40
|
+
def process_repo(repo_name, i)
|
41
|
+
logger.info "Enabling CI for #{repo_name} (#{i + 1} out of #{repos.length})"
|
42
|
+
ci.enable_ci repo_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
class DisableCI < EnableDisableCI
|
48
|
+
def process_repo(repo_name, i)
|
49
|
+
logger.info "Disabling CI for #{repo_name} (#{i + 1} out of #{repos.length})"
|
50
|
+
ci.disable_ci repo_name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'gitomator/task/base_repos_task'
|
3
|
+
require 'gitomator/task/clone_repos'
|
4
|
+
|
5
|
+
module Gitomator
|
6
|
+
module Task
|
7
|
+
class MakeRepos < Gitomator::Task::BaseReposTask
|
8
|
+
|
9
|
+
|
10
|
+
attr_reader :source_repo
|
11
|
+
attr_reader :update_existing
|
12
|
+
attr_reader :repo_properties
|
13
|
+
attr_reader :source_repo_local_root
|
14
|
+
|
15
|
+
#
|
16
|
+
# @param context
|
17
|
+
# @param repos [Array<String>]
|
18
|
+
# @param opts [Hash<Symbol,Object>]
|
19
|
+
# @option opts [Hash<Symbol,Object>] :repo_properties - For example, :private, :description, :has_issues, etc.
|
20
|
+
# @option opts [String] :source_repo - The name of a repo that will be the "starting point" of all the created repos.
|
21
|
+
# @option opts [Boolean] :update_existing - Update existing repos, by pushing latest commit(s) from the source_repo.
|
22
|
+
#
|
23
|
+
def initialize(context, repos, opts={})
|
24
|
+
super(context, repos)
|
25
|
+
@opts = opts
|
26
|
+
|
27
|
+
@source_repo = opts[:source_repo]
|
28
|
+
@update_existing = opts[:update_existing] || false
|
29
|
+
@repo_properties = opts[:repo_properties] || {}
|
30
|
+
@repo_properties = @repo_properties.map {|k,v| [k.to_sym,v] }.to_h
|
31
|
+
|
32
|
+
before_processing_any_repos do
|
33
|
+
logger.info "About to create/update #{repos.length} repo(s) ..."
|
34
|
+
|
35
|
+
if source_repo
|
36
|
+
tmp_dir = Dir.mktmpdir('Gitomator_')
|
37
|
+
Gitomator::Task::CloneRepos.new(context, [source_repo], tmp_dir).run()
|
38
|
+
repo_name = hosting.resolve_repo_name(source_repo)
|
39
|
+
@source_repo_local_root = File.join(tmp_dir, repo_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
def process_repo(repo_name, index)
|
47
|
+
repo = hosting.read_repo(repo_name)
|
48
|
+
|
49
|
+
# If the repo doesn't exist, create it ...
|
50
|
+
if repo.nil?
|
51
|
+
logger.debug "Creating new repo #{repo_name} ..."
|
52
|
+
repo = hosting.create_repo(repo_name, repo_properties)
|
53
|
+
push_commits(repo, source_repo_local_root)
|
54
|
+
|
55
|
+
# If the repo exists, we might need to push changes, or update its properties
|
56
|
+
else
|
57
|
+
if update_existing
|
58
|
+
push_commits(repo, source_repo_local_root)
|
59
|
+
end
|
60
|
+
update_properties_if_needed(repo, repo_properties)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def push_commits(hosted_repo, local_repo)
|
66
|
+
if local_repo
|
67
|
+
logger.debug "Pushing commits from #{local_repo} to #{hosted_repo.name} "
|
68
|
+
git.set_remote(local_repo, hosted_repo.name, hosted_repo.url, {create: true})
|
69
|
+
git.command(local_repo, "push #{hosted_repo.name} HEAD:master")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def update_properties_if_needed(repo, props)
|
75
|
+
p = repo.properties
|
76
|
+
diff = props.select {|k,v| p.has_key?(k) && p[k] != v}
|
77
|
+
unless(diff.empty?)
|
78
|
+
logger.debug "Updating #{repo.name} properties #{diff}"
|
79
|
+
hosting.update_repo(repo.name, diff)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'gitomator/task'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Gitomator
|
5
|
+
module Task
|
6
|
+
|
7
|
+
class SetupTeam < Gitomator::BaseTask
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param context
|
11
|
+
# @param team_name [String]
|
12
|
+
# @param member2role [Hash<String,String>] Map usernames to roles.
|
13
|
+
# @param opts [Hash]
|
14
|
+
#
|
15
|
+
def initialize(context, team_name, member2role, opts={})
|
16
|
+
super(context)
|
17
|
+
@team_name = team_name
|
18
|
+
@member2role = member2role
|
19
|
+
@opts = opts
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
def run
|
25
|
+
create_team_if_missing()
|
26
|
+
@member2role.each do |username, role|
|
27
|
+
begin
|
28
|
+
create_or_update_membership(username, role)
|
29
|
+
rescue => e
|
30
|
+
logger.error("Cannot create/update #{username}'s membership in " +
|
31
|
+
"#{@team_name} - #{e}.\n#{e.backtrace.join("\n\t")}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def create_team_if_missing
|
38
|
+
if hosting.read_team(@team_name).nil?
|
39
|
+
logger.info("Creating team: #{@team_name}")
|
40
|
+
hosting.create_team(@team_name)
|
41
|
+
else
|
42
|
+
logger.debug("Team #{@team_name} exists.")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def create_or_update_membership(username, role)
|
48
|
+
current_role = hosting.read_team_membership(@team_name, username)
|
49
|
+
if current_role.nil?
|
50
|
+
logger.info("Adding #{username} to team #{@team_name} (role: #{role}).")
|
51
|
+
hosting.create_team_membership(@team_name, username, role)
|
52
|
+
elsif current_role != role
|
53
|
+
logger.info("Updating #{username}'s role from #{current_role} to #{role} (team: #{@team_name})")
|
54
|
+
hosting.update_team_membership(@team_name, username, role)
|
55
|
+
else
|
56
|
+
logger.debug("Skipping #{username}, already a #{role} of #{@team_name}.")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'gitomator/task'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Gitomator
|
5
|
+
module Task
|
6
|
+
|
7
|
+
class UpdateRepoAccessPermissions < Gitomator::BaseTask
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param context
|
11
|
+
# @param repo_name [String]
|
12
|
+
# @param user2perm [Hash<String,Symbol>] Map usernames to permission type (:read/:write).
|
13
|
+
# @param team2perm [Hash<String,Symbol>] Map team-names to permission type (:read/:write).
|
14
|
+
# @param opts [Hash]
|
15
|
+
#
|
16
|
+
def initialize(context, repo_name, user2perm, team2perm, opts={})
|
17
|
+
super(context)
|
18
|
+
@repo_name = repo_name
|
19
|
+
@user2perm = user2perm || {}
|
20
|
+
@team2perm = team2perm || {}
|
21
|
+
@opts = opts
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
def run
|
27
|
+
@user2perm.each do |username, permission|
|
28
|
+
logger.info("Granting user #{username} #{permission} permission to #{@repo_name}")
|
29
|
+
hosting.set_user_permission(username, @repo_name, permission)
|
30
|
+
end
|
31
|
+
|
32
|
+
@team2perm.each do |team_name, permission|
|
33
|
+
logger.info("Granting team #{team_name} #{permission} permission to #{@repo_name}")
|
34
|
+
hosting.set_team_permission(team_name, @repo_name, permission)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|