read-only snapshot of the same index to a location different from the
repository's usual working tree).
+core.lockfilePid::
+ A comma-separated list of components for which Git should create
+ a PID file alongside the lock file. When a lock acquisition fails
+ and a PID file exists, Git can provide additional diagnostic
+ information about the process holding the lock, including whether
+ it is still running.
++
+This feature is disabled by default. You can enable it for specific
+components or use `all` to enable for all components.
++
+* `none` disables PID file creation for all components.
+* `index` creates PID files for index lock operations.
+* `config` creates PID files for config file lock operations.
+* `refs` creates PID files for reference lock operations.
+* `commit-graph` creates PID files for commit-graph lock operations.
+* `midx` creates PID files for multi-pack-index lock operations.
+* `shallow` creates PID files for shallow file lock operations.
+* `gc` creates PID files for garbage collection lock operations.
+* `other` creates PID files for other miscellaneous lock operations.
+* `all` enables PID file creation for all components.
++
+The PID file is named by inserting `.pid` before the `.lock` suffix.
+For example, if the lock file is `index.lock`, the PID file will be
+`index.pid.lock`. The file contains `pid <value>` on a single line,
+following the same key-value format used by Git object headers.
+
core.logAllRefUpdates::
Enable the reflog. Updates to a ref <ref> is logged to the file
"`$GIT_DIR/logs/<ref>`", by appending the new and old
}
}
- hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&lock, state->fake_ancestor, LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_OTHER);
res = write_locked_index(&result, &lock, COMMIT_LOCK);
discard_index(&result);
if (state->index_file)
hold_lock_file_for_update(&state->lock_file,
state->index_file,
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_INDEX);
else
repo_hold_locked_index(state->repo, &state->lock_file,
LOCK_DIE_ON_ERROR);
path = repo_git_path(the_repository, "next-index-%"PRIuMAX,
(uintmax_t) getpid());
hold_lock_file_for_update(&false_lock, path,
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR, LOCKFILE_PID_OTHER);
create_base_index(current_head);
add_remove_files(&partial);
int timeout_ms = 1000;
repo_config_get_int(the_repository, "credentialstore.locktimeoutms", &timeout_ms);
- if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0)
+ if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms,
+ LOCKFILE_PID_CONFIG) < 0)
die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms);
if (extra)
print_line(extra);
struct lock_file lock = LOCK_INIT;
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/wtindex", tmpdir.buf);
- if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 ||
+ if (hold_lock_file_for_update(&lock, buf.buf, 0,
+ LOCKFILE_PID_OTHER) < 0 ||
write_locked_index(&wtindex, &lock, COMMIT_LOCK)) {
ret = error("could not write %s", buf.buf);
goto finish;
return;
}
- if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
+ if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0,
+ LOCKFILE_PID_OTHER) < 0) {
failure |= error_errno(_("unable to write marks file %s"),
export_marks_file);
return;
pidfile_path = repo_git_path(the_repository, "gc.pid");
fd = hold_lock_file_for_update(&lock, pidfile_path,
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR, LOCKFILE_PID_GC);
if (!force) {
static char locking_host[HOST_NAME_MAX + 1];
static char *scan_fmt;
if (daemonized) {
char *path = repo_git_path(the_repository, "gc.log");
hold_lock_file_for_update(&log_lock, path,
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR, LOCKFILE_PID_GC);
dup2(get_lock_file_fd(&log_lock), 2);
atexit(process_log_file_at_exit);
free(path);
struct repository *r = the_repository;
char *lock_path = xstrfmt("%s/maintenance", r->objects->sources->path);
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF,
+ LOCKFILE_PID_GC) < 0) {
/*
* Another maintenance command is running.
*
lock_file_timeout_ms = 150;
fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
- lock_file_timeout_ms);
+ lock_file_timeout_ms,
+ LOCKFILE_PID_GC);
/*
* Does this file already exist? With the intended contents? Is it
struct lock_file lk;
char *lock_path = xstrfmt("%s/schedule", the_repository->objects->sources->path);
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) {
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF,
+ LOCKFILE_PID_GC) < 0) {
if (errno == EEXIST)
error(_("unable to create '%s.lock': %s.\n\n"
"Another scheduled git-maintenance(1) process seems to be running in this\n"
if (safe_create_leading_directories(repo, sparse_filename))
die(_("failed to create directory for sparse-checkout file"));
- hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_OTHER);
result = update_working_directory(repo, pl);
if (result) {
bundle_fd = 1;
else
bundle_fd = hold_lock_file_for_update(&lock, path,
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_OTHER);
if (version == -1)
version = min_version;
struct lock_file lock_file = LOCK_INIT;
int ret;
- hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_INDEX);
entries = read_index_from(index_state, index_path,
repo_get_git_dir(the_repository));
char *lock_name = get_commit_graph_chain_filename(ctx->odb_source);
hold_lock_file_for_update_mode(&lk, lock_name,
- LOCK_DIE_ON_ERROR, 0444);
+ LOCK_DIE_ON_ERROR, 0444,
+ LOCKFILE_PID_COMMIT_GRAPH);
free(lock_name);
graph_layer = mks_tempfile_m(ctx->graph_name, 0444);
get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer));
} else {
hold_lock_file_for_update_mode(&lk, ctx->graph_name,
- LOCK_DIE_ON_ERROR, 0444);
+ LOCK_DIE_ON_ERROR, 0444,
+ LOCKFILE_PID_COMMIT_GRAPH);
f = hashfd(ctx->r->hash_algo,
get_lock_file_fd(&lk), get_lock_file_path(&lk));
}
CloseHandle(h);
return 0;
}
+ /*
+ * OpenProcess returns ERROR_INVALID_PARAMETER for
+ * non-existent PIDs. Map this to ESRCH for POSIX
+ * compatibility with kill(pid, 0).
+ */
+ if (GetLastError() == ERROR_INVALID_PARAMETER)
+ errno = ESRCH;
+ else
+ errno = err_win_to_posix(GetLastError());
+ return -1;
}
errno = EINVAL;
* The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
- fd = hold_lock_file_for_update(&lock, config_filename, 0);
+ fd = hold_lock_file_for_update(&lock, config_filename, 0,
+ LOCKFILE_PID_CONFIG);
if (fd < 0) {
error_errno(_("could not lock config file %s"), config_filename);
ret = CONFIG_NO_LOCK;
if (!config_filename)
config_filename = filename_buf = repo_git_path(r, "config");
- out_fd = hold_lock_file_for_update(&lock, config_filename, 0);
+ out_fd = hold_lock_file_for_update(&lock, config_filename, 0,
+ LOCKFILE_PID_CONFIG);
if (out_fd < 0) {
ret = error(_("could not lock config file %s"), config_filename);
goto out;
#include "gettext.h"
#include "git-zlib.h"
#include "ident.h"
+#include "lockfile.h"
#include "mailmap.h"
#include "object-name.h"
#include "repository.h"
return (current & ~negative) | positive;
}
+static const struct lockfile_pid_component_name {
+ const char *name;
+ enum lockfile_pid_component component_bits;
+} lockfile_pid_component_names[] = {
+ { "index", LOCKFILE_PID_INDEX },
+ { "config", LOCKFILE_PID_CONFIG },
+ { "refs", LOCKFILE_PID_REFS },
+ { "commit-graph", LOCKFILE_PID_COMMIT_GRAPH },
+ { "midx", LOCKFILE_PID_MIDX },
+ { "shallow", LOCKFILE_PID_SHALLOW },
+ { "gc", LOCKFILE_PID_GC },
+ { "other", LOCKFILE_PID_OTHER },
+ { "all", LOCKFILE_PID_ALL },
+};
+
+static enum lockfile_pid_component parse_lockfile_pid_components(const char *var,
+ const char *string)
+{
+ enum lockfile_pid_component current = LOCKFILE_PID_DEFAULT;
+ enum lockfile_pid_component positive = 0, negative = 0;
+
+ while (string) {
+ size_t len;
+ const char *ep;
+ int negated = 0;
+ int found = 0;
+
+ string = string + strspn(string, ", \t\n\r");
+ ep = strchrnul(string, ',');
+ len = ep - string;
+ if (len == 4 && !strncmp(string, "none", 4)) {
+ current = LOCKFILE_PID_NONE;
+ goto next_name;
+ }
+
+ if (*string == '-') {
+ negated = 1;
+ string++;
+ len--;
+ if (!len)
+ warning(_("invalid value for variable %s"), var);
+ }
+
+ if (!len)
+ break;
+
+ for (size_t i = 0; i < ARRAY_SIZE(lockfile_pid_component_names); ++i) {
+ const struct lockfile_pid_component_name *n = &lockfile_pid_component_names[i];
+
+ if (strncmp(n->name, string, len) || strlen(n->name) != len)
+ continue;
+
+ found = 1;
+ if (negated)
+ negative |= n->component_bits;
+ else
+ positive |= n->component_bits;
+ }
+
+ if (!found) {
+ char *component = xstrndup(string, len);
+ warning(_("ignoring unknown core.lockfilePid component '%s'"), component);
+ free(component);
+ }
+
+next_name:
+ string = ep;
+ }
+
+ return (current & ~negative) | positive;
+}
+
static int git_default_core_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
return 0;
}
+ if (!strcmp(var, "core.lockfilepid")) {
+ if (!value)
+ return config_error_nonbool(var);
+ lockfile_pid_components = parse_lockfile_pid_components(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.createobject")) {
if (!value)
return config_error_nonbool(var);
#include "abspath.h"
#include "gettext.h"
#include "lockfile.h"
+#include "parse.h"
+#include "strbuf.h"
+#include "wrapper.h"
/*
* path = absolute or relative path name
strbuf_reset(&link);
}
+/*
+ * Lock PID file functions - write PID to a foo-pid.lock file alongside
+ * the lock file for debugging stale locks. The PID file is registered
+ * as a tempfile so it gets cleaned up by signal/atexit handlers.
+ *
+ * Naming: For "foo.lock", the PID file is "foo-pid.lock" (not "foo.lock.pid").
+ * This avoids collision with the refs namespace.
+ */
+
+/* Global config variable, initialized from core.lockfilePid */
+enum lockfile_pid_component lockfile_pid_components = LOCKFILE_PID_DEFAULT;
+
+/*
+ * Path generation helpers.
+ * Given base path "foo", generate:
+ * - lock path: "foo.lock"
+ * - pid path: "foo-pid.lock"
+ */
+static void get_lock_path(struct strbuf *out, const char *path)
+{
+ strbuf_addstr(out, path);
+ strbuf_addstr(out, LOCK_SUFFIX);
+}
+
+static void get_pid_path(struct strbuf *out, const char *path)
+{
+ strbuf_addstr(out, path);
+ strbuf_addstr(out, LOCK_PID_INFIX);
+ strbuf_addstr(out, LOCK_SUFFIX);
+}
+
+static struct tempfile *create_lock_pid_file(const char *pid_path, int mode,
+ enum lockfile_pid_component component)
+{
+ struct strbuf content = STRBUF_INIT;
+ struct tempfile *pid_tempfile = NULL;
+ int fd = -1;
+
+ if (!(lockfile_pid_components & component))
+ goto out;
+
+ fd = open(pid_path, O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (fd < 0)
+ goto out;
+
+ strbuf_addf(&content, "pid %" PRIuMAX "\n", (uintmax_t)getpid());
+ if (write_in_full(fd, content.buf, content.len) < 0) {
+ warning_errno(_("could not write lock pid file '%s'"), pid_path);
+ goto out;
+ }
+
+ close(fd);
+ fd = -1;
+ pid_tempfile = register_tempfile(pid_path);
+
+out:
+ if (fd >= 0)
+ close(fd);
+ strbuf_release(&content);
+ return pid_tempfile;
+}
+
+static int read_lock_pid(const char *pid_path, uintmax_t *pid_out)
+{
+ struct strbuf content = STRBUF_INIT;
+ const char *val;
+ int ret = -1;
+
+ if (strbuf_read_file(&content, pid_path, LOCK_PID_MAXLEN) <= 0)
+ goto out;
+
+ strbuf_rtrim(&content);
+
+ if (skip_prefix(content.buf, "pid ", &val)) {
+ char *endptr;
+ *pid_out = strtoumax(val, &endptr, 10);
+ if (*pid_out > 0 && !*endptr)
+ ret = 0;
+ }
+
+ if (ret)
+ warning(_("malformed lock pid file '%s'"), pid_path);
+
+out:
+ strbuf_release(&content);
+ return ret;
+}
+
/* Make sure errno contains a meaningful value on error */
static int lock_file(struct lock_file *lk, const char *path, int flags,
- int mode)
+ int mode, enum lockfile_pid_component component)
{
- struct strbuf filename = STRBUF_INIT;
+ struct strbuf base_path = STRBUF_INIT;
+ struct strbuf lock_path = STRBUF_INIT;
+ struct strbuf pid_path = STRBUF_INIT;
- strbuf_addstr(&filename, path);
+ strbuf_addstr(&base_path, path);
if (!(flags & LOCK_NO_DEREF))
- resolve_symlink(&filename);
+ resolve_symlink(&base_path);
+
+ get_lock_path(&lock_path, base_path.buf);
+ get_pid_path(&pid_path, base_path.buf);
+
+ lk->tempfile = create_tempfile_mode(lock_path.buf, mode);
+ if (lk->tempfile)
+ lk->pid_tempfile = create_lock_pid_file(pid_path.buf, mode,
+ component);
- strbuf_addstr(&filename, LOCK_SUFFIX);
- lk->tempfile = create_tempfile_mode(filename.buf, mode);
- strbuf_release(&filename);
+ strbuf_release(&base_path);
+ strbuf_release(&lock_path);
+ strbuf_release(&pid_path);
return lk->tempfile ? lk->tempfile->fd : -1;
}
* exactly once. If timeout_ms is -1, try indefinitely.
*/
static int lock_file_timeout(struct lock_file *lk, const char *path,
- int flags, long timeout_ms, int mode)
+ int flags, long timeout_ms, int mode,
+ enum lockfile_pid_component component)
{
int n = 1;
int multiplier = 1;
static int random_initialized = 0;
if (timeout_ms == 0)
- return lock_file(lk, path, flags, mode);
+ return lock_file(lk, path, flags, mode, component);
if (!random_initialized) {
srand((unsigned int)getpid());
long backoff_ms, wait_ms;
int fd;
- fd = lock_file(lk, path, flags, mode);
+ fd = lock_file(lk, path, flags, mode, component);
if (fd >= 0)
return fd; /* success */
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
if (err == EEXIST) {
- strbuf_addf(buf, _("Unable to create '%s.lock': %s.\n\n"
- "Another git process seems to be running in this repository, e.g.\n"
- "an editor opened by 'git commit'. Please make sure all processes\n"
- "are terminated then try again. If it still fails, a git process\n"
- "may have crashed in this repository earlier:\n"
- "remove the file manually to continue."),
- absolute_path(path), strerror(err));
- } else
+ const char *abs_path = absolute_path(path);
+ struct strbuf lock_path = STRBUF_INIT;
+ struct strbuf pid_path = STRBUF_INIT;
+ uintmax_t pid;
+ int pid_status = 0; /* 0 = unknown, 1 = running, -1 = stale */
+
+ get_lock_path(&lock_path, abs_path);
+ get_pid_path(&pid_path, abs_path);
+
+ strbuf_addf(buf, _("Unable to create '%s': %s.\n\n"),
+ lock_path.buf, strerror(err));
+
+ /*
+ * Try to read PID file unconditionally - it may exist if
+ * core.lockfilePid was enabled for this component.
+ */
+ if (!read_lock_pid(pid_path.buf, &pid)) {
+ if (kill((pid_t)pid, 0) == 0 || errno == EPERM)
+ pid_status = 1; /* running (or no permission to signal) */
+ else if (errno == ESRCH)
+ pid_status = -1; /* no such process - stale lock */
+ }
+
+ if (pid_status == 1)
+ strbuf_addf(buf, _("Lock is held by process %" PRIuMAX ". "
+ "Wait for it to finish, or remove the lock file to continue"),
+ pid);
+ else if (pid_status == -1)
+ strbuf_addf(buf, _("Lock was held by process %" PRIuMAX ", "
+ "which is no longer running. Remove the stale lock file to continue"),
+ pid);
+ else
+ strbuf_addstr(buf, _("Another git process seems to be running in this repository. "
+ "Wait for it to finish, or remove the lock file to continue"));
+
+ strbuf_release(&lock_path);
+ strbuf_release(&pid_path);
+ } else {
strbuf_addf(buf, _("Unable to create '%s.lock': %s"),
absolute_path(path), strerror(err));
+ }
}
NORETURN void unable_to_lock_die(const char *path, int err)
/* This should return a meaningful errno on failure */
int hold_lock_file_for_update_timeout_mode(struct lock_file *lk,
const char *path, int flags,
- long timeout_ms, int mode)
+ long timeout_ms, int mode,
+ enum lockfile_pid_component component)
{
- int fd = lock_file_timeout(lk, path, flags, timeout_ms, mode);
+ int fd = lock_file_timeout(lk, path, flags, timeout_ms, mode, component);
if (fd < 0) {
if (flags & LOCK_DIE_ON_ERROR)
unable_to_lock_die(path, errno);
{
char *result_path = get_locked_file_path(lk);
+ delete_tempfile(&lk->pid_tempfile);
+
if (commit_lock_file_to(lk, result_path)) {
int save_errno = errno;
free(result_path);
free(result_path);
return 0;
}
+
+int rollback_lock_file(struct lock_file *lk)
+{
+ delete_tempfile(&lk->pid_tempfile);
+ return delete_tempfile(&lk->tempfile);
+}
struct lock_file {
struct tempfile *tempfile;
+ struct tempfile *pid_tempfile;
};
#define LOCK_INIT { 0 }
#define LOCK_SUFFIX ".lock"
#define LOCK_SUFFIX_LEN 5
+/*
+ * PID file naming: for a lock file "foo.lock", the PID file is "foo.pid.lock".
+ */
+#define LOCK_PID_INFIX ".pid"
+#define LOCK_PID_INFIX_LEN 4
+
+/* Maximum length for PID file content */
+#define LOCK_PID_MAXLEN 32
+
+/*
+ * Per-component lock PID file configuration, following core.fsync pattern.
+ * Each component can be individually enabled via core.lockfilePid config.
+ */
+enum lockfile_pid_component {
+ LOCKFILE_PID_NONE = 0,
+ LOCKFILE_PID_INDEX = 1 << 0, /* .git/index.lock */
+ LOCKFILE_PID_CONFIG = 1 << 1, /* .git/config.lock */
+ LOCKFILE_PID_REFS = 1 << 2, /* refs locks */
+ LOCKFILE_PID_COMMIT_GRAPH = 1 << 3, /* commit-graph.lock */
+ LOCKFILE_PID_MIDX = 1 << 4, /* multi-pack-index.lock */
+ LOCKFILE_PID_SHALLOW = 1 << 5, /* shallow file */
+ LOCKFILE_PID_GC = 1 << 6, /* gc locks */
+ LOCKFILE_PID_OTHER = 1 << 7, /* other locks */
+};
+
+#define LOCKFILE_PID_ALL (LOCKFILE_PID_INDEX | LOCKFILE_PID_CONFIG | \
+ LOCKFILE_PID_REFS | LOCKFILE_PID_COMMIT_GRAPH | \
+ LOCKFILE_PID_MIDX | LOCKFILE_PID_SHALLOW | \
+ LOCKFILE_PID_GC | LOCKFILE_PID_OTHER)
+#define LOCKFILE_PID_DEFAULT LOCKFILE_PID_NONE
+
+/*
+ * Bitmask indicating which components should create PID files.
+ * Configured via core.lockfilePid.
+ */
+extern enum lockfile_pid_component lockfile_pid_components;
/*
* Flags
* timeout_ms milliseconds. If timeout_ms is 0, try exactly once; if
* timeout_ms is -1, retry indefinitely. The flags argument, error
* handling, and mode are described above.
+ *
+ * The `component` argument specifies which lock PID component this lock
+ * belongs to, for selective PID file creation via core.lockfilePid config.
*/
int hold_lock_file_for_update_timeout_mode(
- struct lock_file *lk, const char *path,
- int flags, long timeout_ms, int mode);
+ struct lock_file *lk, const char *path,
+ int flags, long timeout_ms, int mode,
+ enum lockfile_pid_component component);
static inline int hold_lock_file_for_update_timeout(
- struct lock_file *lk, const char *path,
- int flags, long timeout_ms)
+ struct lock_file *lk, const char *path,
+ int flags, long timeout_ms,
+ enum lockfile_pid_component component)
{
return hold_lock_file_for_update_timeout_mode(lk, path, flags,
- timeout_ms, 0666);
+ timeout_ms, 0666,
+ component);
}
/*
* argument and error handling are described above.
*/
static inline int hold_lock_file_for_update(
- struct lock_file *lk, const char *path,
- int flags)
+ struct lock_file *lk, const char *path,
+ int flags, enum lockfile_pid_component component)
{
- return hold_lock_file_for_update_timeout(lk, path, flags, 0);
+ return hold_lock_file_for_update_timeout(lk, path, flags, 0, component);
}
static inline int hold_lock_file_for_update_mode(
- struct lock_file *lk, const char *path,
- int flags, int mode)
+ struct lock_file *lk, const char *path,
+ int flags, int mode, enum lockfile_pid_component component)
{
- return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode);
+ return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode,
+ component);
}
/*
/*
* Roll back `lk`: close the file descriptor and/or file pointer and
- * remove the lockfile. It is a NOOP to call `rollback_lock_file()`
- * for a `lock_file` object that has already been committed or rolled
- * back. No error will be returned in this case.
+ * remove the lockfile and any associated PID file. It is a NOOP to
+ * call `rollback_lock_file()` for a `lock_file` object that has already
+ * been committed or rolled back. No error will be returned in this case.
*/
-static inline int rollback_lock_file(struct lock_file *lk)
-{
- return delete_tempfile(&lk->tempfile);
-}
+int rollback_lock_file(struct lock_file *lk);
#endif /* LOCKFILE_H */
return 0;
repo_common_path_replace(repo, &path, "objects/loose-object-idx");
- fd = hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);
+ fd = hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1,
+ LOCKFILE_PID_OTHER);
iter = kh_begin(map);
if (write_in_full(fd, loose_object_header, strlen(loose_object_header)) < 0)
goto errout;
struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT;
strbuf_addf(&path, "%s/loose-object-idx", source->path);
- hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1);
+ hold_lock_file_for_update_timeout(&lock, path.buf, LOCK_DIE_ON_ERROR, -1,
+ LOCKFILE_PID_OTHER);
fd = open(path.buf, O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0)
struct strbuf lock_name = STRBUF_INIT;
get_midx_chain_filename(source, &lock_name);
- hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_MIDX);
strbuf_release(&lock_name);
incr = mks_tempfile_m(midx_name.buf, 0444);
f = hashfd(r->hash_algo, get_tempfile_fd(incr),
get_tempfile_path(incr));
} else {
- hold_lock_file_for_update(&lk, midx_name.buf, LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&lk, midx_name.buf, LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_MIDX);
f = hashfd(r->hash_algo, get_lock_file_fd(&lk),
get_lock_file_path(&lk));
}
FILE *in, *out;
int found = 0;
- hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR);
+ hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_OTHER);
out = fdopen_lock_file(&lock, "w");
if (!out)
die_errno(_("unable to fdopen alternates lockfile"));
if (hold_lock_file_for_update_timeout(
&lock->lk, ref_file.buf, LOCK_NO_DEREF,
- get_files_ref_lock_timeout_ms()) < 0) {
+ get_files_ref_lock_timeout_ms(),
+ LOCKFILE_PID_REFS) < 0) {
int myerr = errno;
errno = 0;
if (myerr == ENOENT && --attempts_remaining > 0) {
return hold_lock_file_for_update_timeout(
lk, path, LOCK_NO_DEREF,
- get_files_ref_lock_timeout_ms()) < 0 ? -1 : 0;
+ get_files_ref_lock_timeout_ms(),
+ LOCKFILE_PID_REFS) < 0 ? -1 : 0;
}
/*
* work we need, including cleaning up if the program
* exits unexpectedly.
*/
- if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) {
+ if (hold_lock_file_for_update(&reflog_lock, log_file, 0,
+ LOCKFILE_PID_REFS) < 0) {
struct strbuf err = STRBUF_INIT;
unable_to_lock_message(log_file, errno, &err);
error("%s", err.buf);
if (hold_lock_file_for_update_timeout(
&refs->lock,
refs->path,
- flags, timeout_value) < 0) {
+ flags, timeout_value,
+ LOCKFILE_PID_REFS) < 0) {
unable_to_lock_message(refs->path, errno, err);
return -1;
}
return REFTABLE_OUT_OF_MEMORY_ERROR;
err = hold_lock_file_for_update_timeout(lockfile, target_path, LOCK_NO_DEREF,
- timeout_ms);
+ timeout_ms, LOCKFILE_PID_REFS);
if (err < 0) {
reftable_free(lockfile);
if (errno == EEXIST)
{
if (!repo->index_file)
BUG("the repo hasn't been setup");
- return hold_lock_file_for_update(lf, repo->index_file, flags);
+ return hold_lock_file_for_update(lf, repo->index_file, flags,
+ LOCKFILE_PID_INDEX);
}
else
fd = hold_lock_file_for_update(&write_lock,
git_path_merge_rr(r),
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_OTHER);
read_rr(r, merge_rr);
return fd;
}
{
struct lock_file msg_file = LOCK_INIT;
- int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0);
+ int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0,
+ LOCKFILE_PID_OTHER);
if (msg_fd < 0)
return error_errno(_("could not lock '%s'"), filename);
if (write_in_full(msg_fd, buf, len) < 0) {
if (is_rebase_i(opts) && !reschedule)
next++;
- fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
+ fd = hold_lock_file_for_update(&todo_lock, todo_path, 0,
+ LOCKFILE_PID_OTHER);
if (fd < 0)
return error_errno(_("could not lock '%s'"), todo_path);
offset = get_item_line_offset(todo_list, next);
va_list ap;
struct lock_file lock = LOCK_INIT;
int fd = hold_lock_file_for_update(&lock, filename,
- LOCK_REPORT_ON_ERROR);
+ LOCK_REPORT_ON_ERROR,
+ LOCKFILE_PID_OTHER);
struct strbuf buf = STRBUF_INIT;
if (fd < 0)
goto cleanup;
}
- if (hold_lock_file_for_update(&lock, path, 0) < 0) {
+ if (hold_lock_file_for_update(&lock, path, 0, LOCKFILE_PID_OTHER) < 0) {
result = error(_("another 'rebase' process appears to be running; "
"'%s.lock' already exists"),
path);
fd = hold_lock_file_for_update(&shallow_lock->lock,
git_path_shallow(the_repository),
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_SHALLOW);
check_shallow_file_for_update(the_repository);
if (write_shallow_commits(&sb, 0, extra)) {
if (write_in_full(fd, sb.buf, sb.len) < 0)
}
fd = hold_lock_file_for_update(&shallow_lock.lock,
git_path_shallow(the_repository),
- LOCK_DIE_ON_ERROR);
+ LOCK_DIE_ON_ERROR,
+ LOCKFILE_PID_SHALLOW);
check_shallow_file_for_update(the_repository);
if (write_shallow_commits_1(&sb, 0, NULL, flags)) {
if (write_in_full(fd, sb.buf, sb.len) < 0)
't0028-working-tree-encoding.sh',
't0029-core-unsetenvvars.sh',
't0030-stripspace.sh',
+ 't0031-lockfile-pid.sh',
't0033-safe-directory.sh',
't0034-root-safe-directory.sh',
't0035-safe-bare-repository.sh',
--- /dev/null
+#!/bin/sh
+
+test_description='lock file PID info tests
+
+Tests for PID info file alongside lock files.
+The feature is opt-in via core.lockfilePid config setting.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'stale lock detected when PID is not running' '
+ git init repo &&
+ (
+ cd repo &&
+ touch .git/index.lock &&
+ printf "pid 99999" >.git/index.pid.lock &&
+ test_must_fail git -c core.lockfilePid=index add . 2>err &&
+ test_grep "process 99999, which is no longer running" err &&
+ test_grep "Remove the stale lock file" err
+ )
+'
+
+test_expect_success 'PID info not shown by default' '
+ git init repo2 &&
+ (
+ cd repo2 &&
+ touch .git/index.lock &&
+ printf "pid 99999" >.git/index.pid.lock &&
+ test_must_fail git add . 2>err &&
+ # Should not crash, just show normal error without PID
+ test_grep "Unable to create" err &&
+ ! test_grep "is held by process" err
+ )
+'
+
+test_expect_success 'running process detected when PID is alive' '
+ git init repo3 &&
+ (
+ cd repo3 &&
+ echo content >file &&
+ # Get the correct PID for this platform
+ shell_pid=$$ &&
+ if test_have_prereq MINGW && test -f /proc/$shell_pid/winpid
+ then
+ # In Git for Windows, Bash uses MSYS2 PIDs but git.exe
+ # uses Windows PIDs. Use the Windows PID.
+ shell_pid=$(cat /proc/$shell_pid/winpid)
+ fi &&
+ # Create a lock and PID file with current shell PID (which is running)
+ touch .git/index.lock &&
+ printf "pid %d" "$shell_pid" >.git/index.pid.lock &&
+ # Verify our PID is shown in the error message
+ test_must_fail git -c core.lockfilePid=index add file 2>err &&
+ test_grep "held by process $shell_pid" err
+ )
+'
+
+test_expect_success 'PID info file cleaned up on successful operation when enabled' '
+ git init repo4 &&
+ (
+ cd repo4 &&
+ echo content >file &&
+ git -c core.lockfilePid=index add file &&
+ # After successful add, no lock or PID files should exist
+ test_path_is_missing .git/index.lock &&
+ test_path_is_missing .git/index.pid.lock
+ )
+'
+
+test_expect_success 'no PID file created by default' '
+ git init repo5 &&
+ (
+ cd repo5 &&
+ echo content >file &&
+ git add file &&
+ # PID file should not be created when feature is disabled
+ test_path_is_missing .git/index.pid.lock
+ )
+'
+
+test_expect_success 'core.lockfilePid=all enables for all components' '
+ git init repo6 &&
+ (
+ cd repo6 &&
+ touch .git/index.lock &&
+ printf "pid 99999" >.git/index.pid.lock &&
+ test_must_fail git -c core.lockfilePid=all add . 2>err &&
+ test_grep "process 99999" err
+ )
+'
+
+test_expect_success 'multiple components can be specified' '
+ git init repo8 &&
+ (
+ cd repo8 &&
+ touch .git/index.lock &&
+ printf "pid 99999" >.git/index.pid.lock &&
+ test_must_fail git -c core.lockfilePid=index,config add . 2>err &&
+ test_grep "process 99999" err
+ )
+'
+
+test_expect_success 'core.lockfilePid=none does not create PID file' '
+ git init repo9 &&
+ (
+ cd repo9 &&
+ echo content >file &&
+ git -c core.lockfilePid=none add file &&
+ # PID file should not be created when feature is disabled
+ test_path_is_missing .git/index.pid.lock
+ )
+'
+
+test_expect_success 'existing PID files are read even when feature disabled' '
+ git init repo10 &&
+ (
+ cd repo10 &&
+ touch .git/index.lock &&
+ printf "pid 99999" >.git/index.pid.lock &&
+ # Even with lockfilePid disabled, existing PID files are read
+ # to help diagnose stale locks
+ test_must_fail git add . 2>err &&
+ test_grep "process 99999" err
+ )
+'
+
+test_expect_success 'negative component syntax excludes specific components' '
+ git init repo11 &&
+ (
+ cd repo11 &&
+ echo content >file &&
+ # Enable all components except index
+ git -c core.lockfilePid=all,-index add file &&
+ # PID file should not be created for index when excluded
+ test_path_is_missing .git/index.pid.lock
+ )
+'
+
+test_done
/*
* Create a lock at "<path>.lock" if we can.
*/
- if (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms) < 0)
+ if (hold_lock_file_for_update_timeout(&lock, path, 0, timeout_ms,
+ LOCKFILE_PID_OTHER) < 0)
return -1;
/*