Skip to content

Commit a55ed71

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 <[email protected]>
1 parent d621a28 commit a55ed71

File tree

3 files changed

+215
-0
lines changed

3 files changed

+215
-0
lines changed

wt-status-deserialize.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "trace.h"
99
#include "statinfo.h"
1010
#include "hex.h"
11+
#include "path.h"
1112

1213
static struct trace_key trace_deserialize = TRACE_KEY_INIT(DESERIALIZE);
1314

@@ -70,12 +71,69 @@ static int my_validate_index(const char *path, const struct cache_time *mtime_re
7071
return DESERIALIZE_OK;
7172
}
7273

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

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

@@ -175,6 +247,19 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
175247
return DESERIALIZE_ERR;
176248
}
177249

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

wt-status-serialize.c

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,133 @@
1+
#define USE_THE_REPOSITORY_VARIABLE
2+
13
#include "git-compat-util.h"
4+
#include "environment.h"
25
#include "hex.h"
36
#include "repository.h"
47
#include "wt-status.h"
58
#include "pkt-line.h"
69
#include "trace.h"
710
#include "read-cache-ll.h"
11+
#include "path.h"
812

913
static struct trace_key trace_serialize = TRACE_KEY_INIT(SERIALIZE);
1014

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

24146
/*
25147
* 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
@@ -236,4 +236,12 @@ void wt_status_serialize_v1(int fd, struct wt_status *s);
236236
int wt_status_deserialize(const struct wt_status *cmd_s,
237237
const char *path);
238238

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

0 commit comments

Comments
 (0)