giturl 1.3.6 → 1.4.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 +4 -4
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +41 -0
- data/README.md +7 -0
- data/giturl.gemspec +1 -1
- data/lib/command_giturl.rb +30 -45
- data/lib/giturl/git_directory.rb +66 -0
- data/lib/giturl/location.rb +22 -0
- data/lib/giturl/options.rb +62 -0
- data/lib/giturl/remote_url.rb +51 -0
- data/lib/giturl/version.rb +1 -1
- data/lib/giturl.rb +8 -56
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ff2df69734b9bef7d1cab4aec2988a50ca7a0aab7385c95a9dff717b993f842
|
|
4
|
+
data.tar.gz: 7357c5095c2ef759766fde7b7f88d0ab03a33fbfc0d1bc8e5f7d576a8e35c1a2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 529768719225bc69023f48ff584d051dcc6d962d7e3910173d3448cdc23c12f9b894039677430d8c8abedd5ab2b738823eb786e6c4f67e3f0f90f75604010b4b
|
|
7
|
+
data.tar.gz: ab9a6c1986efe1d11d548ccbc7e7bb4d4dd7dbb062fc52d6594c3007920f9366b070f7565ffcb92f0ca40d46b0d86d5d5879b121925e0c3f0a0b846bd93135a6
|
data/.rubocop.yml
CHANGED
|
@@ -12,7 +12,7 @@ plugins:
|
|
|
12
12
|
- rubocop-rspec
|
|
13
13
|
|
|
14
14
|
AllCops:
|
|
15
|
-
TargetRubyVersion: 2
|
|
15
|
+
TargetRubyVersion: 3.2
|
|
16
16
|
NewCops: enable
|
|
17
17
|
|
|
18
18
|
Layout/EndAlignment:
|
|
@@ -41,7 +41,7 @@ Metrics/BlockLength:
|
|
|
41
41
|
|
|
42
42
|
Metrics/MethodLength:
|
|
43
43
|
Enabled: true
|
|
44
|
-
Max:
|
|
44
|
+
Max: 25
|
|
45
45
|
|
|
46
46
|
Style/ExponentialNotation:
|
|
47
47
|
Enabled: true
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v1.4.1](https://github.com/shinyaohtani/giturl/tree/v1.4.1) (2026-09-05)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/shinyaohtani/giturl/compare/v1.4.0...v1.4.1)
|
|
6
|
+
|
|
7
|
+
- The first release on RubyGems to require Ruby 3.2, since v1.4.0 was
|
|
8
|
+
tagged but never pushed there. Everything v1.4.0 describes applies
|
|
9
|
+
here too, so read its entry below as part of this release.
|
|
10
|
+
- Directories whose name contains a space or a shell metacharacter now
|
|
11
|
+
work, and remotes written as a URL (`https://`, `ssh://`, `git://`)
|
|
12
|
+
now convert correctly.
|
|
13
|
+
|
|
14
|
+
**Fixed bugs:**
|
|
15
|
+
|
|
16
|
+
- https:// remotes produce a malformed URL \(https///github.com/...\) [\#41](https://github.com/shinyaohtani/giturl/issues/41)
|
|
17
|
+
- Paths containing spaces or shell metacharacters are not escaped [\#40](https://github.com/shinyaohtani/giturl/issues/40)
|
|
18
|
+
|
|
19
|
+
**Merged pull requests:**
|
|
20
|
+
|
|
21
|
+
- Run git without a shell, and parse remotes properly [\#44](https://github.com/shinyaohtani/giturl/pull/44) ([shinyaohtani](https://github.com/shinyaohtani))
|
|
22
|
+
|
|
23
|
+
## [v1.4.0](https://github.com/shinyaohtani/giturl/tree/v1.4.0) (2026-09-04)
|
|
24
|
+
|
|
25
|
+
[Full Changelog](https://github.com/shinyaohtani/giturl/compare/v1.3.6...v1.4.0)
|
|
26
|
+
|
|
27
|
+
- Tagged but never published to RubyGems: two bugs were found before it
|
|
28
|
+
was pushed, so v1.4.1 went out in its place with the same changes plus
|
|
29
|
+
those fixes. `gem install giturl` never sees this version.
|
|
30
|
+
- **Ruby 3.2 or later is now required.**
|
|
31
|
+
- On Ruby 2.x, install the last release that supported it: `gem install giturl -v 1.3.6`
|
|
32
|
+
- No changes in behavior
|
|
33
|
+
- `Giturl::Giturl.url` / `.convert` / `.git_managed?` keep their signatures and results.
|
|
34
|
+
- The code behind them was reorganized into classes that each hold one responsibility.
|
|
35
|
+
|
|
36
|
+
**Closed issues:**
|
|
37
|
+
|
|
38
|
+
- Refactor into noun classes: behavior lives in class methods, responsibilities are mixed [\#43](https://github.com/shinyaohtani/giturl/issues/43)
|
|
39
|
+
|
|
40
|
+
**Merged pull requests:**
|
|
41
|
+
|
|
42
|
+
- Refactor into noun classes per the Ruby refactoring standard [\#42](https://github.com/shinyaohtani/giturl/pull/42) ([shinyaohtani](https://github.com/shinyaohtani))
|
|
43
|
+
|
|
3
44
|
## [v1.3.6](https://github.com/shinyaohtani/giturl/tree/v1.3.6) (2026-09-03)
|
|
4
45
|
|
|
5
46
|
[Full Changelog](https://github.com/shinyaohtani/giturl/compare/v1.3.5...v1.3.6)
|
data/README.md
CHANGED
|
@@ -96,6 +96,13 @@ Or add `giturl` to your application's Gemfile and run `bundle` command:
|
|
|
96
96
|
gem 'giturl'
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
### Ruby version
|
|
100
|
+
|
|
101
|
+
`giturl` requires **Ruby 3.2 or later**. On an older Ruby, install the last
|
|
102
|
+
release that supported Ruby 2.x:
|
|
103
|
+
|
|
104
|
+
$ gem install giturl -v 1.3.6
|
|
105
|
+
|
|
99
106
|
## Giturl module
|
|
100
107
|
|
|
101
108
|
`Giturl` is also a module, so you can get urls from your ruby code.
|
data/giturl.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = Giturl::DESCRIPTION
|
|
13
13
|
spec.homepage = "#{Giturl::REPOSITORY_URL}/blob/master/README.md"
|
|
14
14
|
spec.license = 'MIT'
|
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.2.0')
|
|
16
16
|
|
|
17
17
|
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
18
18
|
|
data/lib/command_giturl.rb
CHANGED
|
@@ -1,71 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'launchy'
|
|
5
|
+
require 'giturl'
|
|
6
|
+
require 'giturl/options'
|
|
7
|
+
|
|
4
8
|
# converter module from path to url
|
|
5
9
|
module Giturl
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
require 'launchy'
|
|
9
|
-
|
|
10
|
-
# command line wrapper
|
|
10
|
+
# The giturl command: the directories named on the command line, and what the
|
|
11
|
+
# user asked to have done with them.
|
|
11
12
|
class CommandGiturl
|
|
12
13
|
attr_accessor :params
|
|
13
14
|
|
|
14
|
-
def
|
|
15
|
+
def initialize
|
|
16
|
+
@options = Options.new
|
|
15
17
|
@params = {}
|
|
16
|
-
OptionParser.new do |opts|
|
|
17
|
-
opts = define_options(opts)
|
|
18
|
-
opts.parse!(ARGV, into: @params)
|
|
19
|
-
@params[:open] = true if @params.key?(:app)
|
|
20
|
-
end
|
|
21
18
|
end
|
|
22
19
|
|
|
20
|
+
# @return [Hash] the options given on the command line
|
|
21
|
+
def parse_options
|
|
22
|
+
@params = @options.parse!(ARGV)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Prints a URL per target directory, and opens each in a browser when asked.
|
|
26
|
+
# With no target given, the current directory is the target.
|
|
23
27
|
def run
|
|
24
28
|
ARGV << '.' if ARGV.empty?
|
|
25
|
-
ARGV.each
|
|
26
|
-
if Giturl.git_managed?(arg)
|
|
27
|
-
url = Giturl.convert(arg)
|
|
28
|
-
print "#{url}\n"
|
|
29
|
-
browser_open(url) if @params[:open]
|
|
30
|
-
elsif @params[:verbose]
|
|
31
|
-
print "Not git-managed-dir: #{arg}\n"
|
|
32
|
-
end
|
|
33
|
-
end
|
|
29
|
+
ARGV.each { |target| show(target) }
|
|
34
30
|
end
|
|
35
31
|
|
|
36
32
|
private
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
puts opts
|
|
45
|
-
exit
|
|
34
|
+
# @param target [String] a directory named on the command line
|
|
35
|
+
def show(target)
|
|
36
|
+
directory = GitDirectory.new(target)
|
|
37
|
+
unless directory.managed?
|
|
38
|
+
print "Not git-managed-dir: #{target}\n" if @params[:verbose]
|
|
39
|
+
return
|
|
46
40
|
end
|
|
47
|
-
opts.on_tail('-V', '--version', 'Show version') do
|
|
48
|
-
puts opts.ver
|
|
49
|
-
exit
|
|
50
|
-
end
|
|
51
|
-
opts.banner = <<~BANNER
|
|
52
|
-
|
|
53
|
-
#{opts.ver}
|
|
54
|
-
#{DESCRIPTION}
|
|
55
|
-
Usage: #{opts.program_name} [options] [dirs]
|
|
56
|
-
[dirs]:
|
|
57
|
-
Target directories. Omit this when you only specify "."
|
|
58
41
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
42
|
+
url = directory.url
|
|
43
|
+
print "#{url}\n"
|
|
44
|
+
browser_open(url) if @params[:open]
|
|
62
45
|
end
|
|
63
46
|
|
|
47
|
+
# Launchy cannot pick a specific application, so a named browser goes
|
|
48
|
+
# through macOS `open` instead, where that is what the user is running.
|
|
49
|
+
#
|
|
50
|
+
# @param url [String] the URL to show
|
|
64
51
|
def browser_open(url)
|
|
65
52
|
if @params[:app] && Launchy::Detect::HostOsFamily.detect.darwin?
|
|
66
|
-
|
|
67
|
-
comm << " -a #{@params[:app]}" if @params[:app]
|
|
68
|
-
system(comm)
|
|
53
|
+
system("open #{url} -a #{@params[:app]}")
|
|
69
54
|
else
|
|
70
55
|
Launchy.open(url)
|
|
71
56
|
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'giturl/location'
|
|
4
|
+
require 'giturl/remote_url'
|
|
5
|
+
|
|
6
|
+
module Giturl
|
|
7
|
+
# A directory on disk, asked about through git: whether git manages it, and
|
|
8
|
+
# what GitHub page shows it. Every git invocation in giturl happens here.
|
|
9
|
+
class GitDirectory
|
|
10
|
+
FAILURE = 'Git commands failed. Please check if the directory is a git repository.'
|
|
11
|
+
|
|
12
|
+
def initialize(path)
|
|
13
|
+
@path = path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# git prints to stderr when the path is not a repository, which is noise for
|
|
17
|
+
# a question that is allowed to answer "no", so stderr is silenced around it.
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean] whether the directory is inside a git work tree
|
|
20
|
+
def managed?
|
|
21
|
+
stderr_old = $stderr.dup
|
|
22
|
+
$stderr.reopen(File::NULL)
|
|
23
|
+
inside, = capture('rev-parse', '--is-inside-work-tree')
|
|
24
|
+
$stderr.flush
|
|
25
|
+
$stderr.reopen stderr_old
|
|
26
|
+
inside == 'true'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [String] the GitHub page URL for this directory, or '' (after a
|
|
30
|
+
# warning) when git could not answer
|
|
31
|
+
def url
|
|
32
|
+
here = location
|
|
33
|
+
unless here
|
|
34
|
+
warn FAILURE
|
|
35
|
+
return ''
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
"#{RemoteUrl.new(here.remote).https}/tree/#{here.encoded_branch}/#{here.prefix}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# All three commands run before the result is judged, so git reports every
|
|
44
|
+
# problem it has with the directory rather than only the first.
|
|
45
|
+
#
|
|
46
|
+
# @return [Location, nil] nil when any command failed or answered empty
|
|
47
|
+
def location
|
|
48
|
+
prefix, prefix_ok = capture('rev-parse', '--show-prefix')
|
|
49
|
+
branch, branch_ok = capture('rev-parse', '--abbrev-ref', 'HEAD')
|
|
50
|
+
remote, remote_ok = capture('config', '--get', 'remote.origin.url')
|
|
51
|
+
return nil unless prefix_ok && branch_ok && remote_ok
|
|
52
|
+
|
|
53
|
+
here = Location.new(prefix: prefix, branch: branch, remote: remote)
|
|
54
|
+
here.complete? ? here : nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# git is run without a shell, so a directory name is never split on its
|
|
58
|
+
# spaces nor read for metacharacters.
|
|
59
|
+
#
|
|
60
|
+
# @return [Array(String, Boolean)] the command's output and whether it succeeded
|
|
61
|
+
def capture(*args)
|
|
62
|
+
output = IO.popen(['git', '-C', @path, *args], &:read).chomp
|
|
63
|
+
[output, Process.last_status.success?]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
|
|
5
|
+
module Giturl
|
|
6
|
+
# Where a directory sits inside its repository: the path from the repository
|
|
7
|
+
# root down to it, the branch checked out, and the URL of the remote it came
|
|
8
|
+
# from. Immutable; built by GitDirectory from what git reports.
|
|
9
|
+
Location = Data.define(:prefix, :branch, :remote) do
|
|
10
|
+
# @return [Boolean] whether git answered with everything a URL needs.
|
|
11
|
+
# prefix is legitimately empty at the repository root, so it is not required.
|
|
12
|
+
def complete?
|
|
13
|
+
!branch.empty? && !remote.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [String] the branch in the form a URL path can carry, each segment
|
|
17
|
+
# percent-encoded but the separating slashes left intact
|
|
18
|
+
def encoded_branch
|
|
19
|
+
branch.split('/').map { |segment| ERB::Util.url_encode(segment) }.join('/')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'giturl/version'
|
|
5
|
+
|
|
6
|
+
module Giturl
|
|
7
|
+
# The options giturl accepts on the command line, and the values a given
|
|
8
|
+
# command line puts in them.
|
|
9
|
+
class Options
|
|
10
|
+
def initialize
|
|
11
|
+
@values = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Consumes the options from argv, leaving the target directories behind.
|
|
15
|
+
# `--app` names a browser, which only makes sense together with `--open`,
|
|
16
|
+
# so it turns `--open` on by itself.
|
|
17
|
+
#
|
|
18
|
+
# @param argv [Array<String>] the command line, modified in place
|
|
19
|
+
# @return [Hash] the values given, keyed by long option name
|
|
20
|
+
def parse!(argv)
|
|
21
|
+
OptionParser.new do |opts|
|
|
22
|
+
definitions(opts).parse!(argv, into: @values)
|
|
23
|
+
@values[:open] = true if @values.key?(:app)
|
|
24
|
+
end
|
|
25
|
+
@values
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# @return [OptionParser] opts, with every giturl option declared on it
|
|
31
|
+
def definitions(opts)
|
|
32
|
+
opts.version = VERSION
|
|
33
|
+
opts.on('-o', '--open', 'Open the URL in your browser. default: no') { |v| v }
|
|
34
|
+
opts.on('-a [APPNAME]', '--app [APPNAME]', 'Specify a browser. i.e. "Safari.app"') { |v| v }
|
|
35
|
+
opts.on('-v', '--verbose', 'Verbose mode. default: no') { |v| v }
|
|
36
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
|
37
|
+
puts opts
|
|
38
|
+
exit
|
|
39
|
+
end
|
|
40
|
+
opts.on_tail('-V', '--version', 'Show version') do
|
|
41
|
+
puts opts.ver
|
|
42
|
+
exit
|
|
43
|
+
end
|
|
44
|
+
opts.banner = banner(opts)
|
|
45
|
+
opts
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [String] the description and usage shown above the option list
|
|
49
|
+
def banner(opts)
|
|
50
|
+
<<~BANNER
|
|
51
|
+
|
|
52
|
+
#{opts.ver}
|
|
53
|
+
#{DESCRIPTION}
|
|
54
|
+
Usage: #{opts.program_name} [options] [dirs]
|
|
55
|
+
[dirs]:
|
|
56
|
+
Target directories. Omit this when you only specify "."
|
|
57
|
+
|
|
58
|
+
[options]:
|
|
59
|
+
BANNER
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Giturl
|
|
4
|
+
# The URL of a git remote, as `remote.origin.url` reports it, and the https
|
|
5
|
+
# page that serves the same repository.
|
|
6
|
+
#
|
|
7
|
+
# A remote is written either as an scp-style address
|
|
8
|
+
# (git@github.com:owner/repo.git) or as a URL carrying a scheme
|
|
9
|
+
# (https://, ssh://, git://). Only the scp-style form can name a Host alias
|
|
10
|
+
# from ~/.ssh/config (git@github.com-work:owner/repo.git) in place of a real
|
|
11
|
+
# hostname, so that is the only form ssh is asked about.
|
|
12
|
+
class RemoteUrl
|
|
13
|
+
SCHEMED = %r{\A[a-z][a-z0-9+.-]*://(?:[^@/]*@)?(?<host>[^/:]+)(?::\d+)?/(?<path>.*)\z}i
|
|
14
|
+
SCP = %r{\A(?:[^@/]+@)?(?<host>[^/:]+):(?<path>.*)\z}
|
|
15
|
+
|
|
16
|
+
def initialize(raw)
|
|
17
|
+
@raw = raw
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Anything neither form recognizes is handed back untouched: it names no
|
|
21
|
+
# host, so there is no page to point at.
|
|
22
|
+
#
|
|
23
|
+
# @return [String] the https URL of the page this remote is served from
|
|
24
|
+
def https
|
|
25
|
+
schemed = SCHEMED.match(@raw)
|
|
26
|
+
return page(schemed[:host], schemed[:path]) if schemed
|
|
27
|
+
|
|
28
|
+
scp = SCP.match(@raw)
|
|
29
|
+
return @raw unless scp
|
|
30
|
+
|
|
31
|
+
page(hostname(scp[:host]) || scp[:host], scp[:path])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# @return [String] https://<host>/<path>, without the .git git clones carry
|
|
37
|
+
def page(host, path)
|
|
38
|
+
"https://#{host}/#{path.delete_suffix('.git')}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param host_alias [String] a Host entry from ~/.ssh/config
|
|
42
|
+
# @return [String, nil] the HostName ssh resolves it to, nil if ssh says nothing
|
|
43
|
+
def hostname(host_alias)
|
|
44
|
+
output = IO.popen(['ssh', '-G', host_alias], err: File::NULL, &:read)
|
|
45
|
+
line = output.lines.find { |candidate| candidate =~ /^hostname / }
|
|
46
|
+
return nil unless line
|
|
47
|
+
|
|
48
|
+
line.split[1]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/giturl/version.rb
CHANGED
data/lib/giturl.rb
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'giturl/version'
|
|
5
|
+
require 'giturl/git_directory'
|
|
5
6
|
|
|
6
7
|
# converter module from path to url
|
|
7
8
|
module Giturl
|
|
8
|
-
|
|
9
|
-
#
|
|
9
|
+
# Published entry point of the `Giturl` module, kept as class methods because
|
|
10
|
+
# that is the interface released gems call:
|
|
10
11
|
#
|
|
11
|
-
#
|
|
12
|
+
# url = Giturl::Giturl.url('./lib')
|
|
13
|
+
#
|
|
14
|
+
# Nothing is decided here; every answer comes from a GitDirectory.
|
|
12
15
|
class Giturl
|
|
13
16
|
# Check if path is a git-managed directory and return the URL of the GitHub web page for that path
|
|
14
17
|
#
|
|
@@ -23,12 +26,7 @@ module Giturl
|
|
|
23
26
|
# @param path [String] path to check. Both relative and absolute forms are accepted.
|
|
24
27
|
# @return [Boolean] git-managed directory or not.
|
|
25
28
|
def self.git_managed?(path)
|
|
26
|
-
|
|
27
|
-
$stderr.reopen(File::NULL)
|
|
28
|
-
inside = `git -C #{path} rev-parse --is-inside-work-tree`.chomp
|
|
29
|
-
$stderr.flush
|
|
30
|
-
$stderr.reopen stderr_old
|
|
31
|
-
inside == 'true'
|
|
29
|
+
GitDirectory.new(path).managed?
|
|
32
30
|
end
|
|
33
31
|
|
|
34
32
|
# Convert path to git-managed directory to GitHub web page URL
|
|
@@ -36,53 +34,7 @@ module Giturl
|
|
|
36
34
|
# @param path [String] path for a git-managed directory. Both relative and absolute forms are accepted.
|
|
37
35
|
# @return [String] GitHub web page URL for the given git-managed directory
|
|
38
36
|
def self.convert(path)
|
|
39
|
-
|
|
40
|
-
unless ok
|
|
41
|
-
warn 'Git commands failed. Please check if the directory is a git repository.'
|
|
42
|
-
return ''
|
|
43
|
-
end
|
|
44
|
-
baseurl = construct_base_url(remote_origin_url)
|
|
45
|
-
encoded_branch = gitdir_branch.split('/').map { |e| ERB::Util.url_encode(e) }.join('/')
|
|
46
|
-
"#{baseurl}/tree/#{encoded_branch}/#{gitdir_prefix}"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Run the git commands needed to build the URL, and report whether they succeeded.
|
|
50
|
-
#
|
|
51
|
-
# @param path [String] path for a git-managed directory
|
|
52
|
-
# @return [Array(String, String, String, Boolean)] prefix, branch, remote origin URL, and
|
|
53
|
-
# whether the info is usable. gitdir_prefix may legitimately be empty (path is the
|
|
54
|
-
# repository root), so only command success and non-empty branch/remote are required.
|
|
55
|
-
def self.git_location_info(path)
|
|
56
|
-
prefix, prefix_ok = run_git(path, 'rev-parse', '--show-prefix')
|
|
57
|
-
branch, branch_ok = run_git(path, 'rev-parse', '--abbrev-ref', 'HEAD')
|
|
58
|
-
remote, remote_ok = run_git(path, 'config', '--get', 'remote.origin.url')
|
|
59
|
-
ok = prefix_ok && branch_ok && remote_ok && !branch.empty? && !remote.empty?
|
|
60
|
-
[prefix, branch, remote, ok]
|
|
61
|
-
end
|
|
62
|
-
private_class_method :git_location_info
|
|
63
|
-
|
|
64
|
-
def self.run_git(path, *args)
|
|
65
|
-
output = `git -C #{path} #{args.join(' ')}`.chomp
|
|
66
|
-
[output, $?.success?] # rubocop:disable Style/SpecialGlobalVars
|
|
67
|
-
end
|
|
68
|
-
private_class_method :run_git
|
|
69
|
-
|
|
70
|
-
def self.construct_base_url(remote_origin_url)
|
|
71
|
-
baseurl = remote_origin_url
|
|
72
|
-
if remote_origin_url =~ /@(.*?):/
|
|
73
|
-
host_alias = ::Regexp.last_match(1)
|
|
74
|
-
real_hostname = get_real_hostname(host_alias)
|
|
75
|
-
baseurl = remote_origin_url.sub(host_alias, real_hostname) if real_hostname && real_hostname != host_alias
|
|
76
|
-
end
|
|
77
|
-
baseurl.tr(':', '/').gsub(/^.*@/, 'https://').gsub(/\.git$/, '')
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def self.get_real_hostname(host_alias)
|
|
81
|
-
ssh_output = `ssh -G #{host_alias} 2>#{File::NULL}`
|
|
82
|
-
real_hostname_line = ssh_output.lines.find { |line| line =~ /^hostname / }
|
|
83
|
-
return nil unless real_hostname_line
|
|
84
|
-
|
|
85
|
-
real_hostname_line.split[1]
|
|
37
|
+
GitDirectory.new(path).url
|
|
86
38
|
end
|
|
87
39
|
end
|
|
88
40
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: giturl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shinya Ohtani (shinyaohtani@github)
|
|
@@ -55,6 +55,10 @@ files:
|
|
|
55
55
|
- giturl.gemspec
|
|
56
56
|
- lib/command_giturl.rb
|
|
57
57
|
- lib/giturl.rb
|
|
58
|
+
- lib/giturl/git_directory.rb
|
|
59
|
+
- lib/giturl/location.rb
|
|
60
|
+
- lib/giturl/options.rb
|
|
61
|
+
- lib/giturl/remote_url.rb
|
|
58
62
|
- lib/giturl/version.rb
|
|
59
63
|
homepage: https://github.com/shinyaohtani/giturl/blob/master/README.md
|
|
60
64
|
licenses:
|
|
@@ -72,7 +76,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
72
76
|
requirements:
|
|
73
77
|
- - ">="
|
|
74
78
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: 2.
|
|
79
|
+
version: 3.2.0
|
|
76
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
81
|
requirements:
|
|
78
82
|
- - ">="
|