defmodule GitGud.Workflows.EventPayload do
@moduledoc """
The event payload a job reads as `github.event`: GitHub's webhook shapes,
trimmed to the fields workflows actually read
(`github.event.head_commit.message`, `github.event.pull_request.number`,
`github.event.inputs.*`, …).
Built once, when a run is created, and stored on it
(`WorkflowRun.event_payload`); `RunnerController.build_task/1` sends it
as the `event` object of the task's context. Keys are strings, shas are
lowercase hex. Pure: the repository must have `:owner` and
`:organization` preloaded, and users are passed in already loaded.
`sender` is the triggering user's handle, or the repository owner's when
the trigger isn't attributable — the same rule as `github.actor`.
"""
alias GitGud.Accounts.User
alias GitGud.Git
alias GitGud.Repositories.Repository
alias GitGud.Repositories.Storage
@zero_sha String.duplicate("0", 40)
@doc """
A `push`. `before`/`after` are binary shas (`before` nil or all zeros
for a new ref); `commits` are `Git.commit()` maps, newest first, as
`Git.log/3` returns them; `pusher`/`sender` are `%User{}` or nil.
"""
def push(%Repository{} = repo, %{ref: ref, before: before, after: after_sha} = event) do
commits = Enum.map(Map.get(event, :commits, []), &commit(repo, &1))
after_hex = Git.to_hex(after_sha)
before_hex = sha_hex(before)
created = before_hex == @zero_sha
%{
"ref" => ref,
"before" => before_hex,
"after" => after_hex,
"created" => created,
"deleted" => false,
# Not tracked: the post-receive hook doesn't say whether the update
# was a fast-forward, and working it out costs a merge-base per push.
"forced" => false,
"base_ref" => nil,
"compare" =>
if(created,
do: commit_url(repo, after_hex),
else: repo_url(repo) <> "/compare?base=#{before_hex}&head=#{after_hex}"
),
# GitHub lists oldest first.
"commits" => Enum.reverse(commits),
"head_commit" => List.first(commits),
"pusher" => pusher(Map.get(event, :pusher)),
"repository" => repository(repo),
"sender" => sender(repo, Map.get(event, :sender))
}
end
@doc """
A `pull_request`. `pr` is the `%PullRequest{}` (its `author` preloaded
or nil), `action` the activity type; `target_sha` is the target branch's
head (binary sha or nil), `sender` a `%User{}` or nil.
`head.repo` is the target repository: fork and federated pull requests
aren't dispatched (`Workflows.dispatch_pull_request/3`).
"""
def pull_request(%Repository{} = repo, pr, action, %{} = opts) do
repository = repository(repo)
owner = repository["owner"]["login"]
source = short_branch(pr.source_ref)
target = short_branch(pr.target_ref)
%{
"action" => action,
"number" => pr.number,
"pull_request" => %{
"number" => pr.number,
"title" => pr.title || "",
"body" => pr.body || "",
"state" => "open",
"html_url" => repo_url(repo) <> "/pulls/#{pr.number}",
"draft" => false,
"merged" => false,
"user" => %{"login" => author_login(repo, Map.get(pr, :author))},
"head" => %{
"ref" => source,
"sha" => sha_hex(pr.head_sha),
"label" => owner <> ":" <> source,
"repo" => repository
},
"base" => %{
"ref" => target,
"sha" => sha_hex(Map.get(opts, :target_sha)),
"label" => owner <> ":" <> target,
"repo" => repository
}
},
"repository" => repository,
"sender" => sender(repo, Map.get(opts, :sender))
}
end
@doc """
A `workflow_dispatch`. `inputs` is a string → string map; `ref` the full
ref; `workflow_path` the file's path in the repository.
"""
def workflow_dispatch(%Repository{} = repo, ref, workflow_path, inputs, sender) do
%{
"inputs" => Map.new(inputs || %{}, fn {k, v} -> {to_string(k), to_string(v)} end),
"ref" => ref,
"workflow" => workflow_path,
"repository" => repository(repo),
"sender" => sender(repo, sender)
}
end
@doc "A `schedule`: the cron that fired. Nobody triggered it, so `sender` is the owner."
def schedule(%Repository{} = repo, cron) do
%{
"schedule" => cron,
"repository" => repository(repo),
"sender" => sender(repo, nil)
}
end
@doc "The `repository` object every payload carries."
def repository(%Repository{} = repo) do
owner = Storage.repo_handle(repo)
%{
"id" => repo.id,
"name" => repo.name,
"full_name" => owner <> "/" <> repo.name,
"owner" => %{"login" => owner},
"default_branch" => repo.default_branch,
"private" => repo.visibility != "public",
"html_url" => repo_url(repo),
"clone_url" => GitGudWeb.Endpoint.url() <> "/" <> owner <> "/" <> repo.name <> ".git"
}
end
defp commit(repo, c) do
hex = Git.to_hex(c.sha)
%{
"id" => hex,
"message" => c.message,
"timestamp" => c.committed_at && DateTime.to_iso8601(c.committed_at),
"url" => commit_url(repo, hex),
"author" => %{"name" => c.author_name, "email" => c.author_email},
"committer" => %{"name" => c.committer_name, "email" => c.committer_email}
}
end
# The account's public email only: a job's payload ends up in logs.
defp pusher(%User{} = u), do: %{"name" => u.handle, "email" => u.public_email || ""}
defp pusher(_), do: nil
defp sender(_repo, %User{handle: h, id: id}) when is_binary(h) and h != "",
do: %{"login" => h, "id" => id}
defp sender(repo, _), do: %{"login" => Storage.repo_handle(repo)}
defp author_login(_repo, %User{handle: h}) when is_binary(h) and h != "", do: h
defp author_login(repo, _), do: Storage.repo_handle(repo)
defp repo_url(repo),
do: GitGudWeb.Endpoint.url() <> "/r/" <> Storage.repo_handle(repo) <> "/" <> repo.name
defp commit_url(repo, hex), do: repo_url(repo) <> "/commit/" <> hex
defp short_branch("refs/heads/" <> b), do: b
defp short_branch(b) when is_binary(b), do: b
defp short_branch(_), do: ""
defp sha_hex(<<_::binary-size(20)>> = sha), do: Git.to_hex(sha)
defp sha_hex(_), do: @zero_sha
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
6.1 KiB · text
History
ad37081