Browse Source
Some codepaths, including the refs API, get and keep relative paths, that go out of sync when the process does chdir(2). The chdir-notify API is introduced to let these codepaths adjust these cached paths to the new current directory. * jk/relative-directory-fix: refs: use chdir_notify to update cached relative paths set_work_tree: use chdir_notify add chdir-notify API trace.c: export trace_setup_key set_git_dir: die when setenv() failsmaint

11 changed files with 223 additions and 17 deletions
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
#include "cache.h" |
||||
#include "chdir-notify.h" |
||||
#include "list.h" |
||||
#include "strbuf.h" |
||||
|
||||
struct chdir_notify_entry { |
||||
const char *name; |
||||
chdir_notify_callback cb; |
||||
void *data; |
||||
struct list_head list; |
||||
}; |
||||
static LIST_HEAD(chdir_notify_entries); |
||||
|
||||
void chdir_notify_register(const char *name, |
||||
chdir_notify_callback cb, |
||||
void *data) |
||||
{ |
||||
struct chdir_notify_entry *e = xmalloc(sizeof(*e)); |
||||
e->name = name; |
||||
e->cb = cb; |
||||
e->data = data; |
||||
list_add_tail(&e->list, &chdir_notify_entries); |
||||
} |
||||
|
||||
static void reparent_cb(const char *name, |
||||
const char *old_cwd, |
||||
const char *new_cwd, |
||||
void *data) |
||||
{ |
||||
char **path = data; |
||||
char *tmp = *path; |
||||
|
||||
if (!tmp) |
||||
return; |
||||
|
||||
*path = reparent_relative_path(old_cwd, new_cwd, tmp); |
||||
free(tmp); |
||||
|
||||
if (name) { |
||||
trace_printf_key(&trace_setup_key, |
||||
"setup: reparent %s to '%s'", |
||||
name, *path); |
||||
} |
||||
} |
||||
|
||||
void chdir_notify_reparent(const char *name, char **path) |
||||
{ |
||||
chdir_notify_register(name, reparent_cb, path); |
||||
} |
||||
|
||||
int chdir_notify(const char *new_cwd) |
||||
{ |
||||
struct strbuf old_cwd = STRBUF_INIT; |
||||
struct list_head *pos; |
||||
|
||||
if (strbuf_getcwd(&old_cwd) < 0) |
||||
return -1; |
||||
if (chdir(new_cwd) < 0) { |
||||
int saved_errno = errno; |
||||
strbuf_release(&old_cwd); |
||||
errno = saved_errno; |
||||
return -1; |
||||
} |
||||
|
||||
trace_printf_key(&trace_setup_key, |
||||
"setup: chdir from '%s' to '%s'", |
||||
old_cwd.buf, new_cwd); |
||||
|
||||
list_for_each(pos, &chdir_notify_entries) { |
||||
struct chdir_notify_entry *e = |
||||
list_entry(pos, struct chdir_notify_entry, list); |
||||
e->cb(e->name, old_cwd.buf, new_cwd, e->data); |
||||
} |
||||
|
||||
strbuf_release(&old_cwd); |
||||
return 0; |
||||
} |
||||
|
||||
char *reparent_relative_path(const char *old_cwd, |
||||
const char *new_cwd, |
||||
const char *path) |
||||
{ |
||||
char *ret, *full; |
||||
|
||||
if (is_absolute_path(path)) |
||||
return xstrdup(path); |
||||
|
||||
full = xstrfmt("%s/%s", old_cwd, path); |
||||
ret = xstrdup(remove_leading_path(full, new_cwd)); |
||||
free(full); |
||||
|
||||
return ret; |
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
#ifndef CHDIR_NOTIFY_H |
||||
#define CHDIR_NOTIFY_H |
||||
|
||||
/* |
||||
* An API to let code "subscribe" to changes to the current working directory. |
||||
* The general idea is that some code asks to be notified when the working |
||||
* directory changes, and other code that calls chdir uses a special wrapper |
||||
* that notifies everyone. |
||||
*/ |
||||
|
||||
/* |
||||
* Callers who need to know about changes can do: |
||||
* |
||||
* void foo(const char *old_path, const char *new_path, void *data) |
||||
* { |
||||
* warning("switched from %s to %s!", old_path, new_path); |
||||
* } |
||||
* ... |
||||
* chdir_notify_register("description", foo, data); |
||||
* |
||||
* In practice most callers will want to move a relative path to the new root; |
||||
* they can use the reparent_relative_path() helper for that. If that's all |
||||
* you're doing, you can also use the convenience function: |
||||
* |
||||
* chdir_notify_reparent("description", &my_path); |
||||
* |
||||
* Whenever a chdir event occurs, that will update my_path (if it's relative) |
||||
* to adjust for the new cwd by freeing any existing string and allocating a |
||||
* new one. |
||||
* |
||||
* Registered functions are called in the order in which they were added. Note |
||||
* that there's currently no way to remove a function, so make sure that the |
||||
* data parameter remains valid for the rest of the program. |
||||
* |
||||
* The "name" argument is used only for printing trace output from |
||||
* $GIT_TRACE_SETUP. It may be NULL, but if non-NULL should point to |
||||
* storage which lasts as long as the registration is active. |
||||
*/ |
||||
typedef void (*chdir_notify_callback)(const char *name, |
||||
const char *old_cwd, |
||||
const char *new_cwd, |
||||
void *data); |
||||
void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data); |
||||
void chdir_notify_reparent(const char *name, char **path); |
||||
|
||||
/* |
||||
* |
||||
* Callers that want to chdir: |
||||
* |
||||
* chdir_notify(new_path); |
||||
* |
||||
* to switch to the new path and notify any callbacks. |
||||
* |
||||
* Note that you don't need to chdir_notify() if you're just temporarily moving |
||||
* to a directory and back, as long as you don't call any subscribed code in |
||||
* between (but it should be safe to do so if you're unsure). |
||||
*/ |
||||
int chdir_notify(const char *new_cwd); |
||||
|
||||
/* |
||||
* Reparent a relative path from old_root to new_root. For example: |
||||
* |
||||
* reparent_relative_path("/a", "/a/b", "b/rel"); |
||||
* |
||||
* would return the (newly allocated) string "rel". Note that we may return an |
||||
* absolute path in some cases (e.g., if the resulting path is not inside |
||||
* new_cwd). |
||||
*/ |
||||
char *reparent_relative_path(const char *old_cwd, |
||||
const char *new_cwd, |
||||
const char *path); |
||||
|
||||
#endif /* CHDIR_NOTIFY_H */ |
Loading…
Reference in new issue