Merge branch 'jk/relative-directory-fix'

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() fails
This commit is contained in:
Junio C Hamano 2018-04-25 13:28:52 +09:00
commit ff6eb825f0
11 changed files with 223 additions and 17 deletions

View File

@ -783,6 +783,7 @@ LIB_OBJS += branch.o
LIB_OBJS += bulk-checkin.o
LIB_OBJS += bundle.o
LIB_OBJS += cache-tree.o
LIB_OBJS += chdir-notify.o
LIB_OBJS += checkout.o
LIB_OBJS += color.o
LIB_OBJS += column.o

View File

@ -477,7 +477,7 @@ extern const char *get_git_common_dir(void);
extern char *get_object_directory(void);
extern char *get_index_file(void);
extern char *get_graft_file(void);
extern int set_git_dir(const char *path);
extern void set_git_dir(const char *path);
extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
extern int get_common_dir(struct strbuf *sb, const char *gitdir);
extern const char *get_git_namespace(void);

93
chdir-notify.c Normal file
View File

@ -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;
}

73
chdir-notify.h Normal file
View File

@ -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 */

View File

@ -15,6 +15,7 @@
#include "commit.h"
#include "argv-array.h"
#include "object-store.h"
#include "chdir-notify.h"
int trust_executable_bit = 1;
int trust_ctime = 1;
@ -323,12 +324,31 @@ char *get_graft_file(void)
return the_repository->graft_file;
}
int set_git_dir(const char *path)
static void set_git_dir_1(const char *path)
{
if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
return error("Could not set GIT_DIR to '%s'", path);
die("could not set GIT_DIR to '%s'", path);
setup_git_env(path);
return 0;
}
static void update_relative_gitdir(const char *name,
const char *old_cwd,
const char *new_cwd,
void *data)
{
char *path = reparent_relative_path(old_cwd, new_cwd, get_git_dir());
trace_printf_key(&trace_setup_key,
"setup: move $GIT_DIR to '%s'",
path);
set_git_dir_1(path);
free(path);
}
void set_git_dir(const char *path)
{
set_git_dir_1(path);
if (!is_absolute_path(path))
chdir_notify_register(NULL, update_relative_gitdir, NULL);
}
const char *get_log_output_encoding(void)

View File

@ -9,6 +9,7 @@
#include "../lockfile.h"
#include "../object.h"
#include "../dir.h"
#include "../chdir-notify.h"
/*
* This backend uses the following flags in `ref_update::flags` for
@ -106,6 +107,11 @@ static struct ref_store *files_ref_store_create(const char *gitdir,
refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
strbuf_release(&sb);
chdir_notify_reparent("files-backend $GIT_DIR",
&refs->gitdir);
chdir_notify_reparent("files-backend $GIT_COMMONDIR",
&refs->gitcommondir);
return ref_store;
}

View File

@ -5,6 +5,7 @@
#include "packed-backend.h"
#include "../iterator.h"
#include "../lockfile.h"
#include "../chdir-notify.h"
enum mmap_strategy {
/*
@ -202,6 +203,8 @@ struct ref_store *packed_ref_store_create(const char *path,
refs->store_flags = store_flags;
refs->path = xstrdup(path);
chdir_notify_reparent("packed-refs", &refs->path);
return ref_store;
}

View File

@ -3,6 +3,7 @@
#include "config.h"
#include "dir.h"
#include "string-list.h"
#include "chdir-notify.h"
static int inside_git_dir = -1;
static int inside_work_tree = -1;
@ -378,7 +379,7 @@ int is_inside_work_tree(void)
void setup_work_tree(void)
{
const char *work_tree, *git_dir;
const char *work_tree;
static int initialized = 0;
if (initialized)
@ -388,10 +389,7 @@ void setup_work_tree(void)
die(_("unable to set up work tree using invalid config"));
work_tree = get_git_work_tree();
git_dir = get_git_dir();
if (!is_absolute_path(git_dir))
git_dir = real_path(get_git_dir());
if (!work_tree || chdir(work_tree))
if (!work_tree || chdir_notify(work_tree))
die(_("this operation must be run in a work tree"));
/*
@ -401,7 +399,6 @@ void setup_work_tree(void)
if (getenv(GIT_WORK_TREE_ENVIRONMENT))
setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
set_git_dir(remove_leading_path(git_dir, work_tree));
initialized = 1;
}

View File

@ -431,4 +431,16 @@ test_expect_success 'error out gracefully on invalid $GIT_WORK_TREE' '
)
'
test_expect_success 'refs work with relative gitdir and work tree' '
git init relative &&
git -C relative commit --allow-empty -m one &&
git -C relative commit --allow-empty -m two &&
GIT_DIR=relative/.git GIT_WORK_TREE=relative git reset HEAD^ &&
git -C relative log -1 --format=%s >actual &&
echo one >expect &&
test_cmp expect actual
'
test_done

14
trace.c
View File

@ -26,6 +26,7 @@
struct trace_key trace_default_key = { "GIT_TRACE", 0, 0, 0 };
struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
struct trace_key trace_setup_key = TRACE_KEY_INIT(SETUP);
/* Get a trace file descriptor from "key" env variable. */
static int get_trace_fd(struct trace_key *key)
@ -300,11 +301,10 @@ static const char *quote_crnl(const char *path)
/* FIXME: move prefix to startup_info struct and get rid of this arg */
void trace_repo_setup(const char *prefix)
{
static struct trace_key key = TRACE_KEY_INIT(SETUP);
const char *git_work_tree;
char *cwd;
if (!trace_want(&key))
if (!trace_want(&trace_setup_key))
return;
cwd = xgetcwd();
@ -315,11 +315,11 @@ void trace_repo_setup(const char *prefix)
if (!prefix)
prefix = "(null)";
trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
trace_printf_key(&key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd));
trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix));
trace_printf_key(&trace_setup_key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
trace_printf_key(&trace_setup_key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
trace_printf_key(&trace_setup_key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
trace_printf_key(&trace_setup_key, "setup: cwd: %s\n", quote_crnl(cwd));
trace_printf_key(&trace_setup_key, "setup: prefix: %s\n", quote_crnl(prefix));
free(cwd);
}

View File

@ -15,6 +15,7 @@ extern struct trace_key trace_default_key;
#define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 }
extern struct trace_key trace_perf_key;
extern struct trace_key trace_setup_key;
extern void trace_repo_setup(const char *prefix);
extern int trace_want(struct trace_key *key);