Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix docs generator when git executable is missing #9867

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion spec/compiler/crystal/tools/doc/project_info_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,17 @@ describe Crystal::Doc::ProjectInfo do
File.write("shard.yml", "name: foo\nversion: 1.0")
end

it "no git" do
it "git missing" do
ProjectInfo.git_executable = "git-missing-executable"

assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "1.0", refname: nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0", refname: nil))
ensure
ProjectInfo.git_executable = "git"
end

it "not in a git folder" do
assert_with_defaults(ProjectInfo.new(nil, nil), ProjectInfo.new("foo", "1.0", refname: nil))
assert_with_defaults(ProjectInfo.new("bar", "2.0"), ProjectInfo.new("bar", "2.0", refname: nil))
assert_with_defaults(ProjectInfo.new(nil, "2.0"), ProjectInfo.new("foo", "2.0", refname: nil))
Expand Down
58 changes: 32 additions & 26 deletions src/compiler/crystal/tools/doc/project_info.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module Crystal::Doc
property refname : String? = nil
property source_url_pattern : String? = nil

class_property git_executable = "git"

def initialize(@name : String? = nil, @version : String? = nil, @refname : String? = nil, @source_url_pattern : String? = nil)
end

Expand Down Expand Up @@ -54,7 +56,8 @@ module Crystal::Doc
end

def self.git_dir?
Process.run("git", ["rev-parse", "--is-inside-work-tree"]).success?
git_command(["rev-parse", "--is-inside-work-tree"]) { return false }
true
end

VERSION_TAG = /^v(\d+[-.][-.a-zA-Z\d]+)$/
Expand Down Expand Up @@ -112,16 +115,28 @@ module Crystal::Doc
end
end

def self.git_remote
# check whether inside git work-tree
status = Process.run("git", ["rev-parse", "--is-inside-work-tree"])
return unless status.success?
# Tries to run git command with args.
# Yields block if exec fails or process status is not success.
def self.git_command(args, output : Process::Stdio = Process::Redirect::Close)
status = Process.run(git_executable, args, output: output)
yield unless status.success?
status
rescue IO::Error
yield
end

def self.git_capture(args)
io = IO::Memory.new
status = Process.run("git", ["remote", "-v"], output: io)
return unless status.success?
git_command(args, output: io) { yield }
io.to_s
end

remotes = io.to_s.lines.select(&.ends_with?(" (fetch)"))
def self.git_remote
# check whether inside git work-tree
git_command(["rev-parse", "--is-inside-work-tree"]) { return }

capture = git_capture(["remote", "-v"]) { return }
remotes = capture.lines.select(&.ends_with?(" (fetch)"))

git_remote = remotes.find(&.starts_with?("origin\t")) || remotes.first? || return

Expand All @@ -133,44 +148,35 @@ module Crystal::Doc

def self.git_clean?
# Use git to determine if index and working directory are clean
io = IO::Memory.new
status = Process.run("git", ["status", "--porcelain"], output: io)
capture = git_capture(["status", "--porcelain"]) { return }

# If clean, output of `git status --porcelain` is empty. Still need to check
# the status code, to make sure empty doesn't mean error.
return unless status.success?
io.rewind
io.bytesize == 0
capture.bytesize == 0
end

def self.git_ref(*, branch)
io = IO::Memory.new
# Check if current HEAD is tagged
status = Process.run("git", ["tag", "--points-at", "HEAD"], output: io)
return unless status.success?
io.rewind
tags = io.to_s.lines
capture = git_capture(["tag", "--points-at", "HEAD"]) { return }
tags = capture.lines
# Return tag if commit is tagged, select first one if multiple
if tag = tags.first?
return tag
end
io.clear

if branch
# Read current branch name
status = Process.run("git", ["rev-parse", "--abbrev-ref", "HEAD"], output: io)
return unless status.success?
capture = git_capture(["rev-parse", "--abbrev-ref", "HEAD"]) { return }

if branch_name = io.to_s.strip.presence
if branch_name = capture.strip.presence
return branch_name
end
io.clear
end

# Otherwise, return current commit sha
status = Process.run("git", ["rev-parse", "HEAD"], output: io)
return unless status.success?
capture = git_capture(["rev-parse", "HEAD"]) { return }

if sha = io.to_s.strip.presence
if sha = capture.strip.presence
return sha
end
end
Expand Down