Class: GithubOrgManager::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/github_org_manager.rb

Overview

Manages an organization in GitHub, currently in a READONLY fashion for syncing purposes.

Constant Summary collapse

DEV_HOME =

Where your development files are stored

File.join(Dir.home, "dev")
OCTOKIT_PARAMS =

Default login params for Octokit, currently relies on OAuth tokens in ‘~/.netrc`:

( github.com/octokit/octokit.rb#using-a-netrc-file )

{ netrc: true }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(org_name:, team_only: false, dev_home: DEV_HOME, octokit_params: OCTOKIT_PARAMS, &octokit_configuration) ⇒ Manager

Creates a new Manager

Parameters:

  • org_name: (String)

    Organization to pull data from.

  • team_only: (Boolean) (defaults to: false)

    Scope all data to only teams in the organization you belong to, meaning all syncing applies only to teams you’re specifically on.

  • dev_home: (String) (defaults to: DEV_HOME)

    Where development files and repos live on your machine.

  • octokit_params (Hash<Symbol, Any>) (defaults to: OCTOKIT_PARAMS)

    Params passed through to ‘Octokit::Client` constructor.

  • &octokit_configuration (Proc)

    Configuration block passed to ‘Octokit.configure`.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/github_org_manager.rb', line 42

def initialize(
  org_name:,
  team_only: false,
  dev_home: DEV_HOME,
  octokit_params: OCTOKIT_PARAMS,
  &octokit_configuration
)
  path_name = Pathname.new(dev_home)
  File.directory?(path_name) or raise "Directory does not exist: #{path_name}"

  @dev_home = path_name
  @org_name = org_name
  @org_path = File.join(@dev_home, org_name)

  @client   = get_client(octokit_params:, &octokit_configuration)
  @username = client.user[:login]
  @team_only = team_only
end

Instance Attribute Details

#clientObject (readonly)

Returns the value of attribute client.



22
23
24
# File 'lib/github_org_manager.rb', line 22

def client
  @client
end

#dev_homeObject (readonly)

Returns the value of attribute dev_home.



22
23
24
# File 'lib/github_org_manager.rb', line 22

def dev_home
  @dev_home
end

#org_nameObject (readonly)

Returns the value of attribute org_name.



22
23
24
# File 'lib/github_org_manager.rb', line 22

def org_name
  @org_name
end

#usernameObject (readonly)

Returns the value of attribute username.



22
23
24
# File 'lib/github_org_manager.rb', line 22

def username
  @username
end

Instance Method Details

#all_reposHash<String, String>

All unscoped repos belonging to an organization.

Returns:

  • (Hash<String, String>)

    Mapping of repo name to repo URL.



85
86
87
88
89
# File 'lib/github_org_manager.rb', line 85

def all_repos
  @all_repos ||= client.org_repos(@org_name).to_h do |repo_data|
    [repo_data[:name], repo_data[:html_url]]
  end
end

#clear_cache!void

This method returns an undefined value.

If for whatever reason you need to unset all the cached instance variables for refreshing data.



220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/github_org_manager.rb', line 220

def clear_cache!
  @repos = nil
  @repo_paths = nil
  @all_repos = nil
  @my_repos = nil
  @org_teams = nil
  @team_repos = nil
  @team_members = nil
  @my_teams = nil
  @my_repo_names = nil

  true
end

#ensure_repo_directories_exist!void

This method returns an undefined value.

Make sure that every repo in the organization exists on this machine. Scoped to team if ‘team_only` is on.



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/github_org_manager.rb', line 161

def ensure_repo_directories_exist!
  Dir.mkdir(@org_path) unless Dir.exist?(@org_path)

  Dir.chdir(@org_path) do
    repos.each do |name, html_url|
      `git clone "#{html_url}"` unless Dir.exist?(@repo_paths[name])
    end
  end

  true
end

#my_repo_namesSet<String>

Repos that the current logged in user has authority over.

Returns:

  • (Set<String>)

    Names of repos.



149
150
151
152
153
154
155
# File 'lib/github_org_manager.rb', line 149

def my_repo_names
  @my_repo_names ||= team_repos
    .select { |name, _| my_teams.include?(name) }
    .values
    .flatten
    .then { Set.new(_1) }
end

#my_reposHash<String, String>

Repos that the current user in Octokit is a member of a team. of that manages that repo.

Returns:

  • (Hash<String, String>)

    Mapping of repo name to repo URL.



96
97
98
99
100
# File 'lib/github_org_manager.rb', line 96

def my_repos
  @my_repos ||= all_repos.select do |name, _|
    my_repo_names.include?(name)
  end
end

#my_teamsSet<String>

Teams that the current logged in user belongs to.

Returns:

  • (Set<String>)

    Names of teams.



138
139
140
141
142
143
# File 'lib/github_org_manager.rb', line 138

def my_teams
  @my_teams ||= team_members
    .select { |_, members| members.include?(@username) }
    .keys
    .then { Set.new(_1) }
end

#org_teamsHash<String, Numeric>

Gets teams under the current organization.

Returns:

  • (Hash<String, Numeric>)

    Mapping of team name to team id.



106
107
108
109
110
# File 'lib/github_org_manager.rb', line 106

def org_teams
  @org_teams ||= client.org_teams(@org_name).to_h do
    [_1[:name], _1[:id]]
  end
end

#repo_pathsHash<String, String>

File paths of all repos currently in scope for your organization.

Returns:

  • (Hash<String, String>)

    Mapping of repo name to repo file path.



75
76
77
78
79
# File 'lib/github_org_manager.rb', line 75

def repo_paths
  @repo_paths ||= repos.to_h do |repo_name, _repo_url|
    [repo_name, File.join(@org_path, repo_name)]
  end
end

#reposHash<String, String>

Repositories in the organization the Manager is targeting. These values can be scoped to only ones owned by the username specified to Octokit with the ‘team_only` option.

Returns:

  • (Hash<String, String>)

    Mapping of repo name to repo URL



67
68
69
# File 'lib/github_org_manager.rb', line 67

def repos
  @repos ||= @team_only ? my_repos : team_repos
end

#team_membersHash<String, Array<String>>

Members that belong to each team.

Returns:

  • (Hash<String, Array<String>>)

    Mapping of team name to a collection of all of its members.



128
129
130
131
132
# File 'lib/github_org_manager.rb', line 128

def team_members
  @team_members ||= org_teams.to_h do |name, id|
    [name, client.team_members(id).map { _1[:login] }]
  end
end

#team_reposHash<String, Array<String>>

Repos that each team manages, may have overlaps.

Returns:

  • (Hash<String, Array<String>>)

    Mapping of team name to a collection of repo names that they manage.



117
118
119
120
121
# File 'lib/github_org_manager.rb', line 117

def team_repos
  @team_repos ||= org_teams.to_h do |name, id|
    [name, client.team_repos(id).map { _1[:name] }]
  end
end

#update_repos!void

This method returns an undefined value.

Update all repos, scoped to team if ‘team_only` is on.

TODO: While there is a Ruby Git gem I’ve had some difficulty in getting it to work properly, hence plain old system commands instead for the time being.



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/github_org_manager.rb', line 180

def update_repos!
  # Hard to update repos which don't exist on the computer, make sure that
  # we have them all already downloaded, or do so
  ensure_repo_directories_exist!

  puts "📦 Updating #{repo_paths.size} repos: \n"

  repo_paths.each do |name, path|
    Dir.chdir(path) do
      main_branch = `basename $(git symbolic-ref refs/remotes/origin/HEAD)` || "main"
      current_branch = `git rev-parse --abbrev-ref HEAD`
      on_main = main_branch == current_branch
      no_changes = `git diff --stat`.empty?

      puts "  Updating #{name}:"

      puts "    Stashing any potential changes" unless no_changes
      `git stash` unless no_changes

      puts "    Checking out #{main_branch}" unless on_main
      `git checkout #{main_branch}` unless on_main

      puts "    Pulling changes"
      `git pull`

      puts "    Returning to previous branch #{current_branch}" unless on_main
      `git checkout #{current_branch}` unless on_main

      puts "    Popping stash" unless no_changes
      `git stash pop` unless no_changes
    end
  end

  true
end