Skip to content

Commit 10213b4

Browse files
jeffhostetlervdye
authored andcommitted
serialize-status: serialize global and repo-local exclude file metadata
Changes to the global or repo-local excludes files can change the results returned by "git status" for untracked files. Therefore, it is important that the exclude-file values used during serialization are still current at the time of deserialization. Teach "git status --serialize" to report metadata on the user's global exclude file (which defaults to "$XDG_HOME/git/ignore") and for the repo-local excludes file (which is in ".git/info/excludes"). Serialize will record the pathnames and mtimes for these files in the serialization header (next to the mtime data for the .git/index file). Teach "git status --deserialize" to validate this new metadata. If either exclude file has changed since the serialization-cache-file was written, then deserialize will reject the cache file and force a full/normal status run. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
1 parent 98d6e0c commit 10213b4

File tree

3 files changed

+211
-0
lines changed

3 files changed

+211
-0
lines changed

wt-status-deserialize.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,69 @@ static int my_validate_index(const struct cache_time *mtime_reported)
6969
return DESERIALIZE_OK;
7070
}
7171

72+
/*
73+
* Use the given key and exclude pathname to compute a serialization header
74+
* reflecting the current contents on disk. See if that matches the value
75+
* computed for this key when the cache was written. Reject the cache if
76+
* anything has changed.
77+
*/
78+
static int my_validate_excludes(const char *path, const char *key, const char *line)
79+
{
80+
struct strbuf sb = STRBUF_INIT;
81+
int r;
82+
83+
wt_serialize_compute_exclude_header(&sb, key, path);
84+
85+
r = (strcmp(line, sb.buf) ? DESERIALIZE_ERR : DESERIALIZE_OK);
86+
87+
if (r == DESERIALIZE_ERR)
88+
trace_printf_key(&trace_deserialize,
89+
"%s changed [cached '%s'][observed '%s']",
90+
key, line, sb.buf);
91+
92+
strbuf_release(&sb);
93+
return r;
94+
}
95+
96+
static int my_parse_core_excludes(const char *line)
97+
{
98+
/*
99+
* In dir.c:setup_standard_excludes() they use either the value of
100+
* the "core.excludefile" variable (stored in the global "excludes_file"
101+
* variable) -or- the default value "$XDG_HOME/git/ignore". This is done
102+
* during wt_status_collect_untracked() which we are hoping to not call.
103+
*
104+
* Fake the setup here.
105+
*/
106+
107+
if (excludes_file) {
108+
return my_validate_excludes(excludes_file, "core_excludes", line);
109+
} else {
110+
char *path = xdg_config_home("ignore");
111+
int r = my_validate_excludes(path, "core_excludes", line);
112+
free(path);
113+
return r;
114+
}
115+
}
116+
117+
static int my_parse_repo_excludes(const char *line)
118+
{
119+
char *path = git_pathdup("info/exclude");
120+
int r = my_validate_excludes(path, "repo_excludes", line);
121+
free(path);
122+
123+
return r;
124+
}
125+
72126
static int wt_deserialize_v1_header(struct wt_status *s, int fd)
73127
{
74128
struct cache_time index_mtime;
75129
int line_len, nr_fields;
76130
const char *line;
77131
const char *arg;
132+
int have_required_index_mtime = 0;
133+
int have_required_core_excludes = 0;
134+
int have_required_repo_excludes = 0;
78135

79136
/*
80137
* parse header lines up to the first flush packet.
@@ -90,6 +147,20 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
90147
nr_fields, line);
91148
return DESERIALIZE_ERR;
92149
}
150+
have_required_index_mtime = 1;
151+
continue;
152+
}
153+
154+
if (skip_prefix(line, "core_excludes ", &arg)) {
155+
if (my_parse_core_excludes(line) != DESERIALIZE_OK)
156+
return DESERIALIZE_ERR;
157+
have_required_core_excludes = 1;
158+
continue;
159+
}
160+
if (skip_prefix(line, "repo_excludes ", &arg)) {
161+
if (my_parse_repo_excludes(line) != DESERIALIZE_OK)
162+
return DESERIALIZE_ERR;
163+
have_required_repo_excludes = 1;
93164
continue;
94165
}
95166

@@ -174,6 +245,19 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
174245
return DESERIALIZE_ERR;
175246
}
176247

248+
if (!have_required_index_mtime) {
249+
trace_printf_key(&trace_deserialize, "missing '%s'", "index_mtime");
250+
return DESERIALIZE_ERR;
251+
}
252+
if (!have_required_core_excludes) {
253+
trace_printf_key(&trace_deserialize, "missing '%s'", "core_excludes");
254+
return DESERIALIZE_ERR;
255+
}
256+
if (!have_required_repo_excludes) {
257+
trace_printf_key(&trace_deserialize, "missing '%s'", "repo_excludes");
258+
return DESERIALIZE_ERR;
259+
}
260+
177261
return my_validate_index(&index_mtime);
178262
}
179263

wt-status-serialize.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "git-compat-util.h"
2+
#include "environment.h"
23
#include "hex.h"
34
#include "cache.h"
45
#include "repository.h"
@@ -8,6 +9,122 @@
89

910
static struct trace_key trace_serialize = TRACE_KEY_INIT(SERIALIZE);
1011

12+
/*
13+
* Compute header record for exclude file using format:
14+
* <key> SP <status_char> SP <variant> LF
15+
*/
16+
void wt_serialize_compute_exclude_header(struct strbuf *sb,
17+
const char *key,
18+
const char *path)
19+
{
20+
struct stat st;
21+
struct stat_data sd;
22+
23+
memset(&sd, 0, sizeof(sd));
24+
25+
strbuf_setlen(sb, 0);
26+
27+
if (!path || !*path) {
28+
strbuf_addf(sb, "%s U (unset)", key);
29+
} else if (lstat(path, &st) == -1) {
30+
if (is_missing_file_error(errno))
31+
strbuf_addf(sb, "%s E (not-found) %s", key, path);
32+
else
33+
strbuf_addf(sb, "%s E (other) %s", key, path);
34+
} else {
35+
fill_stat_data(&sd, &st);
36+
strbuf_addf(sb, "%s F %d %d %s",
37+
key, sd.sd_mtime.sec, sd.sd_mtime.nsec, path);
38+
}
39+
}
40+
41+
static void append_exclude_info(int fd, const char *path, const char *key)
42+
{
43+
struct strbuf sb = STRBUF_INIT;
44+
45+
wt_serialize_compute_exclude_header(&sb, key, path);
46+
47+
packet_write_fmt(fd, "%s\n", sb.buf);
48+
49+
strbuf_release(&sb);
50+
}
51+
52+
static void append_core_excludes_file_info(int fd)
53+
{
54+
/*
55+
* Write pathname and mtime of the core/global excludes file to
56+
* the status cache header. Since a change in the global excludes
57+
* will/may change the results reported by status, the deserialize
58+
* code should be able to reject the status cache if the excludes
59+
* file changes since when the cache was written.
60+
*
61+
* The "core.excludefile" setting defaults to $XDG_HOME/git/ignore
62+
* and uses a global variable which should have been set during
63+
* wt_status_collect_untracked().
64+
*
65+
* See dir.c:setup_standard_excludes()
66+
*/
67+
append_exclude_info(fd, excludes_file, "core_excludes");
68+
}
69+
70+
static void append_repo_excludes_file_info(int fd)
71+
{
72+
/*
73+
* Likewise, there is a per-repo excludes file in .git/info/excludes
74+
* that can change the results reported by status. And the deserialize
75+
* code needs to be able to reject the status cache if this file
76+
* changes.
77+
*
78+
* See dir.c:setup_standard_excludes() and git_path_info_excludes().
79+
* We replicate the pathname construction here because of the static
80+
* variables/functions used in dir.c.
81+
*/
82+
char *path = git_pathdup("info/exclude");
83+
84+
append_exclude_info(fd, path, "repo_excludes");
85+
86+
free(path);
87+
}
88+
89+
/*
90+
* WARNING: The status cache attempts to preserve the essential in-memory
91+
* status data after a status scan into a "serialization" (aka "status cache")
92+
* file. It allows later "git status --deserialize=<foo>" instances to
93+
* just print the cached status results without scanning the workdir (and
94+
* without reading the index).
95+
*
96+
* The status cache file is valid as long as:
97+
* [1] the set of functional command line options are the same (think "-u").
98+
* [2] repo-local and user-global configuration settings are compatible.
99+
* [3] nothing in the workdir has changed.
100+
*
101+
* We rely on:
102+
* [1.a] We remember the relevant (functional, non-display) command line
103+
* arguments in the status cache header.
104+
* [2.a] We use the mtime of the .git/index to detect staging changes.
105+
* [2.b] We use the mtimes of the excludes files to detect changes that
106+
* might affect untracked file reporting.
107+
*
108+
* But we need external help to verify [3].
109+
* [] This includes changes to tracked files.
110+
* [] This includes changes to tracked .gitignore files that might change
111+
* untracked file reporting.
112+
* [] This includes the creation of new, untracked per-directory .gitignore
113+
* files that might change untracked file reporting.
114+
*
115+
* [3.a] On GVFS repos, we rely on the GVFS service (mount) daemon to
116+
* watch the filesystem and invalidate (delete) the status cache
117+
* when anything changes inside the workdir.
118+
*
119+
* [3.b] TODO This problem is not solved for non-GVFS repos.
120+
* [] It is possible that the untracked-cache index extension
121+
* could help with this but that requires status to read the
122+
* index to load the extension.
123+
* [] It is possible that the new fsmonitor facility could also
124+
* provide this information, but that to requires reading the
125+
* index.
126+
*/
127+
11128
/*
12129
* Write V1 header fields.
13130
*/
@@ -20,6 +137,8 @@ static void wt_serialize_v1_header(struct wt_status *s, int fd)
20137
packet_write_fmt(fd, "index_mtime %d %d\n",
21138
s->repo->index->timestamp.sec,
22139
s->repo->index->timestamp.nsec);
140+
append_core_excludes_file_info(fd);
141+
append_repo_excludes_file_info(fd);
23142

24143
/*
25144
* Write data from wt_status to qualify this status report.

wt-status.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,12 @@ void wt_status_serialize_v1(int fd, struct wt_status *s);
233233
int wt_status_deserialize(const struct wt_status *cmd_s,
234234
const char *path);
235235

236+
/*
237+
* A helper routine for serialize and deserialize to compute
238+
* metadata for the user-global and repo-local excludes files.
239+
*/
240+
void wt_serialize_compute_exclude_header(struct strbuf *sb,
241+
const char *key,
242+
const char *path);
243+
236244
#endif /* STATUS_H */

0 commit comments

Comments
 (0)