This repository has been archived by the owner on May 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
feat(lib): add support for git repositories as data source #49
Merged
alfredbaudisch
merged 21 commits into
alfredbaudisch:master
from
rockchalkwushock:feat/add-git-repo-service
Oct 24, 2021
Merged
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
e90377e
chore(root): adds git_cli dep
rockchalkwushock 20eefa3
feat(lib): adds repository_provider and git modules
rockchalkwushock c477187
feat(config): adds remote repo to config
rockchalkwushock 525e853
feat(lib): adds initial repository_watcher
rockchalkwushock 34cf531
feat(application): adds repository_watcher to tree
rockchalkwushock 1d3b9a8
fix(config): adds missing comma
rockchalkwushock fd195a8
feat(config): adds recheck param for remote
rockchalkwushock 6fa654b
fix(application): fixes call to get_env
rockchalkwushock 4f5e8c6
fix(git): fixes call to get_env
rockchalkwushock f5ac9c9
feat(repository_watcher): implements init/1
rockchalkwushock 673aec8
feat(repository_watcher): implements fetching and polling
rockchalkwushock 932d1e1
Removed unused callbacks and typs
alfredbaudisch c74db71
Updated sample base configuration
alfredbaudisch 88cb69a
Ignore repository from tests
alfredbaudisch 94b29b8
File parser uses the new is_path_hidden? utility, to correctly detect…
alfredbaudisch 3f2ae53
Ignore hidden path and file events
alfredbaudisch b9679c2
Clone the repository inside PardallMarkdown's :root_path, since this …
alfredbaudisch 477f4df
Start RepositoryWatcher only if :remote_repository_url is provided
alfredbaudisch c517ec1
Bump hex version
alfredbaudisch 870279d
Documentation about RepositoryWatcher and Git watching support
alfredbaudisch 0c529b8
Added a "Contributors" section
alfredbaudisch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
defmodule PardallMarkdown.RepositoryProvider do | ||
@moduledoc """ | ||
This implementation closely follows the implementation found in: | ||
https://github.com/meddle0x53/blogit/blob/master/lib/blogit/repository_provider.ex | ||
Many thanks to @meddle0x53 for this! | ||
""" | ||
|
||
@type repository :: term | ||
@type provider :: module | ||
@type fetch_result :: {:no_updates} | {:updates, [String.t()]} | ||
@type timestamp :: String.t() | ||
|
||
@type file_path :: String.t() | ||
@type folder :: String.t() | ||
@type file_read_result :: {:ok, binary} | {:error, File.posix()} | ||
|
||
@type t :: %__MODULE__{repo: repository, provider: provider} | ||
@enforce_keys :provider | ||
defstruct [:repo, :provider] | ||
|
||
@doc """ | ||
Invoked to get a representation value of the repository the provider manages. | ||
The actual data represented by this struct should be updated to its | ||
newest version first. | ||
If for example the repository is remote, all the files in it should be | ||
downloaded so their most recent versions are accessible. | ||
This structure can be passed to other callbacks in order to manage files | ||
in the repository. | ||
""" | ||
@callback repository() :: repository | ||
|
||
@doc """ | ||
Invoked to update the data represented by the given `repository` to its most | ||
recent version. | ||
If, for example the repository is remote, all the files in it should be | ||
downloaded so their most recent versions are accessible. | ||
Returns the path to the changed files in the form of the tuple | ||
`{:updates, list-of-paths}`. These paths should be paths to deleted, updated | ||
or newly created files. | ||
""" | ||
@callback fetch(repository) :: fetch_result | ||
|
||
@doc """ | ||
Invoked to get the path to the locally downloaded data. If the repository | ||
is remote, it should have local copy or something like that. | ||
""" | ||
@callback local_path() :: String.t() | ||
|
||
@doc """ | ||
Invoked to get a list of file paths of set of files contained in the locally | ||
downloaded repository. | ||
""" | ||
@callback list_files(folder) :: [file_path] | ||
|
||
@doc """ | ||
Checks if a file path is contained in the local version of the repository. | ||
""" | ||
@callback file_in?(file_path) :: boolean | ||
|
||
@doc """ | ||
Returns file information for the file located at the given `file_path` in | ||
the given `repository`. The result should be in the form of a map and should | ||
be structured like this: | ||
``` | ||
%{ | ||
"author" => the-file-author, | ||
"created_at" => the-date-the-file-was-created-in-iso-8601-format, | ||
"updated_at" => the-date-of-the-last-update-of-the-file-in-iso-8601-format | ||
} | ||
``` | ||
""" | ||
@callback file_info(repository, file_path) :: %{atom => String.t() | timestamp} | ||
|
||
@doc """ | ||
Invoked in order to read the contents of the file located at the given | ||
`file_path`. | ||
The second parameter can be a path to a folder relative to | ||
`Blogit.RepositoryProvider.local_path/0` in which the given `file_path` should | ||
exist. | ||
""" | ||
@callback read_file(file_path, folder) :: file_read_result | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
defmodule PardallMarkdown.RepositoryProviders.Git do | ||
@moduledoc """ | ||
This implementation closely follows the implementation found in: | ||
https://github.com/meddle0x53/blogit/blob/master/lib/blogit/settings.ex | ||
Many thanks to @meddle0x53 for this! | ||
""" | ||
require Logger | ||
|
||
@behaviour PardallMarkdown.RepositoryProvider | ||
|
||
@repository_url Application.get_env(:pardall_markdown, :repository_url, "") | ||
@local_path @repository_url | ||
|> String.split("/") | ||
|> List.last() | ||
|> String.trim_trailing(".git") | ||
|
||
# Callbacks | ||
|
||
def repository do | ||
repo = git_repository() | ||
|
||
case Git.pull(repo) do | ||
{:ok, msg} -> | ||
Logger.info("Pulling from git repository #{msg}") | ||
|
||
{_, error} -> | ||
Logger.error("Error while pulling from git repository #{inspect(error)}") | ||
end | ||
|
||
repo | ||
end | ||
|
||
def fetch(repo) do | ||
Logger.info("Fetching data from #{@repository_url}") | ||
|
||
case Git.fetch(repo) do | ||
{:error, _} -> | ||
{:no_updates} | ||
|
||
{:ok, ""} -> | ||
{:no_updates} | ||
|
||
{:ok, _} -> | ||
updates = | ||
repo | ||
|> Git.diff!(["--name-only", "HEAD", "origin/master"]) | ||
|> String.split("\n", trim: true) | ||
|> Enum.map(&String.trim/1) | ||
|
||
Logger.info("There are new updates, pulling them.") | ||
Git.pull!(repo) | ||
|
||
{:updates, updates} | ||
end | ||
end | ||
|
||
def local_path, do: @local_path | ||
|
||
def list_files(folder \\ Settings.posts_folder()) do | ||
path = Path.join(@local_path, folder) | ||
size = byte_size(path) + 1 | ||
|
||
path | ||
|> recursive_ls() | ||
|> Enum.map(fn <<_::binary-size(size), rest::binary>> -> rest end) | ||
end | ||
|
||
def file_in?(file), do: File.exists?(Path.join(@local_path, file)) | ||
|
||
def file_info(repository, file_path) do | ||
%{ | ||
author: file_author(repository, file_path), | ||
created_at: file_created_at(repository, file_path), | ||
updated_at: file_updated_at(repository, file_path) | ||
} | ||
end | ||
|
||
def read_file(file_path, folder \\ "") do | ||
local_path() |> Path.join(folder) |> Path.join(file_path) |> File.read() | ||
end | ||
|
||
# Private | ||
|
||
defp log(repository, args), do: Git.log!(repository, args) | ||
|
||
defp first_in_log(repository, args) do | ||
repository | ||
|> log(args) | ||
|> String.split("\n") | ||
|> List.first() | ||
|> String.trim() | ||
end | ||
|
||
defp recursive_ls(path) do | ||
cond do | ||
File.regular?(path) -> | ||
[path] | ||
|
||
File.dir?(path) -> | ||
path | ||
|> File.ls!() | ||
|> Enum.map(&Path.join(path, &1)) | ||
|> Enum.map(&recursive_ls/1) | ||
|> Enum.concat() | ||
|
||
true -> | ||
[] | ||
end | ||
end | ||
|
||
defp git_repository do | ||
Logger.info("Cloning repository #{@repository_url}") | ||
|
||
case Git.clone(@repository_url) do | ||
{:ok, repo} -> repo | ||
{:error, Git.Error} -> Git.new(@local_path) | ||
end | ||
end | ||
|
||
defp file_author(repository, file_name) do | ||
first_in_log(repository, ["--reverse", "--format=%an", file_name]) | ||
end | ||
|
||
defp file_created_at(repository, file_name) do | ||
case first_in_log(repository, ["--reverse", "--format=%ci", file_name]) do | ||
"" -> DateTime.to_iso8601(DateTime.utc_now()) | ||
created_at -> created_at | ||
end | ||
end | ||
|
||
defp file_updated_at(repository, file_name) do | ||
case repository |> log(["-1", "--format=%ci", file_name]) |> String.trim() do | ||
"" -> DateTime.to_iso8601(DateTime.utc_now()) | ||
updated_at -> updated_at | ||
end | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alfredbaudisch So my thoughts here are we can default to the root of the repository and document that in the docs. We can then do one of two things:
git_repo_data_path
,root_path
accept a different data type, perhaps a keyword list would be a good option.For example my website is written in NextJS with MDX all the
md(x)
files live atdata
:What are your thoughts on how to expose this API to the end user?