Skip to content

Commit 33019ba

Browse files
jeffhostetlerKevin Willford
authored and
Kevin Willford
committed
Merge pull request #1 from jeffhostetler/gvfs-serialize-exclude
serialize-status: serialize global and repo-local exclude file metadata
2 parents e46f9fd + 53f4232 commit 33019ba

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed

wt-status-deserialize.c

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

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

75132
/*
76133
* parse header lines up to the first flush packet.
@@ -86,6 +143,20 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
86143
nr_fields, line);
87144
return DESERIALIZE_ERR;
88145
}
146+
have_required_index_mtime = 1;
147+
continue;
148+
}
149+
150+
if (skip_prefix(line, "core_excludes ", &arg)) {
151+
if (my_parse_core_excludes(line) != DESERIALIZE_OK)
152+
return DESERIALIZE_ERR;
153+
have_required_core_excludes = 1;
154+
continue;
155+
}
156+
if (skip_prefix(line, "repo_excludes ", &arg)) {
157+
if (my_parse_repo_excludes(line) != DESERIALIZE_OK)
158+
return DESERIALIZE_ERR;
159+
have_required_repo_excludes = 1;
89160
continue;
90161
}
91162

@@ -170,6 +241,19 @@ static int wt_deserialize_v1_header(struct wt_status *s, int fd)
170241
return DESERIALIZE_ERR;
171242
}
172243

244+
if (!have_required_index_mtime) {
245+
trace_printf_key(&trace_deserialize, "missing '%s'", "index_mtime");
246+
return DESERIALIZE_ERR;
247+
}
248+
if (!have_required_core_excludes) {
249+
trace_printf_key(&trace_deserialize, "missing '%s'", "core_excludes");
250+
return DESERIALIZE_ERR;
251+
}
252+
if (!have_required_repo_excludes) {
253+
trace_printf_key(&trace_deserialize, "missing '%s'", "repo_excludes");
254+
return DESERIALIZE_ERR;
255+
}
256+
173257
return my_validate_index(&index_mtime);
174258
}
175259

wt-status-serialize.c

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,122 @@
44

55
static struct trace_key trace_serialize = TRACE_KEY_INIT(SERIALIZE);
66

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

20138
/*
21139
* 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
@@ -198,4 +198,12 @@ void wt_status_serialize_v1(int fd, struct wt_status *s);
198198
int wt_status_deserialize(const struct wt_status *cmd_s,
199199
const char *path);
200200

201+
/*
202+
* A helper routine for serialize and deserialize to compute
203+
* metadata for the user-global and repo-local excludes files.
204+
*/
205+
void wt_serialize_compute_exclude_header(struct strbuf *sb,
206+
const char *key,
207+
const char *path);
208+
201209
#endif /* STATUS_H */

0 commit comments

Comments
 (0)