Skip to content

Commit

Permalink
Merge branch 'mt/parallel-checkout-part-3'
Browse files Browse the repository at this point in the history
The final part of "parallel checkout".

* mt/parallel-checkout-part-3:
  ci: run test round with parallel-checkout enabled
  parallel-checkout: add tests related to .gitattributes
  t0028: extract encoding helpers to lib-encoding.sh
  parallel-checkout: add tests related to path collisions
  parallel-checkout: add tests for basic operations
  checkout-index: add parallel checkout support
  builtin/checkout.c: complete parallel checkout support
  make_transient_cache_entry(): optionally alloc from mem_pool
  • Loading branch information
gitster committed May 16, 2021
2 parents 644f4a2 + 87094fc commit a737e1f
Show file tree
Hide file tree
Showing 16 changed files with 734 additions and 49 deletions.
2 changes: 1 addition & 1 deletion builtin/checkout--worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ static void packet_to_pc_item(const char *buffer, int len,
}

memset(pc_item, 0, sizeof(*pc_item));
pc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len);
pc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len, NULL);
pc_item->ce->ce_namelen = fixed_portion->name_len;
pc_item->ce->ce_mode = fixed_portion->ce_mode;
memcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen);
Expand Down
24 changes: 15 additions & 9 deletions builtin/checkout-index.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "cache-tree.h"
#include "parse-options.h"
#include "entry.h"
#include "parallel-checkout.h"

#define CHECKOUT_ALL 4
static int nul_term_line;
Expand Down Expand Up @@ -115,7 +116,7 @@ static int checkout_file(const char *name, const char *prefix)
return -1;
}

static void checkout_all(const char *prefix, int prefix_length)
static int checkout_all(const char *prefix, int prefix_length)
{
int i, errs = 0;
struct cache_entry *last_ce = NULL;
Expand Down Expand Up @@ -144,11 +145,7 @@ static void checkout_all(const char *prefix, int prefix_length)
}
if (last_ce && to_tempfile)
write_tempfile_record(last_ce->name, prefix);
if (errs)
/* we have already done our error reporting.
* exit with the same code as die().
*/
exit(128);
return !!errs;
}

static const char * const builtin_checkout_index_usage[] = {
Expand Down Expand Up @@ -184,6 +181,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
int force = 0, quiet = 0, not_new = 0;
int index_opt = 0;
int err = 0;
int pc_workers, pc_threshold;
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
Expand Down Expand Up @@ -238,6 +236,10 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
}

get_parallel_checkout_configs(&pc_workers, &pc_threshold);
if (pc_workers > 1)
init_parallel_checkout();

/* Check out named files first */
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
Expand Down Expand Up @@ -277,12 +279,16 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
strbuf_release(&buf);
}

if (all)
err |= checkout_all(prefix, prefix_length);

if (pc_workers > 1)
err |= run_parallel_checkout(&state, pc_workers, pc_threshold,
NULL, NULL);

if (err)
return 1;

if (all)
checkout_all(prefix, prefix_length);

if (is_lock_file_locked(&lock_file) &&
write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
die("Unable to write new index file");
Expand Down
22 changes: 18 additions & 4 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "wt-status.h"
#include "xdiff-interface.h"
#include "entry.h"
#include "parallel-checkout.h"

static const char * const checkout_usage[] = {
N_("git checkout [<options>] <branch>"),
Expand Down Expand Up @@ -230,7 +231,8 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos,
return error(_("path '%s' does not have their version"), ce->name);
}

static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts)
static int checkout_merged(int pos, const struct checkout *state,
int *nr_checkouts, struct mem_pool *ce_mem_pool)
{
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
Expand Down Expand Up @@ -291,11 +293,10 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko
if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid))
die(_("Unable to add merge result for '%s'"), path);
free(result_buf.ptr);
ce = make_transient_cache_entry(mode, &oid, path, 2);
ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool);
if (!ce)
die(_("make_cache_entry failed for path '%s'"), path);
status = checkout_entry(ce, state, NULL, nr_checkouts);
discard_cache_entry(ce);
return status;
}

Expand Down Expand Up @@ -359,19 +360,27 @@ static int checkout_worktree(const struct checkout_opts *opts,
int nr_checkouts = 0, nr_unmerged = 0;
int errs = 0;
int pos;
int pc_workers, pc_threshold;
struct mem_pool ce_mem_pool;

state.force = 1;
state.refresh_cache = 1;
state.istate = &the_index;

mem_pool_init(&ce_mem_pool, 0);
get_parallel_checkout_configs(&pc_workers, &pc_threshold);
init_checkout_metadata(&state.meta, info->refname,
info->commit ? &info->commit->object.oid : &info->oid,
NULL);

enable_delayed_checkout(&state);

if (pc_workers > 1)
init_parallel_checkout();

/* TODO: audit for interaction with sparse-index. */
ensure_full_index(&the_index);

for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
if (ce->ce_flags & CE_MATCHED) {
Expand All @@ -387,10 +396,15 @@ static int checkout_worktree(const struct checkout_opts *opts,
&nr_checkouts, opts->overlay_mode);
else if (opts->merge)
errs |= checkout_merged(pos, &state,
&nr_unmerged);
&nr_unmerged,
&ce_mem_pool);
pos = skip_same_name(ce, pos) - 1;
}
}
if (pc_workers > 1)
errs |= run_parallel_checkout(&state, pc_workers, pc_threshold,
NULL, NULL);
mem_pool_discard(&ce_mem_pool, should_validate_cache_entries());
remove_marked_cache_entries(&the_index, 1);
remove_scheduled_dirs();
errs |= finish_delayed_checkout(&state, &nr_checkouts);
Expand Down
2 changes: 1 addition & 1 deletion builtin/difftool.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ static int checkout_path(unsigned mode, struct object_id *oid,
struct cache_entry *ce;
int ret;

ce = make_transient_cache_entry(mode, oid, path, 0);
ce = make_transient_cache_entry(mode, oid, path, 0, NULL);
ret = checkout_entry(ce, state, NULL, NULL);

discard_cache_entry(ce);
Expand Down
14 changes: 9 additions & 5 deletions cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,16 +370,20 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate,
size_t name_len);

/*
* Create a cache_entry that is not intended to be added to an index.
* Caller is responsible for discarding the cache_entry
* with `discard_cache_entry`.
* Create a cache_entry that is not intended to be added to an index. If
* `ce_mem_pool` is not NULL, the entry is allocated within the given memory
* pool. Caller is responsible for discarding "loose" entries with
* `discard_cache_entry()` and the memory pool with
* `mem_pool_discard(ce_mem_pool, should_validate_cache_entries())`.
*/
struct cache_entry *make_transient_cache_entry(unsigned int mode,
const struct object_id *oid,
const char *path,
int stage);
int stage,
struct mem_pool *ce_mem_pool);

struct cache_entry *make_empty_transient_cache_entry(size_t name_len);
struct cache_entry *make_empty_transient_cache_entry(size_t len,
struct mem_pool *ce_mem_pool);

/*
* Discard cache entry.
Expand Down
1 change: 1 addition & 0 deletions ci/run-build-and-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ linux-gcc)
export GIT_TEST_ADD_I_USE_BUILTIN=1
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
export GIT_TEST_WRITE_REV_INDEX=1
export GIT_TEST_CHECKOUT_WORKERS=2
make test
;;
linux-clang)
Expand Down
18 changes: 18 additions & 0 deletions parallel-checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "sigchain.h"
#include "streaming.h"
#include "thread-utils.h"
#include "trace2.h"

struct pc_worker {
struct child_process cp;
Expand All @@ -34,6 +35,20 @@ static const int DEFAULT_NUM_WORKERS = 1;

void get_parallel_checkout_configs(int *num_workers, int *threshold)
{
char *env_workers = getenv("GIT_TEST_CHECKOUT_WORKERS");

if (env_workers && *env_workers) {
if (strtol_i(env_workers, 10, num_workers)) {
die("invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'",
env_workers);
}
if (*num_workers < 1)
*num_workers = online_cpus();

*threshold = 0;
return;
}

if (git_config_get_int("checkout.workers", num_workers))
*num_workers = DEFAULT_NUM_WORKERS;
else if (*num_workers < 1)
Expand Down Expand Up @@ -326,6 +341,7 @@ void write_pc_item(struct parallel_checkout_item *pc_item,
if (dir_sep && !has_dirs_only_path(path.buf, dir_sep - path.buf,
state->base_dir_len)) {
pc_item->status = PC_ITEM_COLLIDED;
trace2_data_string("pcheckout", NULL, "collision/dirname", path.buf);
goto out;
}

Expand All @@ -341,6 +357,8 @@ void write_pc_item(struct parallel_checkout_item *pc_item,
* call should have already caught these cases.
*/
pc_item->status = PC_ITEM_COLLIDED;
trace2_data_string("pcheckout", NULL,
"collision/basename", path.buf);
} else {
error_errno("failed to open file '%s'", path.buf);
pc_item->status = PC_ITEM_FAILED;
Expand Down
14 changes: 10 additions & 4 deletions read-cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,11 @@ struct cache_entry *make_empty_cache_entry(struct index_state *istate, size_t le
return mem_pool__ce_calloc(find_mem_pool(istate), len);
}

struct cache_entry *make_empty_transient_cache_entry(size_t len)
struct cache_entry *make_empty_transient_cache_entry(size_t len,
struct mem_pool *ce_mem_pool)
{
if (ce_mem_pool)
return mem_pool__ce_calloc(ce_mem_pool, len);
return xcalloc(1, cache_entry_size(len));
}

Expand Down Expand Up @@ -874,8 +877,11 @@ struct cache_entry *make_cache_entry(struct index_state *istate,
return ret;
}

struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct object_id *oid,
const char *path, int stage)
struct cache_entry *make_transient_cache_entry(unsigned int mode,
const struct object_id *oid,
const char *path,
int stage,
struct mem_pool *ce_mem_pool)
{
struct cache_entry *ce;
int len;
Expand All @@ -886,7 +892,7 @@ struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct o
}

len = strlen(path);
ce = make_empty_transient_cache_entry(len);
ce = make_empty_transient_cache_entry(len, ce_mem_pool);

oidcpy(&ce->oid, oid);
memcpy(ce->name, path, len);
Expand Down
4 changes: 4 additions & 0 deletions t/README
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@ GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the
GIT_TEST_SPARSE_INDEX=<boolean>, when true enables index writes to use the
sparse-index format by default.

GIT_TEST_CHECKOUT_WORKERS=<n> overrides the 'checkout.workers' setting
to <n> and 'checkout.thresholdForParallelism' to 0, forcing the
execution of the parallel-checkout code.

Naming Tests
------------

Expand Down
25 changes: 25 additions & 0 deletions t/lib-encoding.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Encoding helpers

test_lazy_prereq NO_UTF16_BOM '
test $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6
'

test_lazy_prereq NO_UTF32_BOM '
test $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12
'

write_utf16 () {
if test_have_prereq NO_UTF16_BOM
then
printf '\376\377'
fi &&
iconv -f UTF-8 -t UTF-16
}

write_utf32 () {
if test_have_prereq NO_UTF32_BOM
then
printf '\0\0\376\377'
fi &&
iconv -f UTF-8 -t UTF-32
}
45 changes: 45 additions & 0 deletions t/lib-parallel-checkout.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Helpers for tests invoking parallel-checkout

# Parallel checkout tests need full control of the number of workers
unset GIT_TEST_CHECKOUT_WORKERS

set_checkout_config () {
if test $# -ne 2
then
BUG "usage: set_checkout_config <workers> <threshold>"
fi &&

test_config_global checkout.workers $1 &&
test_config_global checkout.thresholdForParallelism $2
}

# Run "${@:2}" and check that $1 checkout workers were used
test_checkout_workers () {
if test $# -lt 2
then
BUG "too few arguments to test_checkout_workers"
fi &&

local expected_workers=$1 &&
shift &&

local trace_file=trace-test-checkout-workers &&
rm -f "$trace_file" &&
GIT_TRACE2="$(pwd)/$trace_file" "$@" 2>&8 &&

local workers=$(grep "child_start\[..*\] git checkout--worker" "$trace_file" | wc -l) &&
test $workers -eq $expected_workers &&
rm "$trace_file"
} 8>&2 2>&4

# Verify that both the working tree and the index were created correctly
verify_checkout () {
if test $# -ne 1
then
BUG "usage: verify_checkout <repository path>"
fi &&

git -C "$1" diff-index --ignore-submodules=none --exit-code HEAD -- &&
git -C "$1" status --porcelain >"$1".status &&
test_must_be_empty "$1".status
}
25 changes: 1 addition & 24 deletions t/t0028-working-tree-encoding.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,10 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh
. "$TEST_DIRECTORY/lib-encoding.sh"

GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING

test_lazy_prereq NO_UTF16_BOM '
test $(printf abc | iconv -f UTF-8 -t UTF-16 | wc -c) = 6
'

test_lazy_prereq NO_UTF32_BOM '
test $(printf abc | iconv -f UTF-8 -t UTF-32 | wc -c) = 12
'

write_utf16 () {
if test_have_prereq NO_UTF16_BOM
then
printf '\376\377'
fi &&
iconv -f UTF-8 -t UTF-16
}

write_utf32 () {
if test_have_prereq NO_UTF32_BOM
then
printf '\0\0\376\377'
fi &&
iconv -f UTF-8 -t UTF-32
}

test_expect_success 'setup test files' '
git config core.eol lf &&
Expand Down

0 comments on commit a737e1f

Please sign in to comment.