Skip to content

Commit ff6eb82

Browse files
committed
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
2 parents 5d8da91 + fb9c2d2 commit ff6eb82

File tree

11 files changed

+223
-17
lines changed

11 files changed

+223
-17
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ LIB_OBJS += branch.o
783783
LIB_OBJS += bulk-checkin.o
784784
LIB_OBJS += bundle.o
785785
LIB_OBJS += cache-tree.o
786+
LIB_OBJS += chdir-notify.o
786787
LIB_OBJS += checkout.o
787788
LIB_OBJS += color.o
788789
LIB_OBJS += column.o

cache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ extern const char *get_git_common_dir(void);
477477
extern char *get_object_directory(void);
478478
extern char *get_index_file(void);
479479
extern char *get_graft_file(void);
480-
extern int set_git_dir(const char *path);
480+
extern void set_git_dir(const char *path);
481481
extern int get_common_dir_noenv(struct strbuf *sb, const char *gitdir);
482482
extern int get_common_dir(struct strbuf *sb, const char *gitdir);
483483
extern const char *get_git_namespace(void);

chdir-notify.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include "cache.h"
2+
#include "chdir-notify.h"
3+
#include "list.h"
4+
#include "strbuf.h"
5+
6+
struct chdir_notify_entry {
7+
const char *name;
8+
chdir_notify_callback cb;
9+
void *data;
10+
struct list_head list;
11+
};
12+
static LIST_HEAD(chdir_notify_entries);
13+
14+
void chdir_notify_register(const char *name,
15+
chdir_notify_callback cb,
16+
void *data)
17+
{
18+
struct chdir_notify_entry *e = xmalloc(sizeof(*e));
19+
e->name = name;
20+
e->cb = cb;
21+
e->data = data;
22+
list_add_tail(&e->list, &chdir_notify_entries);
23+
}
24+
25+
static void reparent_cb(const char *name,
26+
const char *old_cwd,
27+
const char *new_cwd,
28+
void *data)
29+
{
30+
char **path = data;
31+
char *tmp = *path;
32+
33+
if (!tmp)
34+
return;
35+
36+
*path = reparent_relative_path(old_cwd, new_cwd, tmp);
37+
free(tmp);
38+
39+
if (name) {
40+
trace_printf_key(&trace_setup_key,
41+
"setup: reparent %s to '%s'",
42+
name, *path);
43+
}
44+
}
45+
46+
void chdir_notify_reparent(const char *name, char **path)
47+
{
48+
chdir_notify_register(name, reparent_cb, path);
49+
}
50+
51+
int chdir_notify(const char *new_cwd)
52+
{
53+
struct strbuf old_cwd = STRBUF_INIT;
54+
struct list_head *pos;
55+
56+
if (strbuf_getcwd(&old_cwd) < 0)
57+
return -1;
58+
if (chdir(new_cwd) < 0) {
59+
int saved_errno = errno;
60+
strbuf_release(&old_cwd);
61+
errno = saved_errno;
62+
return -1;
63+
}
64+
65+
trace_printf_key(&trace_setup_key,
66+
"setup: chdir from '%s' to '%s'",
67+
old_cwd.buf, new_cwd);
68+
69+
list_for_each(pos, &chdir_notify_entries) {
70+
struct chdir_notify_entry *e =
71+
list_entry(pos, struct chdir_notify_entry, list);
72+
e->cb(e->name, old_cwd.buf, new_cwd, e->data);
73+
}
74+
75+
strbuf_release(&old_cwd);
76+
return 0;
77+
}
78+
79+
char *reparent_relative_path(const char *old_cwd,
80+
const char *new_cwd,
81+
const char *path)
82+
{
83+
char *ret, *full;
84+
85+
if (is_absolute_path(path))
86+
return xstrdup(path);
87+
88+
full = xstrfmt("%s/%s", old_cwd, path);
89+
ret = xstrdup(remove_leading_path(full, new_cwd));
90+
free(full);
91+
92+
return ret;
93+
}

chdir-notify.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef CHDIR_NOTIFY_H
2+
#define CHDIR_NOTIFY_H
3+
4+
/*
5+
* An API to let code "subscribe" to changes to the current working directory.
6+
* The general idea is that some code asks to be notified when the working
7+
* directory changes, and other code that calls chdir uses a special wrapper
8+
* that notifies everyone.
9+
*/
10+
11+
/*
12+
* Callers who need to know about changes can do:
13+
*
14+
* void foo(const char *old_path, const char *new_path, void *data)
15+
* {
16+
* warning("switched from %s to %s!", old_path, new_path);
17+
* }
18+
* ...
19+
* chdir_notify_register("description", foo, data);
20+
*
21+
* In practice most callers will want to move a relative path to the new root;
22+
* they can use the reparent_relative_path() helper for that. If that's all
23+
* you're doing, you can also use the convenience function:
24+
*
25+
* chdir_notify_reparent("description", &my_path);
26+
*
27+
* Whenever a chdir event occurs, that will update my_path (if it's relative)
28+
* to adjust for the new cwd by freeing any existing string and allocating a
29+
* new one.
30+
*
31+
* Registered functions are called in the order in which they were added. Note
32+
* that there's currently no way to remove a function, so make sure that the
33+
* data parameter remains valid for the rest of the program.
34+
*
35+
* The "name" argument is used only for printing trace output from
36+
* $GIT_TRACE_SETUP. It may be NULL, but if non-NULL should point to
37+
* storage which lasts as long as the registration is active.
38+
*/
39+
typedef void (*chdir_notify_callback)(const char *name,
40+
const char *old_cwd,
41+
const char *new_cwd,
42+
void *data);
43+
void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data);
44+
void chdir_notify_reparent(const char *name, char **path);
45+
46+
/*
47+
*
48+
* Callers that want to chdir:
49+
*
50+
* chdir_notify(new_path);
51+
*
52+
* to switch to the new path and notify any callbacks.
53+
*
54+
* Note that you don't need to chdir_notify() if you're just temporarily moving
55+
* to a directory and back, as long as you don't call any subscribed code in
56+
* between (but it should be safe to do so if you're unsure).
57+
*/
58+
int chdir_notify(const char *new_cwd);
59+
60+
/*
61+
* Reparent a relative path from old_root to new_root. For example:
62+
*
63+
* reparent_relative_path("/a", "/a/b", "b/rel");
64+
*
65+
* would return the (newly allocated) string "rel". Note that we may return an
66+
* absolute path in some cases (e.g., if the resulting path is not inside
67+
* new_cwd).
68+
*/
69+
char *reparent_relative_path(const char *old_cwd,
70+
const char *new_cwd,
71+
const char *path);
72+
73+
#endif /* CHDIR_NOTIFY_H */

environment.c

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "commit.h"
1616
#include "argv-array.h"
1717
#include "object-store.h"
18+
#include "chdir-notify.h"
1819

1920
int trust_executable_bit = 1;
2021
int trust_ctime = 1;
@@ -323,12 +324,31 @@ char *get_graft_file(void)
323324
return the_repository->graft_file;
324325
}
325326

326-
int set_git_dir(const char *path)
327+
static void set_git_dir_1(const char *path)
327328
{
328329
if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
329-
return error("Could not set GIT_DIR to '%s'", path);
330+
die("could not set GIT_DIR to '%s'", path);
330331
setup_git_env(path);
331-
return 0;
332+
}
333+
334+
static void update_relative_gitdir(const char *name,
335+
const char *old_cwd,
336+
const char *new_cwd,
337+
void *data)
338+
{
339+
char *path = reparent_relative_path(old_cwd, new_cwd, get_git_dir());
340+
trace_printf_key(&trace_setup_key,
341+
"setup: move $GIT_DIR to '%s'",
342+
path);
343+
set_git_dir_1(path);
344+
free(path);
345+
}
346+
347+
void set_git_dir(const char *path)
348+
{
349+
set_git_dir_1(path);
350+
if (!is_absolute_path(path))
351+
chdir_notify_register(NULL, update_relative_gitdir, NULL);
332352
}
333353

334354
const char *get_log_output_encoding(void)

refs/files-backend.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "../lockfile.h"
1010
#include "../object.h"
1111
#include "../dir.h"
12+
#include "../chdir-notify.h"
1213

1314
/*
1415
* 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,
106107
refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
107108
strbuf_release(&sb);
108109

110+
chdir_notify_reparent("files-backend $GIT_DIR",
111+
&refs->gitdir);
112+
chdir_notify_reparent("files-backend $GIT_COMMONDIR",
113+
&refs->gitcommondir);
114+
109115
return ref_store;
110116
}
111117

refs/packed-backend.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "packed-backend.h"
66
#include "../iterator.h"
77
#include "../lockfile.h"
8+
#include "../chdir-notify.h"
89

910
enum mmap_strategy {
1011
/*
@@ -202,6 +203,8 @@ struct ref_store *packed_ref_store_create(const char *path,
202203
refs->store_flags = store_flags;
203204

204205
refs->path = xstrdup(path);
206+
chdir_notify_reparent("packed-refs", &refs->path);
207+
205208
return ref_store;
206209
}
207210

setup.c

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "config.h"
44
#include "dir.h"
55
#include "string-list.h"
6+
#include "chdir-notify.h"
67

78
static int inside_git_dir = -1;
89
static int inside_work_tree = -1;
@@ -378,7 +379,7 @@ int is_inside_work_tree(void)
378379

379380
void setup_work_tree(void)
380381
{
381-
const char *work_tree, *git_dir;
382+
const char *work_tree;
382383
static int initialized = 0;
383384

384385
if (initialized)
@@ -388,10 +389,7 @@ void setup_work_tree(void)
388389
die(_("unable to set up work tree using invalid config"));
389390

390391
work_tree = get_git_work_tree();
391-
git_dir = get_git_dir();
392-
if (!is_absolute_path(git_dir))
393-
git_dir = real_path(get_git_dir());
394-
if (!work_tree || chdir(work_tree))
392+
if (!work_tree || chdir_notify(work_tree))
395393
die(_("this operation must be run in a work tree"));
396394

397395
/*
@@ -401,7 +399,6 @@ void setup_work_tree(void)
401399
if (getenv(GIT_WORK_TREE_ENVIRONMENT))
402400
setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
403401

404-
set_git_dir(remove_leading_path(git_dir, work_tree));
405402
initialized = 1;
406403
}
407404

t/t1501-work-tree.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,4 +431,16 @@ test_expect_success 'error out gracefully on invalid $GIT_WORK_TREE' '
431431
)
432432
'
433433

434+
test_expect_success 'refs work with relative gitdir and work tree' '
435+
git init relative &&
436+
git -C relative commit --allow-empty -m one &&
437+
git -C relative commit --allow-empty -m two &&
438+
439+
GIT_DIR=relative/.git GIT_WORK_TREE=relative git reset HEAD^ &&
440+
441+
git -C relative log -1 --format=%s >actual &&
442+
echo one >expect &&
443+
test_cmp expect actual
444+
'
445+
434446
test_done

trace.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
struct trace_key trace_default_key = { "GIT_TRACE", 0, 0, 0 };
2828
struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
29+
struct trace_key trace_setup_key = TRACE_KEY_INIT(SETUP);
2930

3031
/* Get a trace file descriptor from "key" env variable. */
3132
static int get_trace_fd(struct trace_key *key)
@@ -300,11 +301,10 @@ static const char *quote_crnl(const char *path)
300301
/* FIXME: move prefix to startup_info struct and get rid of this arg */
301302
void trace_repo_setup(const char *prefix)
302303
{
303-
static struct trace_key key = TRACE_KEY_INIT(SETUP);
304304
const char *git_work_tree;
305305
char *cwd;
306306

307-
if (!trace_want(&key))
307+
if (!trace_want(&trace_setup_key))
308308
return;
309309

310310
cwd = xgetcwd();
@@ -315,11 +315,11 @@ void trace_repo_setup(const char *prefix)
315315
if (!prefix)
316316
prefix = "(null)";
317317

318-
trace_printf_key(&key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
319-
trace_printf_key(&key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
320-
trace_printf_key(&key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
321-
trace_printf_key(&key, "setup: cwd: %s\n", quote_crnl(cwd));
322-
trace_printf_key(&key, "setup: prefix: %s\n", quote_crnl(prefix));
318+
trace_printf_key(&trace_setup_key, "setup: git_dir: %s\n", quote_crnl(get_git_dir()));
319+
trace_printf_key(&trace_setup_key, "setup: git_common_dir: %s\n", quote_crnl(get_git_common_dir()));
320+
trace_printf_key(&trace_setup_key, "setup: worktree: %s\n", quote_crnl(git_work_tree));
321+
trace_printf_key(&trace_setup_key, "setup: cwd: %s\n", quote_crnl(cwd));
322+
trace_printf_key(&trace_setup_key, "setup: prefix: %s\n", quote_crnl(prefix));
323323

324324
free(cwd);
325325
}

trace.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern struct trace_key trace_default_key;
1515

1616
#define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 }
1717
extern struct trace_key trace_perf_key;
18+
extern struct trace_key trace_setup_key;
1819

1920
extern void trace_repo_setup(const char *prefix);
2021
extern int trace_want(struct trace_key *key);

0 commit comments

Comments
 (0)