commit e9823c5da4fa977c46bcb97167fbdd0d70adb5ff Author: Robert Hensing Date: Mon Aug 26 20:07:04 2024 +0200 Make packbuilder interruptible using progress callback Forward errors from packbuilder->progress_cb This allows the callback to terminate long-running operations when the application is interrupted. diff --git a/include/git2/pack.h b/include/git2/pack.h index 0f6bd2ab9..bee72a6c0 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -247,6 +247,9 @@ typedef int GIT_CALLBACK(git_packbuilder_progress)( * @param progress_cb Function to call with progress information during * pack building. Be aware that this is called inline with pack building * operations, so performance may be affected. + * When progress_cb returns an error, the pack building process will be + * aborted and the error will be returned from the invoked function. + * `pb` must then be freed. * @param progress_cb_payload Payload for progress callback. * @return 0 or an error code */ diff --git a/src/libgit2/pack-objects.c b/src/libgit2/pack-objects.c index b2d80cba9..7c331c2d5 100644 --- a/src/libgit2/pack-objects.c +++ b/src/libgit2/pack-objects.c @@ -932,6 +932,9 @@ static int report_delta_progress( { int ret; + if (pb->failure) + return pb->failure; + if (pb->progress_cb) { uint64_t current_time = git_time_monotonic(); uint64_t elapsed = current_time - pb->last_progress_report_time; @@ -943,8 +946,10 @@ static int report_delta_progress( GIT_PACKBUILDER_DELTAFICATION, count, pb->nr_objects, pb->progress_cb_payload); - if (ret) + if (ret) { + pb->failure = ret; return git_error_set_after_callback(ret); + } } } @@ -976,7 +981,10 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, } pb->nr_deltified += 1; - report_delta_progress(pb, pb->nr_deltified, false); + if ((error = report_delta_progress(pb, pb->nr_deltified, false)) < 0) { + GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); + goto on_error; + } po = *list++; (*list_size)--; @@ -1124,6 +1132,10 @@ struct thread_params { size_t depth; size_t working; size_t data_ready; + + /* A pb->progress_cb can stop the packing process by returning an error. + When that happens, all threads observe the error and stop voluntarily. */ + bool stopped; }; static void *threaded_find_deltas(void *arg) @@ -1133,7 +1145,12 @@ static void *threaded_find_deltas(void *arg) while (me->remaining) { if (find_deltas(me->pb, me->list, &me->remaining, me->window, me->depth) < 0) { - ; /* TODO */ + me->stopped = true; + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); + me->working = false; + git_cond_signal(&me->pb->progress_cond); + GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_unlock(me->pb) == 0, NULL); + return NULL; } GIT_ASSERT_WITH_RETVAL(git_packbuilder__progress_lock(me->pb) == 0, NULL); @@ -1175,8 +1192,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, pb->nr_threads = git__online_cpus(); if (pb->nr_threads <= 1) { - find_deltas(pb, list, &list_size, window, depth); - return 0; + return find_deltas(pb, list, &list_size, window, depth); } p = git__mallocarray(pb->nr_threads, sizeof(*p)); @@ -1195,6 +1211,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, p[i].depth = depth; p[i].working = 1; p[i].data_ready = 0; + p[i].stopped = 0; /* try to split chunks on "path" boundaries */ while (sub_size && sub_size < list_size && @@ -1262,7 +1279,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, (!victim || victim->remaining < p[i].remaining)) victim = &p[i]; - if (victim) { + if (victim && !target->stopped) { sub_size = victim->remaining / 2; list = victim->list + victim->list_size - sub_size; while (sub_size && list[0]->hash && @@ -1286,7 +1303,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, } target->list_size = sub_size; target->remaining = sub_size; - target->working = 1; + target->working = 1; /* even when target->stopped, so that we don't process this thread again */ GIT_ASSERT(git_packbuilder__progress_unlock(pb) == 0); if (git_mutex_lock(&target->mutex)) { @@ -1299,7 +1316,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, git_cond_signal(&target->cond); git_mutex_unlock(&target->mutex); - if (!sub_size) { + if (target->stopped || !sub_size) { git_thread_join(&target->thread, NULL); git_cond_free(&target->cond); git_mutex_free(&target->mutex); @@ -1308,7 +1325,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, } git__free(p); - return 0; + return pb->failure; } #else @@ -1319,6 +1336,7 @@ int git_packbuilder__prepare(git_packbuilder *pb) { git_pobject **delta_list; size_t i, n = 0; + int error; if (pb->nr_objects == 0 || pb->done) return 0; /* nothing to do */ @@ -1327,8 +1345,10 @@ int git_packbuilder__prepare(git_packbuilder *pb) * Although we do not report progress during deltafication, we * at least report that we are in the deltafication stage */ - if (pb->progress_cb) - pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload); + if (pb->progress_cb) { + if ((error = pb->progress_cb(GIT_PACKBUILDER_DELTAFICATION, 0, pb->nr_objects, pb->progress_cb_payload)) < 0) + return git_error_set_after_callback(error); + } delta_list = git__mallocarray(pb->nr_objects, sizeof(*delta_list)); GIT_ERROR_CHECK_ALLOC(delta_list); @@ -1345,31 +1365,33 @@ int git_packbuilder__prepare(git_packbuilder *pb) if (n > 1) { git__tsort((void **)delta_list, n, type_size_sort); - if (ll_find_deltas(pb, delta_list, n, + if ((error = ll_find_deltas(pb, delta_list, n, GIT_PACK_WINDOW + 1, - GIT_PACK_DEPTH) < 0) { + GIT_PACK_DEPTH)) < 0) { git__free(delta_list); - return -1; + return error; } } - report_delta_progress(pb, pb->nr_objects, true); + error = report_delta_progress(pb, pb->nr_objects, true); pb->done = true; git__free(delta_list); - return 0; + return error; } -#define PREPARE_PACK if (git_packbuilder__prepare(pb) < 0) { return -1; } +#define PREPARE_PACK error = git_packbuilder__prepare(pb); if (error < 0) { return error; } int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) { + int error; PREPARE_PACK; return write_pack(pb, cb, payload); } int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb) { + int error; PREPARE_PACK; return write_pack(pb, &write_pack_buf, buf); diff --git a/src/libgit2/pack-objects.h b/src/libgit2/pack-objects.h index bbc8b9430..380a28ebe 100644 --- a/src/libgit2/pack-objects.h +++ b/src/libgit2/pack-objects.h @@ -100,6 +100,10 @@ struct git_packbuilder { uint64_t last_progress_report_time; bool done; + + /* A non-zero error code in failure causes all threads to shut themselves + down. Some functions will return this error code. */ + volatile int failure; }; int git_packbuilder__write_buf(git_str *buf, git_packbuilder *pb); diff --git a/tests/libgit2/pack/cancel.c b/tests/libgit2/pack/cancel.c new file mode 100644 index 000000000..a0aa9716a --- /dev/null +++ b/tests/libgit2/pack/cancel.c @@ -0,0 +1,240 @@ +#include "clar_libgit2.h" +#include "futils.h" +#include "pack.h" +#include "hash.h" +#include "iterator.h" +#include "vector.h" +#include "posix.h" +#include "hash.h" +#include "pack-objects.h" + +static git_repository *_repo; +static git_revwalk *_revwalker; +static git_packbuilder *_packbuilder; +static git_indexer *_indexer; +static git_vector _commits; +static int _commits_is_initialized; +static git_indexer_progress _stats; + +extern bool git_disable_pack_keep_file_checks; + +static void pack_packbuilder_init(const char *sandbox) { + _repo = cl_git_sandbox_init(sandbox); + /* cl_git_pass(p_chdir(sandbox)); */ + cl_git_pass(git_revwalk_new(&_revwalker, _repo)); + cl_git_pass(git_packbuilder_new(&_packbuilder, _repo)); + cl_git_pass(git_vector_init(&_commits, 0, NULL)); + _commits_is_initialized = 1; + memset(&_stats, 0, sizeof(_stats)); + p_fsync__cnt = 0; +} + +void test_pack_cancel__initialize(void) +{ + pack_packbuilder_init("small.git"); +} + +void test_pack_cancel__cleanup(void) +{ + git_oid *o; + unsigned int i; + + cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 0)); + cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, false)); + + if (_commits_is_initialized) { + _commits_is_initialized = 0; + git_vector_foreach(&_commits, i, o) { + git__free(o); + } + git_vector_free(&_commits); + } + + git_packbuilder_free(_packbuilder); + _packbuilder = NULL; + + git_revwalk_free(_revwalker); + _revwalker = NULL; + + git_indexer_free(_indexer); + _indexer = NULL; + + /* cl_git_pass(p_chdir("..")); */ + cl_git_sandbox_cleanup(); + _repo = NULL; +} + +static int seed_packbuilder(void) +{ + int error; + git_oid oid, *o; + unsigned int i; + + git_revwalk_sorting(_revwalker, GIT_SORT_TIME); + cl_git_pass(git_revwalk_push_ref(_revwalker, "HEAD")); + + while (git_revwalk_next(&oid, _revwalker) == 0) { + o = git__malloc(sizeof(git_oid)); + cl_assert(o != NULL); + git_oid_cpy(o, &oid); + cl_git_pass(git_vector_insert(&_commits, o)); + } + + git_vector_foreach(&_commits, i, o) { + if((error = git_packbuilder_insert(_packbuilder, o, NULL)) < 0) + return error; + } + + git_vector_foreach(&_commits, i, o) { + git_object *obj; + cl_git_pass(git_object_lookup(&obj, _repo, o, GIT_OBJECT_COMMIT)); + error = git_packbuilder_insert_tree(_packbuilder, + git_commit_tree_id((git_commit *)obj)); + git_object_free(obj); + if (error < 0) + return error; + } + + return 0; +} + +static int fail_stage; + +static int packbuilder_cancel_after_n_calls_cb(int stage, uint32_t current, uint32_t total, void *payload) +{ + + /* Force the callback to run again on the next opportunity regardless + of how fast we're running. */ + _packbuilder->last_progress_report_time = 0; + + if (stage == fail_stage) { + int *calls = (int *)payload; + int n = *calls; + /* Always decrement, including past zero. This way the error is only + triggered once, making sure it is picked up immediately. */ + --*calls; + if (n == 0) + return GIT_EUSER; + } + + return 0; +} + +static void test_cancel(int n) +{ + + int calls_remaining = n; + int err; + git_buf buf = GIT_BUF_INIT; + + /* Switch to a small repository, so that `packbuilder_cancel_after_n_calls_cb` + can hack the time to call the callback on every opportunity. */ + + cl_git_pass(git_packbuilder_set_callbacks(_packbuilder, &packbuilder_cancel_after_n_calls_cb, &calls_remaining)); + err = seed_packbuilder(); + if (!err) + err = git_packbuilder_write_buf(&buf, _packbuilder); + + cl_assert_equal_i(GIT_EUSER, err); +} +void test_pack_cancel__cancel_after_add_0(void) +{ + fail_stage = GIT_PACKBUILDER_ADDING_OBJECTS; + test_cancel(0); +} + +void test_pack_cancel__cancel_after_add_1(void) +{ + cl_skip(); + fail_stage = GIT_PACKBUILDER_ADDING_OBJECTS; + test_cancel(1); +} + +void test_pack_cancel__cancel_after_delta_0(void) +{ + fail_stage = GIT_PACKBUILDER_DELTAFICATION; + test_cancel(0); +} + +void test_pack_cancel__cancel_after_delta_1(void) +{ + fail_stage = GIT_PACKBUILDER_DELTAFICATION; + test_cancel(1); +} + +void test_pack_cancel__cancel_after_delta_0_threaded(void) +{ +#ifdef GIT_THREADS + git_packbuilder_set_threads(_packbuilder, 8); + fail_stage = GIT_PACKBUILDER_DELTAFICATION; + test_cancel(0); +#else + cl_skip(); +#endif +} + +void test_pack_cancel__cancel_after_delta_1_threaded(void) +{ +#ifdef GIT_THREADS + git_packbuilder_set_threads(_packbuilder, 8); + fail_stage = GIT_PACKBUILDER_DELTAFICATION; + test_cancel(1); +#else + cl_skip(); +#endif +} + +static int foreach_cb(void *buf, size_t len, void *payload) +{ + git_indexer *idx = (git_indexer *) payload; + cl_git_pass(git_indexer_append(idx, buf, len, &_stats)); + return 0; +} + +void test_pack_cancel__foreach(void) +{ + git_indexer *idx; + + seed_packbuilder(); + +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); +#else + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); +#endif + + cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx)); + cl_git_pass(git_indexer_commit(idx, &_stats)); + git_indexer_free(idx); +} + +static int foreach_cancel_cb(void *buf, size_t len, void *payload) +{ + git_indexer *idx = (git_indexer *)payload; + cl_git_pass(git_indexer_append(idx, buf, len, &_stats)); + return (_stats.total_objects > 2) ? -1111 : 0; +} + +void test_pack_cancel__foreach_with_cancel(void) +{ + git_indexer *idx; + + seed_packbuilder(); + +#ifdef GIT_EXPERIMENTAL_SHA256 + cl_git_pass(git_indexer_new(&idx, ".", GIT_OID_SHA1, NULL)); +#else + cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL)); +#endif + + cl_git_fail_with( + git_packbuilder_foreach(_packbuilder, foreach_cancel_cb, idx), -1111); + git_indexer_free(idx); +} + +void test_pack_cancel__keep_file_check(void) +{ + assert(!git_disable_pack_keep_file_checks); + cl_git_pass(git_libgit2_opts(GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, true)); + assert(git_disable_pack_keep_file_checks); +} diff --git a/tests/resources/small.git/HEAD b/tests/resources/small.git/HEAD new file mode 100644 index 0000000000000000000000000000000000000000..cb089cd89a7d7686d284d8761201649346b5aa1c GIT binary patch literal 23 ecmXR)O|w!cN=+-)&qz&7Db~+TEG|hc;sO9;xClW2 literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/config b/tests/resources/small.git/config new file mode 100644 index 0000000000000000000000000000000000000000..07d359d07cf1ed0c0074fdad71ffff5942f0adfa GIT binary patch literal 66 zcmaz}&M!)h<>D+#Eyypk5{uv*03B5png9R* literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/description b/tests/resources/small.git/description new file mode 100644 index 0000000000000000000000000000000000000000..498b267a8c7812490d6479839c5577eaaec79d62 GIT binary patch literal 73 zcmWH|%S+5nO;IRHEyyp$t+PQ$;d2LNXyJgRZve!Elw`VEGWs$&r??@ Q$yWgB0LrH#Y0~2Y0PnOK(EtDd literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/applypatch-msg.sample b/tests/resources/small.git/hooks/applypatch-msg.sample new file mode 100755 index 0000000000000000000000000000000000000000..dcbf8167fa503f96ff6a39c68409007eadc9b1f3 GIT binary patch literal 535 zcmY+AX;Q;542A#a6e8^~FyI8r&I~hf2QJ{GO6(?HuvEG*+#R{4EI%zhfA8r{j%sh$ zHE~E-UtQd8{bq4@*S%jq3@bmxwQDXGv#o!N`o3AHMw3xD)hy0#>&E&zzl%vRffomqo=v6>_2NRa#TwDdYvTVQyueO*15Nlo%=#DXgC0bhF3vTa`LQGaO9;jeD$OP?~ za$G4Q{z+Q_{5V?5h;a-noM$P{<>Q~j4o7u%#P6^o^16{y*jU=-K8GYD_dUtdj4FSx zSC0C!DvAnv%S!4dgk XB^)11aoGMJPCqWs%IS0YSv(eBT&%T6 literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/commit-msg.sample b/tests/resources/small.git/hooks/commit-msg.sample new file mode 100755 index 0000000000000000000000000000000000000000..f3780f92349638ebe32f6baf24c7c3027675d7c9 GIT binary patch literal 953 zcmaJy@-{3h^^Cx;#d0zEA@DDc$nY4ez&|=%jTg@_HU*ub=!!y$xW09TSjlj z(`I@QCsM`!9&80$I98wsQ8yK#)Orb<8re8FjkKh630D$QUDwi~(gkX=RunYm$rDjk zlp%RUSnzA#6yjdG5?T?2DcYKp+v_lts0ljn&bh3J0bD5@N@1UKZ190O6ZeWr-BuZ^ zWRebCX%(%=Xoj#(xYk1Cjtr!=tyBesf@m6}8zY6Ijbz9i9ziI_jG9MvR zDH*e>^ga9IR?2wrSrAVm;eButj4Y>7(E2?b~jsu>& zRKCJ7bp#19sqYh627wD%D9R$8=Ml$TNlumDypl~$jBu*G>5fIR^FB0h0Ex&TGZNr> zL5hs1_K>taRb!|ThN9ns7^@4MXKP+6aGI_UK)T-M#rcP$;kN(Vcf#P)+5GzWa{l@J z>-E{`$1iiNVYxq27}j;uo%;)r3kJI2xCFF~Ux;$Q%) wjbk6JlDCM`jU&P+UVOvg`|iYl<7~9k>HHB4I;pdlQ=I-^$DrHaN$@lH1?P!0U;qFB literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/fsmonitor-watchman.sample b/tests/resources/small.git/hooks/fsmonitor-watchman.sample new file mode 100755 index 0000000000000000000000000000000000000000..41184ebc318c159f51cd1ebe2290559805df89d8 GIT binary patch literal 4777 zcmbtYYi}F368$Xwipg4lq(BeHMvzvH-4;n7DGJBPqq#tw3aed8+IU5-m)yvL>;Cqh z8FFRGj$`9CA8aoJ?j^$%==FV``-=rhLcPW`McSytRm~mEO7_&_cAVZrf1fFy*ha@8oe%*-aBYE zcjzZg>LOkgxuUr-XJnHyD;zmPnRaSc#!k_P*d_BttRdc+J6G7za5#+^Y1nkc2Oowk`ya47uUR3Feu?B(w;S{(VYzxh}q-=#zP@uxSx{wbyPUMFU;K(06)$o{07&3yI?q{GqMcQ1c_^M<0< zF4acAV)Il-V(rCTC1(;bsZ*}bl8dmejAk~yb`B}!^0;g^(o9kGUfZfDOvyp@x4OQt zSgWh6T|3eq;9MFs8-#z+FDM1h(IjRUP|``PxupgJ7CUHOH90gbgl^2~97`?_X{P)) zB*$r1cDlF-%azKND}?Gv`2K8-9v5e`gQoft=j?T<&a13c^!wY_$D`5z-X1g?ty&6- zQN50{8?bUk9AI->^W@~~nkOghHIC2YN+AXkLQG_2-{Pq3%{`3KUMeG$iIn%%^6*NYb zn|_BdV#C)n4565VccX;uT8&z3vSi!HXGbUj2B!R zdz~&#fk#L-&k$fLwo$4?>12g@AXOKFekuo#6EHB%gmpD?1eyh%N8s{2wGoTu z*@6cEZ^ZW!FAF_|JL`NkV7k}0ow|-2jHwbgH0;c@Dq*o?@&c*HnGdyx6^su8Qk%2{ z*ye(dxO*6-&>qn1+zw}tc6;=sOX{4WB=VqjTS^))y1jlX2Q;=e!qMmFA5lC$#;BxC z=Y%tRpWxb+_uQAvAw7Q{HGV#R$xb&udLCzZ+HN?kTyB};1EJ8UlQ5!>5eGW@)RX0n zkjj>EF!3=0Gl^8dzv$B^NMGRxJoqN4A`xq-@wCbrx*u2NmIJ1xZ%H zh;{|4T3(!E9sY#Ni(wUJYs1MmIc9bl)(4Nl3_wD_BWB>i<1S(LX7m*{Q7PU$muMS* zM!%0EZx-Vw=Zey;erC?SNxF;pY@^A%-krqzfLV2meBp1vWdyArFYn`DD19T)Hw(?n z)}{NP(Lk(o*?gl#B@pP7^*r|=;PIDT4|F#{2Hzh-AL0Rv$6uT;n|WzE4=slK?on@(fZeGhRgQCu56qB z{+n81Az96qnQjMY*-*r-KV*7;Z#4QuJRJJV$M^KdldiMhj?ImK6~FvwJ*L5a){QoM=L5TYHkGO1$UrO3`a>{?Opw|b zG(#59NQ#jFL9v~vgOVkM@^^(^A}onOE))yWEwhIlk&{ZyseZ^O0b=w8&O=BK{k<5B k^Q-B@eG}LeHrquz%(SVEp_N)VhYZikCW__82JXfD17`J9Qvd(} literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/pre-applypatch.sample b/tests/resources/small.git/hooks/pre-applypatch.sample new file mode 100755 index 0000000000000000000000000000000000000000..625837e25f91421b8809a097f4a3103dd387ef31 GIT binary patch literal 481 zcmY+ATTa6;5Jms9iouO45IBJXEg&Jm9@v1LPHMM_ZR|;#6tQh$71hSXq*MxP;V& zj0cY7SCL=x4`a46sF)C>94Gk%=3q$W2s;j6iHtB2$R0%gix4oK@&T~=ALd_o*CKxt I-`Pv{1Bpzc>;M1& literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/pre-commit.sample b/tests/resources/small.git/hooks/pre-commit.sample new file mode 100755 index 0000000000000000000000000000000000000000..10b39b2e26981b8f87ea424e735ef87359066dbb GIT binary patch literal 1706 zcmZuxU2ohr5PY_N#pZ0-F<{-v&v-X^RA+u>k}E$4d&uD7=g_fA8+pNNV=4s0|iD3p<=DTXClTS zXV23tJ;ECmN@M0j@zUAKEYW@3bv!SeYZ8ZH`YQNTApFVNc;F|9r5p4TqGs=>8E?6y zi|gY{iM#PG1nL?UE9YCnWTk72kgZPG*Usqw!~Qd3c?~@w2?%eg@~)+VlSs6N5Yf2^ zz;owF#K#r^&KMq1A`oqVGFpD&-!Pv|Rc zO3KSqA@h9nSc%bm`0)Amk6*J}@14J*1-219l%%7D!Pl}UK>|lVi0Dfgu2jN3WC!uL z0ej??b2iSehVgdnWHmZV4kUo*QL#aiIp}U=9x)IXk}JJ7VQ;CI9Rtn5e0VcjbYcVt+`x5D+svCGD;Z5hm*E$jSEQZ%SQ(}oLgslTvrKK@9Qf#b!hajVFnp9@oIix;NcI9Wk xjnh0ya!AWet{I7YpD;y6HXyzI*lfSvH=o6*7mJZPkuaYpm>vzZ`wyGEBtOQPo|pgt literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/pre-push.sample b/tests/resources/small.git/hooks/pre-push.sample new file mode 100755 index 0000000000000000000000000000000000000000..02cbd80c287f959fe33975bb66c56293e3f5b396 GIT binary patch literal 1431 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 YOmM2;hq8Egm*4=7_P9T{21QBYH*F=mfB*mh literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/prepare-commit-msg.sample b/tests/resources/small.git/hooks/prepare-commit-msg.sample new file mode 100755 index 0000000000000000000000000000000000000000..b1970da1b6d3f42f00069fd17c325de72cda812e GIT binary patch literal 1702 zcmb_cTW{Mo6n>t6#i?x6xmZ$SFLf{QfG*3r0L?Pg?px55l8$UTGO3bO;spKi{V3XX z))weX0X>M9bNMcZ-6yG%>(n}JI2|25dr}WZBP@ih?JX^+@ zu#5O48P>yRX(mfDIhYP)doc1&TADZa@ZGpusJ$6G+e$ZMcmC zoOosDQPS}l{H?YPsq(4;0SGkATa9eeqAaDcjq8n2wALbFwU@2i@FAaRV!=uw-nwx1gKn2SvY z>Ff>;2sg!+Hxfkwv1lsiii=p6WenF=5)6LZcQaZ=aS_}+-4Y&?!@HWh|<^gJ21!|T@+%On#w6azxPHV}XsRbe*w zR_TZ2XEsQa1lPK~biYqg@0-RW@5J1@=<87cFzEUABdCoFH2CZo?}l(Z*!OFqUxo>K z_d`l#4d9|H6;VPT{X?^{VJ>oL|D7K{BJwwqB>`YcPoGk+9hbvHnoQ{EM|kPgD_`wk zKm4#2xu;-y`RAm!=L_BnLvJ8$AZm8@?)v<%vwvsw8AF2x6!mTT;c72A_~U9nIq0ST zv)N0!I!^1p=g8-RQfx5)E_Mb_4I2vtQpI30XZ&t-9h5!Hn literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/hooks/push-to-checkout.sample b/tests/resources/small.git/hooks/push-to-checkout.sample new file mode 100755 index 0000000000000000000000000000000000000000..a80611e18896f212c390d845e49a3f6d5693b41d GIT binary patch literal 2840 zcmai0U31$u5PXh)#YOS7cE^-rw@uolNhe9&aUS|HtvhX>G$45tVUYj>fRdF?|9kfU zNR~aG=E)WbEbeyq7JTw}ZuHIE2kUtL<AoeCNptd-NM1aZLhESzC;I`+Ns zfmNNjdAp^W8#Q*}l>CT7RB9F5(BbI8ly2l~+E};JW|>&d1)=epZ-8vm8ppkbEVn#R zt30a5A-c(YQR8eM5%;|UAnO>rt!&@x@G@yp+92%w-}%(5P_+P&Wf_zb$f-Qrl5(7z z2ah(bkE;!DK(&aAMuQ%1TS>ai?wSXCOCSj=_}8x4IbCx^$}9q)whwv)SBt| zg#MX4;;Oau`m=MI9(^&zPbueY@~>3*ixX%mvR5m_1&nAg@ZKvY1E$O}&EtLiG;mhV z1xhMIm~fGjmf_#{62f`y;09?I7M1W2tWQvz<}i9lR>OpQyUJi45_&*pQus&EkwY<> zI|ZAx=*3i9a-)g)hXkvO7>UJ5MNgL(Z+-wpXVcgbSgpmFmbf1~DPA(OVGI&FNLeIE zNH!_aiH$vsif$_j7=T2{cS(!DOI`~bn@)vSd-0d7xL=DF;UNP|tW}4ih>DvHtu9tY_pbJ6x(6E*hxgC zzNDao%qlr-IE%YGbS4hF!n!on7#W3$bX-_hbZAaws^nHu#)Dx=WzdbJ>AKzAy@T$x zSWE^x9+|TEHVEPyaPYa0DOChp?AeHSBBDbZNokQpAY{lE!7geZI=jV)G^2@l)&91Zb1+`T+oq9wWF zRV~kGTGce0O~p^6mj{kT5kL(pv>r;Lvd7VDX*P>A^Th`$3cWO0L81p4Ysdo3ZP1(SrR-peEdTo;-@bkB((G zPHYQXUL!@Q$e(OQ;R9r%@Afz+50I7>*^^c&&|E*r-jN)LH=pM4AqMwWxSv|nqjddE Z4{_hwv8!W(T zYw`X3V>TCdnSD1ru8&`j=2DIPbCT@SnIgUw>$+lEYP}+x8(BMYnr=iT3*ndq)xzaV z>I+qjv}vC#8_9M+b1p#uNS0M0)q

8!3p_LRQ0MA3M`!2foxzRUjbFY@}O~(ki=S zqscnq8cU*dY)D$$cqE}n)V0yIk>CNKHCrndOtSP*HbOb;nbwAHSb;R+gs^?^Dve%) zoW}t(*D}$>O3ab0TS^-;J|u&sb-PkZzo#kn*#xYt(;FGuwzSb^g&RDiGcOz9TB;Hu`nJh)$W=C=XCSm2AY=$w3G3P-V#Oo+N*;#2 z4ijJ-pBZ=;T(RTgp_HYrD!uW-dTMfkuqY5jwOy)~gM;#=P^i{!l7`pXTS^s(&^{RU zydaw}OpS#^D1cXM8?FW+fh`t7D(g;yr6|}fdaNtZBx3hlK~IpkTu3!Qq%R+zAo#t}Bs8^3$vHD+-TGT@`F>H1Cc#WAVW;&$S6%fE2d6@kLS0g&ihIM{}0z z8#XhD>b>3{(BH|Px7}&lJ4%y1v(CihZJx@8MPoGdl*BJGD;usf*iS7%;{Joe; zNFuBa>*~o&qETDPo~u&~$FxE1xb^x&(CbE`Y3GfsibL2rl+L;>P6j&Y3U>K$mkp*6 zd`Q{<^+^&;GskGjwD-%!boR&i-TCA9UOR|@=GYb5x#+dhd7fkaVIR^pol`Mv+rUbmZ43dVL6^S7g3{NsPiG$iy$5EDB% z6KIgnb$H(n&t3e4E6d4V7w^B?JS}JkG)PM6+X3Co`SQs($O*AA+MG~{S7RJ=cy-l& z>~%3y`tjfx2>uOutB_^s ziwG=e=ch|FQ0IkN91US7rhdQkXhwwt$gU0WEVDjo=IPb+?6PC=s8}J*ua(Ms))`UL fi$|vMHn?H_tSE3ettp-hLlsZCxaLX8(nU;bVRB;Ce6@s#eu2|WvLz>- zvy(&>Gyfp@+BtKnpqWkKi^+v{4jn_pNw_zeuxETifiGO|)w}OANj2n2D^K=o3j6P6uOL70#cbA{uzWXDlk1wr9GV1X(2W{RuTvjXV zCmd8u zH%V`94=q3)Dk)PHNrnFC(T1)Om6f{Usj;u1R->&XoCYVK2V3ZlgZuF?N}1+33OER*x z*9Z=L=zI8CN>A_^jYjt0F$psO$sL=38q5q|SG)qCN6{^>RFh5E&l5GZ$pEahnF&d+ z5c>64t}uJPkf~_!VUj#&N%nC-gUMj%=@B=!V>&}xtj2%@-mOm#rQUSJ3(ccmc+fza znZ#uxF>N?QN5UrIEd!5RgHEfW#;(nKYF+D<*rdshJ$X-z2OZ2X;)nn@KSVdVhaA?}@3;6gZxb4v zozoWSr{{+!h}zGpumG3H`=AvWpm^9kW;J$Jp^Xl*?8ckr`fqN%c|Z;VC0|cM4vSrk zH_O8Yvh85nvJp^;``wo8=z0f`FWg?`>gO#y1hjX1{}rTlg9rwIKia8eyGexA3GnuR z`Rg~XZoW;0pA)vI8=p5!+6sIn#C^FCvR>ffv39h6SCNi9v);%WD;WZ`of_MgwyRWy z-yY%n*Y>X89W-v4`Ff%bx$Vkn}$!Ay}rnY6F$m-Kg*KD_+;Lx#g4|^&N I02NaX#p`nv=Kufz literal 0 HcmV?d00001 diff --git a/tests/resources/small.git/objects/af/5626b4a114abcb82d63db7c8082c3c4756e51b b/tests/resources/small.git/objects/af/5626b4a114abcb82d63db7c8082c3c4756e51b new file mode 100644 index 0000000000000000000000000000000000000000..822bc151862ec3763cf2d3fa2372b93bbd3a4b65 GIT binary patch literal 30 mcmb>0i}&W3IZ_@1U=^!a~EV1casc=c+{&un1qQN*i9hD|0|m(2n|iwp*q%W z%N;b$hu%cM`$TMo*~EnC1BFP&Pfj~;jZVKXQ96s_PhV<-XAROi+@-v8dBLUa`!;GB k^iXlEv8$>R)1G>9th&t3j;s7J{?^9n|7U^`%mXoWC24Q^m!3%@{ literal 0 HcmV?d00001