%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
|
%% ex: ts=4 sw=4 et
|
|
-module(rebar_hg_resource).
|
|
|
|
-behaviour(rebar_resource_v2).
|
|
|
|
-export([init/2,
|
|
lock/2,
|
|
download/4,
|
|
needs_update/2,
|
|
make_vsn/2]).
|
|
|
|
|
|
%% For backward compatibilty
|
|
-export([ download/3
|
|
]).
|
|
|
|
-include("rebar.hrl").
|
|
|
|
-spec init(atom(), rebar_state:t()) -> {ok, rebar_resource_v2:resource()}.
|
|
init(Type, _State) ->
|
|
Resource = rebar_resource_v2:new(Type, ?MODULE, #{}),
|
|
{ok, Resource}.
|
|
|
|
lock(AppInfo, _) ->
|
|
check_type_support(),
|
|
lock_(rebar_app_info:dir(AppInfo), rebar_app_info:source(AppInfo)).
|
|
|
|
lock_(AppDir, {hg, Url, _}) ->
|
|
lock_(AppDir, {hg, Url});
|
|
lock_(AppDir, {hg, Url}) ->
|
|
Ref = get_ref(AppDir),
|
|
{hg, Url, {ref, Ref}}.
|
|
|
|
%% Return `true' if either the hg url or tag/branch/ref is not the same as
|
|
%% the currently checked out repo for the dep
|
|
needs_update(AppInfo, _) ->
|
|
needs_update_(rebar_app_info:dir(AppInfo), rebar_app_info:source(AppInfo)).
|
|
|
|
needs_update_(Dir, {hg, Url, {tag, Tag}}) ->
|
|
Ref = get_ref(Dir),
|
|
{ClosestTag, Distance} = get_tag_distance(Dir, Ref),
|
|
?DEBUG("Comparing hg tag ~ts with ref ~ts (closest tag is ~ts at distance ~ts)",
|
|
[Tag, Ref, ClosestTag, Distance]),
|
|
not ((Distance =:= "0") andalso (Tag =:= ClosestTag)
|
|
andalso compare_url(Dir, Url));
|
|
needs_update_(Dir, {hg, Url, {branch, Branch}}) ->
|
|
Ref = get_ref(Dir),
|
|
BRef = get_branch_ref(Dir, Branch),
|
|
not ((Ref =:= BRef) andalso compare_url(Dir, Url));
|
|
needs_update_(Dir, {hg, Url, "default"}) ->
|
|
Ref = get_ref(Dir),
|
|
BRef = get_branch_ref(Dir, "default"),
|
|
not ((Ref =:= BRef) andalso compare_url(Dir, Url));
|
|
needs_update_(Dir, {hg, Url, Ref}) ->
|
|
LocalRef = get_ref(Dir),
|
|
TargetRef = case Ref of
|
|
{ref, Ref1} ->
|
|
Length = length(LocalRef),
|
|
if Length >= 7 -> lists:sublist(Ref1, Length);
|
|
Length < 7 -> Ref1
|
|
end;
|
|
Ref1 ->
|
|
Ref1
|
|
end,
|
|
?DEBUG("Comparing hg ref ~ts with ~ts", [Ref1, LocalRef]),
|
|
not ((LocalRef =:= TargetRef) andalso compare_url(Dir, Url)).
|
|
|
|
download(TmpDir, AppInfo, State, _) ->
|
|
check_type_support(),
|
|
case download_(TmpDir, rebar_app_info:source(AppInfo), State) of
|
|
{ok, _} ->
|
|
ok;
|
|
{error, Reason} ->
|
|
{error, Reason};
|
|
Error ->
|
|
{error, Error}
|
|
end.
|
|
|
|
%% For backward compatibilty
|
|
download(Dir, AppInfo, State) ->
|
|
download_(Dir, AppInfo, State).
|
|
|
|
download_(Dir, {hg, Url}, State) ->
|
|
?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.", []),
|
|
download_(Dir, {hg, Url, {branch, "default"}}, State);
|
|
download_(Dir, {hg, Url, ""}, State) ->
|
|
?WARN("WARNING: It is recommended to use {branch, Name}, {tag, Tag} or {ref, Ref}, otherwise updating the dep may not work as expected.", []),
|
|
download_(Dir, {hg, Url, {branch, "default"}}, State);
|
|
download_(Dir, {hg, Url, {branch, Branch}}, _State) ->
|
|
ok = filelib:ensure_dir(Dir),
|
|
maybe_warn_local_url(Url),
|
|
rebar_utils:sh(?FMT("hg clone -q -b ~ts ~ts ~ts",
|
|
[rebar_utils:escape_chars(Branch),
|
|
rebar_utils:escape_chars(Url),
|
|
rebar_utils:escape_chars(filename:basename(Dir))]),
|
|
[{cd, filename:dirname(Dir)}]);
|
|
download_(Dir, {hg, Url, {tag, Tag}}, _State) ->
|
|
ok = filelib:ensure_dir(Dir),
|
|
maybe_warn_local_url(Url),
|
|
rebar_utils:sh(?FMT("hg clone -q -u ~ts ~ts ~ts",
|
|
[rebar_utils:escape_chars(Tag),
|
|
rebar_utils:escape_chars(Url),
|
|
rebar_utils:escape_chars(filename:basename(Dir))]),
|
|
[{cd, filename:dirname(Dir)}]);
|
|
download_(Dir, {hg, Url, {ref, Ref}}, _State) ->
|
|
ok = filelib:ensure_dir(Dir),
|
|
maybe_warn_local_url(Url),
|
|
rebar_utils:sh(?FMT("hg clone -q -r ~ts ~ts ~ts",
|
|
[rebar_utils:escape_chars(Ref),
|
|
rebar_utils:escape_chars(Url),
|
|
rebar_utils:escape_chars(filename:basename(Dir))]),
|
|
[{cd, filename:dirname(Dir)}]);
|
|
download_(Dir, {hg, Url, Rev}, _State) ->
|
|
ok = filelib:ensure_dir(Dir),
|
|
maybe_warn_local_url(Url),
|
|
rebar_utils:sh(?FMT("hg clone -q -r ~ts ~ts ~ts",
|
|
[rebar_utils:escape_chars(Rev),
|
|
rebar_utils:escape_chars(Url),
|
|
rebar_utils:escape_chars(filename:basename(Dir))]),
|
|
[{cd, filename:dirname(Dir)}]).
|
|
|
|
make_vsn(AppInfo, _) ->
|
|
check_type_support(),
|
|
make_vsn_(rebar_app_info:dir(AppInfo)).
|
|
|
|
make_vsn_(Dir) ->
|
|
BaseHg = "hg -R \"" ++ rebar_utils:escape_double_quotes(Dir) ++ "\" ",
|
|
Ref = get_ref(Dir),
|
|
Cmd = BaseHg ++ "log --template \"{latesttag}+build.{latesttagdistance}.rev.{node|short}\""
|
|
" --rev " ++ Ref,
|
|
AbortMsg = io_lib:format("Version resolution of hg dependency failed in ~ts", [Dir]),
|
|
{ok, VsnString} =
|
|
rebar_utils:sh(Cmd,
|
|
[{use_stdout, false}, {debug_abort_on_error, AbortMsg}]),
|
|
RawVsn = rebar_string:trim(VsnString, both, "\n"),
|
|
|
|
Vsn = case RawVsn of
|
|
"null+" ++ Rest -> "0.0.0+" ++ Rest;
|
|
_ -> RawVsn
|
|
end,
|
|
{plain, Vsn}.
|
|
|
|
%%% Internal functions
|
|
|
|
compare_url(Dir, Url) ->
|
|
CurrentUrl = rebar_string:trim(os:cmd("hg -R \"" ++ rebar_utils:escape_double_quotes(Dir) ++"\" paths default"), both, "\n"),
|
|
CurrentUrl1 = rebar_string:trim(CurrentUrl, both, "\r"),
|
|
parse_hg_url(CurrentUrl1) =:= parse_hg_url(Url).
|
|
|
|
get_ref(Dir) ->
|
|
AbortMsg = io_lib:format("Get ref of hg dependency failed in ~ts", [Dir]),
|
|
{ok, RefString} =
|
|
rebar_utils:sh("hg -R \"" ++ rebar_utils:escape_double_quotes(Dir) ++ "\" --debug id -i",
|
|
[{use_stdout, false}, {debug_abort_on_error, AbortMsg}]),
|
|
rebar_string:trim(RefString, both, "\n").
|
|
|
|
get_tag_distance(Dir, Ref) ->
|
|
AbortMsg = io_lib:format("Get tag distance of hg dependency failed in ~ts", [Dir]),
|
|
{ok, LogString} =
|
|
rebar_utils:sh("hg -R \"" ++ rebar_utils:escape_double_quotes(Dir) ++ "\" "
|
|
"log --template \"{latesttag}-{latesttagdistance}\n\" "
|
|
"--rev " ++ rebar_utils:escape_chars(Ref),
|
|
[{use_stdout, false}, {debug_abort_on_error, AbortMsg}]),
|
|
Log = rebar_string:trim(LogString,
|
|
both, "\n"),
|
|
[Tag, Distance] = re:split(Log, "-([0-9]+)$",
|
|
[{parts,0}, {return,list}, unicode]),
|
|
{Tag, Distance}.
|
|
|
|
get_branch_ref(Dir, Branch) ->
|
|
AbortMsg = io_lib:format("Get branch ref of hg dependency failed in ~ts", [Dir]),
|
|
{ok, BranchRefString} =
|
|
rebar_utils:sh("hg -R \"" ++ rebar_utils:escape_double_quotes(Dir) ++
|
|
"\" log --template \"{node}\n\" --rev " ++ rebar_utils:escape_chars(Branch),
|
|
[{use_stdout, false}, {debug_abort_on_error, AbortMsg}]),
|
|
rebar_string:trim(BranchRefString, both, "\n").
|
|
|
|
|
|
maybe_warn_local_url(Url) ->
|
|
try
|
|
_ = parse_hg_url(Url),
|
|
ok
|
|
catch
|
|
_:_ ->
|
|
?WARN("URL format (~ts) unsupported.", [])
|
|
end.
|
|
|
|
parse_hg_url("ssh://" ++ HostPath) ->
|
|
[Host | Path] = rebar_string:lexemes(HostPath, "/"),
|
|
{Host, filename:rootname(filename:join(Path), ".hg")};
|
|
parse_hg_url("http://" ++ HostPath) ->
|
|
[Host | Path] = rebar_string:lexemes(HostPath, "/"),
|
|
{Host, filename:rootname(filename:join(Path), ".hg")};
|
|
parse_hg_url("https://" ++ HostPath) ->
|
|
[Host | Path] = rebar_string:lexemes(HostPath, "/"),
|
|
{Host, filename:rootname(filename:join(Path), ".hg")}.
|
|
|
|
check_type_support() ->
|
|
case get({is_supported, ?MODULE}) of
|
|
true ->
|
|
ok;
|
|
_ ->
|
|
case rebar_utils:sh("hg --version", [{return_on_error, true},
|
|
{use_stdout, false}]) of
|
|
{error, _} ->
|
|
?ABORT("hg not installed", []);
|
|
_ ->
|
|
put({is_supported, ?MODULE}, true),
|
|
ok
|
|
end
|
|
end.
|