From bd9f4fd24cd9f011dc5105d6f24ea90dfdf4f1b3 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 11 Nov 2015 10:54:08 +0100 Subject: [PATCH 01/31] tests: add worktree test data --- tests/iterator/workdir.c | 3 ++- tests/refs/list.c | 4 ++-- tests/resources/testrepo-worktree/.gitted | Bin 0 -> 53 bytes tests/resources/testrepo-worktree/README | Bin 0 -> 10 bytes tests/resources/testrepo-worktree/branch_file.txt | Bin 0 -> 8 bytes tests/resources/testrepo-worktree/link_to_new.txt | 1 + tests/resources/testrepo-worktree/new.txt | Bin 0 -> 12 bytes .../.gitted/logs/refs/heads/testrepo-worktree | Bin 0 -> 156 bytes .../testrepo/.gitted/refs/heads/testrepo-worktree | Bin 0 -> 41 bytes .../.gitted/worktrees/testrepo-worktree/HEAD | Bin 0 -> 34 bytes .../.gitted/worktrees/testrepo-worktree/commondir | Bin 0 -> 6 bytes .../.gitted/worktrees/testrepo-worktree/gitdir | Bin 0 -> 35 bytes .../.gitted/worktrees/testrepo-worktree/index | Bin 0 -> 369 bytes .../.gitted/worktrees/testrepo-worktree/logs/HEAD | Bin 0 -> 214 bytes 14 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 tests/resources/testrepo-worktree/.gitted create mode 100644 tests/resources/testrepo-worktree/README create mode 100644 tests/resources/testrepo-worktree/branch_file.txt create mode 120000 tests/resources/testrepo-worktree/link_to_new.txt create mode 100644 tests/resources/testrepo-worktree/new.txt create mode 100644 tests/resources/testrepo/.gitted/logs/refs/heads/testrepo-worktree create mode 100644 tests/resources/testrepo/.gitted/refs/heads/testrepo-worktree create mode 100644 tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/HEAD create mode 100644 tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/commondir create mode 100644 tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/gitdir create mode 100644 tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/index create mode 100644 tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/logs/HEAD diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c index 28fcc0d23..165cca5ed 100644 --- a/tests/iterator/workdir.c +++ b/tests/iterator/workdir.c @@ -616,6 +616,7 @@ void test_iterator_workdir__filesystem2(void) "heads/packed-test", "heads/subtrees", "heads/test", + "heads/testrepo-worktree", "tags/e90810b", "tags/foo/bar", "tags/foo/foo/bar", @@ -628,7 +629,7 @@ void test_iterator_workdir__filesystem2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", NULL)); - expect_iterator_items(i, 13, expect_base, 13, expect_base); + expect_iterator_items(i, 14, expect_base, 14, expect_base); git_iterator_free(i); } diff --git a/tests/refs/list.c b/tests/refs/list.c index 374943b05..b4101ba7a 100644 --- a/tests/refs/list.c +++ b/tests/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 15); + cl_assert_equal_i((int)ref_list.count, 16); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 15); + cl_assert_equal_i((int)ref_list.count, 16); git_strarray_free(&ref_list); } diff --git a/tests/resources/testrepo-worktree/.gitted b/tests/resources/testrepo-worktree/.gitted new file mode 100644 index 0000000000000000000000000000000000000000..fe4556a92e68ed14b7ac05eb465bcee87ec11fba GIT binary patch literal 53 tcmYe#EJ?{MvQp5~(=SOaE-6Ya$k*3P&n(d|&o9aba#M@p;<_*)E&vVs6XXB@ literal 0 HcmV?d00001 diff --git a/tests/resources/testrepo-worktree/README b/tests/resources/testrepo-worktree/README new file mode 100644 index 0000000000000000000000000000000000000000..a8233120f6ad708f843d861ce2b7228ec4e3dec6 GIT binary patch literal 10 Rcmc~utyCzU`w|QI)3=4p=aCfmE2l$6nVkJOLh~Jb!kqpa?qy&{m=hA@3i7Wc zgNcFxm+Rfu{JXxLJ6aAq?BZi754yRb^;+YPi$SX%OHa&gs*EdjW2#W%@F@iV+^&ba literal 0 HcmV?d00001 diff --git a/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/logs/HEAD b/tests/resources/testrepo/.gitted/worktrees/testrepo-worktree/logs/HEAD new file mode 100644 index 0000000000000000000000000000000000000000..3bede502ee05ea8314e9e0244b9f43d1f4c6eda8 GIT binary patch literal 214 zcma*g%?$z}5J2IxRxx*@8Tk`7##n-h6(9~SAuI$&ZC^{++dF*8L+7G@_#nL_@1)kw zYGH#oMQaR6h1LZ@F#@iIH_ttV40ooq%)UPv&TU*<9*0yea8ybttCW$jLxH#nGlg8= cJVB|Sw7ekpTGsEl0@L75t?s|I=YP@m2NSJ8JOBUy literal 0 HcmV?d00001 From e5a620de803ddcb0c05e43c6a8d7d6d4baeb808c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 9 Nov 2015 17:00:41 +0100 Subject: [PATCH 02/31] tests: add submodule worktree test data Create worktrees for submodule repositories. The worktrees are created for the parent repository (e.g. the one containing submodules) and for the contained child repository. --- tests/resources/submodules-worktree-child/.gitted | Bin 0 -> 72 bytes tests/resources/submodules-worktree-child/README | Bin 0 -> 10 bytes .../submodules-worktree-child/branch_file.txt | Bin 0 -> 8 bytes tests/resources/submodules-worktree-child/new.txt | Bin 0 -> 12 bytes .../submodules-worktree-parent/.gitmodules | Bin 0 -> 98 bytes .../resources/submodules-worktree-parent/.gitted | Bin 0 -> 64 bytes .../resources/submodules-worktree-parent/deleted | Bin 0 -> 3 bytes .../resources/submodules-worktree-parent/modified | Bin 0 -> 3 bytes .../submodules-worktree-parent/unmodified | Bin 0 -> 3 bytes .../logs/refs/heads/submodules-worktree-parent | Bin 0 -> 156 bytes .../.gitted/refs/heads/submodules-worktree-parent | Bin 0 -> 41 bytes .../worktrees/submodules-worktree-parent/HEAD | Bin 0 -> 43 bytes .../submodules-worktree-parent/ORIG_HEAD | Bin 0 -> 41 bytes .../submodules-worktree-parent/commondir | Bin 0 -> 6 bytes .../worktrees/submodules-worktree-parent/gitdir | Bin 0 -> 44 bytes .../worktrees/submodules-worktree-parent/index | Bin 0 -> 441 bytes .../logs/refs/heads/submodules-worktree-child | Bin 0 -> 156 bytes .../.gitted/refs/heads/submodules-worktree-child | Bin 0 -> 41 bytes .../worktrees/submodules-worktree-child/HEAD | Bin 0 -> 42 bytes .../worktrees/submodules-worktree-child/ORIG_HEAD | Bin 0 -> 41 bytes .../worktrees/submodules-worktree-child/commondir | Bin 0 -> 6 bytes .../worktrees/submodules-worktree-child/gitdir | Bin 0 -> 46 bytes .../worktrees/submodules-worktree-child/index | Bin 0 -> 289 bytes 23 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/resources/submodules-worktree-child/.gitted create mode 100644 tests/resources/submodules-worktree-child/README create mode 100644 tests/resources/submodules-worktree-child/branch_file.txt create mode 100644 tests/resources/submodules-worktree-child/new.txt create mode 100644 tests/resources/submodules-worktree-parent/.gitmodules create mode 100644 tests/resources/submodules-worktree-parent/.gitted create mode 100644 tests/resources/submodules-worktree-parent/deleted create mode 100644 tests/resources/submodules-worktree-parent/modified create mode 100644 tests/resources/submodules-worktree-parent/unmodified create mode 100644 tests/resources/submodules/.gitted/logs/refs/heads/submodules-worktree-parent create mode 100644 tests/resources/submodules/.gitted/refs/heads/submodules-worktree-parent create mode 100644 tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/HEAD create mode 100644 tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/ORIG_HEAD create mode 100644 tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/commondir create mode 100644 tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/gitdir create mode 100644 tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/index create mode 100644 tests/resources/submodules/testrepo/.gitted/logs/refs/heads/submodules-worktree-child create mode 100644 tests/resources/submodules/testrepo/.gitted/refs/heads/submodules-worktree-child create mode 100644 tests/resources/submodules/testrepo/.gitted/worktrees/submodules-worktree-child/HEAD create mode 100644 tests/resources/submodules/testrepo/.gitted/worktrees/submodules-worktree-child/ORIG_HEAD create mode 100644 tests/resources/submodules/testrepo/.gitted/worktrees/submodules-worktree-child/commondir create mode 100644 tests/resources/submodules/testrepo/.gitted/worktrees/submodules-worktree-child/gitdir create mode 100644 tests/resources/submodules/testrepo/.gitted/worktrees/submodules-worktree-child/index diff --git a/tests/resources/submodules-worktree-child/.gitted b/tests/resources/submodules-worktree-child/.gitted new file mode 100644 index 0000000000000000000000000000000000000000..03286f522907adee181866b2b81be0fcc4ab6979 GIT binary patch literal 72 zcmYe#EJ?{MvQp5~(=RSf%FRzH%}FiRFG(#fDM~HK*VjwWEYUB|FUkgTf#L`yx-dcA KD+zEXh!?Re%X`ah4Y40Qvf%#i>Qb`bA0l k#YM^bIhjf6nI%U0AXPxY)Z+ZoqU6+KFdt-)9#EDG0J8cc9RL6T literal 0 HcmV?d00001 diff --git a/tests/resources/submodules-worktree-parent/.gitted b/tests/resources/submodules-worktree-parent/.gitted new file mode 100644 index 0000000000000000000000000000000000000000..87bd9ae29417dc439261769d615e7c45db0ddf0e GIT binary patch literal 64 zcmYe#EJ?{MvQp5~(=RSf%FRzH%}FiR*GtbV(J#+0$}TBN1#%HGx-dcAg2bZKyb>+| D1@{;= literal 0 HcmV?d00001 diff --git a/tests/resources/submodules-worktree-parent/deleted b/tests/resources/submodules-worktree-parent/deleted new file mode 100644 index 0000000000000000000000000000000000000000..092bfb9bdf74dd8cfd22e812151281ee9aa6f01a GIT binary patch literal 3 Kcmb=-=K=r%Rsiz= literal 0 HcmV?d00001 diff --git a/tests/resources/submodules-worktree-parent/modified b/tests/resources/submodules-worktree-parent/modified new file mode 100644 index 0000000000000000000000000000000000000000..092bfb9bdf74dd8cfd22e812151281ee9aa6f01a GIT binary patch literal 3 Kcmb=-=K=r%Rsiz= literal 0 HcmV?d00001 diff --git a/tests/resources/submodules-worktree-parent/unmodified b/tests/resources/submodules-worktree-parent/unmodified new file mode 100644 index 0000000000000000000000000000000000000000..092bfb9bdf74dd8cfd22e812151281ee9aa6f01a GIT binary patch literal 3 Kcmb=-=K=r%Rsiz= literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/.gitted/logs/refs/heads/submodules-worktree-parent b/tests/resources/submodules/.gitted/logs/refs/heads/submodules-worktree-parent new file mode 100644 index 0000000000000000000000000000000000000000..65e98853528b8ebec8b75c1e91a28d34eaa32150 GIT binary patch literal 156 zcma*UK?=e!5Czb+o?`Zb{KRQGDI$srE(K38NmCk1Ye{o|yn^om{~_OscVZAu*1$5W zvnrMw36dbjr#M6>4d|`4b1paTrB3q_YWc62XE PYwBqz-Ik6;p);Z&&&6_}+X2|Fx~P-FVF^T_(jQ3>Y& literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/HEAD b/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/HEAD new file mode 100644 index 0000000000000000000000000000000000000000..a07134b85d6c4da0beff679a9ad6fa1680941235 GIT binary patch literal 43 ycmXR)O|w!cN=+-)&qz&7Db_D8P0GzrDa}bO)-BI3$}TBNP1P+(EK1EQ;Q|0;=nzi; literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/ORIG_HEAD b/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/ORIG_HEAD new file mode 100644 index 0000000000000000000000000000000000000000..32b935853701f9b5a0ff56cde617a37403860cc8 GIT binary patch literal 41 ucmV~$!4Uu;2m`Rc(?CTm;{b>Ik6;p);Z&&&6_}+X2|Fx~P-FVF^T_(jQ3>Y& literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/commondir b/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/commondir new file mode 100644 index 0000000000000000000000000000000000000000..aab0408ceca61b98818f783c76b074312fd5cd80 GIT binary patch literal 6 NcmdPX)7R7E0ssT70P+9; literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/gitdir b/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/gitdir new file mode 100644 index 0000000000000000000000000000000000000000..eaaf13b9560a93c6923755353b00b9ca611834d3 GIT binary patch literal 44 scmdPX)7OKc;?ktt{FKt1)MDN8{G#lVqSRE~g2bZKyb^uA^vn`204c~0asU7T literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/index b/tests/resources/submodules/.gitted/worktrees/submodules-worktree-parent/index new file mode 100644 index 0000000000000000000000000000000000000000..5b68f18a4c0d3a4d650a5186d49be9a1ecffaec7 GIT binary patch literal 441 zcmZ?q402{*U|<4b)-Z-N@-4NG0-p&fSCiMq2{rnna9kj{d@NPlDj>Bm0k#m z3N^l)wd{iw1A9tpPHIVN3T|`Qi86-+=%&oHOprk^cfe?{dv-DJVPJRwv<2or0fuGq zZ$Hm^|AguC?mU(IzW(t~LdpXeI7(8BONvqp@^P56gOh_O_i&ZwAzTEs76?LuTwQ^b zB!j7f0oOMEn{Ao1m%Mz|{fhm3U!}c~-H#6a_kY*%7oYb#5Wb9mVnJ7ux!ndg02ouA A1ONa4 literal 0 HcmV?d00001 diff --git a/tests/resources/submodules/testrepo/.gitted/logs/refs/heads/submodules-worktree-child b/tests/resources/submodules/testrepo/.gitted/logs/refs/heads/submodules-worktree-child new file mode 100644 index 0000000000000000000000000000000000000000..dd4650ff8b2465e234a5311d83ef4484fab5974b GIT binary patch literal 156 zcma*cOA5j;5CG7%o?`ZbOw&oDMWmvFOTiONIx{qsen@kF^a#G~L;MeL<4witGpi|~ zlRCym9f^^U{f<0^;1S+ja_UF;6ixHMSqfa%ty{V%$!ucl8O@C<{d;ck7v1P+rj%UmdoVq&k>sM;1lNb-*7JjdtPce z)SQqYSCIQ88H^PSxTg5<=lT9}jEu|MeD2L{k+1v@8 Date: Tue, 10 Nov 2015 15:53:09 +0100 Subject: [PATCH 03/31] tests: add merge-conflict branch for testrepo Add a new branch that causes a merge conflict to `testrepo` so that we are able to test merging in worktrees. --- tests/iterator/workdir.c | 3 ++- tests/refs/list.c | 4 ++-- .../9b/1719f5cf069568785080a0bbabbe7c377e22ae | Bin 0 -> 24 bytes .../a3/8d028f71eaa590febb7d716b1ca32350cf70da | Bin 0 -> 155 bytes .../ad/edac69457183c8265c8a9614c1c4fed31d1ff3 | Bin 0 -> 119 bytes .../testrepo/.gitted/refs/heads/merge-conflict | Bin 0 -> 41 bytes tests/revwalk/basic.c | 2 +- 7 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 tests/resources/testrepo/.gitted/objects/9b/1719f5cf069568785080a0bbabbe7c377e22ae create mode 100644 tests/resources/testrepo/.gitted/objects/a3/8d028f71eaa590febb7d716b1ca32350cf70da create mode 100644 tests/resources/testrepo/.gitted/objects/ad/edac69457183c8265c8a9614c1c4fed31d1ff3 create mode 100644 tests/resources/testrepo/.gitted/refs/heads/merge-conflict diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c index 165cca5ed..f33fd98f1 100644 --- a/tests/iterator/workdir.c +++ b/tests/iterator/workdir.c @@ -613,6 +613,7 @@ void test_iterator_workdir__filesystem2(void) "heads/ident", "heads/long-file-name", "heads/master", + "heads/merge-conflict", "heads/packed-test", "heads/subtrees", "heads/test", @@ -629,7 +630,7 @@ void test_iterator_workdir__filesystem2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", NULL)); - expect_iterator_items(i, 14, expect_base, 14, expect_base); + expect_iterator_items(i, 15, expect_base, 15, expect_base); git_iterator_free(i); } diff --git a/tests/refs/list.c b/tests/refs/list.c index b4101ba7a..f7ca3f707 100644 --- a/tests/refs/list.c +++ b/tests/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 16); + cl_assert_equal_i((int)ref_list.count, 17); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 16); + cl_assert_equal_i((int)ref_list.count, 17); git_strarray_free(&ref_list); } diff --git a/tests/resources/testrepo/.gitted/objects/9b/1719f5cf069568785080a0bbabbe7c377e22ae b/tests/resources/testrepo/.gitted/objects/9b/1719f5cf069568785080a0bbabbe7c377e22ae new file mode 100644 index 0000000000000000000000000000000000000000..13e3f581a83ab9bb85f981c1ced8c88be9a191f4 GIT binary patch literal 24 gcmbW%hJpR z$UEz*j*AwSG*QejqKqL$8F@-T0zy>_LV;Rw*tqLzZdQy5S+V9R&S55uIEyH944I2( zs<|ejf!}s}c6VXxP44b$+SVKQV(z%T&fEQUXv@g~QHGQzAKc!9_k;X?rvJ+?UF)NX J4IlRaMI(p8ODF&U literal 0 HcmV?d00001 diff --git a/tests/resources/testrepo/.gitted/objects/ad/edac69457183c8265c8a9614c1c4fed31d1ff3 b/tests/resources/testrepo/.gitted/objects/ad/edac69457183c8265c8a9614c1c4fed31d1ff3 new file mode 100644 index 0000000000000000000000000000000000000000..c054fc0c4845646a59fa291938a981d056e7e3f3 GIT binary patch literal 119 zcmV--0Eqv10V^p=O;s>7G-5C`FfcPQQ3!H%bn$g%SfOmF@NI2De~WFK%%kl}eMcVO zI|fyeRFs&PoDrXvnUktlQc=P%TU_$%dA6w;6#)$kcCX%7V_v7U4yrUSwH&NuxoF$L Z`!ANuE literal 0 HcmV?d00001 diff --git a/tests/resources/testrepo/.gitted/refs/heads/merge-conflict b/tests/resources/testrepo/.gitted/refs/heads/merge-conflict new file mode 100644 index 0000000000000000000000000000000000000000..3e24a24e06317336f3d0cff372a6ffa1443bfd3e GIT binary patch literal 41 vcmV~$Nf7`r2n4Wy)o=`j%2_V|5}H7xNn)J`utz(Y-p)6 16 */ - cl_assert_equal_i(18, i); + cl_assert_equal_i(19, i); } void test_revwalk_basic__push_head(void) From 807d57e7df1b7f71abc445523b88262b63b1b4e3 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 11 Nov 2015 10:54:41 +0100 Subject: [PATCH 04/31] tests: implement worktree helpers --- tests/worktree/worktree_helpers.c | 30 ++++++++++++++++++++++++++++++ tests/worktree/worktree_helpers.h | 11 +++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/worktree/worktree_helpers.c create mode 100644 tests/worktree/worktree_helpers.h diff --git a/tests/worktree/worktree_helpers.c b/tests/worktree/worktree_helpers.c new file mode 100644 index 000000000..6d4cdbaeb --- /dev/null +++ b/tests/worktree/worktree_helpers.c @@ -0,0 +1,30 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +void cleanup_fixture_worktree(worktree_fixture *fixture) +{ + if (!fixture) + return; + + if (fixture->repo) { + git_repository_free(fixture->repo); + fixture->repo = NULL; + } + if (fixture->worktree) { + git_repository_free(fixture->worktree); + fixture->worktree = NULL; + } + + if (fixture->reponame) + cl_fixture_cleanup(fixture->reponame); + if (fixture->worktreename) + cl_fixture_cleanup(fixture->worktreename); +} + +void setup_fixture_worktree(worktree_fixture *fixture) +{ + if (fixture->reponame) + fixture->repo = cl_git_sandbox_init(fixture->reponame); + if (fixture->worktreename) + fixture->worktree = cl_git_sandbox_init(fixture->worktreename); +} diff --git a/tests/worktree/worktree_helpers.h b/tests/worktree/worktree_helpers.h new file mode 100644 index 000000000..35ea9ed4c --- /dev/null +++ b/tests/worktree/worktree_helpers.h @@ -0,0 +1,11 @@ +typedef struct { + const char *reponame; + const char *worktreename; + git_repository *repo; + git_repository *worktree; +} worktree_fixture; + +#define WORKTREE_FIXTURE_INIT(repo, worktree) { (repo), (worktree), NULL, NULL } + +void cleanup_fixture_worktree(worktree_fixture *fixture); +void setup_fixture_worktree(worktree_fixture *fixture); From c09fd54e2ecb5d43c281ee0fccef6d95787ec510 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 16 Sep 2015 12:10:11 +0200 Subject: [PATCH 05/31] repository: introduce commondir variable The commondir variable stores the path to the common directory. The common directory is used to store objects and references shared across multiple repositories. A current use case is the newly introduced `git worktree` feature, which sets up a separate working copy, where the backing git object store and references are pointed to by the common directory. --- include/git2/repository.h | 11 ++++++ src/repository.c | 81 +++++++++++++++++++++++++++++++-------- src/repository.h | 1 + tests/worktree/open.c | 60 +++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 tests/worktree/open.c diff --git a/include/git2/repository.h b/include/git2/repository.h index 3d70d1b89..f60544553 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -392,6 +392,17 @@ GIT_EXTERN(const char *) git_repository_path(git_repository *repo); */ GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); +/** + * Get the path of the shared common directory for this repository + * + * If the repository is bare is not a worktree, the git directory + * path is returned. + * + * @param repo A repository object + * @return the path to the common dir + */ +GIT_EXTERN(const char *) git_repository_commondir(git_repository *repo); + /** * Set the path to the working directory for this repository * diff --git a/src/repository.c b/src/repository.c index 2185632bf..71b6ec44f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -38,6 +38,8 @@ GIT__USE_STRMAP static int check_repositoryformatversion(git_config *config); +#define GIT_COMMONDIR_FILE "commondir" + #define GIT_FILE_CONTENT_PREFIX "gitdir:" #define GIT_BRANCH_MASTER "master" @@ -143,6 +145,7 @@ void git_repository_free(git_repository *repo) git__free(repo->path_gitlink); git__free(repo->path_repository); + git__free(repo->commondir); git__free(repo->workdir); git__free(repo->namespace); git__free(repo->ident_name); @@ -157,17 +160,41 @@ void git_repository_free(git_repository *repo) * * Open a repository object from its path */ -static bool valid_repository_path(git_buf *repository_path) +static bool valid_repository_path(git_buf *repository_path, git_buf *common_path) { - /* Check OBJECTS_DIR first, since it will generate the longest path name */ - if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) - return false; + /* Check if we have a separate commondir (e.g. we have a + * worktree) */ + if (git_path_contains_file(repository_path, GIT_COMMONDIR_FILE)) { + git_buf common_link = GIT_BUF_INIT; + git_buf_joinpath(&common_link, repository_path->ptr, GIT_COMMONDIR_FILE); + + git_futils_readbuffer(&common_link, common_link.ptr); + git_buf_rtrim(&common_link); + + if (git_path_is_relative(common_link.ptr)) { + git_buf_joinpath(common_path, repository_path->ptr, common_link.ptr); + } else { + git_buf_swap(common_path, &common_link); + } + + git_buf_free(&common_link); + } + else { + git_buf_set(common_path, repository_path->ptr, repository_path->size); + } + + /* Make sure the commondir path always has a trailing * slash */ + if (git_buf_rfind(common_path, '/') != (ssize_t)common_path->size - 1) + git_buf_putc(common_path, '/'); /* Ensure HEAD file exists */ if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) return false; - if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) + /* Check files in common dir */ + if (git_path_contains_dir(common_path, GIT_OBJECTS_DIR) == false) + return false; + if (git_path_contains_dir(common_path, GIT_REFS_DIR) == false) return false; return true; @@ -356,6 +383,7 @@ static int find_repo( git_buf *repo_path, git_buf *parent_path, git_buf *link_path, + git_buf *common_path, const char *start_path, uint32_t flags, const char *ceiling_dirs) @@ -363,6 +391,7 @@ static int find_repo( int error; git_buf path = GIT_BUF_INIT; git_buf repo_link = GIT_BUF_INIT; + git_buf common_link = GIT_BUF_INIT; struct stat st; dev_t initial_device = 0; int min_iterations; @@ -409,9 +438,13 @@ static int find_repo( break; if (S_ISDIR(st.st_mode)) { - if (valid_repository_path(&path)) { + if (valid_repository_path(&path, &common_link)) { git_path_to_dir(&path); git_buf_set(repo_path, path.ptr, path.size); + + if (common_path) + git_buf_swap(&common_link, common_path); + break; } } @@ -419,11 +452,13 @@ static int find_repo( error = read_gitfile(&repo_link, path.ptr); if (error < 0) break; - if (valid_repository_path(&repo_link)) { + if (valid_repository_path(&repo_link, &common_link)) { git_buf_swap(repo_path, &repo_link); if (link_path) error = git_buf_put(link_path, path.ptr, path.size); + if (common_path) + git_buf_swap(&common_link, common_path); } break; } @@ -470,6 +505,7 @@ static int find_repo( git_buf_free(&path); git_buf_free(&repo_link); + git_buf_free(&common_link); return error; } @@ -478,14 +514,15 @@ int git_repository_open_bare( const char *bare_path) { int error; - git_buf path = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT, common_path = GIT_BUF_INIT; git_repository *repo = NULL; if ((error = git_path_prettify_dir(&path, bare_path, NULL)) < 0) return error; - if (!valid_repository_path(&path)) { + if (!valid_repository_path(&path, &common_path)) { git_buf_free(&path); + git_buf_free(&common_path); giterr_set(GITERR_REPOSITORY, "path is not a repository: %s", bare_path); return GIT_ENOTFOUND; } @@ -495,6 +532,8 @@ int git_repository_open_bare( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); + repo->commondir = git_buf_detach(&common_path); + GITERR_CHECK_ALLOC(repo->commondir); /* of course we're bare! */ repo->is_bare = 1; @@ -681,7 +720,7 @@ int git_repository_open_ext( { int error; git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT, - link_path = GIT_BUF_INIT; + link_path = GIT_BUF_INIT, common_path = GIT_BUF_INIT; git_repository *repo; git_config *config = NULL; @@ -692,7 +731,7 @@ int git_repository_open_ext( *repo_ptr = NULL; error = find_repo( - &path, &parent, &link_path, start_path, flags, ceiling_dirs); + &path, &parent, &link_path, &common_path, start_path, flags, ceiling_dirs); if (error < 0 || !repo_ptr) return error; @@ -707,6 +746,10 @@ int git_repository_open_ext( repo->path_gitlink = git_buf_detach(&link_path); GITERR_CHECK_ALLOC(repo->path_gitlink); } + if (common_path.size) { + repo->commondir = git_buf_detach(&common_path); + GITERR_CHECK_ALLOC(repo->commondir); + } /* * We'd like to have the config, but git doesn't particularly @@ -773,7 +816,7 @@ int git_repository_discover( git_buf_sanitize(out); - return find_repo(out, NULL, NULL, start_path, flags, ceiling_dirs); + return find_repo(out, NULL, NULL, NULL, start_path, flags, ceiling_dirs); } static int load_config( @@ -928,7 +971,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) git_buf odb_path = GIT_BUF_INIT; git_odb *odb; - if ((error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR)) < 0) + if ((error = git_buf_joinpath(&odb_path, repo->commondir, GIT_OBJECTS_DIR)) < 0) return error; error = git_odb_open(&odb, odb_path.ptr); @@ -1856,7 +1899,8 @@ int git_repository_init_ext( git_repository_init_options *opts) { int error; - git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT; + git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT, + common_path = GIT_BUF_INIT; const char *wd; assert(out && given_repo && opts); @@ -1868,7 +1912,7 @@ int git_repository_init_ext( goto cleanup; wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_buf_cstr(&wd_path); - if (valid_repository_path(&repo_path)) { + if (valid_repository_path(&repo_path, &common_path)) { if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) { giterr_set(GITERR_REPOSITORY, @@ -1901,6 +1945,7 @@ int git_repository_init_ext( error = repo_init_create_origin(*out, opts->origin_url); cleanup: + git_buf_free(&common_path); git_buf_free(&repo_path); git_buf_free(&wd_path); @@ -2023,6 +2068,12 @@ const char *git_repository_workdir(git_repository *repo) return repo->workdir; } +const char *git_repository_commondir(git_repository *repo) +{ + assert(repo); + return repo->commondir; +} + int git_repository_set_workdir( git_repository *repo, const char *workdir, int update_gitlink) { diff --git a/src/repository.h b/src/repository.h index 9d276f376..5dc67218f 100644 --- a/src/repository.h +++ b/src/repository.h @@ -128,6 +128,7 @@ struct git_repository { char *path_repository; char *path_gitlink; + char *commondir; char *workdir; char *namespace; diff --git a/tests/worktree/open.c b/tests/worktree/open.c new file mode 100644 index 000000000..772f7601f --- /dev/null +++ b/tests/worktree/open.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#define WORKTREE_PARENT "submodules-worktree-parent" +#define WORKTREE_CHILD "submodules-worktree-child" + +void test_worktree_open__repository(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree"); + setup_fixture_worktree(&fixture); + + cl_assert(git_repository_path(fixture.worktree) != NULL); + cl_assert(git_repository_workdir(fixture.worktree) != NULL); + + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__repository_with_nonexistent_parent(void) +{ + git_repository *repo; + + cl_fixture_sandbox("testrepo-worktree"); + cl_git_pass(p_chdir("testrepo-worktree")); + cl_git_pass(cl_rename(".gitted", ".git")); + cl_git_pass(p_chdir("..")); + + cl_git_fail(git_repository_open(&repo, "testrepo-worktree")); + + cl_fixture_cleanup("testrepo-worktree"); +} + +void test_worktree_open__submodule_worktree_parent(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); + setup_fixture_worktree(&fixture); + + cl_assert(git_repository_path(fixture.worktree) != NULL); + cl_assert(git_repository_workdir(fixture.worktree) != NULL); + + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__submodule_worktree_child(void) +{ + worktree_fixture parent_fixture = + WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); + worktree_fixture child_fixture = + WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); + + setup_fixture_worktree(&parent_fixture); + cl_git_pass(p_rename( + "submodules/testrepo/.gitted", + "submodules/testrepo/.git")); + setup_fixture_worktree(&child_fixture); + + cleanup_fixture_worktree(&child_fixture); + cleanup_fixture_worktree(&parent_fixture); +} From cb3269c970db6766f8a953fee438b56119fd9847 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Nov 2016 13:46:59 +0100 Subject: [PATCH 06/31] repository: add function to retrieve paths for repo items --- include/git2/repository.h | 36 +++++++++++++++++++++++ src/repository.c | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index f60544553..74ce4c795 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -370,6 +370,42 @@ GIT_EXTERN(int) git_repository_head_unborn(git_repository *repo); */ GIT_EXTERN(int) git_repository_is_empty(git_repository *repo); +/** + * List of items which belong to the git repository layout + */ +typedef enum { + GIT_REPOSITORY_ITEM_GITDIR, + GIT_REPOSITORY_ITEM_WORKDIR, + GIT_REPOSITORY_ITEM_COMMONDIR, + GIT_REPOSITORY_ITEM_INDEX, + GIT_REPOSITORY_ITEM_OBJECTS, + GIT_REPOSITORY_ITEM_REFS, + GIT_REPOSITORY_ITEM_PACKED_REFS, + GIT_REPOSITORY_ITEM_REMOTES, + GIT_REPOSITORY_ITEM_CONFIG, + GIT_REPOSITORY_ITEM_INFO, + GIT_REPOSITORY_ITEM_HOOKS, + GIT_REPOSITORY_ITEM_LOGS, + GIT_REPOSITORY_ITEM_MODULES, + GIT_REPOSITORY_ITEM_WORKTREES +} git_repository_item_t; + +/** + * Get the location of a specific repository file or directory + * + * This function will retrieve the path of a specific repository + * item. It will thereby honor things like the repository's + * common directory, gitdir, etc. In case a file path cannot + * exist for a given item (e.g. the working directory of a bare + * repository), an error is returned. + * + * @param out Buffer to store the path at + * @param repo Repository to get path for + * @param item The repository item for which to retrieve the path + * @return 0 on success, otherwise a negative value + */ +GIT_EXTERN(int) git_repository_item_path(git_buf *out, git_repository *repo, git_repository_item_t item); + /** * Get the path of this repository * diff --git a/src/repository.c b/src/repository.c index 71b6ec44f..14ad7a599 100644 --- a/src/repository.c +++ b/src/repository.c @@ -36,6 +36,27 @@ GIT__USE_STRMAP # include "win32/w32_util.h" #endif +static const struct { + git_repository_item_t parent; + const char *name; + bool directory; +} items[] = { + { GIT_REPOSITORY_ITEM_GITDIR, NULL, true }, + { GIT_REPOSITORY_ITEM_WORKDIR, NULL, true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, NULL, true }, + { GIT_REPOSITORY_ITEM_GITDIR, "index", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "objects", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "refs", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "packed-refs", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "remotes", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "config", false }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "info", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "hooks", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "logs", true }, + { GIT_REPOSITORY_ITEM_GITDIR, "modules", true }, + { GIT_REPOSITORY_ITEM_COMMONDIR, "worktrees", true } +}; + static int check_repositoryformatversion(git_config *config); #define GIT_COMMONDIR_FILE "commondir" @@ -2052,6 +2073,46 @@ int git_repository_is_empty(git_repository *repo) return is_empty; } +int git_repository_item_path(git_buf *out, git_repository *repo, git_repository_item_t item) +{ + const char *parent; + + switch (items[item].parent) { + case GIT_REPOSITORY_ITEM_GITDIR: + parent = git_repository_path(repo); + break; + case GIT_REPOSITORY_ITEM_WORKDIR: + parent = git_repository_workdir(repo); + break; + case GIT_REPOSITORY_ITEM_COMMONDIR: + parent = git_repository_commondir(repo); + break; + default: + giterr_set(GITERR_INVALID, "Invalid item directory"); + return -1; + } + + if (parent == NULL) { + giterr_set(GITERR_INVALID, "Path cannot exist in repository"); + return -1; + } + + if (git_buf_sets(out, parent) < 0) + return -1; + + if (items[item].name) { + if (git_buf_joinpath(out, parent, items[item].name) < 0) + return -1; + } + + if (items[item].directory) { + if (git_path_to_dir(out) < 0) + return -1; + } + + return 0; +} + const char *git_repository_path(git_repository *repo) { assert(repo); From c5f3da9692f8de15550fed47e377c586f99f7c5a Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 11 Nov 2016 14:36:43 +0100 Subject: [PATCH 07/31] repository: use `git_repository_item_path` The recent introduction of the commondir variable of a repository requires callers to distinguish whether their files are part of the dot-git directory or the common directory shared between multpile worktrees. In order to take the burden from callers and unify knowledge on which files reside where, the `git_repository_item_path` function has been introduced which encapsulate this knowledge. Modify most existing callers of `git_repository_path` to use `git_repository_item_path` instead, thus making them implicitly aware of the common directory. --- src/attr.c | 36 +++++++++++++++++++++++------------- src/attr_file.h | 2 +- src/blob.c | 4 ++-- src/clone.c | 5 ++--- src/ignore.c | 8 +++++++- src/ignore.h | 2 +- src/repository.c | 3 ++- src/submodule.c | 12 ++++++++---- src/transports/local.c | 3 ++- 9 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/attr.c b/src/attr.c index d43a15f50..93dea123f 100644 --- a/src/attr.c +++ b/src/attr.c @@ -292,7 +292,7 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session) int error = 0; const char *workdir = git_repository_workdir(repo); git_index *idx = NULL; - git_buf sys = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT; if (attr_session && attr_session->init_setup) return 0; @@ -304,40 +304,45 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session) * definitions will be available for later file parsing */ - error = system_attr_file(&sys, attr_session); + error = system_attr_file(&path, attr_session); if (error == 0) error = preload_attr_file( - repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr); + repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, path.ptr); if (error != GIT_ENOTFOUND) - return error; - - git_buf_free(&sys); + goto out; if ((error = preload_attr_file( repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0) - return error; + goto out; + + if ((error = git_repository_item_path(&path, + repo, GIT_REPOSITORY_ITEM_INFO)) < 0) + goto out; if ((error = preload_attr_file( repo, attr_session, GIT_ATTR_FILE__FROM_FILE, - git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0) - return error; + path.ptr, GIT_ATTR_FILE_INREPO)) < 0) + goto out; if (workdir != NULL && (error = preload_attr_file( repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0) - return error; + goto out; if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || (error = preload_attr_file( repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0) - return error; + goto out; if (attr_session) attr_session->init_setup = 1; +out: + git_buf_free(&path); + return error; } @@ -472,7 +477,7 @@ static int collect_attr_files( git_vector *files) { int error = 0; - git_buf dir = GIT_BUF_INIT; + git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT; const char *workdir = git_repository_workdir(repo); attr_walk_up_info info = { NULL }; @@ -494,9 +499,13 @@ static int collect_attr_files( * - $GIT_PREFIX/etc/gitattributes */ + error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO); + if (error < 0) + goto cleanup; + error = push_attr_file( repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, - git_repository_path(repo), GIT_ATTR_FILE_INREPO); + attrfile.ptr, GIT_ATTR_FILE_INREPO); if (error < 0) goto cleanup; @@ -538,6 +547,7 @@ static int collect_attr_files( cleanup: if (error < 0) release_attr_files(files); + git_buf_free(&attrfile); git_buf_free(&dir); return error; diff --git a/src/attr_file.h b/src/attr_file.h index 388ecf4c0..a9af2403a 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -15,7 +15,7 @@ #include "fileops.h" #define GIT_ATTR_FILE ".gitattributes" -#define GIT_ATTR_FILE_INREPO "info/attributes" +#define GIT_ATTR_FILE_INREPO "attributes" #define GIT_ATTR_FILE_SYSTEM "gitattributes" #define GIT_ATTR_FILE_XDG "attributes" diff --git a/src/blob.c b/src/blob.c index cd5df3537..19d3039fb 100644 --- a/src/blob.c +++ b/src/blob.c @@ -326,8 +326,8 @@ int git_blob_create_fromstream(git_writestream **out, git_repository *repo, cons stream->parent.close = blob_writestream_close; stream->parent.free = blob_writestream_free; - if ((error = git_buf_joinpath(&path, - git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0) + if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 + || (error = git_buf_joinpath(&path, path.ptr, "streamed")) < 0) goto cleanup; if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, diff --git a/src/clone.c b/src/clone.c index 0d4756e28..16ddface2 100644 --- a/src/clone.c +++ b/src/clone.c @@ -513,9 +513,8 @@ static int clone_local_into(git_repository *repo, git_remote *remote, const git_ return error; } - git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR); - git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR); - if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) { + if (git_repository_item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 + || git_repository_item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { error = -1; goto cleanup; } diff --git a/src/ignore.c b/src/ignore.c index cc9e08e35..c324d4dd4 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -277,6 +277,7 @@ int git_ignore__for_path( { int error = 0; const char *workdir = git_repository_workdir(repo); + git_buf infopath = GIT_BUF_INIT; assert(repo && ignores && path); @@ -322,10 +323,14 @@ int git_ignore__for_path( goto cleanup; } + if ((error = git_repository_item_path(&infopath, + repo, GIT_REPOSITORY_ITEM_INFO)) < 0) + goto cleanup; + /* load .git/info/exclude */ error = push_ignore_file( ignores, &ignores->ign_global, - git_repository_path(repo), GIT_IGNORE_FILE_INREPO); + infopath.ptr, GIT_IGNORE_FILE_INREPO); if (error < 0) goto cleanup; @@ -336,6 +341,7 @@ int git_ignore__for_path( git_repository_attr_cache(repo)->cfg_excl_file); cleanup: + git_buf_free(&infopath); if (error < 0) git_ignore__free(ignores); diff --git a/src/ignore.h b/src/ignore.h index d40bd60f9..876c8e0ea 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -12,7 +12,7 @@ #include "attr_file.h" #define GIT_IGNORE_FILE ".gitignore" -#define GIT_IGNORE_FILE_INREPO "info/exclude" +#define GIT_IGNORE_FILE_INREPO "exclude" #define GIT_IGNORE_FILE_XDG "ignore" /* The git_ignores structure maintains three sets of ignores: diff --git a/src/repository.c b/src/repository.c index 14ad7a599..ed7038543 100644 --- a/src/repository.c +++ b/src/repository.c @@ -992,7 +992,8 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) git_buf odb_path = GIT_BUF_INIT; git_odb *odb; - if ((error = git_buf_joinpath(&odb_path, repo->commondir, GIT_OBJECTS_DIR)) < 0) + if ((error = git_repository_item_path(&odb_path, repo, + GIT_REPOSITORY_ITEM_OBJECTS)) < 0) return error; error = git_odb_open(&odb, odb_path.ptr); diff --git a/src/submodule.c b/src/submodule.c index 1c17075bf..3007d25df 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -616,8 +616,10 @@ static int submodule_repo_init( * Old style: sub-repo goes directly into repo//.git/ */ if (use_gitlink) { - error = git_buf_join3( - &repodir, '/', git_repository_path(parent_repo), "modules", path); + error = git_repository_item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); + if (error < 0) + goto cleanup; + error = git_buf_joinpath(&repodir, repodir.ptr, path); if (error < 0) goto cleanup; @@ -1084,8 +1086,10 @@ static int submodule_repo_create( * /modules// with a gitlink in the * sub-repo workdir directory to that repository. */ - error = git_buf_join3( - &repodir, '/', git_repository_path(parent_repo), "modules", path); + error = git_repository_item_path(&repodir, parent_repo, GIT_REPOSITORY_ITEM_MODULES); + if (error < 0) + goto cleanup; + error = git_buf_joinpath(&repodir, repodir.ptr, path); if (error < 0) goto cleanup; diff --git a/src/transports/local.c b/src/transports/local.c index 87745add5..e24e99860 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -375,7 +375,8 @@ static int local_push( goto on_error; } - if ((error = git_buf_joinpath(&odb_path, git_repository_path(remote_repo), "objects/pack")) < 0) + if ((error = git_repository_item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 + || (error = git_buf_joinpath(&odb_path, odb_path.ptr, "pack")) < 0) goto on_error; error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); From 79ab3ef69f4905a548bd3a301c348a48454c48f7 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 15 Oct 2015 15:58:05 +0200 Subject: [PATCH 08/31] repository: introduce is_worktree variable --- include/git2/repository.h | 8 ++++++++ src/repository.c | 11 +++++++++++ src/repository.h | 1 + tests/worktree/open.c | 11 +++++++++++ 4 files changed, 31 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index 74ce4c795..8cf7e8e0c 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -467,6 +467,14 @@ GIT_EXTERN(int) git_repository_set_workdir( */ GIT_EXTERN(int) git_repository_is_bare(git_repository *repo); +/** + * Check if a repository is a linked work tree + * + * @param repo Repo to test + * @return 1 if the repository is a linked work tree, 0 otherwise. + */ +GIT_EXTERN(int) git_repository_is_worktree(git_repository *repo); + /** * Get the configuration file for this repository. * diff --git a/src/repository.c b/src/repository.c index ed7038543..1f8035aed 100644 --- a/src/repository.c +++ b/src/repository.c @@ -254,6 +254,7 @@ int git_repository_new(git_repository **out) GITERR_CHECK_ALLOC(repo); repo->is_bare = 1; + repo->is_worktree = 0; return 0; } @@ -558,6 +559,7 @@ int git_repository_open_bare( /* of course we're bare! */ repo->is_bare = 1; + repo->is_worktree = 0; repo->workdir = NULL; *repo_ptr = repo; @@ -772,6 +774,9 @@ int git_repository_open_ext( GITERR_CHECK_ALLOC(repo->commondir); } + if (repo->path_gitlink && repo->commondir && strcmp(repo->path_gitlink, repo->commondir)) + repo->is_worktree = 1; + /* * We'd like to have the config, but git doesn't particularly * care if it's not there, so we need to deal with that. @@ -2186,6 +2191,12 @@ int git_repository_is_bare(git_repository *repo) return repo->is_bare; } +int git_repository_is_worktree(git_repository *repo) +{ + assert(repo); + return repo->is_worktree; +} + int git_repository_set_bare(git_repository *repo) { int error; diff --git a/src/repository.h b/src/repository.h index 5dc67218f..de9ccb1e8 100644 --- a/src/repository.h +++ b/src/repository.h @@ -138,6 +138,7 @@ struct git_repository { git_array_t(git_buf) reserved_names; unsigned is_bare:1; + unsigned is_worktree:1; unsigned int lru_counter; diff --git a/tests/worktree/open.c b/tests/worktree/open.c index 772f7601f..54a8af4a3 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -1,4 +1,5 @@ #include "clar_libgit2.h" +#include "repository.h" #include "worktree_helpers.h" #define WORKTREE_PARENT "submodules-worktree-parent" @@ -13,6 +14,9 @@ void test_worktree_open__repository(void) cl_assert(git_repository_path(fixture.worktree) != NULL); cl_assert(git_repository_workdir(fixture.worktree) != NULL); + cl_assert(!fixture.repo->is_worktree); + cl_assert(fixture.worktree->is_worktree); + cleanup_fixture_worktree(&fixture); } @@ -39,6 +43,9 @@ void test_worktree_open__submodule_worktree_parent(void) cl_assert(git_repository_path(fixture.worktree) != NULL); cl_assert(git_repository_workdir(fixture.worktree) != NULL); + cl_assert(!fixture.repo->is_worktree); + cl_assert(fixture.worktree->is_worktree); + cleanup_fixture_worktree(&fixture); } @@ -55,6 +62,10 @@ void test_worktree_open__submodule_worktree_child(void) "submodules/testrepo/.git")); setup_fixture_worktree(&child_fixture); + cl_assert(!parent_fixture.repo->is_worktree); + cl_assert(parent_fixture.worktree->is_worktree); + cl_assert(child_fixture.worktree->is_worktree); + cleanup_fixture_worktree(&child_fixture); cleanup_fixture_worktree(&parent_fixture); } From 71dd086195ff9f83b0225fc523a27807acc11ace Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 17 Sep 2015 11:40:55 +0200 Subject: [PATCH 09/31] refdb: rename refdb_fs_backend's .path to .gitpath The variable '.path' of the refdb_fs_backend struct becomes confusing regarding the introduction of the git commondir. It does not immediatly become obvious what it should point to. Fix this problem by renaming the variable to `gitpath`, clarifying that it acutally points to the `.git` directory of the repository, in contrast to the commonpath directory, which points to the directory containing shared objects like references and the object store. --- src/refdb_fs.c | 59 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index e40f48bd5..45a0963fe 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -55,7 +55,8 @@ typedef struct refdb_fs_backend { git_refdb_backend parent; git_repository *repo; - char *path; + /* path to git directory */ + char *gitpath; git_sortedcache *refcache; int peeling_mode; @@ -77,7 +78,7 @@ static int packed_reload(refdb_fs_backend *backend) git_buf packedrefs = GIT_BUF_INIT; char *scan, *eof, *eol; - if (!backend->path) + if (!backend->gitpath) return 0; error = git_sortedcache_lockandload(backend->refcache, &packedrefs); @@ -238,7 +239,7 @@ static int loose_lookup_to_packfile(refdb_fs_backend *backend, const char *name) /* if we fail to load the loose reference, assume someone changed * the filesystem under us and skip it... */ - if (loose_readbuffer(&ref_file, backend->path, name) < 0) { + if (loose_readbuffer(&ref_file, backend->gitpath, name) < 0) { giterr_clear(); goto done; } @@ -287,7 +288,7 @@ static int _dirent_loose_load(void *payload, git_buf *full_path) return error; } - file_path = full_path->ptr + strlen(backend->path); + file_path = full_path->ptr + strlen(backend->gitpath); return loose_lookup_to_packfile(backend, file_path); } @@ -303,7 +304,7 @@ static int packed_loadloose(refdb_fs_backend *backend) int error; git_buf refs_path = GIT_BUF_INIT; - if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0) + if (git_buf_joinpath(&refs_path, backend->gitpath, GIT_REFS_DIR) < 0) return -1; /* @@ -331,7 +332,7 @@ static int refdb_fs_backend__exists( assert(backend); if ((error = packed_reload(backend)) < 0 || - (error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0) + (error = git_buf_joinpath(&ref_path, backend->gitpath, ref_name)) < 0) return error; *exists = git_path_isfile(ref_path.ptr) || @@ -373,7 +374,7 @@ static int loose_lookup( if (out) *out = NULL; - if ((error = loose_readbuffer(&ref_file, backend->path, ref_name)) < 0) + if ((error = loose_readbuffer(&ref_file, backend->gitpath, ref_name)) < 0) /* cannot read loose ref file - gah */; else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { const char *target; @@ -484,12 +485,12 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry = NULL; - if (!backend->path) /* do nothing if no path for loose refs */ + if (!backend->gitpath) /* do nothing if no gitpath for loose refs */ return 0; fsit_opts.flags = backend->iterator_flags; - if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 || + if ((error = git_buf_printf(&path, "%s/refs", backend->gitpath)) < 0 || (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { git_buf_free(&path); return error; @@ -729,10 +730,10 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * /* Remove a possibly existing empty directory hierarchy * which name would collide with the reference name */ - if ((error = git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY)) < 0) + if ((error = git_futils_rmdir_r(name, backend->gitpath, GIT_RMDIR_SKIP_NONEMPTY)) < 0) return error; - if (git_buf_joinpath(&ref_path, backend->path, name) < 0) + if (git_buf_joinpath(&ref_path, backend->gitpath, name) < 0) return -1; error = git_filebuf_open(file, ref_path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE); @@ -1283,7 +1284,7 @@ static int refdb_fs_backend__delete_tail( } /* If a loose reference exists, remove it from the filesystem */ - if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0) + if (git_buf_joinpath(&loose_path, backend->gitpath, ref_name) < 0) return -1; @@ -1408,20 +1409,20 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend) assert(backend); git_sortedcache_free(backend->refcache); - git__free(backend->path); + git__free(backend->gitpath); git__free(backend); } -static int setup_namespace(git_buf *path, git_repository *repo) +static int setup_namespace(git_buf *gitpath, git_repository *repo) { char *parts, *start, *end; - /* Not all repositories have a path */ + /* Not all repositories have a gitpath */ if (repo->path_repository == NULL) return 0; /* Load the path to the repo first */ - git_buf_puts(path, repo->path_repository); + git_buf_puts(gitpath, repo->path_repository); /* if the repo is not namespaced, nothing else to do */ if (repo->namespace == NULL) @@ -1438,19 +1439,19 @@ static int setup_namespace(git_buf *path, git_repository *repo) * refs under refs/namespaces/foo/refs/namespaces/bar/ */ while ((start = git__strsep(&end, "/")) != NULL) { - git_buf_printf(path, "refs/namespaces/%s/", start); + git_buf_printf(gitpath, "refs/namespaces/%s/", start); } - git_buf_printf(path, "refs/namespaces/%s/refs", end); + git_buf_printf(gitpath, "refs/namespaces/%s/refs", end); git__free(parts); /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository, + if (git_futils_mkdir_relative(git_buf_cstr(gitpath), repo->path_repository, 0777, GIT_MKDIR_PATH, NULL) < 0) return -1; - /* Return root of the namespaced path, i.e. without the trailing '/refs' */ - git_buf_rtruncate_at_char(path, '/'); + /* Return root of the namespaced gitpath, i.e. without the trailing '/refs' */ + git_buf_rtruncate_at_char(gitpath, '/'); return 0; } @@ -1948,7 +1949,7 @@ int git_refdb_backend_fs( git_repository *repository) { int t = 0; - git_buf path = GIT_BUF_INIT; + git_buf gitpath = GIT_BUF_INIT; refdb_fs_backend *backend; backend = git__calloc(1, sizeof(refdb_fs_backend)); @@ -1956,18 +1957,18 @@ int git_refdb_backend_fs( backend->repo = repository; - if (setup_namespace(&path, repository) < 0) + if (setup_namespace(&gitpath, repository) < 0) goto fail; - backend->path = git_buf_detach(&path); + backend->gitpath = git_buf_detach(&gitpath); - if (git_buf_joinpath(&path, backend->path, GIT_PACKEDREFS_FILE) < 0 || + if (git_buf_joinpath(&gitpath, backend->gitpath, GIT_PACKEDREFS_FILE) < 0 || git_sortedcache_new( &backend->refcache, offsetof(struct packref, name), - NULL, NULL, packref_cmp, git_buf_cstr(&path)) < 0) + NULL, NULL, packref_cmp, git_buf_cstr(&gitpath)) < 0) goto fail; - git_buf_free(&path); + git_buf_free(&gitpath); if (!git_repository__cvar(&t, backend->repo, GIT_CVAR_IGNORECASE) && t) { backend->iterator_flags |= GIT_ITERATOR_IGNORE_CASE; @@ -1999,8 +2000,8 @@ int git_refdb_backend_fs( return 0; fail: - git_buf_free(&path); - git__free(backend->path); + git_buf_free(&gitpath); + git__free(backend->gitpath); git__free(backend); return -1; } From e0a6c28eb3977ffb675195878345025025e41a83 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 16 Sep 2015 16:09:24 +0200 Subject: [PATCH 10/31] refdb: introduce commondir awareness The refdb_fs_backend is not aware of the git commondir, which stores common objects like the o bject database and packed/loose refereensces when worktrees are used. Make refdb_fs_backend aware of the common directory by introducing a new commonpath variable that points to the actual common path of the database and using it instead of the gitdir for the mentioned objects. --- src/refdb_fs.c | 34 ++++++++++++++++++---- tests/worktree/refs.c | 68 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 tests/worktree/refs.c diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 45a0963fe..cb279d9f9 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -57,6 +57,8 @@ typedef struct refdb_fs_backend { git_repository *repo; /* path to git directory */ char *gitpath; + /* path to common objects' directory */ + char *commonpath; git_sortedcache *refcache; int peeling_mode; @@ -363,6 +365,14 @@ static const char *loose_parse_symbolic(git_buf *file_content) return refname_start; } +static bool is_per_worktree_ref(const char *ref_name) +{ + return strcmp("HEAD", ref_name) == 0 || + strcmp("FETCH_HEAD", ref_name) == 0 || + strcmp("MERGE_HEAD", ref_name) == 0 || + strcmp("ORIG_HEAD", ref_name) == 0; +} + static int loose_lookup( git_reference **out, refdb_fs_backend *backend, @@ -370,11 +380,17 @@ static int loose_lookup( { git_buf ref_file = GIT_BUF_INIT; int error = 0; + const char *ref_dir; if (out) *out = NULL; - if ((error = loose_readbuffer(&ref_file, backend->gitpath, ref_name)) < 0) + if (is_per_worktree_ref(ref_name)) + ref_dir = backend->gitpath; + else + ref_dir = backend->commonpath; + + if ((error = loose_readbuffer(&ref_file, ref_dir, ref_name)) < 0) /* cannot read loose ref file - gah */; else if (git__prefixcmp(git_buf_cstr(&ref_file), GIT_SYMREF) == 0) { const char *target; @@ -485,12 +501,12 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry = NULL; - if (!backend->gitpath) /* do nothing if no gitpath for loose refs */ + if (!backend->commonpath) /* do nothing if no commonpath for loose refs */ return 0; fsit_opts.flags = backend->iterator_flags; - if ((error = git_buf_printf(&path, "%s/refs", backend->gitpath)) < 0 || + if ((error = git_buf_printf(&path, "%s/refs", backend->commonpath)) < 0 || (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { git_buf_free(&path); return error; @@ -1410,6 +1426,7 @@ static void refdb_fs_backend__free(git_refdb_backend *_backend) git_sortedcache_free(backend->refcache); git__free(backend->gitpath); + git__free(backend->commonpath); git__free(backend); } @@ -1420,6 +1437,8 @@ static int setup_namespace(git_buf *gitpath, git_repository *repo) /* Not all repositories have a gitpath */ if (repo->path_repository == NULL) return 0; + if (repo->commondir == NULL) + return 0; /* Load the path to the repo first */ git_buf_puts(gitpath, repo->path_repository); @@ -1446,7 +1465,7 @@ static int setup_namespace(git_buf *gitpath, git_repository *repo) git__free(parts); /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_relative(git_buf_cstr(gitpath), repo->path_repository, + if (git_futils_mkdir_relative(git_buf_cstr(gitpath), repo->commondir, 0777, GIT_MKDIR_PATH, NULL) < 0) return -1; @@ -1960,9 +1979,11 @@ int git_refdb_backend_fs( if (setup_namespace(&gitpath, repository) < 0) goto fail; - backend->gitpath = git_buf_detach(&gitpath); + backend->gitpath = backend->commonpath = git_buf_detach(&gitpath); + if (repository->commondir) + backend->commonpath = git__strdup(repository->commondir); - if (git_buf_joinpath(&gitpath, backend->gitpath, GIT_PACKEDREFS_FILE) < 0 || + if (git_buf_joinpath(&gitpath, backend->commonpath, GIT_PACKEDREFS_FILE) < 0 || git_sortedcache_new( &backend->refcache, offsetof(struct packref, name), NULL, NULL, packref_cmp, git_buf_cstr(&gitpath)) < 0) @@ -2002,6 +2023,7 @@ int git_refdb_backend_fs( fail: git_buf_free(&gitpath); git__free(backend->gitpath); + git__free(backend->commonpath); git__free(backend); return -1; } diff --git a/tests/worktree/refs.c b/tests/worktree/refs.c new file mode 100644 index 000000000..e08e55372 --- /dev/null +++ b/tests/worktree/refs.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_refs__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_refs__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_refs__list(void) +{ + git_strarray refs, wtrefs; + unsigned i, j; + int error = 0; + + cl_git_pass(git_reference_list(&refs, fixture.repo)); + cl_git_pass(git_reference_list(&wtrefs, fixture.worktree)); + + if (refs.count != wtrefs.count) + { + error = GIT_ERROR; + goto exit; + } + + for (i = 0; i < refs.count; i++) + { + int found = 0; + + for (j = 0; j < wtrefs.count; j++) + { + if (!strcmp(refs.strings[i], wtrefs.strings[j])) + { + found = 1; + break; + } + } + + if (!found) + { + error = GIT_ERROR; + goto exit; + } + } + +exit: + git_strarray_free(&refs); + git_strarray_free(&wtrefs); + cl_git_pass(error); +} + +void test_worktree_refs__read_head(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + + git_reference_free(head); +} From e9403024fe65528a4125ae08a89cd5a8f2eb61e2 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 24 Sep 2015 15:32:26 +0200 Subject: [PATCH 11/31] refdb: look for reflog in commondir --- src/refdb_fs.c | 2 +- tests/worktree/reflog.c | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/worktree/reflog.c diff --git a/src/refdb_fs.c b/src/refdb_fs.c index cb279d9f9..91183cd71 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -1582,7 +1582,7 @@ static int create_new_reflog_file(const char *filepath) GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name) { - return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name); + return git_buf_join3(path, '/', repo->commondir, GIT_REFLOG_DIR, name); } static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name) diff --git a/tests/worktree/reflog.c b/tests/worktree/reflog.c new file mode 100644 index 000000000..6152eb385 --- /dev/null +++ b/tests/worktree/reflog.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#include "reflog.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +#define REFLOG "refs/heads/testrepo-worktree" +#define REFLOG_MESSAGE "reflog message" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_reflog__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_reflog__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_reflog__read(void) +{ + git_reflog *reflog; + const git_reflog_entry *entry; + + cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG)); + cl_assert_equal_i(git_reflog_entrycount(reflog), 1); + + entry = git_reflog_entry_byindex(reflog, 0); + cl_assert(entry != NULL); + cl_assert_equal_s(git_reflog_entry_message(entry), "branch: Created from HEAD"); + + git_reflog_free(reflog); +} + +void test_worktree_reflog__append_then_read(void) +{ + git_reflog *reflog, *parent_reflog; + const git_reflog_entry *entry; + git_reference *head; + git_signature *sig; + const git_oid *oid; + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + cl_assert((oid = git_reference_target(head)) != NULL); + cl_git_pass(git_signature_now(&sig, "foo", "foo@bar")); + + cl_git_pass(git_reflog_read(&reflog, fixture.worktree, REFLOG)); + cl_git_pass(git_reflog_append(reflog, oid, sig, REFLOG_MESSAGE)); + git_reflog_write(reflog); + + cl_git_pass(git_reflog_read(&parent_reflog, fixture.repo, REFLOG)); + entry = git_reflog_entry_byindex(parent_reflog, 0); + cl_assert(git_oid_cmp(oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(oid, &entry->oid_cur) == 0); + + git_reference_free(head); + git_signature_free(sig); + git_reflog_free(reflog); + git_reflog_free(parent_reflog); +} From 4292837d502fedbbf1f24abe355eb349e4b3b0c9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 24 Sep 2015 14:37:10 +0200 Subject: [PATCH 12/31] config: open configuration in commondir A repository's configuartion file can always be found in the GIT_COMMON_DIR, which has been newly introduced. For normal repositories this does change nothing, but for working trees this change allows to access the shared configuration file. --- src/repository.c | 3 +-- tests/worktree/config.c | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/worktree/config.c diff --git a/src/repository.c b/src/repository.c index 1f8035aed..a829161e8 100644 --- a/src/repository.c +++ b/src/repository.c @@ -862,8 +862,7 @@ static int load_config( if ((error = git_config_new(&cfg)) < 0) return error; - error = git_buf_joinpath( - &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO); + error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG); if (error < 0) goto on_error; diff --git a/tests/worktree/config.c b/tests/worktree/config.c new file mode 100644 index 000000000..3ab317bb5 --- /dev/null +++ b/tests/worktree/config.c @@ -0,0 +1,45 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_config__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_config__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_config__open(void) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, fixture.worktree)); + cl_assert(cfg != NULL); + + git_config_free(cfg); +} + +void test_worktree_config__set(void) +{ + git_config *cfg; + int32_t val; + + cl_git_pass(git_repository_config(&cfg, fixture.worktree)); + cl_git_pass(git_config_set_int32(cfg, "core.dummy", 5)); + git_config_free(cfg); + + // reopen to verify configuration has been set in the + // common dir + cl_git_pass(git_repository_config(&cfg, fixture.repo)); + cl_git_pass(git_config_get_int32(&val, cfg, "core.dummy")); + cl_assert_equal_i(val, 5); + git_config_free(cfg); +} From 854b5c70e3f8f4701d005cbd0623f0bef8d00060 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 26 Oct 2015 16:21:09 +0100 Subject: [PATCH 13/31] repository: expose `repo_init_create_head` Expose the function `repo_init_create_head` as `git_repository_create_head`. --- src/repository.c | 4 ++-- src/repository.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index a829161e8..2e267b72d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1262,7 +1262,7 @@ static int check_repositoryformatversion(git_config *config) return 0; } -static int repo_init_create_head(const char *git_dir, const char *ref_name) +int git_repository_create_head(const char *git_dir, const char *ref_name) { git_buf ref_path = GIT_BUF_INIT; git_filebuf ref = GIT_FILEBUF_INIT; @@ -1959,7 +1959,7 @@ int git_repository_init_ext( repo_path.ptr, wd, opts)) && !(error = repo_init_config( repo_path.ptr, wd, opts->flags, opts->mode))) - error = repo_init_create_head( + error = git_repository_create_head( repo_path.ptr, opts->initial_head); } if (error < 0) diff --git a/src/repository.h b/src/repository.h index de9ccb1e8..74e15e146 100644 --- a/src/repository.h +++ b/src/repository.h @@ -154,6 +154,7 @@ GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) } int git_repository_head_tree(git_tree **tree, git_repository *repo); +int git_repository_create_head(const char *git_dir, const char *ref_name); /* * Weak pointers to repository internals. From 45f2b7a43ffe77bac3acbf21a041b56f03842ba8 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 21 Oct 2015 11:48:02 +0200 Subject: [PATCH 14/31] worktree: implement `git_worktree_list` Add new module for working trees with the `git_worktree_list` function. The function lists names for all working trees of a certain repository. --- include/git2/worktree.h | 37 +++++++++++++ src/worktree.c | 58 +++++++++++++++++++++ tests/worktree/worktree.c | 107 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 include/git2/worktree.h create mode 100644 src/worktree.c create mode 100644 tests/worktree/worktree.c diff --git a/include/git2/worktree.h b/include/git2/worktree.h new file mode 100644 index 000000000..c09fa32d0 --- /dev/null +++ b/include/git2/worktree.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_worktree_h__ +#define INCLUDE_git_worktree_h__ + +#include "common.h" +#include "types.h" +#include "strarray.h" + +/** + * @file git2/worktrees.h + * @brief Git worktree related functions + * @defgroup git_commit Git worktree related functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * List names of linked working trees + * + * The returned list should be released with `git_strarray_free` + * when no longer needed. + * + * @param out pointer to the array of working tree names + * @param repo the repo to use when listing working trees + * @return 0 or an error code + */ +GIT_EXTERN(int) git_worktree_list(git_strarray *out, git_repository *repo); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/worktree.c b/src/worktree.c new file mode 100644 index 000000000..28d895d5c --- /dev/null +++ b/src/worktree.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/worktree.h" + +#include "common.h" +#include "repository.h" + +static bool is_worktree_dir(git_buf *dir) +{ + return git_path_contains_file(dir, "commondir") + && git_path_contains_file(dir, "gitdir") + && git_path_contains_file(dir, "HEAD"); +} + +int git_worktree_list(git_strarray *wts, git_repository *repo) +{ + git_vector worktrees = GIT_VECTOR_INIT; + git_buf path = GIT_BUF_INIT; + char *worktree; + unsigned i, len; + int error; + + assert(wts && repo); + + wts->count = 0; + wts->strings = NULL; + + if ((error = git_buf_printf(&path, "%s/worktrees/", repo->commondir)) < 0) + goto exit; + if (!git_path_exists(path.ptr) || git_path_is_empty_dir(path.ptr)) + goto exit; + if ((error = git_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0) + goto exit; + + len = path.size; + + git_vector_foreach(&worktrees, i, worktree) { + git_buf_truncate(&path, len); + git_buf_puts(&path, worktree); + + if (!is_worktree_dir(&path)) { + git_vector_remove(&worktrees, i); + git__free(worktree); + } + } + + wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees); + +exit: + git_buf_free(&path); + + return error; +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c new file mode 100644 index 000000000..3acae886e --- /dev/null +++ b/tests/worktree/worktree.c @@ -0,0 +1,107 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" + +#include "git2/worktree.h" +#include "repository.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_worktree__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_worktree__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_worktree__list(void) +{ + git_strarray wts; + + cl_git_pass(git_worktree_list(&wts, fixture.repo)); + cl_assert_equal_i(wts.count, 1); + cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); + + git_strarray_free(&wts); +} + +void test_worktree_worktree__list_with_invalid_worktree_dirs(void) +{ + const char *filesets[3][2] = { + { "gitdir", "commondir" }, + { "gitdir", "HEAD" }, + { "HEAD", "commondir" }, + }; + git_buf path = GIT_BUF_INIT; + git_strarray wts; + unsigned i, j, len; + + cl_git_pass(git_buf_printf(&path, "%s/worktrees/invalid", + fixture.repo->commondir)); + cl_git_pass(p_mkdir(path.ptr, 0755)); + + len = path.size; + + for (i = 0; i < ARRAY_SIZE(filesets); i++) { + + for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) { + git_buf_truncate(&path, len); + cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j])); + cl_git_pass(p_close(p_creat(path.ptr, 0644))); + } + + cl_git_pass(git_worktree_list(&wts, fixture.worktree)); + cl_assert_equal_i(wts.count, 1); + cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); + git_strarray_free(&wts); + + for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) { + git_buf_truncate(&path, len); + cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j])); + p_unlink(path.ptr); + } + } + + git_buf_free(&path); +} + +void test_worktree_worktree__list_in_worktree_repo(void) +{ + git_strarray wts; + + cl_git_pass(git_worktree_list(&wts, fixture.worktree)); + cl_assert_equal_i(wts.count, 1); + cl_assert_equal_s(wts.strings[0], "testrepo-worktree"); + + git_strarray_free(&wts); +} + +void test_worktree_worktree__list_bare(void) +{ + git_repository *repo; + git_strarray wts; + + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_worktree_list(&wts, repo)); + cl_assert_equal_i(wts.count, 0); + + git_repository_free(repo); +} + +void test_worktree_worktree__list_without_worktrees(void) +{ + git_repository *repo; + git_strarray wts; + + repo = cl_git_sandbox_init("testrepo2"); + cl_git_pass(git_worktree_list(&wts, repo)); + cl_assert_equal_i(wts.count, 0); + + git_repository_free(repo); +} From d3bc09e81687ca132226e93ce69b9a28b8d3c66b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 21 Oct 2015 12:02:31 +0200 Subject: [PATCH 15/31] worktree: introduce `struct git_worktree` Introduce a new `struct git_worktree`, which holds information about a possible working tree connected to a repository. Introduce functions to allow opening working trees for a repository. --- include/git2/types.h | 3 ++ include/git2/worktree.h | 17 ++++++++ src/worktree.c | 89 +++++++++++++++++++++++++++++++++++++++ src/worktree.h | 31 ++++++++++++++ tests/worktree/worktree.c | 28 +++++++++++- 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/worktree.h diff --git a/include/git2/types.h b/include/git2/types.h index 6f41014b3..dfdaa2920 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -104,6 +104,9 @@ typedef struct git_refdb_backend git_refdb_backend; */ typedef struct git_repository git_repository; +/** Representation of a working tree */ +typedef struct git_worktree git_worktree; + /** Representation of a generic object in a repository */ typedef struct git_object git_object; diff --git a/include/git2/worktree.h b/include/git2/worktree.h index c09fa32d0..8313265d5 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -32,6 +32,23 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_worktree_list(git_strarray *out, git_repository *repo); +/** + * Lookup a working tree by its name for a given repository + * + * @param out Output pointer to looked up worktree or `NULL` + * @param repo The repository containing worktrees + * @param name Name of the working tree to look up + * @return 0 or an error code + */ +GIT_EXTERN(int) git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name); + +/** + * Free a previously allocated worktree + * + * @param wt worktree handle to close. If NULL nothing occurs. + */ +GIT_EXTERN(void) git_worktree_free(git_worktree *wt); + /** @} */ GIT_END_DECL #endif diff --git a/src/worktree.c b/src/worktree.c index 28d895d5c..a0e5d934a 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -9,6 +9,7 @@ #include "common.h" #include "repository.h" +#include "worktree.h" static bool is_worktree_dir(git_buf *dir) { @@ -56,3 +57,91 @@ exit: return error; } + +static char *read_link(const char *base, const char *file) +{ + git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; + + assert(base && file); + + if (git_buf_joinpath(&path, base, file) < 0) + goto err; + if (git_futils_readbuffer(&buf, path.ptr) < 0) + goto err; + git_buf_free(&path); + + git_buf_rtrim(&buf); + + if (!git_path_is_relative(buf.ptr)) + return git_buf_detach(&buf); + + if (git_buf_sets(&path, base) < 0) + goto err; + if (git_path_apply_relative(&path, buf.ptr) < 0) + goto err; + git_buf_free(&buf); + + return git_buf_detach(&path); + +err: + git_buf_free(&buf); + git_buf_free(&path); + + return NULL; +} + +int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) +{ + git_buf path = GIT_BUF_INIT; + git_worktree *wt = NULL; + int error; + + assert(repo && name); + + *out = NULL; + + if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0) + goto out; + + if (!is_worktree_dir(&path)) { + error = -1; + goto out; + } + + if ((wt = git__malloc(sizeof(struct git_repository))) == NULL) { + error = -1; + goto out; + } + + if ((wt->name = git__strdup(name)) == NULL + || (wt->commondir_path = read_link(path.ptr, "commondir")) == NULL + || (wt->gitlink_path = read_link(path.ptr, "gitdir")) == NULL + || (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) { + error = -1; + goto out; + } + wt->gitdir_path = git_buf_detach(&path); + + (*out) = wt; + +out: + git_buf_free(&path); + + if (error) + git_worktree_free(wt); + + return error; +} + +void git_worktree_free(git_worktree *wt) +{ + if (!wt) + return; + + git__free(wt->commondir_path); + git__free(wt->gitlink_path); + git__free(wt->gitdir_path); + git__free(wt->parent_path); + git__free(wt->name); + git__free(wt); +} diff --git a/src/worktree.h b/src/worktree.h new file mode 100644 index 000000000..0e1666c42 --- /dev/null +++ b/src/worktree.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_worktree_h__ +#define INCLUDE_worktree_h__ + +#include "git2/common.h" +#include "git2/worktree.h" + +struct git_worktree { + /* Name of the working tree. This is the name of the + * containing directory in the `$PARENT/.git/worktrees/` + * directory. */ + char *name; + + /* Path to the .git file in the working tree's repository */ + char *gitlink_path; + /* Path to the .git directory inside the parent's + * worktrees directory */ + char *gitdir_path; + /* Path to the common directory contained in the parent + * repository */ + char *commondir_path; + /* Path to the parent's .git directory */ + char *parent_path; +}; + +#endif diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 3acae886e..28d88993d 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -1,8 +1,8 @@ #include "clar_libgit2.h" #include "worktree_helpers.h" -#include "git2/worktree.h" #include "repository.h" +#include "worktree.h" #define COMMON_REPO "testrepo" #define WORKTREE_REPO "testrepo-worktree" @@ -105,3 +105,29 @@ void test_worktree_worktree__list_without_worktrees(void) git_repository_free(repo); } + +void test_worktree_worktree__lookup(void) +{ + git_worktree *wt; + git_buf gitdir_path = GIT_BUF_INIT; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + git_buf_printf(&gitdir_path, "%s/worktrees/%s", fixture.repo->commondir, "testrepo-worktree"); + + cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr); + cl_assert_equal_s(wt->parent_path, fixture.repo->path_repository); + cl_assert_equal_s(wt->gitlink_path, fixture.worktree->path_gitlink); + cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir); + + git_buf_free(&gitdir_path); + git_worktree_free(wt); +} + +void test_worktree_worktree__lookup_nonexistent_worktree(void) +{ + git_worktree *wt; + + cl_git_fail(git_worktree_lookup(&wt, fixture.repo, "nonexistent")); + cl_assert_equal_p(wt, NULL); +} From 8c8d726ef784b3f47ed3cd9427202a74563f626e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 21 Oct 2015 12:10:30 +0200 Subject: [PATCH 16/31] worktree: implement `git_repository_open_from_worktree` Add function `git_repository_open_from_worktree`, which allows to open a `git_worktree` as repository. --- include/git2/repository.h | 11 ++++++ src/repository.c | 31 +++++++++++++++++ tests/worktree/worktree.c | 72 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index 8cf7e8e0c..29eb2da49 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -35,6 +35,17 @@ GIT_BEGIN_DECL * @return 0 or an error code */ GIT_EXTERN(int) git_repository_open(git_repository **out, const char *path); +/** + * Open working tree as a repository + * + * Open the working directory of the working tree as a normal + * repository that can then be worked on. + * + * @param out Output pointer containing opened repository + * @param wt Working tree to open + * @return 0 or an error code + */ +GIT_EXTERN(int) git_repository_open_from_worktree(git_repository **out, git_worktree *wt); /** * Create a "fake" repository to wrap an object database diff --git a/src/repository.c b/src/repository.c index 2e267b72d..03e43909b 100644 --- a/src/repository.c +++ b/src/repository.c @@ -28,6 +28,7 @@ #include "diff_driver.h" #include "annotated_commit.h" #include "submodule.h" +#include "worktree.h" GIT__USE_STRMAP #include "strmap.h" @@ -817,6 +818,36 @@ int git_repository_open(git_repository **repo_out, const char *path) repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); } +int git_repository_open_from_worktree(git_repository **repo_out, git_worktree *wt) +{ + git_buf path = GIT_BUF_INIT; + git_repository *repo = NULL; + int len, err; + + assert(repo_out && wt); + + *repo_out = NULL; + len = strlen(wt->gitlink_path); + + if (len <= 4 || strcasecmp(wt->gitlink_path + len - 4, ".git")) { + err = -1; + goto out; + } + + if ((err = git_buf_set(&path, wt->gitlink_path, len - 4)) < 0) + goto out; + + if ((err = git_repository_open(&repo, path.ptr)) < 0) + goto out; + + *repo_out = repo; + +out: + git_buf_free(&path); + + return err; +} + int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb) { git_repository *repo; diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 28d88993d..d891d6f8f 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -131,3 +131,75 @@ void test_worktree_worktree__lookup_nonexistent_worktree(void) cl_git_fail(git_worktree_lookup(&wt, fixture.repo, "nonexistent")); cl_assert_equal_p(wt, NULL); } + +void test_worktree_worktree__open(void) +{ + git_worktree *wt; + git_repository *repo; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_git_pass(git_repository_open_from_worktree(&repo, wt)); + cl_assert_equal_s(git_repository_workdir(repo), + git_repository_workdir(fixture.worktree)); + + git_repository_free(repo); + git_worktree_free(wt); +} + +void test_worktree_worktree__open_invalid_commondir(void) +{ + git_worktree *wt; + git_repository *repo; + git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/commondir")); + cl_git_pass(git_buf_printf(&path, + "%s/worktrees/testrepo-worktree/commondir", + fixture.repo->commondir)); + cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_buf_free(&buf); + git_buf_free(&path); + git_worktree_free(wt); +} + +void test_worktree_worktree__open_invalid_gitdir(void) +{ + git_worktree *wt; + git_repository *repo; + git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir")); + cl_git_pass(git_buf_printf(&path, + "%s/worktrees/testrepo-worktree/gitdir", + fixture.repo->commondir)); + cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_buf_free(&buf); + git_buf_free(&path); + git_worktree_free(wt); +} + +void test_worktree_worktree__open_invalid_parent(void) +{ + git_worktree *wt; + git_repository *repo; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir")); + cl_git_pass(git_futils_writebuffer(&buf, + fixture.worktree->path_gitlink, O_RDWR, 0644)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_buf_free(&buf); + git_worktree_free(wt); +} From 372dc9ff6ada409204b7c3de882e5dad16f30b36 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 21 Oct 2015 13:49:55 +0200 Subject: [PATCH 17/31] worktree: implement `git_worktree_validate` Add a new function that checks wether a given `struct git_worktree` is valid. The validation includes checking if the gitdir, parent directory and common directory are present. --- include/git2/errors.h | 1 + include/git2/worktree.h | 12 ++++++++++ src/worktree.c | 38 +++++++++++++++++++++++++++++ tests/worktree/worktree.c | 50 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/include/git2/errors.h b/include/git2/errors.h index e959ffd8a..1d271366f 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -100,6 +100,7 @@ typedef enum { GITERR_REBASE, GITERR_FILESYSTEM, GITERR_PATCH, + GITERR_WORKTREE } git_error_t; /** diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 8313265d5..c6ca30bcd 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -49,6 +49,18 @@ GIT_EXTERN(int) git_worktree_lookup(git_worktree **out, git_repository *repo, co */ GIT_EXTERN(void) git_worktree_free(git_worktree *wt); +/** + * Check if worktree is valid + * + * A valid worktree requires both the git data structures inside + * the linked parent repository and the linked working copy to be + * present. + * + * @param wt Worktree to check + * @return 0 when worktree is valid, error-code otherwise + */ +GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); + /** @} */ GIT_END_DECL #endif diff --git a/src/worktree.c b/src/worktree.c index a0e5d934a..2852c1888 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -145,3 +145,41 @@ void git_worktree_free(git_worktree *wt) git__free(wt->name); git__free(wt); } + +int git_worktree_validate(const git_worktree *wt) +{ + git_buf buf = GIT_BUF_INIT; + int err = 0; + + assert(wt); + + git_buf_puts(&buf, wt->gitdir_path); + if (!is_worktree_dir(&buf)) { + giterr_set(GITERR_WORKTREE, + "Worktree gitdir ('%s') is not valid", + wt->gitlink_path); + err = -1; + goto out; + } + + if (!git_path_exists(wt->parent_path)) { + giterr_set(GITERR_WORKTREE, + "Worktree parent directory ('%s') does not exist ", + wt->parent_path); + err = -2; + goto out; + } + + if (!git_path_exists(wt->commondir_path)) { + giterr_set(GITERR_WORKTREE, + "Worktree common directory ('%s') does not exist ", + wt->commondir_path); + err = -3; + goto out; + } + +out: + git_buf_free(&buf); + + return err; +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index d891d6f8f..7e9cd2528 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -203,3 +203,53 @@ void test_worktree_worktree__open_invalid_parent(void) git_buf_free(&buf); git_worktree_free(wt); } + +void test_worktree_worktree__validate(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_validate(wt)); + + git_worktree_free(wt); +} + +void test_worktree_worktree__validate_invalid_commondir(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + git__free(wt->commondir_path); + wt->commondir_path = "/path/to/invalid/commondir"; + + cl_git_fail(git_worktree_validate(wt)); + + wt->commondir_path = NULL; + git_worktree_free(wt); +} + +void test_worktree_worktree__validate_invalid_gitdir(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + git__free(wt->gitdir_path); + wt->gitdir_path = "/path/to/invalid/gitdir"; + cl_git_fail(git_worktree_validate(wt)); + + wt->gitdir_path = NULL; + git_worktree_free(wt); +} + +void test_worktree_worktree__validate_invalid_parent(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + git__free(wt->parent_path); + wt->parent_path = "/path/to/invalid/parent"; + cl_git_fail(git_worktree_validate(wt)); + + wt->parent_path = NULL; + git_worktree_free(wt); +} From dea7488e93bdd9a0291d518af58b1cde6d71aca9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 23 Oct 2015 14:11:44 +0200 Subject: [PATCH 18/31] worktree: implement `git_worktree_add` Implement the `git_worktree_add` function which can be used to create new working trees for a given repository. --- include/git2/worktree.h | 15 ++++++ src/worktree.c | 102 +++++++++++++++++++++++++++++++++++++- tests/worktree/worktree.c | 83 +++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 1 deletion(-) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index c6ca30bcd..4b045eeb8 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -61,6 +61,21 @@ GIT_EXTERN(void) git_worktree_free(git_worktree *wt); */ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); +/** + * Add a new working tree + * + * Add a new working tree for the repository, that is create the + * required data structures inside the repository and check out + * the current HEAD at `path` + * + * @param out Output pointer containing new working tree + * @param repo Repository to create working tree for + * @param name Name of the working tree + * @param path Path to create working tree at + * @return 0 or an error code + */ +GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path); + /** @} */ GIT_END_DECL #endif diff --git a/src/worktree.c b/src/worktree.c index 2852c1888..3c6cfee45 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -5,9 +5,12 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "common.h" + +#include "git2/branch.h" +#include "git2/commit.h" #include "git2/worktree.h" -#include "common.h" #include "repository.h" #include "worktree.h" @@ -90,6 +93,25 @@ err: return NULL; } +static int write_wtfile(const char *base, const char *file, const git_buf *buf) +{ + git_buf path = GIT_BUF_INIT; + int err; + + assert(base && file && buf); + + if ((err = git_buf_joinpath(&path, base, file)) < 0) + goto out; + + if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + goto out; + +out: + git_buf_free(&path); + + return err; +} + int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) { git_buf path = GIT_BUF_INIT; @@ -183,3 +205,81 @@ out: return err; } + +int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree) +{ + git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; + git_reference *ref = NULL, *head = NULL; + git_commit *commit = NULL; + git_repository *wt = NULL; + git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT; + int err; + + assert(out && repo && name && worktree); + + *out = NULL; + + /* Create worktree related files in commondir */ + if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0) + goto out; + if (!git_path_exists(path.ptr)) + if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0) + goto out; + if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + + /* Create worktree work dir */ + if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0) + goto out; + + /* Create worktree .git file */ + if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0) + goto out; + if ((err = write_wtfile(worktree, ".git", &buf)) < 0) + goto out; + + /* Create commondir files */ + if ((err = git_buf_sets(&buf, repo->commondir)) < 0 + || (err = git_buf_putc(&buf, '\n')) < 0 + || (err = write_wtfile(path.ptr, "commondir", &buf)) < 0) + goto out; + if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0 + || (err = git_buf_putc(&buf, '\n')) < 0 + || (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0) + goto out; + + /* Create new branch */ + if ((err = git_repository_head(&head, repo)) < 0) + goto out; + if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0) + goto out; + if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0) + goto out; + + /* Set worktree's HEAD */ + if ((err = git_repository_create_head(path.ptr, name)) < 0) + goto out; + if ((err = git_repository_open(&wt, worktree)) < 0) + goto out; + + /* Checkout worktree's HEAD */ + coopts.checkout_strategy = GIT_CHECKOUT_FORCE; + if ((err = git_checkout_head(wt, &coopts)) < 0) + goto out; + + /* Load result */ + if ((err = git_worktree_lookup(out, repo, name)) < 0) + goto out; + +out: + git_buf_free(&path); + git_buf_free(&buf); + git_reference_free(ref); + git_reference_free(head); + git_commit_free(commit); + git_repository_free(wt); + + return err; +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 7e9cd2528..8154baa32 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -204,6 +204,89 @@ void test_worktree_worktree__open_invalid_parent(void) git_worktree_free(wt); } +void test_worktree_worktree__init(void) +{ + git_worktree *wt; + git_repository *repo; + git_reference *branch; + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new")); + cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr)); + + /* Open and verify created repo */ + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL)); + + git_buf_free(&path); + git_worktree_free(wt); + git_reference_free(branch); + git_repository_free(repo); +} + +void test_worktree_worktree__init_existing_branch(void) +{ + git_reference *head, *branch; + git_commit *commit; + git_worktree *wt; + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid)); + cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false)); + + cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new")); + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr)); + + git_buf_free(&path); + git_commit_free(commit); + git_reference_free(head); + git_reference_free(branch); +} + +void test_worktree_worktree__init_existing_worktree(void) +{ + git_worktree *wt; + git_buf path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new")); + cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr)); + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_assert_equal_s(wt->gitlink_path, fixture.worktree->path_gitlink); + + git_buf_free(&path); + git_worktree_free(wt); +} + +void test_worktree_worktree__init_existing_path(void) +{ + const char *wtfiles[] = { "HEAD", "commondir", "gitdir", "index" }; + git_worktree *wt; + git_buf path = GIT_BUF_INIT; + unsigned i; + + /* Delete files to verify they have not been created by + * the init call */ + for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { + cl_git_pass(git_buf_joinpath(&path, + fixture.worktree->path_repository, wtfiles[i])); + cl_git_pass(p_unlink(path.ptr)); + } + + cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree")); + cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr)); + + /* Verify files have not been re-created */ + for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { + cl_git_pass(git_buf_joinpath(&path, + fixture.worktree->path_repository, wtfiles[i])); + cl_assert(!git_path_exists(path.ptr)); + } + + git_buf_free(&path); +} + void test_worktree_worktree__validate(void) { git_worktree *wt; From 2a503485fae6c93c76bd0465c8b3fad5d9e19f6d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 21 Oct 2015 16:03:04 +0200 Subject: [PATCH 19/31] worktree: implement locking mechanisms Working trees support locking by creating a file `locked` inside the tree's gitdir with an optional reason inside. Support this feature by adding functions to get and set the locking status. --- include/git2/worktree.h | 36 +++++++++++++++++++ src/worktree.c | 73 +++++++++++++++++++++++++++++++++++++++ src/worktree.h | 2 ++ tests/worktree/worktree.c | 59 +++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 4b045eeb8..62b4b5e79 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -8,6 +8,7 @@ #define INCLUDE_git_worktree_h__ #include "common.h" +#include "buffer.h" #include "types.h" #include "strarray.h" @@ -76,6 +77,41 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); */ GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path); +/* + * Lock worktree if not already locked + * + * Lock a worktree, optionally specifying a reason why the linked + * working tree is being locked. + * + * @param wt Worktree to lock + * @param reason Reason why the working tree is being locked + * @return 0 on success, non-zero otherwise + */ +GIT_EXTERN(int) git_worktree_lock(git_worktree *wt, char *reason); + +/** + * Unlock a locked worktree + * + * @param wt Worktree to unlock + * @return 0 on success, 1 if worktree was not locked, error-code + * otherwise + */ +GIT_EXTERN(int) git_worktree_unlock(git_worktree *wt); + +/** + * Check if worktree is locked + * + * A worktree may be locked if the linked working tree is stored + * on a portable device which is not available. + * + * @param reason Buffer to store reason in. If NULL no reason is stored. + * @param wt Worktree to check + * @return 0 when the working tree not locked, a value greater + * than zero if it is locked, less than zero if there was an + * error + */ +GIT_EXTERN(int) git_worktree_is_locked(git_buf *reason, const git_worktree *wt); + /** @} */ GIT_END_DECL #endif diff --git a/src/worktree.c b/src/worktree.c index 3c6cfee45..fa5a916c0 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -143,6 +143,7 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na goto out; } wt->gitdir_path = git_buf_detach(&path); + wt->locked = !!git_worktree_is_locked(NULL, wt); (*out) = wt; @@ -283,3 +284,75 @@ out: return err; } + +int git_worktree_lock(git_worktree *wt, char *creason) +{ + git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; + int err; + + assert(wt); + + if ((err = git_worktree_is_locked(NULL, wt)) < 0) + goto out; + + if ((err = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + + if (creason) + git_buf_attach_notowned(&buf, creason, strlen(creason)); + + if ((err = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0) + goto out; + + wt->locked = 1; + +out: + git_buf_free(&path); + + return err; +} + +int git_worktree_unlock(git_worktree *wt) +{ + git_buf path = GIT_BUF_INIT; + + assert(wt); + + if (!git_worktree_is_locked(NULL, wt)) + return 0; + + if (git_buf_joinpath(&path, wt->gitdir_path, "locked") < 0) + return -1; + + if (p_unlink(path.ptr) != 0) { + git_buf_free(&path); + return -1; + } + + wt->locked = 0; + + git_buf_free(&path); + + return 0; +} + +int git_worktree_is_locked(git_buf *reason, const git_worktree *wt) +{ + git_buf path = GIT_BUF_INIT; + int ret; + + assert(wt); + + if (reason) + git_buf_clear(reason); + + if ((ret = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0) + goto out; + if ((ret = git_path_exists(path.ptr)) && reason) + git_futils_readbuffer(reason, path.ptr); + +out: + git_buf_free(&path); + + return ret; +} diff --git a/src/worktree.h b/src/worktree.h index 0e1666c42..0e1a88d98 100644 --- a/src/worktree.h +++ b/src/worktree.h @@ -26,6 +26,8 @@ struct git_worktree { char *commondir_path; /* Path to the parent's .git directory */ char *parent_path; + + int locked:1; }; #endif diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 8154baa32..82b4ebc0d 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -336,3 +336,62 @@ void test_worktree_worktree__validate_invalid_parent(void) wt->parent_path = NULL; git_worktree_free(wt); } + +void test_worktree_worktree__lock_with_reason(void) +{ + git_worktree *wt; + git_buf reason = GIT_BUF_INIT; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_assert(!git_worktree_is_locked(NULL, wt)); + cl_git_pass(git_worktree_lock(wt, "because")); + cl_assert(git_worktree_is_locked(&reason, wt) > 0); + cl_assert_equal_s(reason.ptr, "because"); + cl_assert(wt->locked); + + git_buf_free(&reason); + git_worktree_free(wt); +} + +void test_worktree_worktree__lock_without_reason(void) +{ + git_worktree *wt; + git_buf reason = GIT_BUF_INIT; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + + cl_assert(!git_worktree_is_locked(NULL, wt)); + cl_git_pass(git_worktree_lock(wt, NULL)); + cl_assert(git_worktree_is_locked(&reason, wt) > 0); + cl_assert_equal_i(reason.size, 0); + cl_assert(wt->locked); + + git_buf_free(&reason); + git_worktree_free(wt); +} + +void test_worktree_worktree__unlock_unlocked_worktree(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_assert(!git_worktree_is_locked(NULL, wt)); + cl_assert(git_worktree_unlock(wt) == 0); + cl_assert(!wt->locked); + + git_worktree_free(wt); +} + +void test_worktree_worktree__unlock_locked_worktree(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_lock(wt, NULL)); + cl_assert(git_worktree_is_locked(NULL, wt)); + cl_git_pass(git_worktree_unlock(wt)); + cl_assert(!wt->locked); + + git_worktree_free(wt); +} From f0cfc34105fd68af9eb6e2024459c40c45e7d3a0 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 21 Oct 2015 13:53:18 +0200 Subject: [PATCH 20/31] worktree: implement `git_worktree_prune` Implement the `git_worktree_prune` function. This function can be used to delete working trees from a repository. According to the flags passed to it, it can either delete the working tree's gitdir only or both gitdir and the working directory. --- include/git2/worktree.h | 26 ++++++++++++++++ src/worktree.c | 64 +++++++++++++++++++++++++++++++++++++++ tests/worktree/worktree.c | 58 +++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 62b4b5e79..594ff795b 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -112,6 +112,32 @@ GIT_EXTERN(int) git_worktree_unlock(git_worktree *wt); */ GIT_EXTERN(int) git_worktree_is_locked(git_buf *reason, const git_worktree *wt); +/** + * Flags which can be passed to git_worktree_prune to alter its + * behavior. + */ +typedef enum { + /* Prune working tree even if working tree is valid */ + GIT_WORKTREE_PRUNE_VALID = 1u << 0, + /* Prune working tree even if it is locked */ + GIT_WORKTREE_PRUNE_LOCKED = 1u << 1, + /* Prune checked out working tree */ + GIT_WORKTREE_PRUNE_WORKING_TREE = 1u << 2, +} git_worktree_prune_t; + +/** + * Prune working tree + * + * Prune the working tree, that is remove the git data + * structures on disk. The repository will only be pruned of + * `git_worktree_is_prunable` succeeds. + * + * @param wt Worktree to prune + * @param flags git_worktree_prune_t flags + * @return 0 or an error code + */ +GIT_EXTERN(int) git_worktree_prune(git_worktree *wt, unsigned flags); + /** @} */ GIT_END_DECL #endif diff --git a/src/worktree.c b/src/worktree.c index fa5a916c0..95a2757fe 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -356,3 +356,67 @@ out: return ret; } + +int git_worktree_prune(git_worktree *wt, unsigned flags) +{ + git_buf reason = GIT_BUF_INIT, path = GIT_BUF_INIT; + char *wtpath; + int err; + + if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 && + git_worktree_is_locked(&reason, wt)) + { + if (!reason.size) + git_buf_attach_notowned(&reason, "no reason given", 15); + giterr_set(GITERR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr); + + err = -1; + goto out; + } + + if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 && + git_worktree_validate(wt) == 0) + { + giterr_set(GITERR_WORKTREE, "Not pruning valid working tree"); + err = -1; + goto out; + } + + /* Delete gitdir in parent repository */ + if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0) + goto out; + if (!git_path_exists(path.ptr)) + { + giterr_set(GITERR_WORKTREE, "Worktree gitdir '%s' does not exist", path.ptr); + err = -1; + goto out; + } + if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) + goto out; + + /* Skip deletion of the actual working tree if it does + * not exist or deletion was not requested */ + if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 || + !git_path_exists(wt->gitlink_path)) + { + goto out; + } + + if ((wtpath = git_path_dirname(wt->gitlink_path)) == NULL) + goto out; + git_buf_attach(&path, wtpath, 0); + if (!git_path_exists(path.ptr)) + { + giterr_set(GITERR_WORKTREE, "Working tree '%s' does not exist", path.ptr); + err = -1; + goto out; + } + if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0) + goto out; + +out: + git_buf_free(&reason); + git_buf_free(&path); + + return err; +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 82b4ebc0d..7758b1b1c 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -395,3 +395,61 @@ void test_worktree_worktree__unlock_locked_worktree(void) git_worktree_free(wt); } + +void test_worktree_worktree__prune_valid(void) +{ + git_worktree *wt; + git_repository *repo; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID)); + + /* Assert the repository is not valid anymore */ + cl_git_fail(git_repository_open_from_worktree(&repo, wt)); + + git_worktree_free(wt); + git_repository_free(repo); +} + +void test_worktree_worktree__prune_locked(void) +{ + git_worktree *wt; + git_repository *repo; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_lock(wt, NULL)); + cl_git_fail(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID)); + cl_git_fail(git_worktree_prune(wt, ~GIT_WORKTREE_PRUNE_LOCKED)); + + /* Assert the repository is still valid */ + cl_git_pass(git_repository_open_from_worktree(&repo, wt)); + + git_worktree_free(wt); + git_repository_free(repo); +} + +void test_worktree_worktree__prune_gitdir(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID)); + + cl_assert(!git_path_exists(wt->gitdir_path)); + cl_assert(git_path_exists(wt->gitlink_path)); + + git_worktree_free(wt); +} + +void test_worktree_worktree__prune_both(void) +{ + git_worktree *wt; + + cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); + cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_WORKING_TREE | GIT_WORKTREE_PRUNE_VALID)); + + cl_assert(!git_path_exists(wt->gitdir_path)); + cl_assert(!git_path_exists(wt->gitlink_path)); + + git_worktree_free(wt); +} From 04fb12abb24810391fa19af5696eb38629d650df Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 27 Oct 2015 12:37:51 +0100 Subject: [PATCH 21/31] worktree: implement functions reading HEAD Implement `git_repository_head_for_worktree` and `git_repository_head_detached_for_worktree` for directly accessing a worktree's HEAD without opening it as a `git_repository` first. --- include/git2/repository.h | 25 +++++++++++ include/git2/worktree.h | 2 +- src/repository.c | 85 +++++++++++++++++++++++++++++++++++++ tests/worktree/repository.c | 63 +++++++++++++++++++++++++++ tests/worktree/worktree.c | 1 + 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 tests/worktree/repository.c diff --git a/include/git2/repository.h b/include/git2/repository.h index 29eb2da49..a396a5409 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -345,6 +345,17 @@ GIT_EXTERN(int) git_repository_init_ext( */ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); +/** + * Retrieve the referenced HEAD for the worktree + * + * @param out pointer to the reference which will be retrieved + * @param repo a repository object + * @param name name of the worktree to retrieve HEAD for + * @return 0 when successful, error-code otherwise + */ +GIT_EXTERN(int) git_repository_head_for_worktree(git_reference **out, git_repository *repo, + const char *name); + /** * Check if a repository's HEAD is detached * @@ -357,6 +368,20 @@ GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); */ GIT_EXTERN(int) git_repository_head_detached(git_repository *repo); +/* + * Check if a worktree's HEAD is detached + * + * A worktree's HEAD is detached when it points directly to a + * commit instead of a branch. + * + * @param repo a repository object + * @param name name of the worktree to retrieve HEAD for + * @return 1 if HEAD is detached, 0 if its not; error code if + * there was an error + */ +GIT_EXTERN(int) git_repository_head_detached_for_worktree(git_repository *repo, + const char *name); + /** * Check if the current branch is unborn * diff --git a/include/git2/worktree.h b/include/git2/worktree.h index 594ff795b..ec869fb59 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -77,7 +77,7 @@ GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); */ GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path); -/* +/** * Lock worktree if not already locked * * Lock a worktree, optionally specifying a reason why the linked diff --git a/src/repository.c b/src/repository.c index 03e43909b..445005e96 100644 --- a/src/repository.c +++ b/src/repository.c @@ -2032,6 +2032,49 @@ int git_repository_head_detached(git_repository *repo) return exists; } +static int read_worktree_head(git_buf *out, git_repository *repo, const char *name) +{ + git_buf path = GIT_BUF_INIT; + int err; + + assert(out && repo && name); + + git_buf_clear(out); + + if ((err = git_buf_printf(&path, "%s/worktrees/%s/HEAD", repo->commondir, name)) < 0) + goto out; + if (!git_path_exists(path.ptr)) + { + err = -1; + goto out; + } + + if ((err = git_futils_readbuffer(out, path.ptr)) < 0) + goto out; + git_buf_rtrim(out); + +out: + git_buf_free(&path); + + return err; +} + +int git_repository_head_detached_for_worktree(git_repository *repo, const char *name) +{ + git_buf buf = GIT_BUF_INIT; + int ret; + + assert(repo && name); + + if (read_worktree_head(&buf, repo, name) < 0) + return -1; + + ret = git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) != 0; + git_buf_free(&buf); + + return ret; +} + int git_repository_head(git_reference **head_out, git_repository *repo) { git_reference *head; @@ -2051,6 +2094,48 @@ int git_repository_head(git_reference **head_out, git_repository *repo) return error == GIT_ENOTFOUND ? GIT_EUNBORNBRANCH : error; } +int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name) +{ + git_buf buf = GIT_BUF_INIT; + git_reference *head; + int err; + + assert(out && repo && name); + + *out = NULL; + + if (git_repository_head_detached_for_worktree(repo, name)) + return -1; + if ((err = read_worktree_head(&buf, repo, name)) < 0) + goto out; + + /* We can only resolve symbolic references */ + if (git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF))) + { + err = -1; + goto out; + } + git_buf_consume(&buf, buf.ptr + strlen(GIT_SYMREF)); + + if ((err = git_reference_lookup(&head, repo, buf.ptr)) < 0) + goto out; + if (git_reference_type(head) == GIT_REF_OID) + { + *out = head; + err = 0; + goto out; + } + + err = git_reference_lookup_resolved( + out, repo, git_reference_symbolic_target(head), -1); + git_reference_free(head); + +out: + git_buf_free(&buf); + + return err; +} + int git_repository_head_unborn(git_repository *repo) { git_reference *ref = NULL; diff --git a/tests/worktree/repository.c b/tests/worktree/repository.c new file mode 100644 index 000000000..5c7595c64 --- /dev/null +++ b/tests/worktree/repository.c @@ -0,0 +1,63 @@ +#include "clar_libgit2.h" +#include "worktree_helpers.h" +#include "submodule/submodule_helpers.h" + +#include "repository.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +void test_worktree_repository__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_repository__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_repository__head(void) +{ + git_reference *ref, *head; + + cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); + cl_git_pass(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); + cl_assert(git_reference_cmp(ref, head) == 0); + + git_reference_free(ref); + git_reference_free(head); +} + +void test_worktree_repository__head_fails_for_invalid_worktree(void) +{ + git_reference *head = NULL; + + cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "invalid")); + cl_assert(head == NULL); +} + +void test_worktree_repository__head_detached(void) +{ + git_reference *ref, *head; + + cl_git_pass(git_reference_lookup(&ref, fixture.repo, "refs/heads/testrepo-worktree")); + cl_git_pass(git_repository_set_head_detached(fixture.worktree, &ref->target.oid)); + + cl_assert(git_repository_head_detached(fixture.worktree)); + cl_assert(git_repository_head_detached_for_worktree(fixture.repo, "testrepo-worktree")); + cl_git_fail(git_repository_head_for_worktree(&head, fixture.repo, "testrepo-worktree")); + + git_reference_free(ref); +} + +void test_worktree_repository__head_detached_fails_for_invalid_worktree(void) +{ + git_reference *head = NULL; + + cl_git_fail(git_repository_head_detached_for_worktree(fixture.repo, "invalid")); + cl_assert(head == NULL); +} diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 7758b1b1c..756cf387b 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "worktree_helpers.h" +#include "checkout.h" #include "repository.h" #include "worktree.h" From 4321595dcb83131675c7b38ac7fec8337476026e Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 10 Nov 2015 16:54:48 +0100 Subject: [PATCH 22/31] worktree: test basic merge functionality --- tests/worktree/merge.c | 121 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 tests/worktree/merge.c diff --git a/tests/worktree/merge.c b/tests/worktree/merge.c new file mode 100644 index 000000000..8221e4fc1 --- /dev/null +++ b/tests/worktree/merge.c @@ -0,0 +1,121 @@ +#include "clar_libgit2.h" + +#include "worktree_helpers.h" +#include "merge/merge_helpers.h" + +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + +#define MASTER_BRANCH "refs/heads/master" +#define CONFLICT_BRANCH "refs/heads/merge-conflict" + +#define CONFLICT_BRANCH_FILE_TXT \ + "<<<<<<< HEAD\n" \ + "hi\n" \ + "bye!\n" \ + "=======\n" \ + "conflict\n" \ + ">>>>>>> merge-conflict\n" \ + +static worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + +static const char *merge_files[] = { + GIT_MERGE_HEAD_FILE, + GIT_ORIG_HEAD_FILE, + GIT_MERGE_MODE_FILE, + GIT_MERGE_MSG_FILE, +}; + +void test_worktree_merge__initialize(void) +{ + setup_fixture_worktree(&fixture); +} + +void test_worktree_merge__cleanup(void) +{ + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_merge__merge_head(void) +{ + git_reference *theirs_ref, *ref; + git_annotated_commit *theirs; + + cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); + cl_git_pass(git_merge(fixture.worktree, (const git_annotated_commit **)&theirs, 1, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&ref, fixture.worktree, GIT_MERGE_HEAD_FILE)); + + git_reference_free(ref); + git_reference_free(theirs_ref); + git_annotated_commit_free(theirs); +} + +void test_worktree_merge__merge_setup(void) +{ + git_reference *ours_ref, *theirs_ref; + git_annotated_commit *ours, *theirs; + git_buf path = GIT_BUF_INIT; + unsigned i; + + cl_git_pass(git_reference_lookup(&ours_ref, fixture.worktree, MASTER_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&ours, fixture.worktree, ours_ref)); + + cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); + + cl_git_pass(git_merge__setup(fixture.worktree, + ours, (const git_annotated_commit **)&theirs, 1)); + + for (i = 0; i < ARRAY_SIZE(merge_files); i++) { + git_buf_clear(&path); + cl_git_pass(git_buf_printf(&path, "%s/%s", + fixture.worktree->path_repository, merge_files[i])); + cl_assert(git_path_exists(path.ptr)); + } + + git_buf_free(&path); + git_reference_free(ours_ref); + git_reference_free(theirs_ref); + git_annotated_commit_free(ours); + git_annotated_commit_free(theirs); +} + +void test_worktree_merge__merge_conflict(void) +{ + git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; + git_reference *theirs_ref; + git_annotated_commit *theirs; + git_index *index; + const git_index_entry *entry; + size_t i, conflicts = 0; + + cl_git_pass(git_reference_lookup(&theirs_ref, fixture.worktree, CONFLICT_BRANCH)); + cl_git_pass(git_annotated_commit_from_ref(&theirs, fixture.worktree, theirs_ref)); + + cl_git_pass(git_merge(fixture.worktree, + (const git_annotated_commit **)&theirs, 1, NULL, NULL)); + + cl_git_pass(git_repository_index(&index, fixture.worktree)); + for (i = 0; i < git_index_entrycount(index); i++) { + cl_assert(entry = git_index_get_byindex(index, i)); + + if (git_index_entry_is_conflict(entry)) + conflicts++; + } + cl_assert_equal_sz(conflicts, 3); + + git_reference_free(theirs_ref); + git_annotated_commit_free(theirs); + git_index_free(index); + + cl_git_pass(git_buf_joinpath(&path, fixture.worktree->workdir, "branch_file.txt")); + cl_git_pass(git_futils_readbuffer(&buf, path.ptr)); + cl_assert_equal_s(buf.ptr, CONFLICT_BRANCH_FILE_TXT); + + git_buf_free(&path); + git_buf_free(&buf); +} + From e3acd37b70dc6d8f1ff256b99a26b4e0f13701ef Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 6 Nov 2015 12:08:15 +0100 Subject: [PATCH 23/31] branch: implement `git_branch_is_checked_out` Implement a new function that is able to determine if a branch is checked out in any repository connected to the current repository. In particular, this is required to check if for a given repository and branch, there exists any working tree connected to that repository that is referencing this branch. --- include/git2/branch.h | 12 +++++++++ src/branch.c | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/include/git2/branch.h b/include/git2/branch.h index 34354f4e5..88fe723a0 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -245,6 +245,18 @@ GIT_EXTERN(int) git_branch_upstream_name( GIT_EXTERN(int) git_branch_is_head( const git_reference *branch); +/** + * Determine if the current branch is checked out in any linked + * repository. + * + * @param branch Reference to the branch. + * + * @return 1 if branch is checked out, 0 if it isn't, + * error code otherwise. + */ +GIT_EXTERN(int) git_branch_is_checked_out( + const git_reference *branch); + /** * Return the name of remote that the remote tracking branch belongs to. * diff --git a/src/branch.c b/src/branch.c index 7ddcb3da7..e48cb1f68 100644 --- a/src/branch.c +++ b/src/branch.c @@ -13,6 +13,7 @@ #include "refs.h" #include "remote.h" #include "annotated_commit.h" +#include "worktree.h" #include "git2/branch.h" @@ -126,6 +127,62 @@ int git_branch_create_from_annotated( repository, branch_name, commit->commit, commit->description, force); } +int git_branch_is_checked_out( + const git_reference *branch) +{ + git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; + git_strarray worktrees; + git_reference *ref = NULL; + git_repository *repo; + const char *worktree; + int found = false; + size_t i; + + assert(branch && git_reference_is_branch(branch)); + + repo = git_reference_owner(branch); + + if (git_worktree_list(&worktrees, repo) < 0) + return -1; + + for (i = 0; i < worktrees.count; i++) { + worktree = worktrees.strings[i]; + + if (git_repository_head_for_worktree(&ref, repo, worktree) < 0) + continue; + + if (git__strcmp(ref->name, branch->name) == 0) { + found = true; + git_reference_free(ref); + break; + } + + git_reference_free(ref); + } + git_strarray_free(&worktrees); + + if (found) + return found; + + /* Check HEAD of parent */ + if (git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE) < 0) + goto out; + if (git_futils_readbuffer(&buf, path.ptr) < 0) + goto out; + if (git__prefixcmp(buf.ptr, "ref: ") == 0) + git_buf_consume(&buf, buf.ptr + strlen("ref: ")); + git_buf_rtrim(&buf); + + found = git__strcmp(buf.ptr, branch->name) == 0; + +out: + git_buf_free(&buf); + git_buf_free(&path); + + return found; +} + + int git_branch_delete(git_reference *branch) { int is_head; From 143e539fd0e9b83c6a3a369d5baf508ccb51697b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 6 Nov 2015 12:33:59 +0100 Subject: [PATCH 24/31] branch: restrict branch deletion for worktrees Restrict the ability to delete branches that are checked out in any linked repository. --- src/branch.c | 6 ++++++ tests/worktree/refs.c | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/branch.c b/src/branch.c index e48cb1f68..7d5e9cb7f 100644 --- a/src/branch.c +++ b/src/branch.c @@ -206,6 +206,12 @@ int git_branch_delete(git_reference *branch) return -1; } + if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) { + giterr_set(GITERR_REFERENCE, "Cannot delete branch '%s' as it is " + "the current HEAD of a linked repository.", git_reference_name(branch)); + return -1; + } + if (git_buf_join(&config_section, '.', "branch", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) goto on_error; diff --git a/tests/worktree/refs.c b/tests/worktree/refs.c index e08e55372..38f612713 100644 --- a/tests/worktree/refs.c +++ b/tests/worktree/refs.c @@ -1,4 +1,5 @@ #include "clar_libgit2.h" +#include "worktree.h" #include "worktree_helpers.h" #define COMMON_REPO "testrepo" @@ -66,3 +67,29 @@ void test_worktree_refs__read_head(void) git_reference_free(head); } + +void test_worktree_refs__delete_fails_for_checked_out_branch(void) +{ + git_reference *branch; + + cl_git_pass(git_branch_lookup(&branch, fixture.repo, + "testrepo-worktree", GIT_BRANCH_LOCAL)); + cl_git_fail(git_branch_delete(branch)); + + git_reference_free(branch); +} + +void test_worktree_refs__delete_succeeds_after_pruning_worktree(void) +{ + git_reference *branch; + git_worktree *worktree; + + cl_git_pass(git_worktree_lookup(&worktree, fixture.repo, fixture.worktreename)); + cl_git_pass(git_worktree_prune(worktree, GIT_WORKTREE_PRUNE_VALID)); + git_worktree_free(worktree); + + cl_git_pass(git_branch_lookup(&branch, fixture.repo, + "testrepo-worktree", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); +} From 384518d09dc16b8a7dae22069e0c612e4b65c5e8 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 27 Oct 2015 14:17:52 +0100 Subject: [PATCH 25/31] repository: restrict checking out checked out branches If a branch is already checked out in a working tree we are not allowed to check out that branch in another repository. Introduce this restriction when setting a repository's HEAD. --- src/repository.c | 6 ++++++ tests/worktree/refs.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/repository.c b/src/repository.c index 445005e96..8753636cb 100644 --- a/src/repository.c +++ b/src/repository.c @@ -2529,6 +2529,12 @@ int git_repository_set_head( if (error < 0 && error != GIT_ENOTFOUND) goto cleanup; + if (ref && current->type == GIT_REF_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) && + git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) { + error = -1; + goto cleanup; + } + if (!error) { if (git_reference_is_branch(ref)) { error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, diff --git a/tests/worktree/refs.c b/tests/worktree/refs.c index 38f612713..ccac8be29 100644 --- a/tests/worktree/refs.c +++ b/tests/worktree/refs.c @@ -68,6 +68,41 @@ void test_worktree_refs__read_head(void) git_reference_free(head); } +void test_worktree_refs__set_head_fails_when_worktree_wants_linked_repos_HEAD(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_fail(git_repository_set_head(fixture.worktree, git_reference_name(head))); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_fails_when_main_repo_wants_worktree_head(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.worktree)); + cl_git_fail(git_repository_set_head(fixture.repo, git_reference_name(head))); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_works_for_current_HEAD(void) +{ + git_reference *head; + + cl_git_pass(git_repository_head(&head, fixture.repo)); + cl_git_pass(git_repository_set_head(fixture.repo, git_reference_name(head))); + + git_reference_free(head); +} + +void test_worktree_refs__set_head_fails_when_already_checked_out(void) +{ + cl_git_fail(git_repository_set_head(fixture.repo, "refs/heads/testrepo-worktree")); +} + void test_worktree_refs__delete_fails_for_checked_out_branch(void) { git_reference *branch; From 84f56cb05afe7a60501f310ba1329bb98ef8756d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 4 Nov 2016 11:59:52 +0100 Subject: [PATCH 26/31] repository: rename `path_repository` and `path_gitlink` The `path_repository` variable is actually confusing to think about, as it is not always clear what the repository actually is. It may either be the path to the folder containing worktree and .git directory, the path to .git itself, a worktree or something entirely different. Actually, the intent of the variable is to hold the path to the gitdir, which is either the .git directory or the bare repository. Rename the variable to `gitdir` to avoid confusion. While at it, also rename `path_gitlink` to `gitlink` to improve consistency. --- src/cherrypick.c | 4 ++-- src/fetchhead.c | 4 ++-- src/merge.c | 10 ++++---- src/rebase.c | 6 ++--- src/refdb_fs.c | 6 ++--- src/repository.c | 48 +++++++++++++++++++-------------------- src/repository.h | 4 ++-- src/revert.c | 4 ++-- tests/worktree/merge.c | 2 +- tests/worktree/worktree.c | 12 +++++----- 10 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/cherrypick.c b/src/cherrypick.c index ab067339e..d8b6858ae 100644 --- a/src/cherrypick.c +++ b/src/cherrypick.c @@ -28,7 +28,7 @@ static int write_cherrypick_head( git_buf file_path = GIT_BUF_INIT; int error = 0; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 && (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) >= 0 && (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) error = git_filebuf_commit(&file); @@ -49,7 +49,7 @@ static int write_merge_msg( git_buf file_path = GIT_BUF_INIT; int error = 0; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_CHERRYPICK_FILE_MODE)) < 0 || (error = git_filebuf_printf(&file, "%s", commit_msg)) < 0) goto cleanup; diff --git a/src/fetchhead.c b/src/fetchhead.c index 0d9ab2c25..6e6f3eb5e 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -115,7 +115,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs) assert(repo && fetchhead_refs); - if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0) + if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) return -1; if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) { @@ -249,7 +249,7 @@ int git_repository_fetchhead_foreach(git_repository *repo, assert(repo && cb); - if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0) + if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0) return -1; if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0) diff --git a/src/merge.c b/src/merge.c index eceadf08a..857d51311 100644 --- a/src/merge.c +++ b/src/merge.c @@ -562,7 +562,7 @@ int git_repository_mergehead_foreach( assert(repo && cb); - if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository, + if ((error = git_buf_joinpath(&merge_head_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0) return error; @@ -2277,7 +2277,7 @@ static int write_merge_head( assert(repo && heads); - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_HEAD_FILE)) < 0 || + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_HEAD_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; @@ -2305,7 +2305,7 @@ static int write_merge_mode(git_repository *repo) assert(repo); - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MODE_FILE)) < 0 || + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MODE_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; @@ -2536,7 +2536,7 @@ static int write_merge_msg( for (i = 0; i < heads_len; i++) entries[i].merge_head = heads[i]; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) < 0 || (error = git_filebuf_write(&file, "Merge ", 6)) < 0) goto cleanup; @@ -2914,7 +2914,7 @@ int git_merge__append_conflicts_to_merge_msg( if (!git_index_has_conflicts(index)) return 0; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_APPEND, GIT_MERGE_FILE_MODE)) < 0) goto cleanup; diff --git a/src/rebase.c b/src/rebase.c index b2024a439..09941a2a2 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -92,7 +92,7 @@ static int rebase_state_type( git_buf path = GIT_BUF_INIT; git_rebase_type_t type = GIT_REBASE_TYPE_NONE; - if (git_buf_joinpath(&path, repo->path_repository, REBASE_APPLY_DIR) < 0) + if (git_buf_joinpath(&path, repo->gitdir, REBASE_APPLY_DIR) < 0) return -1; if (git_path_isdir(git_buf_cstr(&path))) { @@ -101,7 +101,7 @@ static int rebase_state_type( } git_buf_clear(&path); - if (git_buf_joinpath(&path, repo->path_repository, REBASE_MERGE_DIR) < 0) + if (git_buf_joinpath(&path, repo->gitdir, REBASE_MERGE_DIR) < 0) return -1; if (git_path_isdir(git_buf_cstr(&path))) { @@ -624,7 +624,7 @@ static int rebase_init_merge( GIT_UNUSED(upstream); - if ((error = git_buf_joinpath(&state_path, repo->path_repository, REBASE_MERGE_DIR)) < 0) + if ((error = git_buf_joinpath(&state_path, repo->gitdir, REBASE_MERGE_DIR)) < 0) goto done; rebase->state_path = git_buf_detach(&state_path); diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 91183cd71..3d690630e 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -1435,13 +1435,13 @@ static int setup_namespace(git_buf *gitpath, git_repository *repo) char *parts, *start, *end; /* Not all repositories have a gitpath */ - if (repo->path_repository == NULL) + if (repo->gitdir == NULL) return 0; if (repo->commondir == NULL) return 0; /* Load the path to the repo first */ - git_buf_puts(gitpath, repo->path_repository); + git_buf_puts(gitpath, repo->gitdir); /* if the repo is not namespaced, nothing else to do */ if (repo->namespace == NULL) @@ -1877,7 +1877,7 @@ static int refdb_reflog_fs__rename(git_refdb_backend *_backend, const char *old_ &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0) return error; - if (git_buf_joinpath(&temp_path, repo->path_repository, GIT_REFLOG_DIR) < 0) + if (git_buf_joinpath(&temp_path, repo->gitdir, GIT_REFLOG_DIR) < 0) return -1; if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), old_name) < 0) diff --git a/src/repository.c b/src/repository.c index 8753636cb..55724845f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -165,8 +165,8 @@ void git_repository_free(git_repository *repo) git_buf_free(git_array_get(repo->reserved_names, i)); git_array_clear(repo->reserved_names); - git__free(repo->path_gitlink); - git__free(repo->path_repository); + git__free(repo->gitlink); + git__free(repo->gitdir); git__free(repo->commondir); git__free(repo->workdir); git__free(repo->namespace); @@ -288,7 +288,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren if (ce && ce->value) { if ((error = git_path_prettify_dir( - &worktree, ce->value, repo->path_repository)) < 0) + &worktree, ce->value, repo->gitdir)) < 0) goto cleanup; repo->workdir = git_buf_detach(&worktree); @@ -296,7 +296,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren else if (parent_path && git_path_isdir(parent_path->ptr)) repo->workdir = git_buf_detach(parent_path); else { - if (git_path_dirname_r(&worktree, repo->path_repository) < 0 || + if (git_path_dirname_r(&worktree, repo->gitdir) < 0 || git_path_to_dir(&worktree) < 0) { error = -1; goto cleanup; @@ -553,8 +553,8 @@ int git_repository_open_bare( repo = repository_alloc(); GITERR_CHECK_ALLOC(repo); - repo->path_repository = git_buf_detach(&path); - GITERR_CHECK_ALLOC(repo->path_repository); + repo->gitdir = git_buf_detach(&path); + GITERR_CHECK_ALLOC(repo->gitdir); repo->commondir = git_buf_detach(&common_path); GITERR_CHECK_ALLOC(repo->commondir); @@ -763,19 +763,19 @@ int git_repository_open_ext( repo = repository_alloc(); GITERR_CHECK_ALLOC(repo); - repo->path_repository = git_buf_detach(&path); - GITERR_CHECK_ALLOC(repo->path_repository); + repo->gitdir = git_buf_detach(&path); + GITERR_CHECK_ALLOC(repo->gitdir); if (link_path.size) { - repo->path_gitlink = git_buf_detach(&link_path); - GITERR_CHECK_ALLOC(repo->path_gitlink); + repo->gitlink = git_buf_detach(&link_path); + GITERR_CHECK_ALLOC(repo->gitlink); } if (common_path.size) { repo->commondir = git_buf_detach(&common_path); GITERR_CHECK_ALLOC(repo->commondir); } - if (repo->path_gitlink && repo->commondir && strcmp(repo->path_gitlink, repo->commondir)) + if (repo->gitlink && repo->commondir && strcmp(repo->gitlink, repo->commondir)) repo->is_worktree = 1; /* @@ -1114,7 +1114,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) git_buf index_path = GIT_BUF_INIT; git_index *index; - if ((error = git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE)) < 0) + if ((error = git_buf_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) return error; error = git_index_open(&index, index_path.ptr); @@ -1230,13 +1230,13 @@ bool git_repository__reserved_names( prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : git__prefixcmp; - if (repo->path_gitlink && - reserved_names_add8dot3(repo, repo->path_gitlink) < 0) + if (repo->gitlink && + reserved_names_add8dot3(repo, repo->gitlink) < 0) goto on_error; - if (repo->path_repository && - prefixcmp(repo->path_repository, repo->workdir) == 0 && - reserved_names_add8dot3(repo, repo->path_repository) < 0) + if (repo->gitdir && + prefixcmp(repo->gitdir, repo->workdir) == 0 && + reserved_names_add8dot3(repo, repo->gitdir) < 0) goto on_error; } } @@ -2237,7 +2237,7 @@ int git_repository_item_path(git_buf *out, git_repository *repo, git_repository_ const char *git_repository_path(git_repository *repo) { assert(repo); - return repo->path_repository; + return repo->gitdir; } const char *git_repository_workdir(git_repository *repo) @@ -2366,7 +2366,7 @@ int git_repository__set_orig_head(git_repository *repo, const git_oid *orig_head git_oid_fmt(orig_head_str, orig_head); - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_ORIG_HEAD_FILE)) == 0 && + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_ORIG_HEAD_FILE)) == 0 && (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_MERGE_FILE_MODE)) == 0 && (error = git_filebuf_printf(&file, "%.*s\n", GIT_OID_HEXSZ, orig_head_str)) == 0) error = git_filebuf_commit(&file); @@ -2387,7 +2387,7 @@ int git_repository_message(git_buf *out, git_repository *repo) git_buf_sanitize(out); - if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0) + if (git_buf_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) return -1; if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) { @@ -2408,7 +2408,7 @@ int git_repository_message_remove(git_repository *repo) git_buf path = GIT_BUF_INIT; int error; - if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0) + if (git_buf_joinpath(&path, repo->gitdir, GIT_MERGE_MSG_FILE) < 0) return -1; error = p_unlink(git_buf_cstr(&path)); @@ -2650,7 +2650,7 @@ int git_repository_state(git_repository *repo) assert(repo); - if (git_buf_puts(&repo_path, repo->path_repository) < 0) + if (git_buf_puts(&repo_path, repo->gitdir) < 0) return -1; if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE)) @@ -2692,7 +2692,7 @@ int git_repository__cleanup_files( for (error = 0, i = 0; !error && i < files_len; ++i) { const char *path; - if (git_buf_joinpath(&buf, repo->path_repository, files[i]) < 0) + if (git_buf_joinpath(&buf, repo->gitdir, files[i]) < 0) return -1; path = git_buf_cstr(&buf); @@ -2736,7 +2736,7 @@ int git_repository_is_shallow(git_repository *repo) struct stat st; int error; - if ((error = git_buf_joinpath(&path, repo->path_repository, "shallow")) < 0) + if ((error = git_buf_joinpath(&path, repo->gitdir, "shallow")) < 0) return error; error = git_path_lstat(path.ptr, &st); diff --git a/src/repository.h b/src/repository.h index 74e15e146..c328ecd21 100644 --- a/src/repository.h +++ b/src/repository.h @@ -126,8 +126,8 @@ struct git_repository { git_attr_cache *attrcache; git_diff_driver_registry *diff_drivers; - char *path_repository; - char *path_gitlink; + char *gitlink; + char *gitdir; char *commondir; char *workdir; char *namespace; diff --git a/src/revert.c b/src/revert.c index b255245bf..747938fb3 100644 --- a/src/revert.c +++ b/src/revert.c @@ -27,7 +27,7 @@ static int write_revert_head( git_buf file_path = GIT_BUF_INIT; int error = 0; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_REVERT_HEAD_FILE)) >= 0 && + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_REVERT_HEAD_FILE)) >= 0 && (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) >= 0 && (error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0) error = git_filebuf_commit(&file); @@ -49,7 +49,7 @@ static int write_merge_msg( git_buf file_path = GIT_BUF_INIT; int error = 0; - if ((error = git_buf_joinpath(&file_path, repo->path_repository, GIT_MERGE_MSG_FILE)) < 0 || + if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 || (error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_FORCE, GIT_REVERT_FILE_MODE)) < 0 || (error = git_filebuf_printf(&file, "Revert \"%s\"\n\nThis reverts commit %s.\n", commit_msgline, commit_oidstr)) < 0) diff --git a/tests/worktree/merge.c b/tests/worktree/merge.c index 8221e4fc1..36cc2a6c1 100644 --- a/tests/worktree/merge.c +++ b/tests/worktree/merge.c @@ -72,7 +72,7 @@ void test_worktree_merge__merge_setup(void) for (i = 0; i < ARRAY_SIZE(merge_files); i++) { git_buf_clear(&path); cl_git_pass(git_buf_printf(&path, "%s/%s", - fixture.worktree->path_repository, merge_files[i])); + fixture.worktree->gitdir, merge_files[i])); cl_assert(git_path_exists(path.ptr)); } diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 756cf387b..81d592951 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -117,8 +117,8 @@ void test_worktree_worktree__lookup(void) git_buf_printf(&gitdir_path, "%s/worktrees/%s", fixture.repo->commondir, "testrepo-worktree"); cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr); - cl_assert_equal_s(wt->parent_path, fixture.repo->path_repository); - cl_assert_equal_s(wt->gitlink_path, fixture.worktree->path_gitlink); + cl_assert_equal_s(wt->parent_path, fixture.repo->gitdir); + cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir); git_buf_free(&gitdir_path); @@ -196,7 +196,7 @@ void test_worktree_worktree__open_invalid_parent(void) cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir")); cl_git_pass(git_futils_writebuffer(&buf, - fixture.worktree->path_gitlink, O_RDWR, 0644)); + fixture.worktree->gitlink, O_RDWR, 0644)); cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); cl_git_fail(git_repository_open_from_worktree(&repo, wt)); @@ -254,7 +254,7 @@ void test_worktree_worktree__init_existing_worktree(void) cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr)); cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree")); - cl_assert_equal_s(wt->gitlink_path, fixture.worktree->path_gitlink); + cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink); git_buf_free(&path); git_worktree_free(wt); @@ -271,7 +271,7 @@ void test_worktree_worktree__init_existing_path(void) * the init call */ for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { cl_git_pass(git_buf_joinpath(&path, - fixture.worktree->path_repository, wtfiles[i])); + fixture.worktree->gitdir, wtfiles[i])); cl_git_pass(p_unlink(path.ptr)); } @@ -281,7 +281,7 @@ void test_worktree_worktree__init_existing_path(void) /* Verify files have not been re-created */ for (i = 0; i < ARRAY_SIZE(wtfiles); i++) { cl_git_pass(git_buf_joinpath(&path, - fixture.worktree->path_repository, wtfiles[i])); + fixture.worktree->gitdir, wtfiles[i])); cl_assert(!git_path_exists(path.ptr)); } From 39abd3adaa62f287f5cda0a63c7cfca647283e9c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 4 Nov 2016 13:39:54 +0100 Subject: [PATCH 27/31] worktree: compute workdir for worktrees opened via their gitdir When opening a worktree via the gitdir of its parent repository we fail to correctly set up the worktree's working directory. The problem here is two-fold: we first fail to see that the gitdir actually is a gitdir of a working tree and then subsequently fail to determine the working tree location from the gitdir. The first problem of not noticing a gitdir belongs to a worktree can be solved by checking for the existence of a `gitdir` file in the gitdir. This file points back to the gitlink file located in the working tree's working directory. As this file only exists for worktrees, it should be sufficient indication of the gitdir belonging to a worktree. The second problem, that is determining the location of the worktree's working directory, can then be solved by reading the `gitdir` file in the working directory's gitdir. When we now resolve relative paths and strip the final `.git` component, we have the actual worktree's working directory location. --- src/repository.c | 36 ++++++++++++++++++++++++++++++++---- src/worktree.c | 6 +++--- src/worktree.h | 2 ++ tests/worktree/open.c | 33 ++++++++++++++++++++++++++++----- tests/worktree/worktree.c | 1 + 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/repository.c b/src/repository.c index 55724845f..4b937be20 100644 --- a/src/repository.c +++ b/src/repository.c @@ -61,6 +61,7 @@ static const struct { static int check_repositoryformatversion(git_config *config); #define GIT_COMMONDIR_FILE "commondir" +#define GIT_GITDIR_FILE "gitdir" #define GIT_FILE_CONTENT_PREFIX "gitdir:" @@ -275,9 +276,10 @@ static int load_config_data(git_repository *repo, const git_config *config) static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path) { - int error; + int error; git_config_entry *ce; - git_buf worktree = GIT_BUF_INIT; + git_buf worktree = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT; if (repo->is_bare) return 0; @@ -286,7 +288,24 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren &ce, config, "core.worktree", false)) < 0) return error; - if (ce && ce->value) { + if (repo->is_worktree) { + char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); + if (!gitlink) { + error = -1; + goto cleanup; + } + + git_buf_attach(&worktree, gitlink, 0); + + if ((git_path_dirname_r(&worktree, worktree.ptr)) < 0 || + git_path_to_dir(&worktree) < 0) { + error = -1; + goto cleanup; + } + + repo->workdir = git_buf_detach(&worktree); + } + else if (ce && ce->value) { if ((error = git_path_prettify_dir( &worktree, ce->value, repo->gitdir)) < 0) goto cleanup; @@ -307,6 +326,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren GITERR_CHECK_ALLOC(repo->workdir); cleanup: + git_buf_free(&path); git_config_entry_free(ce); return error; } @@ -465,6 +485,9 @@ static int find_repo( git_path_to_dir(&path); git_buf_set(repo_path, path.ptr, path.size); + if (link_path) + git_buf_attach(link_path, + git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0); if (common_path) git_buf_swap(&common_link, common_path); @@ -775,7 +798,11 @@ int git_repository_open_ext( GITERR_CHECK_ALLOC(repo->commondir); } - if (repo->gitlink && repo->commondir && strcmp(repo->gitlink, repo->commondir)) + if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0) + goto cleanup; + /* A 'gitdir' file inside a git directory is currently + * only used when the repository is a working tree. */ + if (git_path_exists(path.ptr)) repo->is_worktree = 1; /* @@ -801,6 +828,7 @@ int git_repository_open_ext( } cleanup: + git_buf_free(&path); git_buf_free(&parent); git_config_free(config); diff --git a/src/worktree.c b/src/worktree.c index 95a2757fe..a3fe07a23 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -61,7 +61,7 @@ exit: return error; } -static char *read_link(const char *base, const char *file) +char *git_worktree__read_link(const char *base, const char *file) { git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT; @@ -136,8 +136,8 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na } if ((wt->name = git__strdup(name)) == NULL - || (wt->commondir_path = read_link(path.ptr, "commondir")) == NULL - || (wt->gitlink_path = read_link(path.ptr, "gitdir")) == NULL + || (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL + || (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL || (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) { error = -1; goto out; diff --git a/src/worktree.h b/src/worktree.h index 0e1a88d98..b8e527968 100644 --- a/src/worktree.h +++ b/src/worktree.h @@ -30,4 +30,6 @@ struct git_worktree { int locked:1; }; +char *git_worktree__read_link(const char *base, const char *file); + #endif diff --git a/tests/worktree/open.c b/tests/worktree/open.c index 54a8af4a3..f5b668177 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -5,10 +5,13 @@ #define WORKTREE_PARENT "submodules-worktree-parent" #define WORKTREE_CHILD "submodules-worktree-child" +#define COMMON_REPO "testrepo" +#define WORKTREE_REPO "testrepo-worktree" + void test_worktree_open__repository(void) { worktree_fixture fixture = - WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree"); + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); setup_fixture_worktree(&fixture); cl_assert(git_repository_path(fixture.worktree) != NULL); @@ -20,18 +23,38 @@ void test_worktree_open__repository(void) cleanup_fixture_worktree(&fixture); } +void test_worktree_open__open_discovered_worktree(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + git_buf path = GIT_BUF_INIT; + git_repository *repo; + + setup_fixture_worktree(&fixture); + + cl_git_pass(git_repository_discover(&path, + git_repository_workdir(fixture.worktree), false, NULL)); + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert_equal_s(git_repository_workdir(fixture.worktree), + git_repository_workdir(repo)); + + git_buf_free(&path); + git_repository_free(repo); + cleanup_fixture_worktree(&fixture); +} + void test_worktree_open__repository_with_nonexistent_parent(void) { git_repository *repo; - cl_fixture_sandbox("testrepo-worktree"); - cl_git_pass(p_chdir("testrepo-worktree")); + cl_fixture_sandbox(WORKTREE_REPO); + cl_git_pass(p_chdir(WORKTREE_REPO)); cl_git_pass(cl_rename(".gitted", ".git")); cl_git_pass(p_chdir("..")); - cl_git_fail(git_repository_open(&repo, "testrepo-worktree")); + cl_git_fail(git_repository_open(&repo, WORKTREE_REPO)); - cl_fixture_cleanup("testrepo-worktree"); + cl_fixture_cleanup(WORKTREE_REPO); } void test_worktree_open__submodule_worktree_parent(void) diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 81d592951..959c56520 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -217,6 +217,7 @@ void test_worktree_worktree__init(void) /* Open and verify created repo */ cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-new/") == 0); cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL)); git_buf_free(&path); From 1fd6e035ddb27a0271c5cc734698835317e93249 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 7 Nov 2016 10:23:34 +0100 Subject: [PATCH 28/31] worktree: test opening discovered submodule worktrees --- tests/worktree/open.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/worktree/open.c b/tests/worktree/open.c index f5b668177..2dc445a66 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -92,3 +92,30 @@ void test_worktree_open__submodule_worktree_child(void) cleanup_fixture_worktree(&child_fixture); cleanup_fixture_worktree(&parent_fixture); } + +void test_worktree_open__open_discovered_submodule_worktree(void) +{ + worktree_fixture parent_fixture = + WORKTREE_FIXTURE_INIT("submodules", WORKTREE_PARENT); + worktree_fixture child_fixture = + WORKTREE_FIXTURE_INIT(NULL, WORKTREE_CHILD); + git_buf path = GIT_BUF_INIT; + git_repository *repo; + + setup_fixture_worktree(&parent_fixture); + cl_git_pass(p_rename( + "submodules/testrepo/.gitted", + "submodules/testrepo/.git")); + setup_fixture_worktree(&child_fixture); + + cl_git_pass(git_repository_discover(&path, + git_repository_workdir(child_fixture.worktree), false, NULL)); + cl_git_pass(git_repository_open(&repo, path.ptr)); + cl_assert_equal_s(git_repository_workdir(child_fixture.worktree), + git_repository_workdir(repo)); + + git_buf_free(&path); + git_repository_free(repo); + cleanup_fixture_worktree(&child_fixture); + cleanup_fixture_worktree(&parent_fixture); +} From 6f6dd17cb280d7d0f45f48fb618cc02df18b868a Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Tue, 8 Nov 2016 12:13:59 +0100 Subject: [PATCH 29/31] worktree: test creating and opening submodule worktrees --- tests/worktree/worktree.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c index 959c56520..f0c423599 100644 --- a/tests/worktree/worktree.c +++ b/tests/worktree/worktree.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "worktree_helpers.h" +#include "submodule/submodule_helpers.h" #include "checkout.h" #include "repository.h" @@ -289,6 +290,33 @@ void test_worktree_worktree__init_existing_path(void) git_buf_free(&path); } +void test_worktree_worktree__init_submodule(void) +{ + git_repository *repo, *sm, *wt; + git_worktree *worktree; + git_buf path = GIT_BUF_INIT; + + cleanup_fixture_worktree(&fixture); + repo = setup_fixture_submod2(); + + cl_git_pass(git_buf_joinpath(&path, repo->workdir, "sm_unchanged")); + cl_git_pass(git_repository_open(&sm, path.ptr)); + cl_git_pass(git_buf_joinpath(&path, repo->workdir, "../worktree/")); + cl_git_pass(git_worktree_add(&worktree, sm, "repo-worktree", path.ptr)); + cl_git_pass(git_repository_open_from_worktree(&wt, worktree)); + + cl_assert_equal_s(path.ptr, wt->workdir); + cl_assert_equal_s(sm->commondir, wt->commondir); + + cl_git_pass(git_buf_joinpath(&path, sm->gitdir, "worktrees/repo-worktree/")); + cl_assert_equal_s(path.ptr, wt->gitdir); + + git_buf_free(&path); + git_worktree_free(worktree); + git_repository_free(sm); + git_repository_free(wt); +} + void test_worktree_worktree__validate(void) { git_worktree *wt; From 3f3a4ce7bcd9862ef1eb87dfc5871e4012159244 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 9 Nov 2016 14:18:22 +0100 Subject: [PATCH 30/31] worktree: test opening worktree via gitlink, gitdir and worktree --- tests/worktree/open.c | 81 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/tests/worktree/open.c b/tests/worktree/open.c index 2dc445a66..bdc8bcf9d 100644 --- a/tests/worktree/open.c +++ b/tests/worktree/open.c @@ -8,18 +8,91 @@ #define COMMON_REPO "testrepo" #define WORKTREE_REPO "testrepo-worktree" +static void assert_worktree_valid(git_repository *wt, const char *parentdir, const char *wtdir) +{ + git_buf path = GIT_BUF_INIT; + + cl_assert(wt->is_worktree); + + cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), wtdir)); + cl_git_pass(git_path_prettify(&path, path.ptr, NULL)); + cl_git_pass(git_path_to_dir(&path)); + cl_assert_equal_s(wt->workdir, path.ptr); + + cl_git_pass(git_buf_joinpath(&path, path.ptr, ".git")); + cl_git_pass(git_path_prettify(&path, path.ptr, NULL)); + cl_assert_equal_s(wt->gitlink, path.ptr); + + cl_git_pass(git_buf_joinpath(&path, clar_sandbox_path(), parentdir)); + cl_git_pass(git_buf_joinpath(&path, path.ptr, ".git")); + cl_git_pass(git_buf_joinpath(&path, path.ptr, "worktrees")); + cl_git_pass(git_buf_joinpath(&path, path.ptr, wtdir)); + cl_git_pass(git_path_prettify(&path, path.ptr, NULL)); + cl_git_pass(git_path_to_dir(&path)); + cl_assert_equal_s(wt->gitdir, path.ptr); + + git_buf_free(&path); +} + void test_worktree_open__repository(void) { worktree_fixture fixture = WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); setup_fixture_worktree(&fixture); - cl_assert(git_repository_path(fixture.worktree) != NULL); - cl_assert(git_repository_workdir(fixture.worktree) != NULL); + assert_worktree_valid(fixture.worktree, COMMON_REPO, WORKTREE_REPO); - cl_assert(!fixture.repo->is_worktree); - cl_assert(fixture.worktree->is_worktree); + cleanup_fixture_worktree(&fixture); +} +void test_worktree_open__repository_through_workdir(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + git_repository *wt; + + setup_fixture_worktree(&fixture); + + cl_git_pass(git_repository_open(&wt, WORKTREE_REPO)); + assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); + + git_repository_free(wt); + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__repository_through_gitlink(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + git_repository *wt; + + setup_fixture_worktree(&fixture); + + cl_git_pass(git_repository_open(&wt, WORKTREE_REPO "/.git")); + assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); + + git_repository_free(wt); + cleanup_fixture_worktree(&fixture); +} + +void test_worktree_open__repository_through_gitdir(void) +{ + worktree_fixture fixture = + WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO); + git_buf gitdir_path = GIT_BUF_INIT; + git_repository *wt; + + setup_fixture_worktree(&fixture); + + cl_git_pass(git_buf_joinpath(&gitdir_path, COMMON_REPO, ".git")); + cl_git_pass(git_buf_joinpath(&gitdir_path, gitdir_path.ptr, "worktrees")); + cl_git_pass(git_buf_joinpath(&gitdir_path, gitdir_path.ptr, "testrepo-worktree")); + + cl_git_pass(git_repository_open(&wt, gitdir_path.ptr)); + assert_worktree_valid(wt, COMMON_REPO, WORKTREE_REPO); + + git_buf_free(&gitdir_path); + git_repository_free(wt); cleanup_fixture_worktree(&fixture); } From 1ba242c9ab0eb323abed1b3bbc770aeb3367d855 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 3 Feb 2017 13:52:23 +0100 Subject: [PATCH 31/31] worktree: extract git_worktree_is_prunable --- include/git2/worktree.h | 18 ++++++++++++++++++ src/worktree.c | 24 +++++++++++++++++------- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/include/git2/worktree.h b/include/git2/worktree.h index ec869fb59..cad1284fa 100644 --- a/include/git2/worktree.h +++ b/include/git2/worktree.h @@ -125,6 +125,24 @@ typedef enum { GIT_WORKTREE_PRUNE_WORKING_TREE = 1u << 2, } git_worktree_prune_t; +/** + * Is the worktree prunable with the given set of flags? + * + * A worktree is not prunable in the following scenarios: + * + * - the worktree is linking to a valid on-disk worktree. The + * GIT_WORKTREE_PRUNE_VALID flag will cause this check to be + * ignored. + * - the worktree is not valid but locked. The + * GIT_WORKRTEE_PRUNE_LOCKED flag will cause this check to be + * ignored. + * + * If the worktree is not valid and not locked or if the above + * flags have been passed in, this function will return a + * positive value. + */ +GIT_EXTERN(int) git_worktree_is_prunable(git_worktree *wt, unsigned flags); + /** * Prune working tree * diff --git a/src/worktree.c b/src/worktree.c index a3fe07a23..5abc98945 100644 --- a/src/worktree.c +++ b/src/worktree.c @@ -357,11 +357,9 @@ out: return ret; } -int git_worktree_prune(git_worktree *wt, unsigned flags) +int git_worktree_is_prunable(git_worktree *wt, unsigned flags) { - git_buf reason = GIT_BUF_INIT, path = GIT_BUF_INIT; - char *wtpath; - int err; + git_buf reason = GIT_BUF_INIT; if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 && git_worktree_is_locked(&reason, wt)) @@ -369,15 +367,28 @@ int git_worktree_prune(git_worktree *wt, unsigned flags) if (!reason.size) git_buf_attach_notowned(&reason, "no reason given", 15); giterr_set(GITERR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr); + git_buf_free(&reason); - err = -1; - goto out; + return 0; } if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 && git_worktree_validate(wt) == 0) { giterr_set(GITERR_WORKTREE, "Not pruning valid working tree"); + return 0; + } + + return 1; +} + +int git_worktree_prune(git_worktree *wt, unsigned flags) +{ + git_buf path = GIT_BUF_INIT; + char *wtpath; + int err; + + if (!git_worktree_is_prunable(wt, flags)) { err = -1; goto out; } @@ -415,7 +426,6 @@ int git_worktree_prune(git_worktree *wt, unsigned flags) goto out; out: - git_buf_free(&reason); git_buf_free(&path); return err;