diff options
| -rw-r--r-- | src/rebar_app_info.erl | 41 | ||||
| -rw-r--r-- | src/rebar_prv_install_deps.erl | 95 | ||||
| -rw-r--r-- | test/rebar_test_utils.erl | 24 | ||||
| -rw-r--r-- | test/rebar_upgrade_SUITE.erl | 84 | 
4 files changed, 174 insertions, 70 deletions
| diff --git a/src/rebar_app_info.erl b/src/rebar_app_info.erl index 91640f2..247e5f4 100644 --- a/src/rebar_app_info.erl +++ b/src/rebar_app_info.erl @@ -34,6 +34,8 @@           source/2,           state/1,           state/2, +         is_lock/1, +         is_lock/2,           is_checkout/1,           is_checkout/2,           valid/1, @@ -41,22 +43,23 @@  -export_type([t/0]). --record(app_info_t, {name :: binary(), -                     app_file_src :: file:filename_all() | undefined, -                     app_file :: file:filename_all() | undefined, -                     config :: rebar_state:t() | undefined, -                     original_vsn :: binary() | string() | undefined, -                     app_details=[] :: list(), -                     applications=[] :: list(), -                     deps=[] :: list(), +-record(app_info_t, {name               :: binary(), +                     app_file_src       :: file:filename_all() | undefined, +                     app_file           :: file:filename_all() | undefined, +                     config             :: rebar_state:t() | undefined, +                     original_vsn       :: binary() | string() | undefined, +                     app_details=[]     :: list(), +                     applications=[]    :: list(), +                     deps=[]            :: list(),                       profiles=[default] :: [atom()], -                     dep_level=0 :: integer(), -                     dir :: file:name(), -                     out_dir :: file:name(), -                     source :: string() | tuple() | undefined, -                     state :: rebar_state:t() | undefined, -                     is_checkout=false :: boolean(), -                     valid :: boolean()}). +                     dep_level=0        :: integer(), +                     dir                :: file:name(), +                     out_dir            :: file:name(), +                     source             :: string() | tuple() | undefined, +                     state              :: rebar_state:t() | undefined, +                     is_lock=false      :: boolean(), +                     is_checkout=false  :: boolean(), +                     valid              :: boolean()}).  %%============================================================================  %% types @@ -254,6 +257,14 @@ state(AppInfo=#app_info_t{}, State) ->  state(#app_info_t{state=State}) ->      State. +-spec is_lock(t(), boolean()) -> t(). +is_lock(AppInfo=#app_info_t{}, IsLock) -> +    AppInfo#app_info_t{is_lock=IsLock}. + +-spec is_lock(t()) -> boolean(). +is_lock(#app_info_t{is_lock=IsLock}) -> +    IsLock. +  -spec is_checkout(t(), boolean()) -> t().  is_checkout(AppInfo=#app_info_t{}, IsCheckout) ->      AppInfo#app_info_t{is_checkout=IsCheckout}. diff --git a/src/rebar_prv_install_deps.erl b/src/rebar_prv_install_deps.erl index 2d69aa9..cdc008c 100644 --- a/src/rebar_prv_install_deps.erl +++ b/src/rebar_prv_install_deps.erl @@ -202,20 +202,25 @@ update_pkg_deps(Profile, Packages, PkgDeps, Graph, Upgrade, Seen, State, Locks)              update_pkg_deps(Profile, S, Packages, Upgrade, Seen, State, Locks)      end. +pkg_locked({Name, _, _}, Locks) -> +    pkg_locked(Name, Locks);  pkg_locked({Name, _}, Locks) -> +    pkg_locked(Name, Locks); +pkg_locked(Name, Locks) ->      false =/= lists:keyfind(Name, 1, Locks). -update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State, _Locks) -> +update_pkg_deps(Profile, Pkgs, Packages, Upgrade, Seen, State, Locks) ->      %% Create app_info record for each pkg dep      DepsDir = profile_dep_dir(State, Profile),      {Solved, _, State1}          = lists:foldl(fun(Pkg, {Acc, SeenAcc, StateAcc}) -> -                        handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Acc, SeenAcc, StateAcc) +                        handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Acc, SeenAcc, Locks, StateAcc)                        end, {[], Seen, State}, Pkgs),      {Solved, State1}. -handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, State) -> -    AppInfo = package_to_app(DepsDir, Packages, Pkg, State), +handle_pkg_dep(Profile, Pkg, Packages, Upgrade, DepsDir, Fetched, Seen, Locks, State) -> +    IsLock = pkg_locked(Pkg, Locks), +    AppInfo = package_to_app(DepsDir, Packages, Pkg, IsLock, State),      Level = rebar_app_info:dep_level(AppInfo),      {NewSeen, NewState} = maybe_lock(Profile, AppInfo, Seen, State, Level),      {_, AppInfo1} = maybe_fetch(AppInfo, Profile, Upgrade, Seen, NewState), @@ -247,7 +252,7 @@ maybe_lock(Profile, AppInfo, Seen, State, Level) ->              {Seen, State}      end. -package_to_app(DepsDir, Packages, {Name, Vsn, Level}, State) -> +package_to_app(DepsDir, Packages, {Name, Vsn, Level}, IsLock, State) ->      case dict:find({Name, Vsn}, Packages) of          error ->              case rebar_packages:check_registry(Name, Vsn, State) of @@ -263,7 +268,8 @@ package_to_app(DepsDir, Packages, {Name, Vsn, Level}, State) ->              AppInfo1 = rebar_app_info:deps(AppInfo, PkgDeps),              AppInfo2 = rebar_app_info:dir(AppInfo1, filename:join([DepsDir, Name])),              AppInfo3 = rebar_app_info:dep_level(AppInfo2, Level), -            rebar_app_info:source(AppInfo3, {pkg, Name, Vsn}) +            AppInfo4 = rebar_app_info:is_lock(AppInfo3, IsLock), +            rebar_app_info:source(AppInfo4, {pkg, Name, Vsn})      end.  -spec update_src_deps(atom(), non_neg_integer(), list(), list(), list(), rebar_state:t(), boolean(), sets:set(binary()), list()) -> {rebar_state:t(), list(), list(), sets:set(binary())}. @@ -385,10 +391,10 @@ handle_dep(State, DepsDir, AppInfo, Locks, Level) ->      NewLocks = [{DepName, Source, LockLevel+Level} ||                     {DepName, Source, LockLevel} <- rebar_state:get(S3, {locks, default}, [])],      AppInfo2 = rebar_app_info:deps(AppInfo1, rebar_state:deps_names(Deps)), -    {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, S3, Locks, Level), +    {SrcDeps, PkgDeps} = parse_deps(DepsDir, Deps, S3, Locks, Level+1),      {AppInfo2, SrcDeps, PkgDeps, Locks++NewLocks, State1}. --spec maybe_fetch(rebar_app_info:t(), atom(), boolean() | {true, binary(), integer()}, +-spec maybe_fetch(rebar_app_info:t(), atom(), boolean(),                    sets:set(binary()), rebar_state:t()) -> {boolean(), rebar_app_info:t()}.  maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) ->      AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)), @@ -476,69 +482,69 @@ parse_deps(DepsDir, Deps, State, Locks, Level) ->                                 end,                          case lists:keyfind(ec_cnv:to_binary(Name), 1, Locks) of                              false -> -                                parse_dep(Dep, Acc, DepsDir, State); +                                parse_dep(Dep, Acc, DepsDir, false, State);                              LockedDep ->                                  LockedLevel = element(3, LockedDep),                                  case LockedLevel > Level of                                      true -> -                                        parse_dep(Dep, Acc, DepsDir, State); +                                        parse_dep(Dep, Acc, DepsDir, false, State);                                      false -> -                                        parse_dep(LockedDep, Acc, DepsDir, State) +                                        parse_dep(LockedDep, Acc, DepsDir, true, State)                                  end                          end                  end, {[], []}, Deps). -parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_list(Vsn) -> +parse_dep({Name, Vsn}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_list(Vsn) ->      CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)),      case rebar_app_info:discover(CheckoutsDir) of          {ok, _App} -> -            Dep = new_dep(DepsDir, Name, [], [], State), +            Dep = new_dep(DepsDir, Name, [], [], IsLock, State),              {[Dep | SrcDepsAcc], PkgDepsAcc};          not_found ->              {SrcDepsAcc, [parse_goal(ec_cnv:to_binary(Name)                                      ,ec_cnv:to_binary(Vsn)) | PkgDepsAcc]}      end; -parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_atom(Name) -> +parse_dep(Name, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_atom(Name) ->      {PkgName, PkgVsn} = get_package(ec_cnv:to_binary(Name), State),      CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)),      case rebar_app_info:discover(CheckoutsDir) of          {ok, _App} -> -            Dep = new_dep(DepsDir, Name, [], [], State), +            Dep = new_dep(DepsDir, Name, [], [], IsLock, State),              {[Dep | SrcDepsAcc], PkgDepsAcc};          not_found ->              {SrcDepsAcc, [{PkgName, PkgVsn} | PkgDepsAcc]}      end; -parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> -    Dep = new_dep(DepsDir, Name, [], Source, State), +parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> +    Dep = new_dep(DepsDir, Name, [], Source, IsLock, State),      {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> -    Dep = new_dep(DepsDir, Name, [], Source, State), +parse_dep({Name, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> +    Dep = new_dep(DepsDir, Name, [], Source, IsLock, State),      {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({Name, _Vsn, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> -    Dep = new_dep(DepsDir, Name, [], Source, State), +parse_dep({Name, _Vsn, Source}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) -> +    Dep = new_dep(DepsDir, Name, [], Source, IsLock, State),      {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({Name, _Vsn, Source, Opts}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) -> +parse_dep({Name, _Vsn, Source, Opts}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source) ->      ?WARN("Dependency option list ~p in ~p is not supported and will be ignored", [Opts, Name]), -    Dep = new_dep(DepsDir, Name, [], Source, State), +    Dep = new_dep(DepsDir, Name, [], Source, IsLock, State),      {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep({_Name, {pkg, Name, Vsn}, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_integer(Level) -> +parse_dep({_Name, {pkg, Name, Vsn}, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_integer(Level) ->      CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)),      case rebar_app_info:discover(CheckoutsDir) of          {ok, _App} -> -            Dep = new_dep(DepsDir, Name, [], [], State), +            Dep = new_dep(DepsDir, Name, [], [], IsLock, State),              {[Dep | SrcDepsAcc], PkgDepsAcc};          not_found ->              {SrcDepsAcc, [{Name, Vsn} | PkgDepsAcc]}      end; -parse_dep({Name, Source, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, State) when is_tuple(Source) +parse_dep({Name, Source, Level}, {SrcDepsAcc, PkgDepsAcc}, DepsDir, IsLock, State) when is_tuple(Source)                                                                                , is_integer(Level) -> -    Dep = new_dep(DepsDir, Name, [], Source, State), +    Dep = new_dep(DepsDir, Name, [], Source, IsLock, State),      {[Dep | SrcDepsAcc], PkgDepsAcc}; -parse_dep(Dep, _, _, _) -> +parse_dep(Dep, _, _, _, _) ->      throw(?PRV_ERROR({parse_dep, Dep})). -new_dep(DepsDir, Name, Vsn, Source, State) -> +new_dep(DepsDir, Name, Vsn, Source, IsLock, State) ->      CheckoutsDir = ec_cnv:to_list(rebar_dir:checkouts_dir(State, Name)),      {ok, Dep} = case rebar_app_info:discover(CheckoutsDir) of                      {ok, App} -> @@ -559,7 +565,7 @@ new_dep(DepsDir, Name, Vsn, Source, State) ->      ParentOverrides = rebar_state:overrides(State),      Dep1 = rebar_app_info:state(Dep,                                 rebar_state:overrides(S, ParentOverrides++Overrides)), -    rebar_app_info:source(Dep1, Source). +    rebar_app_info:is_lock(rebar_app_info:source(Dep1, Source), IsLock).  fetch_app(AppInfo, AppDir, State) ->      ?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]), @@ -580,22 +586,29 @@ update_app_info(AppInfo) ->                   IncludedApplications++Applications),      rebar_app_info:valid(AppInfo1, false). -maybe_upgrade(AppInfo, AppDir, false, State) -> +maybe_upgrade(AppInfo, AppDir, Upgrade, State) ->      Source = rebar_app_info:source(AppInfo), -    rebar_fetch:needs_update(AppDir, Source, State); -maybe_upgrade(AppInfo, AppDir, true, State) -> -    Source = rebar_app_info:source(AppInfo), -    case rebar_fetch:needs_update(AppDir, Source, State) of +    case Upgrade orelse rebar_app_info:is_lock(AppInfo) of          true -> -            ?INFO("Upgrading ~s", [rebar_app_info:name(AppInfo)]), -            case rebar_fetch:download_source(AppDir, Source, State) of +            case rebar_fetch:needs_update(AppDir, Source, State) of                  true -> -                    true; -                Error -> -                    throw(Error) +                    ?INFO("Upgrading ~s", [rebar_app_info:name(AppInfo)]), +                    case rebar_fetch:download_source(AppDir, Source, State) of +                        true -> +                            true; +                        Error -> +                            throw(Error) +                    end; +                false -> +                    case Upgrade of +                        true -> +                            ?INFO("No upgrade needed for ~s", [rebar_app_info:name(AppInfo)]), +                            false; +                        false -> +                            false +                    end              end;          false -> -            ?INFO("No upgrade needed for ~s", [rebar_app_info:name(AppInfo)]),              false      end. diff --git a/test/rebar_test_utils.erl b/test/rebar_test_utils.erl index c4dc663..68e0603 100644 --- a/test/rebar_test_utils.erl +++ b/test/rebar_test_utils.erl @@ -182,7 +182,7 @@ check_results(AppDir, Expected) ->      lists:foreach(          fun({app, Name}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("App Name: ~p", [Name]),                  case lists:keyfind(Name, 1, DepsNames) of                      false ->                          error({app_not_found, Name}); @@ -190,7 +190,7 @@ check_results(AppDir, Expected) ->                          ok                  end          ; ({app, Name, invalid}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Invalid Name: ~p", [Name]),                  case lists:keyfind(Name, 1, InvalidDepsNames) of                      false ->                          error({app_not_found, Name}); @@ -198,7 +198,7 @@ check_results(AppDir, Expected) ->                          ok                  end          ; ({app, Name, valid}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Valid Name: ~p", [Name]),                  case lists:keyfind(Name, 1, ValidDepsNames) of                      false ->                          error({app_not_found, Name}); @@ -206,13 +206,13 @@ check_results(AppDir, Expected) ->                          ok                  end          ;  ({checkout, Name}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Checkout Name: ~p", [Name]),                  ?assertNotEqual(false, lists:keyfind(Name, 1, CheckoutsNames))          ;  ({dep, Name}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Dep Name: ~p", [Name]),                  ?assertNotEqual(false, lists:keyfind(Name, 1, DepsNames))          ;  ({dep, Name, Vsn}) -> -                ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]), +                ct:pal("Dep Name: ~p, Vsn: ~p", [Name, Vsn]),                  case lists:keyfind(Name, 1, DepsNames) of                      false ->                          error({dep_not_found, Name}); @@ -221,10 +221,10 @@ check_results(AppDir, Expected) ->                                       iolist_to_binary(rebar_app_info:original_vsn(App)))                  end          ;  ({plugin, Name}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Plugin Name: ~p", [Name]),                  ?assertNotEqual(false, lists:keyfind(Name, 1, PluginsNames))          ;  ({plugin, Name, Vsn}) -> -                ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]), +                ct:pal("Plugin Name: ~p, Vsn: ~p", [Name, Vsn]),                  case lists:keyfind(Name, 1, PluginsNames) of                      false ->                          error({plugin_not_found, Name}); @@ -233,10 +233,10 @@ check_results(AppDir, Expected) ->                                       iolist_to_binary(rebar_app_info:original_vsn(App)))                  end          ;  ({global_plugin, Name}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Global Plugin Name: ~p", [Name]),                  ?assertNotEqual(false, lists:keyfind(Name, 1, GlobalPluginsNames))          ;  ({global_plugin, Name, Vsn}) -> -                ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]), +                ct:pal("Global Plugin Name: ~p, Vsn: ~p", [Name, Vsn]),                  case lists:keyfind(Name, 1, GlobalPluginsNames) of                      false ->                          error({global_plugin_not_found, Name}); @@ -245,10 +245,10 @@ check_results(AppDir, Expected) ->                                       iolist_to_binary(rebar_app_info:original_vsn(App)))                  end          ;  ({lock, Name}) -> -                ct:pal("Name: ~p", [Name]), +                ct:pal("Lock Name: ~p", [Name]),                  ?assertNotEqual(false, lists:keyfind(iolist_to_binary(Name), 1, Locks))          ;  ({lock, Name, Vsn}) -> -                ct:pal("Name: ~p, Vsn: ~p", [Name, Vsn]), +                ct:pal("Lock Name: ~p, Vsn: ~p", [Name, Vsn]),                  case lists:keyfind(iolist_to_binary(Name), 1, Locks) of                      false ->                          error({lock_not_found, Name}); diff --git a/test/rebar_upgrade_SUITE.erl b/test/rebar_upgrade_SUITE.erl index 1dc0af2..793f10a 100644 --- a/test/rebar_upgrade_SUITE.erl +++ b/test/rebar_upgrade_SUITE.erl @@ -10,7 +10,7 @@ groups() ->                  pair_a, pair_b, pair_ab, pair_c, pair_all,                  triplet_a, triplet_b, triplet_c,                  tree_a, tree_b, tree_c, tree_c2, tree_ac, tree_all, -                delete_d, promote]}, +                delete_d, promote, stable_lock, fwd_lock]},       {git, [], [{group, all}]},       {pkg, [], [{group, all}]}]. @@ -361,7 +361,30 @@ upgrades(promote) ->        {"C", "3", []}       ],       ["A","B","C","D"], -     {"C", [{"A","1"},{"C","3"},{"B","1"},{"D","1"}]}}. +     {"C", [{"A","1"},{"C","3"},{"B","1"},{"D","1"}]}}; +upgrades(stable_lock) -> +    {[{"A", "1", [{"C", "1", []}]}, +      {"B", "1", [{"D", "1", []}]} +     ], % lock after this +     [{"A", "2", [{"C", "2", []}]}, +      {"B", "2", [{"D", "2", []}]} +     ], +     [], +     %% Run a regular lock and no app should be upgraded +     {"any", [{"A","1"},{"C","1"},{"B","1"},{"D","1"}]}}; +upgrades(fwd_lock) -> +    {[{"A", "1", [{"C", "1", []}]}, +      {"B", "1", [{"D", "1", []}]} +     ], +     [{"A", "2", [{"C", "2", []}]}, +      {"B", "2", [{"D", "2", []}]} +     ], +     ["A","B","C","D"], +     %% For this one, we should build, rewrite the lock +     %% file to include the result post-upgrade, and then +     %% run a regular lock to see that the lock file is respected +     %% in deps. +     {"any", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}}.  %% TODO: add a test that verifies that unlocking files and then  %% running the upgrade code is enough to properly upgrade things. @@ -435,6 +458,46 @@ delete_d(Config) ->      ?assertNotEqual([],                      [1 || {"App ~ts is no longer needed and can be deleted.",                             [<<"D">>]} <- Infos]). + +stable_lock(Config) -> +    apply(?config(mock, Config), []), +    {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), +    %% Install dependencies before re-mocking for an upgrade +    rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}), +    {App, Unlocks} = ?config(expected, Config), +    ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]), +    Expectation = case Unlocks of +        {error, Term} -> {error, Term}; +        _ -> {ok, Unlocks} +    end, +    apply(?config(mock_update, Config), []), +    NewRebarConf = rebar_test_utils:create_config(?config(apps, Config), +                                                  [{deps, ?config(next_top_deps, Config)}]), +    {ok, NewRebarConfig} = file:consult(NewRebarConf), +    rebar_test_utils:run_and_check( +        Config, NewRebarConfig, ["lock", App], Expectation +    ). + +fwd_lock(Config) -> +    apply(?config(mock, Config), []), +    {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), +    %% Install dependencies before re-mocking for an upgrade +    rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}), +    {App, Unlocks} = ?config(expected, Config), +    ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]), +    Expectation = case Unlocks of +        {error, Term} -> {error, Term}; +        _ -> {ok, Unlocks} +    end, +    rewrite_locks(Expectation, Config), +    apply(?config(mock_update, Config), []), +    NewRebarConf = rebar_test_utils:create_config(?config(apps, Config), +                                                  [{deps, ?config(next_top_deps, Config)}]), +    {ok, NewRebarConfig} = file:consult(NewRebarConf), +    rebar_test_utils:run_and_check( +        Config, NewRebarConfig, ["lock", App], Expectation +    ). +  run(Config) ->      apply(?config(mock, Config), []),      {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)), @@ -453,3 +516,20 @@ run(Config) ->      rebar_test_utils:run_and_check(          Config, NewRebarConfig, ["upgrade", App], Expectation      ). + +rewrite_locks({ok, Expectations}, Config) -> +    AppDir = ?config(apps, Config), +    LockFile = filename:join([AppDir, "rebar.lock"]), +    {ok, [Locks]} = file:consult(LockFile), +    ExpLocks = [{list_to_binary(Name), Vsn} +               || {lock, Name, Vsn} <- Expectations], +    NewLocks = lists:foldl( +        fun({App, {pkg, Name, _}, Lvl}, Acc) -> +                Vsn = list_to_binary(proplists:get_value(App,ExpLocks)), +                [{App, {pkg, Name, Vsn}, Lvl} | Acc] +        ;  ({App, {git, URL, {ref, _}}, Lvl}, Acc) -> +                Vsn = proplists:get_value(App,ExpLocks), +                [{App, {git, URL, {ref, Vsn}}, Lvl} | Acc] +        end, [], Locks), +    ct:pal("rewriting locks from ~p to~n~p", [Locks, NewLocks]), +    file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])). | 
