Merge branch 'jt/odb-keep-lockfile-fix'
The creation of '.keep' lockfiles during object database transactions has been deferred to commit time, explicitly managing the lockfiles within the files backend rather than relying on 'git-index-pack', to prevent race conditions that could lead to concurrent lockfiles being erroneously deleted. * jt/odb-keep-lockfile-fix: object-file: write ".keep" lockfiles during transaction commit object-file: explicitly write ".keep" lockfile pack-write: lift and expose index-pack report parsing pack-write: make index-pack output comment hash agnostic
This commit is contained in:
commit
cc3b09a046
4 files changed
+186
-49
No files matched your search
+100
-27
@@ -491,6 +491,7 @@ struct odb_transaction_files {
|
||||
struct transaction_packfile packfile;
|
||||
const char *prefix;
|
||||
|
||||
struct strvec pack_names;
|
||||
struct tempfile **pack_lockfiles;
|
||||
size_t pack_lockfiles_nr;
|
||||
size_t pack_lockfiles_alloc;
|
||||
@@ -1253,6 +1254,82 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int write_pack_lockfile(struct odb_source *source, const char *pack_name,
|
||||
const char *keep_msg, struct tempfile **out)
|
||||
{
|
||||
struct strbuf lock_path = STRBUF_INIT;
|
||||
struct strbuf contents = STRBUF_INIT;
|
||||
int ret;
|
||||
int fd;
|
||||
|
||||
strbuf_addf(&lock_path, "%s/pack/pack-%s.keep",
|
||||
source->path, pack_name);
|
||||
strbuf_addf(&contents, "%s\n", keep_msg);
|
||||
|
||||
fd = safe_create_file_with_leading_directories(source->odb->repo,
|
||||
lock_path.buf);
|
||||
if (fd < 0) {
|
||||
ret = error_errno(_("unable to create '%s'"), lock_path.buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_in_full(fd, contents.buf, contents.len) < 0) {
|
||||
ret = error_errno(_("unable to write '%s'"), lock_path.buf);
|
||||
close(fd);
|
||||
unlink(lock_path.buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (close(fd) < 0) {
|
||||
ret = error_errno(_("unable to close '%s'"), lock_path.buf);
|
||||
unlink(lock_path.buf);
|
||||
goto out;
|
||||
}
|
||||
|
||||
*out = register_tempfile(lock_path.buf);
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
strbuf_release(&lock_path);
|
||||
strbuf_release(&contents);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int write_pack_lockfiles(struct odb_transaction_files *transaction)
|
||||
{
|
||||
struct strbuf keep_msg = STRBUF_INIT;
|
||||
char hostname[HOST_NAME_MAX + 1];
|
||||
int ret = 0;
|
||||
|
||||
if (!transaction->pack_names.nr)
|
||||
return 0;
|
||||
|
||||
if (xgethostname(hostname, sizeof(hostname)))
|
||||
xsnprintf(hostname, sizeof(hostname), "localhost");
|
||||
strbuf_addf(&keep_msg, "receive-pack %"PRIuMAX" on %s",
|
||||
(uintmax_t)getpid(), hostname);
|
||||
|
||||
for (size_t i = 0; i < transaction->pack_names.nr; i++) {
|
||||
struct tempfile *lock;
|
||||
|
||||
ret = write_pack_lockfile(transaction->base.source,
|
||||
transaction->pack_names.v[i],
|
||||
keep_msg.buf, &lock);
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
ALLOC_GROW(transaction->pack_lockfiles,
|
||||
transaction->pack_lockfiles_nr + 1,
|
||||
transaction->pack_lockfiles_alloc);
|
||||
transaction->pack_lockfiles[transaction->pack_lockfiles_nr++] = lock;
|
||||
}
|
||||
|
||||
strbuf_release(&keep_msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int odb_transaction_files_commit(struct odb_transaction *base)
|
||||
{
|
||||
struct odb_transaction_files *transaction =
|
||||
@@ -1281,6 +1358,9 @@ static int odb_transaction_files_commit(struct odb_transaction *base)
|
||||
delete_tempfile(&temp);
|
||||
strbuf_release(&temp_path);
|
||||
|
||||
if (write_pack_lockfiles(transaction))
|
||||
return error(_("unable to lock packfiles"));
|
||||
|
||||
/*
|
||||
* Make the object files visible in the primary ODB after their data is
|
||||
* fully durable.
|
||||
@@ -1345,9 +1425,11 @@ static int odb_transaction_files_write_pack(struct odb_transaction *base,
|
||||
container_of(base, struct odb_transaction_files, base);
|
||||
struct repository *repo = base->source->odb->repo;
|
||||
struct child_process child = CHILD_PROCESS_INIT;
|
||||
struct strbuf pack_name = STRBUF_INIT;
|
||||
struct pack_header hdr;
|
||||
const char *hdr_err;
|
||||
int err_fd = opts->err_fd;
|
||||
int ret = -1;
|
||||
int status;
|
||||
|
||||
hdr_err = parse_pack_header(&hdr, pack_fd);
|
||||
@@ -1386,19 +1468,9 @@ static int odb_transaction_files_write_pack(struct odb_transaction *base,
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
char hostname[HOST_NAME_MAX + 1];
|
||||
char *lockfile;
|
||||
|
||||
strvec_pushl(&child.args, "index-pack", "--stdin", NULL);
|
||||
push_header_arg(&child.args, &hdr);
|
||||
|
||||
if (xgethostname(hostname, sizeof(hostname)))
|
||||
xsnprintf(hostname, sizeof(hostname), "localhost");
|
||||
strvec_pushf(&child.args,
|
||||
"--keep=receive-pack %"PRIuMAX" on %s",
|
||||
(uintmax_t)getpid(),
|
||||
hostname);
|
||||
|
||||
if (!opts->quiet && err_fd)
|
||||
strvec_push(&child.args, "--show-resolving-progress");
|
||||
if (err_fd)
|
||||
@@ -1421,33 +1493,32 @@ static int odb_transaction_files_write_pack(struct odb_transaction *base,
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The lockfile filepath is expected to be the final location of
|
||||
* the ".keep" file after being migrated to the main ODB source.
|
||||
* This ensures the lockfile can be found and removed later
|
||||
* after the ODB transaction has been committed.
|
||||
*/
|
||||
lockfile = index_pack_lockfile(base->source, child.out, NULL);
|
||||
if (lockfile) {
|
||||
ALLOC_GROW(transaction->pack_lockfiles,
|
||||
transaction->pack_lockfiles_nr + 1,
|
||||
transaction->pack_lockfiles_alloc);
|
||||
transaction->pack_lockfiles[transaction->pack_lockfiles_nr++] =
|
||||
register_tempfile(lockfile);
|
||||
free(lockfile);
|
||||
}
|
||||
ret = read_index_pack_report(repo, child.out, &pack_name, NULL);
|
||||
|
||||
close(child.out);
|
||||
|
||||
status = finish_command(&child);
|
||||
if (status) {
|
||||
strbuf_addstr(err_msg, "index-pack abnormal exit");
|
||||
return -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
strbuf_addstr(err_msg, "index-pack failed to report pack name");
|
||||
goto out;
|
||||
}
|
||||
|
||||
strvec_push(&transaction->pack_names, pack_name.buf);
|
||||
|
||||
odb_source_prepare(base->source, ODB_PREPARE_FLUSH_CACHES);
|
||||
}
|
||||
|
||||
return 0;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
strbuf_release(&pack_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int odb_transaction_files_finalize(struct odb_transaction *base)
|
||||
@@ -1460,6 +1531,7 @@ static int odb_transaction_files_finalize(struct odb_transaction *base)
|
||||
ret |= delete_tempfile(&transaction->pack_lockfiles[i]);
|
||||
|
||||
free(transaction->pack_lockfiles);
|
||||
strvec_clear(&transaction->pack_names);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1492,6 +1564,7 @@ int odb_transaction_files_begin(struct odb_source *source,
|
||||
transaction->base.write_pack = odb_transaction_files_write_pack;
|
||||
transaction->base.env = odb_transaction_files_env;
|
||||
transaction->flags = flags;
|
||||
strvec_init(&transaction->pack_names);
|
||||
|
||||
transaction->prefix = "bulk-fsync";
|
||||
if (flags & ODB_TRANSACTION_RECEIVE) {
|
||||
|
||||
+49
-19
@@ -469,33 +469,63 @@ void fixup_pack_header_footer(const struct git_hash_algo *hash_algo,
|
||||
fsync_component_or_die(FSYNC_COMPONENT_PACK, pack_fd, pack_name);
|
||||
}
|
||||
|
||||
int read_index_pack_report(struct repository *repo, int ip_out,
|
||||
struct strbuf *pack_name, int *is_keep)
|
||||
{
|
||||
const int len = repo->hash_algo->hexsz + 6;
|
||||
char packname[GIT_MAX_HEXSZ + 6];
|
||||
int is_keep_ignored;
|
||||
const char *name;
|
||||
|
||||
if (!is_keep)
|
||||
is_keep = &is_keep_ignored;
|
||||
|
||||
/*
|
||||
* The first thing we expect from index-pack's output is
|
||||
* "pack\t<name>\n" or "keep\t<name>\n", where <name> is the hexadecimal
|
||||
* name of the packfile it has just written. The line is therefore five
|
||||
* bytes of prefix, `hexsz` bytes of name, and a trailing newline. A
|
||||
* "keep" prefix tells us that index-pack has also created the
|
||||
* corresponding ".keep" file.
|
||||
*/
|
||||
if (read_in_full(ip_out, packname, len) != len || packname[len - 1] != '\n')
|
||||
return -1;
|
||||
|
||||
packname[len - 1] = 0;
|
||||
if (skip_prefix(packname, "pack\t", &name))
|
||||
*is_keep = 0;
|
||||
else if (skip_prefix(packname, "keep\t", &name))
|
||||
*is_keep = 1;
|
||||
else
|
||||
return -1;
|
||||
|
||||
strbuf_addstr(pack_name, name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *index_pack_lockfile(struct odb_source *source, int ip_out,
|
||||
int *is_well_formed)
|
||||
{
|
||||
char packname[GIT_MAX_HEXSZ + 6];
|
||||
const int len = source->odb->repo->hash_algo->hexsz + 6;
|
||||
|
||||
/*
|
||||
* The first thing we expect from index-pack's output
|
||||
* is "pack\t%40s\n" or "keep\t%40s\n" (46 bytes) where
|
||||
* %40s is the newly created pack SHA1 name. In the "keep"
|
||||
* case, we need it to remove the corresponding .keep file
|
||||
* later on. If we don't get that then tough luck with it.
|
||||
*/
|
||||
if (read_in_full(ip_out, packname, len) == len && packname[len-1] == '\n') {
|
||||
const char *name;
|
||||
struct strbuf pack_name = STRBUF_INIT;
|
||||
char *lockfile = NULL;
|
||||
int is_keep = 0;
|
||||
|
||||
if (read_index_pack_report(source->odb->repo, ip_out, &pack_name, &is_keep)) {
|
||||
if (is_well_formed)
|
||||
*is_well_formed = 1;
|
||||
packname[len-1] = 0;
|
||||
if (skip_prefix(packname, "keep\t", &name))
|
||||
return xstrfmt("%s/pack/pack-%s.keep",
|
||||
source->path, name);
|
||||
*is_well_formed = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (is_well_formed)
|
||||
*is_well_formed = 0;
|
||||
return NULL;
|
||||
*is_well_formed = 1;
|
||||
|
||||
/* If there is no ".keep" file, then there is no lockfile to return. */
|
||||
if (is_keep)
|
||||
lockfile = xstrfmt("%s/pack/pack-%s.keep", source->path, pack_name.buf);
|
||||
|
||||
strbuf_release(&pack_name);
|
||||
return lockfile;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -106,6 +106,16 @@ off_t write_pack_header(struct hashfile *f, uint32_t);
|
||||
void fixup_pack_header_footer(const struct git_hash_algo *, int,
|
||||
unsigned char *, const char *, uint32_t,
|
||||
unsigned char *, off_t);
|
||||
|
||||
/*
|
||||
* Read the report that git-index-pack(1) writes to its standard output after
|
||||
* the packfile is written. The hexadecimal name of the packfile is appended to
|
||||
* `pack_name` and, if `is_keep` is non-NULL, it is set to indicate whether a
|
||||
* ".keep" file was written. Return 0 on success and a negative error code on
|
||||
* failure.
|
||||
*/
|
||||
int read_index_pack_report(struct repository *repo, int ip_out,
|
||||
struct strbuf *pack_name, int *is_keep);
|
||||
char *index_pack_lockfile(struct odb_source *source, int fd,
|
||||
int *is_well_formed);
|
||||
|
||||
|
||||
@@ -77,10 +77,9 @@ test_expect_success '.keep file is removed after push' '
|
||||
git -C keep.git config set receive.unpackLimit 0 &&
|
||||
|
||||
# While incoming objects are still quarantined, validate that the
|
||||
# ".keep" lockfile is present in the quarantine directory.
|
||||
# ".keep" lockfile is not present in the quarantine directory.
|
||||
test_hook -C keep.git pre-receive <<-\EOF &&
|
||||
keep="$(ls "$GIT_QUARANTINE_PATH"/pack/pack-*.keep)" &&
|
||||
test -f "$keep"
|
||||
test ! -e "$GIT_QUARANTINE_PATH"/pack/pack-*.keep
|
||||
EOF
|
||||
|
||||
# After quarantined objects are migrated, validate that the ".keep"
|
||||
@@ -101,4 +100,29 @@ test_expect_success '.keep file is removed after push' '
|
||||
test_path_is_missing "$keep"
|
||||
'
|
||||
|
||||
test_expect_success '.keep file created outside of process is not removed' '
|
||||
test_when_finished rm -rf keep.git &&
|
||||
git init --bare keep.git &&
|
||||
|
||||
git -C keep.git config set receive.unpackLimit 0 &&
|
||||
|
||||
# Create a ".keep" lockfile matching the packfile to simulate a
|
||||
# concurrent git-receive-pack(1) that has already written its lockfile.
|
||||
test_hook -C keep.git pre-receive <<-\EOF &&
|
||||
pack="$(ls "$GIT_QUARANTINE_PATH"/pack/pack-*.pack)" &&
|
||||
pack="${pack##*/}" &&
|
||||
echo "baz" >"objects/pack/${pack%.pack}.keep"
|
||||
EOF
|
||||
|
||||
test_commit bar &&
|
||||
test_must_fail git push keep.git HEAD &&
|
||||
|
||||
# The ".keep" lockfile of the concurrent git-receive-pack(1) should be
|
||||
# left in its place.
|
||||
keep="$(ls keep.git/objects/pack/pack-*.keep)" &&
|
||||
test_path_is_file "$keep" &&
|
||||
test_grep "baz" "$keep" &&
|
||||
test_path_is_missing "${keep%.keep}.pack"
|
||||
'
|
||||
|
||||
test_done
|
||||
Reference in new issue
Block a user