Skip to content

Commit f69a8f3

Browse files
jeffhostetlerdscho
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 2b79cef commit f69a8f3

File tree

3 files changed

+213
-0
lines changed

3 files changed

+213
-0
lines changed

wt-status-deserialize.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pkt-line.h"
77
#include "trace.h"
88
#include "statinfo.h"
9+
#include "path.h"
910

1011
static struct trace_key trace_deserialize = TRACE_KEY_INIT(DESERIALIZE);
1112

@@ -69,12 +70,69 @@ static int my_validate_index(const struct cache_time *mtime_reported)
6970
return DESERIALIZE_OK;
7071
}
7172

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

79137
/*
80138
* parse header lines up to the first flush packet.
@@ -90,6 +148,20 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
90148
nr_fields, line);
91149
return DESERIALIZE_ERR;
92150
}
151+
have_required_index_mtime = 1;
152+
continue;
153+
}
154+
155+
if (skip_prefix(line, "core_excludes ", &arg)) {
156+
if (my_parse_core_excludes(line) != DESERIALIZE_OK)
157+
return DESERIALIZE_ERR;
158+
have_required_core_excludes = 1;
159+
continue;
160+
}
161+
if (skip_prefix(line, "repo_excludes ", &arg)) {
162+
if (my_parse_repo_excludes(line) != DESERIALIZE_OK)
163+
return DESERIALIZE_ERR;
164+
have_required_repo_excludes = 1;
93165
continue;
94166
}
95167

@@ -174,6 +246,19 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
174246
return DESERIALIZE_ERR;
175247
}
176248

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

wt-status-serialize.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,131 @@
11
#include "git-compat-util.h"
2+
#include "environment.h"
23
#include "hex.h"
34
#include "repository.h"
45
#include "wt-status.h"
56
#include "pkt-line.h"
67
#include "trace.h"
78
#include "read-cache-ll.h"
9+
#include "path.h"
810

911
static struct trace_key trace_serialize = TRACE_KEY_INIT(SERIALIZE);
1012

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

24144
/*
25145
* 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)