Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

535 righe
18 KiB

  1. -module(rebar_upgrade_SUITE).
  2. -include_lib("common_test/include/ct.hrl").
  3. -include_lib("eunit/include/eunit.hrl").
  4. -compile(export_all).
  5. all() -> [{group, git}, {group, pkg}].
  6. groups() ->
  7. [{all, [], [top_a, top_b, top_c, top_d1, top_d2, top_e,
  8. pair_a, pair_b, pair_ab, pair_c, pair_all,
  9. triplet_a, triplet_b, triplet_c,
  10. tree_a, tree_b, tree_c, tree_c2, tree_ac, tree_all,
  11. delete_d, promote, stable_lock, fwd_lock]},
  12. {git, [], [{group, all}]},
  13. {pkg, [], [{group, all}]}].
  14. init_per_suite(Config) ->
  15. application:start(meck),
  16. Config.
  17. end_per_suite(_Config) ->
  18. application:stop(meck).
  19. init_per_group(git, Config) ->
  20. [{deps_type, git} | Config];
  21. init_per_group(pkg, Config) ->
  22. [{deps_type, pkg} | Config];
  23. init_per_group(_, Config) ->
  24. Config.
  25. end_per_group(_, Config) ->
  26. Config.
  27. init_per_testcase(Case, Config) ->
  28. DepsType = ?config(deps_type, Config),
  29. {Deps, UpDeps, ToUp, Expectations} = upgrades(Case),
  30. Expanded = rebar_test_utils:expand_deps(DepsType, Deps),
  31. UpExpanded = rebar_test_utils:expand_deps(DepsType, UpDeps),
  32. [{expected, normalize_unlocks(Expectations)},
  33. {mock, fun() -> mock_deps(DepsType, Expanded, []) end},
  34. {mock_update, fun() -> mock_deps(DepsType, Expanded, UpExpanded, ToUp) end}
  35. | setup_project(Case, Config, Expanded, UpExpanded)].
  36. end_per_testcase(_, Config) ->
  37. meck:unload(),
  38. Config.
  39. setup_project(Case, Config0, Deps, UpDeps) ->
  40. DepsType = ?config(deps_type, Config0),
  41. Config = rebar_test_utils:init_rebar_state(
  42. Config0,
  43. atom_to_list(Case)++"_"++atom_to_list(DepsType)++"_"
  44. ),
  45. AppDir = ?config(apps, Config),
  46. rebar_test_utils:create_app(AppDir, "Root", "0.0.0", [kernel, stdlib]),
  47. TopDeps = rebar_test_utils:top_level_deps(Deps),
  48. RebarConf = rebar_test_utils:create_config(AppDir, [{deps, TopDeps}]),
  49. [{rebarconfig, RebarConf},
  50. {next_top_deps, rebar_test_utils:top_level_deps(UpDeps)} | Config].
  51. upgrades(top_a) ->
  52. %% Original tree
  53. {[{"A", "1", [{"B", [{"D", "1", []}]},
  54. {"C", [{"D", "2", []}]}]}
  55. ],
  56. %% Updated tree
  57. [{"A", "1", [{"B", [{"D", "3", []}]},
  58. {"C", [{"D", "2", []}]}]}
  59. ],
  60. %% Modified apps, gobally
  61. ["A","B","D"],
  62. %% upgrade vs. new tree
  63. {"A", [{"A","1"}, "B", "C", {"D","3"}]}};
  64. upgrades(top_b) ->
  65. %% Original tree
  66. {[{"A", "1", [{"B", [{"D", "1", []}]},
  67. {"C", [{"D", "2", []}]}]}
  68. ],
  69. %% Updated tree
  70. [{"A", "1", [{"B", [{"D", "3", []}]},
  71. {"C", [{"D", "2", []}]}]}
  72. ],
  73. %% Modified apps, gobally
  74. ["A","B","D"],
  75. %% upgrade vs. new tree
  76. {"B", {error, {rebar_prv_upgrade, {transitive_dependency, <<"B">>}}}}};
  77. upgrades(top_c) ->
  78. %% Original tree
  79. {[{"A", "1", [{"B", [{"D", "1", []}]},
  80. {"C", [{"D", "2", []}]}]}
  81. ],
  82. %% Updated tree
  83. [{"A", "1", [{"B", [{"D", "3", []}]},
  84. {"C", [{"D", "2", []}]}]}
  85. ],
  86. %% Modified apps, gobally
  87. ["A","B","D"],
  88. %% upgrade vs. new tree
  89. {"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}};
  90. upgrades(top_d1) ->
  91. %% Original tree
  92. {[{"A", "1", [{"B", [{"D", "1", []}]},
  93. {"C", [{"D", "2", []}]}]}
  94. ],
  95. %% Updated tree
  96. [{"A", "1", [{"B", [{"D", "3", []}]},
  97. {"C", [{"D", "2", []}]}]}
  98. ],
  99. %% Modified apps, gobally
  100. ["A","B","D"],
  101. %% upgrade vs. new tree
  102. {"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}};
  103. upgrades(top_d2) ->
  104. %% Original tree
  105. {[{"A", "1", [{"B", [{"D", "1", []}]},
  106. {"C", [{"D", "2", []}]}]}
  107. ],
  108. %% Updated tree
  109. [{"A", "1", [{"B", [{"D", "3", []}]},
  110. {"C", [{"D", "2", []}]}]}
  111. ],
  112. %% Modified apps, gobally
  113. ["A","B","D"],
  114. %% upgrade vs. new tree
  115. {"D", {error, {rebar_prv_upgrade, {transitive_dependency, <<"D">>}}}}};
  116. upgrades(top_e) ->
  117. %% Original tree
  118. {[{"A", "1", [{"B", [{"D", "1", []}]},
  119. {"C", [{"D", "2", []}]}]}
  120. ],
  121. %% Updated tree
  122. [{"A", "1", [{"B", [{"D", "3", []}]},
  123. {"C", [{"D", "2", []}]}]}
  124. ],
  125. %% Modified apps, gobally
  126. ["A","B","D"],
  127. %% upgrade vs. new tree
  128. {"E", {error, {rebar_prv_upgrade, {unknown_dependency, <<"E">>}}}}};
  129. upgrades(pair_a) ->
  130. {[{"A", "1", [{"C", "1", []}]},
  131. {"B", "1", [{"D", "1", []}]}
  132. ],
  133. [{"A", "2", [{"C", "2", []}]},
  134. {"B", "2", [{"D", "2", []}]}
  135. ],
  136. ["A","B","C","D"],
  137. {"A", [{"A","2"},{"C","2"},{"B","1"},{"D","1"}]}};
  138. upgrades(pair_b) ->
  139. {[{"A", "1", [{"C", "1", []}]},
  140. {"B", "1", [{"D", "1", []}]}
  141. ],
  142. [{"A", "2", [{"C", "2", []}]},
  143. {"B", "2", [{"D", "2", []}]}
  144. ],
  145. ["A","B","C","D"],
  146. {"B", [{"A","1"},{"C","1"},{"B","2"},{"D","2"}]}};
  147. upgrades(pair_ab) ->
  148. {[{"A", "1", [{"C", "1", []}]},
  149. {"B", "1", [{"D", "1", []}]}
  150. ],
  151. [{"A", "2", [{"C", "2", []}]},
  152. {"B", "2", [{"D", "2", []}]}
  153. ],
  154. ["A","B","C","D"],
  155. {"A,B", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
  156. upgrades(pair_c) ->
  157. {[{"A", "1", [{"C", "1", []}]},
  158. {"B", "1", [{"D", "1", []}]}
  159. ],
  160. [{"A", "2", [{"C", "2", []}]},
  161. {"B", "2", [{"D", "2", []}]}
  162. ],
  163. ["A","B","C","D"],
  164. {"C", {error, {rebar_prv_upgrade, {transitive_dependency, <<"C">>}}}}};
  165. upgrades(pair_all) ->
  166. {[{"A", "1", [{"C", "1", []}]},
  167. {"B", "1", [{"D", "1", []}]}
  168. ],
  169. [{"A", "2", [{"C", "2", []}]},
  170. {"B", "2", [{"D", "2", []}]}
  171. ],
  172. ["A","B","C","D"],
  173. {"", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}};
  174. upgrades(triplet_a) ->
  175. {[{"A", "1", [{"D",[]},
  176. {"E","3",[]}]},
  177. {"B", "1", [{"F","1",[]},
  178. {"G",[]}]},
  179. {"C", "0", [{"H","3",[]},
  180. {"I",[]}]}],
  181. [{"A", "1", [{"D",[]},
  182. {"E","2",[]}]},
  183. {"B", "1", [{"F","1",[]},
  184. {"G",[]}]},
  185. {"C", "1", [{"H","4",[]},
  186. {"I",[]}]}],
  187. ["A","C","E","H"],
  188. {"A", [{"A","1"}, "D", {"E","2"},
  189. {"B","1"}, {"F","1"}, "G",
  190. {"C","0"}, {"H","3"}, "I"]}};
  191. upgrades(triplet_b) ->
  192. {[{"A", "1", [{"D",[]},
  193. {"E","3",[]}]},
  194. {"B", "1", [{"F","1",[]},
  195. {"G",[]}]},
  196. {"C", "0", [{"H","3",[]},
  197. {"I",[]}]}],
  198. [{"A", "2", [{"D",[]},
  199. {"E","2",[]}]},
  200. {"B", "1", [{"F","1",[]},
  201. {"G",[]}]},
  202. {"C", "1", [{"H","4",[]},
  203. {"I",[]}]}],
  204. ["A","C","E","H"],
  205. {"B", [{"A","1"}, "D", {"E","3"},
  206. {"B","1"}, {"F","1"}, "G",
  207. {"C","0"}, {"H","3"}, "I"]}};
  208. upgrades(triplet_c) ->
  209. {[{"A", "1", [{"D",[]},
  210. {"E","3",[]}]},
  211. {"B", "1", [{"F","1",[]},
  212. {"G",[]}]},
  213. {"C", "0", [{"H","3",[]},
  214. {"I",[]}]}],
  215. [{"A", "2", [{"D",[]},
  216. {"E","2",[]}]},
  217. {"B", "1", [{"F","1",[]},
  218. {"G",[]}]},
  219. {"C", "1", [{"H","4",[]},
  220. {"I",[]}]}],
  221. ["A","C","E","H"],
  222. {"C", [{"A","1"}, "D", {"E","3"},
  223. {"B","1"}, {"F","1"}, "G",
  224. {"C","1"}, {"H","4"}, "I"]}};
  225. upgrades(tree_a) ->
  226. {[{"A", "1", [{"D",[{"J",[]}]},
  227. {"E",[{"I","1",[]}]}]},
  228. {"B", "1", [{"F",[]},
  229. {"G",[]}]},
  230. {"C", "1", [{"H",[]},
  231. {"I","2",[]}]}
  232. ],
  233. [{"A", "1", [{"D",[{"J",[]}]},
  234. {"E",[{"I","1",[]}]}]},
  235. {"B", "1", [{"F",[]},
  236. {"G",[]}]},
  237. {"C", "2", [{"H",[]}]}
  238. ],
  239. ["C"],
  240. {"A", [{"A","1"}, "D", "J", "E",
  241. {"B","1"}, "F", "G",
  242. {"C","1"}, "H", {"I","2"}]}};
  243. upgrades(tree_b) ->
  244. {[{"A", "1", [{"D",[{"J",[]}]},
  245. {"E",[{"I","1",[]}]}]},
  246. {"B", "1", [{"F",[]},
  247. {"G",[]}]},
  248. {"C", "1", [{"H",[]},
  249. {"I","2",[]}]}
  250. ],
  251. [{"A", "1", [{"D",[{"J",[]}]},
  252. {"E",[{"I","1",[]}]}]},
  253. {"B", "1", [{"F",[]},
  254. {"G",[]}]},
  255. {"C", "2", [{"H",[]}]}
  256. ],
  257. ["C"],
  258. {"B", [{"A","1"}, "D", "J", "E",
  259. {"B","1"}, "F", "G",
  260. {"C","1"}, "H", {"I","2"}]}};
  261. upgrades(tree_c) ->
  262. {[{"A", "1", [{"D",[{"J",[]}]},
  263. {"E",[{"I","1",[]}]}]},
  264. {"B", "1", [{"F",[]},
  265. {"G",[]}]},
  266. {"C", "1", [{"H",[]},
  267. {"I","2",[]}]}
  268. ],
  269. [{"A", "1", [{"D",[{"J",[]}]},
  270. {"E",[{"I","1",[]}]}]},
  271. {"B", "1", [{"F",[]},
  272. {"G",[]}]},
  273. {"C", "1", [{"H",[]}]}
  274. ],
  275. ["C","I"],
  276. {"C", [{"A","1"}, "D", "J", "E", {"I","1"},
  277. {"B","1"}, "F", "G",
  278. {"C","1"}, "H"]}};
  279. upgrades(tree_c2) ->
  280. {[{"A", "1", [{"D",[{"J",[]}]},
  281. {"E",[{"I","1",[]}]}]},
  282. {"B", "1", [{"F",[]},
  283. {"G",[]}]},
  284. {"C", "1", [{"H",[]},
  285. {"I","2",[]}]}
  286. ],
  287. [{"A", "1", [{"D",[{"J",[]}]},
  288. {"E",[{"I","1",[]}]}]},
  289. {"B", "1", [{"F",[]},
  290. {"G",[]}]},
  291. {"C", "1", [{"H",[{"K",[]}]},
  292. {"I","2",[]}]}
  293. ],
  294. ["C", "H"],
  295. {"C", [{"A","1"}, "D", "J", "E",
  296. {"B","1"}, "F", "G",
  297. {"C","1"}, "H", {"I", "2"}, "K"]}};
  298. upgrades(tree_ac) ->
  299. {[{"A", "1", [{"D",[{"J",[]}]},
  300. {"E",[{"I","1",[]}]}]},
  301. {"B", "1", [{"F",[]},
  302. {"G",[]}]},
  303. {"C", "1", [{"H",[]},
  304. {"I","2",[]}]}
  305. ],
  306. [{"A", "1", [{"D",[{"J",[]}]},
  307. {"E",[{"I","1",[]}]}]},
  308. {"B", "1", [{"F",[]},
  309. {"G",[]}]},
  310. {"C", "1", [{"H",[]}]}
  311. ],
  312. ["C","I"],
  313. {"C, A", [{"A","1"}, "D", "J", "E", {"I","1"},
  314. {"B","1"}, "F", "G",
  315. {"C","1"}, "H"]}};
  316. upgrades(tree_all) ->
  317. {[{"A", "1", [{"D",[{"J",[]}]},
  318. {"E",[{"I","1",[]}]}]},
  319. {"B", "1", [{"F",[]},
  320. {"G",[]}]},
  321. {"C", "1", [{"H",[]},
  322. {"I","2",[]}]}
  323. ],
  324. [{"A", "1", [{"D",[{"J",[]}]},
  325. {"E",[{"I","1",[]}]}]},
  326. {"B", "1", [{"F",[]},
  327. {"G",[]}]},
  328. {"C", "1", [{"H",[]}]}
  329. ],
  330. ["C","I"],
  331. {"", [{"A","1"}, "D", "J", "E", {"I","1"},
  332. {"B","1"}, "F", "G",
  333. {"C","1"}, "H"]}};
  334. upgrades(delete_d) ->
  335. {[{"A", "1", [{"B", [{"D", "1", []}]},
  336. {"C", [{"D", "2", []}]}]}
  337. ],
  338. [{"A", "2", [{"B", []},
  339. {"C", []}]}
  340. ],
  341. ["A","B", "C"],
  342. %% upgrade vs. new tree
  343. {"", [{"A","2"}, "B", "C"]}};
  344. upgrades(promote) ->
  345. {[{"A", "1", [{"C", "1", []}]},
  346. {"B", "1", [{"D", "1", []}]}
  347. ],
  348. [{"A", "2", [{"C", "2", []}]},
  349. {"B", "2", [{"D", "2", []}]},
  350. {"C", "3", []}
  351. ],
  352. ["A","B","C","D"],
  353. {"C", [{"A","1"},{"C","3"},{"B","1"},{"D","1"}]}};
  354. upgrades(stable_lock) ->
  355. {[{"A", "1", [{"C", "1", []}]},
  356. {"B", "1", [{"D", "1", []}]}
  357. ], % lock after this
  358. [{"A", "2", [{"C", "2", []}]},
  359. {"B", "2", [{"D", "2", []}]}
  360. ],
  361. ["A","B","C","D"],
  362. %% Run a regular lock and no app should be upgraded
  363. {"any", [{"A","1"},{"C","1"},{"B","1"},{"D","1"}]}};
  364. upgrades(fwd_lock) ->
  365. {[{"A", "1", [{"C", "1", []}]},
  366. {"B", "1", [{"D", "1", []}]}
  367. ],
  368. [{"A", "2", [{"C", "2", []}]},
  369. {"B", "2", [{"D", "2", []}]}
  370. ],
  371. ["A","B","C","D"],
  372. %% For this one, we should build, rewrite the lock
  373. %% file to include the result post-upgrade, and then
  374. %% run a regular lock to see that the lock file is respected
  375. %% in deps.
  376. {"any", [{"A","2"},{"C","2"},{"B","2"},{"D","2"}]}}.
  377. %% TODO: add a test that verifies that unlocking files and then
  378. %% running the upgrade code is enough to properly upgrade things.
  379. mock_deps(git, Deps, Upgrades) ->
  380. catch mock_git_resource:unmock(),
  381. mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}, {upgrade, Upgrades}]);
  382. mock_deps(pkg, Deps, Upgrades) ->
  383. catch mock_pkg_resource:unmock(),
  384. mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Deps)}, {upgrade, Upgrades}]).
  385. mock_deps(git, _OldDeps, Deps, Upgrades) ->
  386. catch mock_git_resource:unmock(),
  387. mock_git_resource:mock([{deps, rebar_test_utils:flat_deps(Deps)}, {upgrade, Upgrades}]);
  388. mock_deps(pkg, OldDeps, Deps, Upgrades) ->
  389. Merged = Deps ++ [Dep || Dep <- OldDeps,
  390. not lists:keymember(element(1, Dep), 1, Deps)],
  391. catch mock_pkg_resource:unmock(),
  392. mock_pkg_resource:mock([{pkgdeps, rebar_test_utils:flat_pkgdeps(Merged)}, {upgrade, Upgrades}]).
  393. normalize_unlocks({App, Locks}) ->
  394. {iolist_to_binary(App),
  395. normalize_unlocks_expect(Locks)};
  396. normalize_unlocks({App, Vsn, Locks}) ->
  397. {iolist_to_binary(App), iolist_to_binary(Vsn),
  398. normalize_unlocks_expect(Locks)}.
  399. normalize_unlocks_expect({error, Reason}) ->
  400. {error, Reason};
  401. normalize_unlocks_expect([]) ->
  402. [];
  403. normalize_unlocks_expect([{App,Vsn} | Rest]) ->
  404. [{dep, App, Vsn},
  405. {lock, App, Vsn}
  406. | normalize_unlocks_expect(Rest)];
  407. normalize_unlocks_expect([App | Rest]) ->
  408. [{dep, App},
  409. {lock, App} | normalize_unlocks_expect(Rest)].
  410. top_a(Config) -> run(Config).
  411. top_b(Config) -> run(Config).
  412. top_c(Config) -> run(Config).
  413. top_d1(Config) -> run(Config).
  414. top_d2(Config) -> run(Config).
  415. top_e(Config) -> run(Config).
  416. pair_a(Config) -> run(Config).
  417. pair_b(Config) -> run(Config).
  418. pair_ab(Config) -> run(Config).
  419. pair_c(Config) -> run(Config).
  420. pair_all(Config) -> run(Config).
  421. triplet_a(Config) -> run(Config).
  422. triplet_b(Config) -> run(Config).
  423. triplet_c(Config) -> run(Config).
  424. tree_a(Config) -> run(Config).
  425. tree_b(Config) -> run(Config).
  426. tree_c(Config) -> run(Config).
  427. tree_c2(Config) -> run(Config).
  428. tree_ac(Config) -> run(Config).
  429. tree_all(Config) -> run(Config).
  430. promote(Config) -> run(Config).
  431. delete_d(Config) ->
  432. meck:new(rebar_log, [no_link, passthrough]),
  433. run(Config),
  434. Infos = [{Str, Args}
  435. || {_, {rebar_log, log, [info, Str, Args]}, _} <- meck:history(rebar_log)],
  436. meck:unload(rebar_log),
  437. ?assertNotEqual([],
  438. [1 || {"App ~ts is no longer needed and can be deleted.",
  439. [<<"D">>]} <- Infos]).
  440. stable_lock(Config) ->
  441. apply(?config(mock, Config), []),
  442. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  443. %% Install dependencies before re-mocking for an upgrade
  444. rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}),
  445. {App, Unlocks} = ?config(expected, Config),
  446. ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]),
  447. Expectation = case Unlocks of
  448. {error, Term} -> {error, Term};
  449. _ -> {ok, Unlocks}
  450. end,
  451. apply(?config(mock_update, Config), []),
  452. NewRebarConf = rebar_test_utils:create_config(?config(apps, Config),
  453. [{deps, ?config(next_top_deps, Config)}]),
  454. {ok, NewRebarConfig} = file:consult(NewRebarConf),
  455. rebar_test_utils:run_and_check(
  456. Config, NewRebarConfig, ["lock", App], Expectation
  457. ).
  458. fwd_lock(Config) ->
  459. apply(?config(mock, Config), []),
  460. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  461. %% Install dependencies before re-mocking for an upgrade
  462. rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}),
  463. {App, Unlocks} = ?config(expected, Config),
  464. ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]),
  465. Expectation = case Unlocks of
  466. {error, Term} -> {error, Term};
  467. _ -> {ok, Unlocks}
  468. end,
  469. rewrite_locks(Expectation, Config),
  470. apply(?config(mock_update, Config), []),
  471. NewRebarConf = rebar_test_utils:create_config(?config(apps, Config),
  472. [{deps, ?config(next_top_deps, Config)}]),
  473. {ok, NewRebarConfig} = file:consult(NewRebarConf),
  474. rebar_test_utils:run_and_check(
  475. Config, NewRebarConfig, ["lock", App], Expectation
  476. ).
  477. run(Config) ->
  478. apply(?config(mock, Config), []),
  479. {ok, RebarConfig} = file:consult(?config(rebarconfig, Config)),
  480. %% Install dependencies before re-mocking for an upgrade
  481. rebar_test_utils:run_and_check(Config, RebarConfig, ["lock"], {ok, []}),
  482. {App, Unlocks} = ?config(expected, Config),
  483. ct:pal("Upgrades: ~p -> ~p", [App, Unlocks]),
  484. Expectation = case Unlocks of
  485. {error, Term} -> {error, Term};
  486. _ -> {ok, Unlocks}
  487. end,
  488. apply(?config(mock_update, Config), []),
  489. NewRebarConf = rebar_test_utils:create_config(?config(apps, Config),
  490. [{deps, ?config(next_top_deps, Config)}]),
  491. {ok, NewRebarConfig} = file:consult(NewRebarConf),
  492. rebar_test_utils:run_and_check(
  493. Config, NewRebarConfig, ["upgrade", App], Expectation
  494. ).
  495. rewrite_locks({ok, Expectations}, Config) ->
  496. AppDir = ?config(apps, Config),
  497. LockFile = filename:join([AppDir, "rebar.lock"]),
  498. {ok, [Locks]} = file:consult(LockFile),
  499. ExpLocks = [{list_to_binary(Name), Vsn}
  500. || {lock, Name, Vsn} <- Expectations],
  501. NewLocks = lists:foldl(
  502. fun({App, {pkg, Name, _}, Lvl}, Acc) ->
  503. Vsn = list_to_binary(proplists:get_value(App,ExpLocks)),
  504. [{App, {pkg, Name, Vsn}, Lvl} | Acc]
  505. ; ({App, {git, URL, {ref, _}}, Lvl}, Acc) ->
  506. Vsn = proplists:get_value(App,ExpLocks),
  507. [{App, {git, URL, {ref, Vsn}}, Lvl} | Acc]
  508. end, [], Locks),
  509. ct:pal("rewriting locks from ~p to~n~p", [Locks, NewLocks]),
  510. file:write_file(LockFile, io_lib:format("~p.~n", [NewLocks])).