fue 0.1.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -1
- data/CONTRIBUTING.md +3 -3
- data/LICENSE.md +1 -1
- data/README.md +54 -11
- data/bin/fue +40 -8
- data/lib/fue.rb +10 -0
- data/lib/fue/auth.rb +108 -0
- data/lib/fue/finder.rb +104 -19
- data/lib/fue/security.rb +42 -0
- data/lib/fue/shell.rb +14 -0
- data/lib/fue/version.rb +3 -1
- metadata +30 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6784a7e45c93b1e0622f4e719f58c541246a15fd64927245c97bd0f03574252a
|
4
|
+
data.tar.gz: ce2c2b1ef2f06c9cdd56d638ab4bbd7df8e1497ffb26190514d1e4b9f0ab621f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d503619065aab23dfd208305f1aafd9b8869538662397e6e471008be90c9713d510ac6e6af3ac5bfe2f1c392514749fa7eee8bd175aac2604da6e2223bb41ff4
|
7
|
+
data.tar.gz: 4d9ee34e80d5d031b291c4318fdab7733f95a5f5cf19e956078212b0a83e3700f54ee0e5227ee1635dcc2e0169e3d2a74d605a5db118f42d061e3ff0f17631b5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
|
-
### 0.1
|
1
|
+
### 0.3.1 (Next)
|
2
2
|
|
3
|
+
* [#29](https://github.com/dblock/fue/issues/29): Fix: `error: can't modify frozen String` - [@dblock](https://github.com/dblock).
|
4
|
+
|
5
|
+
### 0.3.0 (2020/05/22)
|
6
|
+
|
7
|
+
* [#25](https://github.com/dblock/fue/pull/25): Added `contributors`, find repo contributors' e-mails - [@dblock](https://github.com/dblock).
|
8
|
+
* [#12](https://github.com/dblock/fue/issues/12): Added explicit ruby call to run fue - [@SYNstack](https://github.com/SYNstack).
|
9
|
+
* [#13](https://github.com/dblock/fue/issues/13): Changed shell tests to be OS-neutral - [@SYNstack](https://github.com/SYNstack).
|
10
|
+
* [#16](https://github.com/dblock/fue/issues/16): Setup Windows CI - [@SYNstack](https://github.com/synstack).
|
11
|
+
* [#17](https://github.com/dblock/fue/issues/19): Fix open-ended dependencies in gemspec - [@SYNstack](https://github.com/synstack).
|
12
|
+
|
13
|
+
### 0.2.1 (2018/8/23)
|
14
|
+
|
15
|
+
* [#2](https://github.com/dblock/fue/issues/2): Prompt to authenticate with Github - [@dblock](https://github.com/dblock).
|
16
|
+
* [#7](https://github.com/dblock/fue/issues/7): Display version - [@dblock](https://github.com/dblock).
|
17
|
+
|
18
|
+
### 0.2.0 (2018/8/22)
|
19
|
+
|
20
|
+
* [#6](https://github.com/dblock/fue/issues/6): Configure max depth and breadth separately - [@dblock](https://github.com/dblock).
|
21
|
+
* [#5](https://github.com/dblock/fue/issues/5): Iterate over more than 100 repositories - [@dblock](https://github.com/dblock).
|
3
22
|
* [#5](https://github.com/dblock/fue/issues/5): Fix error message and clarify max depth - [@dblock](https://github.com/dblock).
|
4
23
|
|
5
24
|
### 0.1.1 (2018/8/14)
|
data/CONTRIBUTING.md
CHANGED
@@ -4,11 +4,11 @@ This project is work of [many contributors](https://github.com/dblock/fue/graphs
|
|
4
4
|
|
5
5
|
You're encouraged to submit [pull requests](https://github.com/dblock/fue/pulls), [propose features and discuss issues](https://github.com/dblock/fue/issues).
|
6
6
|
|
7
|
-
In the examples below, substitute your
|
7
|
+
In the examples below, substitute your GitHub username for `contributor` in URLs.
|
8
8
|
|
9
9
|
### Fork the Project
|
10
10
|
|
11
|
-
Fork the [project on
|
11
|
+
Fork the [project on GitHub](https://github.com/dblock/fue) and check out your copy.
|
12
12
|
|
13
13
|
```
|
14
14
|
git clone https://github.com/contributor/fue.git
|
@@ -47,7 +47,7 @@ We definitely appreciate pull requests that highlight or reproduce a problem, ev
|
|
47
47
|
|
48
48
|
Implement your feature or bug fix.
|
49
49
|
|
50
|
-
Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop). Run `bundle exec rubocop` and fix any style issues highlighted, auto-correct issues when possible with `bundle exec rubocop -a`. To silence generally
|
50
|
+
Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop). Run `bundle exec rubocop` and fix any style issues highlighted, auto-correct issues when possible with `bundle exec rubocop -a`. To silence generally ignored issues, including line lengths or code complexity metrics, run `bundle exec rubocop --auto-gen-config`.
|
51
51
|
|
52
52
|
Make sure that `bundle exec rake` completes without errors.
|
53
53
|
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -3,8 +3,11 @@ Fue
|
|
3
3
|
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/fue.svg)](https://badge.fury.io/rb/fue)
|
5
5
|
[![Build Status](https://travis-ci.org/dblock/fue.svg)](https://travis-ci.org/dblock/fue)
|
6
|
+
[![Build status](https://ci.appveyor.com/api/projects/status/github/dblock/fue?branch=master&svg=true)](https://ci.appveyor.com/project/dblock/fue)
|
6
7
|
|
7
|
-
Find
|
8
|
+
Find e-mail addresses of a Github users from their commit logs.
|
9
|
+
|
10
|
+
Fue is short for "Finding Unicorn Engineers".
|
8
11
|
|
9
12
|
![](images/fue.gif)
|
10
13
|
|
@@ -14,34 +17,45 @@ Find an e-mail address of a Github user from their commit log.
|
|
14
17
|
gem install fue
|
15
18
|
```
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
Obtain a Github access token from [here](https://github.com/settings/tokens) with `public_repo` permissions. See [help](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) for more information. Set the token as `GITHUB_ACCESS_TOKEN`.
|
20
|
+
### Commands
|
20
21
|
|
21
22
|
#### Find Someone's Email
|
22
23
|
|
23
|
-
The `find` command looks through user's initial
|
24
|
+
The `find` command looks through user's initial repository commits.
|
24
25
|
|
25
26
|
```
|
26
|
-
|
27
|
+
$ fue --verbose find defunkt
|
27
28
|
|
28
29
|
Chris Wanstrath <chris@ozmm.org>
|
29
30
|
Chris Wanstrath <chris@github.com>
|
30
31
|
```
|
31
32
|
|
33
|
+
#### Find All Repo Contributors' Emails
|
34
|
+
|
35
|
+
The `contributors` command looks through a git log of contributors, then fetches their e-mails.
|
36
|
+
|
37
|
+
```
|
38
|
+
$ fue --verbose contributors defunkt/colored
|
39
|
+
|
40
|
+
defunkt: Chris Wanstrath <chris@ozmm.org>
|
41
|
+
kch: Caio Chassot <kch@users.noreply.github.com>
|
42
|
+
```
|
43
|
+
|
44
|
+
### Options
|
45
|
+
|
32
46
|
#### Specify More Depth
|
33
47
|
|
34
|
-
By default the code looks at the last 10
|
48
|
+
By default the code looks at 1 commit from the last 10 repositories. You can look at more repositories (breadth) and more commits (depth). The maximum value for depth is 100, enforced by Github. Fue will iterate over a number of repositories larger than 100.
|
35
49
|
|
36
50
|
```
|
37
|
-
|
51
|
+
$ fue find --breadth=100 --depth=5 defunkt
|
38
52
|
|
39
53
|
Chris Wanstrath <chris@ozmm.org>
|
40
54
|
Chris Wanstrath <chris@github.com>
|
41
55
|
defunkt <chris@ozmm.org>
|
42
56
|
```
|
43
57
|
|
44
|
-
|
58
|
+
### Get Help
|
45
59
|
|
46
60
|
```
|
47
61
|
fue help
|
@@ -49,12 +63,41 @@ fue help
|
|
49
63
|
|
50
64
|
Displays additional options.
|
51
65
|
|
66
|
+
### Access Tokens
|
67
|
+
|
68
|
+
Fue will prompt you for Github credentials and 2FA, if enabled.
|
69
|
+
|
70
|
+
```
|
71
|
+
$ fue find defunkt
|
72
|
+
Enter dblock's GitHub password (never stored): ******************
|
73
|
+
Enter GitHub 2FA code: ******
|
74
|
+
Token saved to keychain.
|
75
|
+
```
|
76
|
+
|
77
|
+
The access token will be generated with `public_repo` scope and stored in the keychain. It can be later deleted from [here](https://github.com/settings/tokens). You can also skip the prompts and use a previously obtained token with `-t` or by setting the `GITHUB_ACCESS_TOKEN` environment variable.
|
78
|
+
|
79
|
+
See [Creating a Personal Access Token for the Command Line](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) for more information about personal tokens.
|
80
|
+
|
81
|
+
## Debugging
|
82
|
+
|
83
|
+
If you run into an unexpected error, try getting a stack trace with `GLI_DEBUG=true`.
|
84
|
+
|
85
|
+
```
|
86
|
+
$ GLI_DEBUG=true fue find dblock
|
87
|
+
|
88
|
+
FrozenError: can't modify frozen String
|
89
|
+
/Users/dblock/source/dblock/fue/lib/fue/auth.rb:97:in `get_secure'
|
90
|
+
/Users/dblock/source/dblock/fue/lib/fue/auth.rb:80:in `get_password'
|
91
|
+
/Users/dblock/source/dblock/fue/lib/fue/auth.rb:50:in `password'
|
92
|
+
/Users/dblock/source/dblock/fue/lib/fue/auth.rb:59:in `block in github'
|
93
|
+
```
|
94
|
+
|
52
95
|
## Contributing
|
53
96
|
|
54
|
-
There
|
97
|
+
There are [a few feature requests and known issues](https://github.com/dblock/fue/issues). Please contribute! See [CONTRIBUTING](CONTRIBUTING.md).
|
55
98
|
|
56
99
|
## Copyright and License
|
57
100
|
|
58
|
-
Copyright (c) 2018, Daniel Doubrovkine, [Artsy](http://artsy.github.io), with help from [Orta](https://github.com/orta).
|
101
|
+
Copyright (c) 2018-2020, Daniel Doubrovkine, [Artsy](http://artsy.github.io), with help from [Orta](https://github.com/orta).
|
59
102
|
|
60
103
|
This project is licensed under the [MIT License](LICENSE.md).
|
data/bin/fue
CHANGED
@@ -1,35 +1,67 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require 'gli'
|
3
5
|
require 'fue'
|
4
|
-
require 'graphlient'
|
5
6
|
|
6
7
|
class App
|
7
8
|
extend GLI::App
|
8
9
|
|
9
|
-
program_desc "Find a
|
10
|
+
program_desc "Find a GitHub user's e-mail address."
|
11
|
+
version Fue::VERSION
|
10
12
|
|
11
13
|
switch %i[v verbose], desc: 'Produce verbose output.', default_value: false
|
12
|
-
flag %i[t token], desc: '
|
14
|
+
flag %i[t token], desc: 'GitHub access token.', default_value: ENV['GITHUB_ACCESS_TOKEN']
|
15
|
+
flag %i[u username], desc: 'GitHub username.', default_value: Fue::Auth.instance.username
|
13
16
|
|
14
17
|
arguments :strict
|
15
18
|
subcommand_option_handling :normal
|
16
19
|
|
17
20
|
pre do |global_options, _command, options, _args|
|
18
21
|
options = global_options.dup
|
19
|
-
token = options.delete(:token)
|
22
|
+
token = options.delete(:token) || Fue::Auth.instance.token
|
20
23
|
$fue = Fue::Finder.new(token, options)
|
21
24
|
end
|
22
25
|
|
23
26
|
default_command :help
|
24
27
|
|
25
|
-
desc "Find a
|
28
|
+
desc "Find a GitHub user's e-mail address."
|
26
29
|
arg 'username'
|
27
30
|
command :find do |c|
|
28
|
-
c.flag %i[d depth], desc: 'Maximum search depth.', default_value:
|
31
|
+
c.flag %i[d depth], desc: 'Maximum search depth in each repository.', default_value: 1
|
32
|
+
c.flag %i[b breadth], desc: 'Maximum number of repositories to search.', default_value: 10
|
29
33
|
c.action do |global_options, options, args|
|
30
34
|
username = args.first
|
31
|
-
|
32
|
-
|
35
|
+
options[:depth] = options[:depth].to_i
|
36
|
+
options[:breadth] = options[:breadth].to_i
|
37
|
+
if global_options[:verbose]
|
38
|
+
puts "Searching for e-mail address for '#{username}' " \
|
39
|
+
"in the first #{options[:depth].to_i > 1 ? "#{options[:depth]} commits" : 'commit'} " \
|
40
|
+
"of #{options[:breadth]} most recent repositor#{options[:breadth] == 1 ? 'y' : 'ies'} ..."
|
41
|
+
end
|
42
|
+
puts $fue.emails(options.merge(username: username, verbose: global_options[:verbose]))
|
43
|
+
exit_now! nil, 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Find a GitHub repo contributors' e-mail addresses."
|
48
|
+
arg 'repo'
|
49
|
+
command :contributors do |c|
|
50
|
+
c.flag %i[d depth], desc: 'Maximum search depth in each repository for a given user.', default_value: 1
|
51
|
+
c.flag %i[b breadth], desc: 'Maximum number of repositories to search.', default_value: 10
|
52
|
+
c.action do |global_options, options, args|
|
53
|
+
repo = args.first
|
54
|
+
options[:depth] = options[:depth].to_i
|
55
|
+
options[:breadth] = options[:breadth].to_i
|
56
|
+
if global_options[:verbose]
|
57
|
+
puts "Searching for contributors' e-mail addresses in '#{repo}' " \
|
58
|
+
"in the first #{options[:depth].to_i > 1 ? "#{options[:depth]} commits" : 'commit'} " \
|
59
|
+
"of #{options[:breadth]} most recent repositor#{options[:breadth] == 1 ? 'y' : 'ies'} ..."
|
60
|
+
end
|
61
|
+
contributor_emails = $fue.contributors(options.merge(repo: repo, verbose: global_options[:verbose]))
|
62
|
+
contributor_emails.each_pair do |username, emails|
|
63
|
+
puts "#{username}: #{emails.join(', ')}"
|
64
|
+
end
|
33
65
|
exit_now! nil, 0
|
34
66
|
end
|
35
67
|
end
|
data/lib/fue.rb
CHANGED
data/lib/fue/auth.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fue
|
4
|
+
class Auth
|
5
|
+
def self.instance
|
6
|
+
@instance ||= new
|
7
|
+
end
|
8
|
+
|
9
|
+
def token
|
10
|
+
stored_options = { username: username, server: 'github.com', label: 'fue' }
|
11
|
+
stored_token = Fue::Security.get(stored_options)
|
12
|
+
unless stored_token
|
13
|
+
stored_token = github_token
|
14
|
+
Fue::Security.store!(stored_options.merge(password: stored_token))
|
15
|
+
puts 'Token saved to keychain.'
|
16
|
+
end
|
17
|
+
stored_token
|
18
|
+
end
|
19
|
+
|
20
|
+
def username
|
21
|
+
@username ||= begin
|
22
|
+
username = get_git_username&.chomp
|
23
|
+
username = get_username if username.nil? || username.empty?
|
24
|
+
username
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_git_username
|
31
|
+
Fue::Shell.system!('git config user.name')
|
32
|
+
rescue RuntimeError
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def github_token(code = nil)
|
37
|
+
github(code).auth.create(scopes: ['public_repo'], note: note).token
|
38
|
+
rescue Github::Error::Unauthorized => e
|
39
|
+
case e.response_headers['X-GitHub-OTP']
|
40
|
+
when /required/
|
41
|
+
github_token(get_code)
|
42
|
+
else
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
rescue Github::Error::UnprocessableEntity => e
|
46
|
+
raise e, 'A fue token already exists! Please revoke all previously-generated fue personal access tokens at https://github.com/settings/tokens.'
|
47
|
+
end
|
48
|
+
|
49
|
+
def password
|
50
|
+
@password ||= get_password
|
51
|
+
end
|
52
|
+
|
53
|
+
def note
|
54
|
+
"Fui (https://github.com/dblock/fue) on #{Socket.gethostname}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def github(code = nil)
|
58
|
+
Github.new do |config|
|
59
|
+
config.basic_auth = [username, password].join(':')
|
60
|
+
if code
|
61
|
+
config.connection_options = {
|
62
|
+
headers: {
|
63
|
+
'X-GitHub-OTP' => code
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_username
|
71
|
+
print 'Enter GitHub username: '
|
72
|
+
username = $stdin.gets
|
73
|
+
username&.chomp
|
74
|
+
rescue Interrupt => e
|
75
|
+
raise e, 'ctrl + c'
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_password
|
79
|
+
print "Enter #{username}'s GitHub password (never stored): "
|
80
|
+
get_secure
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_code
|
84
|
+
print 'Enter GitHub 2FA code: '
|
85
|
+
get_secure
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_secure
|
89
|
+
current_tty = `stty -g`
|
90
|
+
system 'stty raw -echo -icanon isig' if $CHILD_STATUS.success?
|
91
|
+
input = String.new
|
92
|
+
while (char = $stdin.getbyte) && !((char == 13) || (char == 10))
|
93
|
+
if (char == 127) || (char == 8)
|
94
|
+
input[-1, 1] = '' unless input.empty?
|
95
|
+
else
|
96
|
+
$stdout.write '*'
|
97
|
+
input << char.chr
|
98
|
+
end
|
99
|
+
end
|
100
|
+
print "\r\n"
|
101
|
+
input
|
102
|
+
rescue Interrupt => e
|
103
|
+
raise e, 'ctrl + c'
|
104
|
+
ensure
|
105
|
+
system "stty #{current_tty}" unless current_tty.empty?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/fue/finder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Fue
|
2
4
|
class Finder
|
3
5
|
attr_reader :token
|
@@ -19,20 +21,18 @@ module Fue
|
|
19
21
|
|
20
22
|
def emails(options = {})
|
21
23
|
query = <<-GRAPHQL
|
22
|
-
query($login: String!, $author_id: ID!, $depth: Int
|
24
|
+
query($login: String!, $author_id: ID!, $depth: Int!, $breadth: Int!, $cursor: String) {
|
23
25
|
user(login: $login) {
|
24
|
-
repositories(last: $
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
name
|
35
|
-
}
|
26
|
+
repositories(last: $breadth, after: $cursor, isFork:false, privacy: PUBLIC) {
|
27
|
+
nodes {
|
28
|
+
defaultBranchRef {
|
29
|
+
target {
|
30
|
+
... on Commit {
|
31
|
+
history(first: $depth, author: { id: $author_id }) {
|
32
|
+
nodes {
|
33
|
+
author {
|
34
|
+
email
|
35
|
+
name
|
36
36
|
}
|
37
37
|
}
|
38
38
|
}
|
@@ -40,23 +40,108 @@ module Fue
|
|
40
40
|
}
|
41
41
|
}
|
42
42
|
}
|
43
|
+
pageInfo {
|
44
|
+
startCursor
|
45
|
+
}
|
43
46
|
}
|
44
47
|
}
|
45
48
|
}
|
46
49
|
GRAPHQL
|
47
50
|
|
51
|
+
# max number of repositories to search
|
52
|
+
max_breadth = options[:breadth] || 10
|
53
|
+
|
48
54
|
query_options = {
|
49
55
|
login: options[:username],
|
50
56
|
author_id: author_id(options[:username]),
|
51
|
-
|
57
|
+
# max number of commits to look into
|
58
|
+
depth: options[:depth] || 1
|
52
59
|
}
|
53
60
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
61
|
+
$stdout.write "Searching for emails for #{options[:username]} ." if options[:verbose]
|
62
|
+
|
63
|
+
emails = Set.new
|
64
|
+
|
65
|
+
loop do
|
66
|
+
query_options[:breadth] = [max_breadth, 100].min
|
67
|
+
response = graphql_client.query(query, query_options)
|
68
|
+
repositories = response&.data&.user&.repositories
|
69
|
+
repositories&.nodes&.each do |history|
|
70
|
+
default_history = history.default_branch_ref&.target&.history
|
71
|
+
default_history&.nodes&.each do |node|
|
72
|
+
emails << "#{node.author.name} <#{node.author.email}>"
|
73
|
+
end
|
58
74
|
end
|
59
|
-
|
75
|
+
query_options[:cursor] = repositories&.page_info&.start_cursor
|
76
|
+
break unless query_options[:cursor]
|
77
|
+
|
78
|
+
max_breadth -= 100
|
79
|
+
break if max_breadth <= 0
|
80
|
+
|
81
|
+
$stdout.write '.' if options[:verbose]
|
82
|
+
end
|
83
|
+
|
84
|
+
puts " found #{emails.size} email address#{emails.size == 1 ? '' : 'es'}." if options[:verbose]
|
85
|
+
|
86
|
+
emails.to_a
|
87
|
+
end
|
88
|
+
|
89
|
+
def contributors(options = {})
|
90
|
+
query = <<-GRAPHQL
|
91
|
+
query($owner: String!, $name: String!, $cursor: String) {
|
92
|
+
repository(owner: $owner, name: $name) {
|
93
|
+
defaultBranchRef {
|
94
|
+
target {
|
95
|
+
... on Commit {
|
96
|
+
history(first: 100, after: $cursor) {
|
97
|
+
nodes {
|
98
|
+
author {
|
99
|
+
user {
|
100
|
+
login
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
pageInfo {
|
105
|
+
endCursor
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
}
|
111
|
+
}
|
112
|
+
}
|
113
|
+
GRAPHQL
|
114
|
+
|
115
|
+
repo_owner, repo_name = options[:repo].split('/', 2)
|
116
|
+
|
117
|
+
query_options = {
|
118
|
+
owner: repo_owner,
|
119
|
+
name: repo_name
|
120
|
+
}
|
121
|
+
|
122
|
+
logins = Set.new
|
123
|
+
|
124
|
+
$stdout.write 'Fetching contributors .' if options[:verbose]
|
125
|
+
|
126
|
+
loop do
|
127
|
+
response = graphql_client.query(query, query_options)
|
128
|
+
history = response&.data&.repository&.default_branch_ref&.target&.history
|
129
|
+
history&.nodes&.each do |node|
|
130
|
+
login = node.author.user&.login
|
131
|
+
logins << login if login
|
132
|
+
end
|
133
|
+
query_options[:cursor] = history&.page_info.end_cursor
|
134
|
+
$stdout.write '.' if options[:verbose]
|
135
|
+
break unless query_options[:cursor]
|
136
|
+
end
|
137
|
+
|
138
|
+
puts " found #{logins.size} contributor#{logins.size == 1 ? '' : 's'}." if options[:verbose]
|
139
|
+
|
140
|
+
logins.map do |login|
|
141
|
+
[login, emails(options.merge(username: login))]
|
142
|
+
rescue StandardError => e
|
143
|
+
warn e.to_s
|
144
|
+
end.compact.to_h
|
60
145
|
end
|
61
146
|
|
62
147
|
private
|
data/lib/fue/security.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fue
|
4
|
+
module Security
|
5
|
+
class << self
|
6
|
+
def store!(options)
|
7
|
+
Fue::Shell.system!(security('add', options))
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(options)
|
11
|
+
Fue::Shell.system!(security('find', options))
|
12
|
+
rescue RuntimeError
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def security(command = nil, options = nil)
|
19
|
+
run = [security_path]
|
20
|
+
run << "#{command}-internet-password"
|
21
|
+
run << "-a #{options[:username]}"
|
22
|
+
run << "-s #{options[:server]}"
|
23
|
+
if command == 'add'
|
24
|
+
run << "-l #{options[:label]}"
|
25
|
+
run << '-U'
|
26
|
+
run << "-w #{options[:password]}" if options.key?(:password)
|
27
|
+
else
|
28
|
+
run << '-w'
|
29
|
+
end
|
30
|
+
run.join ' '
|
31
|
+
end
|
32
|
+
|
33
|
+
def security_path
|
34
|
+
@security_path ||= begin
|
35
|
+
`which security`.chomp
|
36
|
+
rescue StandardError
|
37
|
+
'security'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/fue/shell.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fue
|
4
|
+
module Shell
|
5
|
+
class << self
|
6
|
+
def system!(*cmd)
|
7
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
8
|
+
raise ["exit code #{status}", stderr].compact.join("\n") unless status.success?
|
9
|
+
|
10
|
+
stdout.slice!(0..-(1 + $INPUT_RECORD_SEPARATOR.size))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/fue/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Doubrovkine
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: github_api
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.18.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.18.2
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: gli
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
|
-
- - "
|
31
|
+
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
33
|
+
version: '2.17'
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
|
-
- - "
|
38
|
+
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
40
|
+
version: '2.17'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: graphlient
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,7 +52,7 @@ dependencies:
|
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: 0.3.2
|
41
|
-
description:
|
55
|
+
description:
|
42
56
|
email: dblock@dblock.org
|
43
57
|
executables:
|
44
58
|
- fue
|
@@ -51,13 +65,16 @@ files:
|
|
51
65
|
- README.md
|
52
66
|
- bin/fue
|
53
67
|
- lib/fue.rb
|
68
|
+
- lib/fue/auth.rb
|
54
69
|
- lib/fue/finder.rb
|
70
|
+
- lib/fue/security.rb
|
71
|
+
- lib/fue/shell.rb
|
55
72
|
- lib/fue/version.rb
|
56
73
|
homepage: http://github.com/dblock/fue
|
57
74
|
licenses:
|
58
75
|
- MIT
|
59
76
|
metadata: {}
|
60
|
-
post_install_message:
|
77
|
+
post_install_message:
|
61
78
|
rdoc_options: []
|
62
79
|
require_paths:
|
63
80
|
- lib
|
@@ -65,16 +82,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
82
|
requirements:
|
66
83
|
- - ">="
|
67
84
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
85
|
+
version: '2.5'
|
69
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
87
|
requirements:
|
71
88
|
- - ">="
|
72
89
|
- !ruby/object:Gem::Version
|
73
90
|
version: 1.3.6
|
74
91
|
requirements: []
|
75
|
-
|
76
|
-
|
77
|
-
signing_key:
|
92
|
+
rubygems_version: 3.0.4
|
93
|
+
signing_key:
|
78
94
|
specification_version: 4
|
79
|
-
summary: Find an e-mail address of a
|
95
|
+
summary: Find an e-mail address of a GitHub user.
|
80
96
|
test_files: []
|