Skip to content

Commit ed8c247

Browse files
jeffhostetlerdscho
authored andcommitted
status: deserialization wait
Teach `git status --deserialize` to either wait indefintely or immediately fail if the status serialization cache file is stale. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
1 parent 03aa602 commit ed8c247

File tree

5 files changed

+245
-13
lines changed

5 files changed

+245
-13
lines changed

Documentation/config/status.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,19 @@ status.deserializePath::
8181
generated by `--serialize`. This will be overridden by
8282
`--deserialize=<path>` on the command line. If the cache file is
8383
invalid or stale, git will fall-back and compute status normally.
84+
85+
status.deserializeWait::
86+
EXPERIMENTAL, Specifies what `git status --deserialize` should do
87+
if the serialization cache file is stale and whether it should
88+
fall-back and compute status normally. This will be overridden by
89+
`--deserialize-wait=<value>` on the command line.
90+
+
91+
--
92+
* `fail` - cause git to exit with an error when the status cache file
93+
is stale; this is intended for testing and debugging.
94+
* `block` - cause git to spin and periodically retry the cache file
95+
every 100 ms; this is intended to help coordinate with another git
96+
instance concurrently computing the cache file.
97+
* `no` - to immediately fall-back if cache file is stale. This is the default.
98+
* `<timeout>` - time (in tenths of a second) to spin and retry.
99+
--

builtin/commit.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ static int do_implicit_deserialize = 0;
167167
static int do_explicit_deserialize = 0;
168168
static char *deserialize_path = NULL;
169169

170+
static enum wt_status_deserialize_wait implicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
171+
static enum wt_status_deserialize_wait explicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
172+
170173
/*
171174
* --serialize | --serialize=<path>
172175
*
@@ -232,6 +235,40 @@ static int opt_parse_deserialize(const struct option *opt, const char *arg, int
232235
return 0;
233236
}
234237

238+
static enum wt_status_deserialize_wait parse_dw(const char *arg)
239+
{
240+
int tenths;
241+
242+
if (!strcmp(arg, "fail"))
243+
return DESERIALIZE_WAIT__FAIL;
244+
else if (!strcmp(arg, "block"))
245+
return DESERIALIZE_WAIT__BLOCK;
246+
else if (!strcmp(arg, "no"))
247+
return DESERIALIZE_WAIT__NO;
248+
249+
/*
250+
* Otherwise, assume it is a timeout in tenths of a second.
251+
* If it contains a bogus value, atol() will return zero
252+
* which is OK.
253+
*/
254+
tenths = atol(arg);
255+
if (tenths < 0)
256+
tenths = DESERIALIZE_WAIT__NO;
257+
return tenths;
258+
}
259+
260+
static int opt_parse_deserialize_wait(const struct option *opt,
261+
const char *arg,
262+
int unset)
263+
{
264+
if (unset)
265+
explicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
266+
else
267+
explicit_deserialize_wait = parse_dw(arg);
268+
269+
return 0;
270+
}
271+
235272
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
236273
{
237274
struct strbuf *buf = opt->value;
@@ -1524,6 +1561,13 @@ static int git_status_config(const char *k, const char *v, void *cb)
15241561
}
15251562
return 0;
15261563
}
1564+
if (!strcmp(k, "status.deserializewait")) {
1565+
if (!v || !*v)
1566+
implicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
1567+
else
1568+
implicit_deserialize_wait = parse_dw(v);
1569+
return 0;
1570+
}
15271571
if (!strcmp(k, "status.showuntrackedfiles")) {
15281572
if (!v)
15291573
return config_error_nonbool(k);
@@ -1586,6 +1630,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
15861630
{ OPTION_CALLBACK, 0, "deserialize", NULL,
15871631
N_("path"), N_("deserialize raw status data from file"),
15881632
PARSE_OPT_OPTARG, opt_parse_deserialize },
1633+
{ OPTION_CALLBACK, 0, "deserialize-wait", NULL,
1634+
N_("fail|block|no"), N_("how to wait if status cache file is invalid"),
1635+
PARSE_OPT_OPTARG, opt_parse_deserialize_wait },
15891636
OPT_SET_INT(0, "long", &status_format,
15901637
N_("show status in long format (default)"),
15911638
STATUS_FORMAT_LONG),
@@ -1682,11 +1729,21 @@ int cmd_status(int argc, const char **argv, const char *prefix)
16821729
}
16831730

16841731
if (try_deserialize) {
1732+
int result;
1733+
enum wt_status_deserialize_wait dw = implicit_deserialize_wait;
1734+
if (explicit_deserialize_wait != DESERIALIZE_WAIT__UNSET)
1735+
dw = explicit_deserialize_wait;
1736+
if (dw == DESERIALIZE_WAIT__UNSET)
1737+
dw = DESERIALIZE_WAIT__NO;
1738+
16851739
if (s.relative_paths)
16861740
s.prefix = prefix;
16871741

1688-
if (wt_status_deserialize(&s, deserialize_path) == DESERIALIZE_OK)
1742+
result = wt_status_deserialize(&s, deserialize_path, dw);
1743+
if (result == DESERIALIZE_OK)
16891744
return 0;
1745+
if (dw == DESERIALIZE_WAIT__FAIL)
1746+
die(_("Rejected status serialization cache"));
16901747

16911748
/* deserialize failed, so force the initialization we skipped above. */
16921749
enable_fscache(1);

t/t7522-serialized-status.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,58 @@ test_expect_success 'verify new --serialize=path mode' '
199199
test_cmp expect output.2
200200
'
201201

202+
test_expect_success 'try deserialize-wait feature' '
203+
test_when_finished "rm -f serialized_status.dat dirt expect.* output.* trace.*" &&
204+
205+
git status --serialize=serialized_status.dat >output.1 &&
206+
207+
# make status cache stale by updating the mtime on the index. confirm that
208+
# deserialize fails when requested.
209+
sleep 1 &&
210+
touch .git/index &&
211+
test_must_fail git status --deserialize=serialized_status.dat --deserialize-wait=fail &&
212+
test_must_fail git -c status.deserializeWait=fail status --deserialize=serialized_status.dat &&
213+
214+
cat >expect.1 <<-\EOF &&
215+
? expect.1
216+
? output.1
217+
? serialized_status.dat
218+
? untracked/
219+
? untracked_1.txt
220+
EOF
221+
222+
# refresh the status cache.
223+
git status --porcelain=v2 --serialize=serialized_status.dat >output.1 &&
224+
test_cmp expect.1 output.1 &&
225+
226+
# create some dirt. confirm deserialize used the existing status cache.
227+
echo x >dirt &&
228+
git status --porcelain=v2 --deserialize=serialized_status.dat >output.2 &&
229+
test_cmp output.1 output.2 &&
230+
231+
# make the cache stale and try the timeout feature and wait upto
232+
# 2 tenths of a second. confirm deserialize timed out and rejected
233+
# the status cache and did a normal scan.
234+
235+
cat >expect.2 <<-\EOF &&
236+
? dirt
237+
? expect.1
238+
? expect.2
239+
? output.1
240+
? output.2
241+
? serialized_status.dat
242+
? trace.2
243+
? untracked/
244+
? untracked_1.txt
245+
EOF
246+
247+
sleep 1 &&
248+
touch .git/index &&
249+
GIT_TRACE_DESERIALIZE=1 git status --porcelain=v2 --deserialize=serialized_status.dat --deserialize-wait=2 >output.2 2>trace.2 &&
250+
test_cmp expect.2 output.2 &&
251+
grep "wait polled=2 result=1" trace.2 >trace.2g
252+
'
253+
202254
test_expect_success 'merge conflicts' '
203255
204256
# create a merge conflict.

wt-status-deserialize.c

Lines changed: 108 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ static int my_validate_index(const struct cache_time *mtime_reported)
5656
mtime_observed_on_disk.nsec = ST_MTIME_NSEC(st);
5757
if ((mtime_observed_on_disk.sec != mtime_reported->sec) ||
5858
(mtime_observed_on_disk.nsec != mtime_reported->nsec)) {
59-
trace_printf_key(&trace_deserialize, "index mtime changed [des %d.%d][obs %d.%d]",
59+
trace_printf_key(&trace_deserialize,
60+
"index mtime changed [des %d %d][obs %d %d]",
6061
mtime_reported->sec, mtime_reported->nsec,
6162
mtime_observed_on_disk.sec, mtime_observed_on_disk.nsec);
6263
return DESERIALIZE_ERR;
@@ -548,6 +549,8 @@ static inline int my_strcmp_null(const char *a, const char *b)
548549

549550
static int wt_deserialize_fd(const struct wt_status *cmd_s, struct wt_status *des_s, int fd)
550551
{
552+
memset(des_s, 0, sizeof(*des_s));
553+
551554
/*
552555
* Check the path spec on the current command
553556
*/
@@ -671,33 +674,127 @@ static int wt_deserialize_fd(const struct wt_status *cmd_s, struct wt_status *de
671674
return DESERIALIZE_OK;
672675
}
673676

677+
static struct cache_time deserialize_prev_mtime = { 0, 0 };
678+
679+
static int try_deserialize_read_from_file_1(const struct wt_status *cmd_s,
680+
const char *path,
681+
struct wt_status *des_s)
682+
{
683+
struct stat st;
684+
int result;
685+
int fd;
686+
687+
/*
688+
* If we are spinning waiting for the status cache to become
689+
* valid, skip re-reading it if the mtime has not changed
690+
* since the last time we read it.
691+
*/
692+
if (lstat(path, &st)) {
693+
trace_printf_key(&trace_deserialize,
694+
"could not lstat '%s'", path);
695+
return DESERIALIZE_ERR;
696+
}
697+
if (st.st_mtime == deserialize_prev_mtime.sec &&
698+
ST_MTIME_NSEC(st) == deserialize_prev_mtime.nsec) {
699+
trace_printf_key(&trace_deserialize,
700+
"mtime has not changed '%s'", path);
701+
return DESERIALIZE_ERR;
702+
}
703+
704+
fd = xopen(path, O_RDONLY);
705+
if (fd == -1) {
706+
trace_printf_key(&trace_deserialize,
707+
"could not read '%s'", path);
708+
return DESERIALIZE_ERR;
709+
}
710+
711+
deserialize_prev_mtime.sec = st.st_mtime;
712+
deserialize_prev_mtime.nsec = ST_MTIME_NSEC(st);
713+
714+
trace_printf_key(&trace_deserialize,
715+
"reading serialization file (%d %d) '%s'",
716+
deserialize_prev_mtime.sec,
717+
deserialize_prev_mtime.nsec,
718+
path);
719+
720+
result = wt_deserialize_fd(cmd_s, des_s, fd);
721+
close(fd);
722+
723+
return result;
724+
}
725+
726+
static int try_deserialize_read_from_file(const struct wt_status *cmd_s,
727+
const char *path,
728+
enum wt_status_deserialize_wait dw,
729+
struct wt_status *des_s)
730+
{
731+
int k, limit;
732+
int result = DESERIALIZE_ERR;
733+
734+
/*
735+
* For "fail" or "no", try exactly once to read the status cache.
736+
* Return an error if the file is stale.
737+
*/
738+
if (dw == DESERIALIZE_WAIT__FAIL || dw == DESERIALIZE_WAIT__NO)
739+
return try_deserialize_read_from_file_1(cmd_s, path, des_s);
740+
741+
/*
742+
* Wait for the status cache file to refresh. Wait duration can
743+
* be in tenths of a second or unlimited. Poll every 100ms.
744+
*/
745+
if (dw == DESERIALIZE_WAIT__BLOCK) {
746+
/*
747+
* Convert "unlimited" to 1 day.
748+
*/
749+
limit = 10 * 60 * 60 * 24;
750+
} else {
751+
/* spin for dw tenths of a second */
752+
limit = dw;
753+
}
754+
for (k = 0; k < limit; k++) {
755+
result = try_deserialize_read_from_file_1(
756+
cmd_s, path, des_s);
757+
758+
if (result == DESERIALIZE_OK)
759+
break;
760+
761+
sleep_millisec(100);
762+
}
763+
764+
trace_printf_key(&trace_deserialize,
765+
"wait polled=%d result=%d '%s'",
766+
k, result, path);
767+
return result;
768+
}
769+
674770
/*
675-
* Read raw serialized status data from the given file
771+
* Read raw serialized status data from the given file (or STDIN).
676772
*
677773
* Verify that the args specified in the current command
678774
* are compatible with the deserialized data (such as "-uno").
679775
*
680776
* Copy display-related fields from the current command
681777
* into the deserialized data (so that the user can request
682778
* long or short as they please).
779+
*
780+
* Print status report using cached data.
683781
*/
684782
int wt_status_deserialize(const struct wt_status *cmd_s,
685-
const char *path)
783+
const char *path,
784+
enum wt_status_deserialize_wait dw)
686785
{
687786
struct wt_status des_s;
688787
int result;
689788

690789
if (path && *path && strcmp(path, "0")) {
691-
int fd = xopen(path, O_RDONLY);
692-
if (fd == -1) {
693-
trace_printf_key(&trace_deserialize, "could not read '%s'", path);
694-
return DESERIALIZE_ERR;
695-
}
696-
trace_printf_key(&trace_deserialize, "reading serialization file '%s'", path);
697-
result = wt_deserialize_fd(cmd_s, &des_s, fd);
698-
close(fd);
790+
result = try_deserialize_read_from_file(cmd_s, path, dw, &des_s);
699791
} else {
700792
trace_printf_key(&trace_deserialize, "reading stdin");
793+
794+
/*
795+
* Read status cache data from stdin. Ignore the deserialize-wait
796+
* term, since we cannot read stdin multiple times.
797+
*/
701798
result = wt_deserialize_fd(cmd_s, &des_s, 0);
702799
}
703800

wt-status.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,15 @@ struct wt_status_serialize_data
217217
- sizeof(struct wt_status_serialize_data_fixed)];
218218
};
219219

220+
enum wt_status_deserialize_wait
221+
{
222+
DESERIALIZE_WAIT__UNSET = -3,
223+
DESERIALIZE_WAIT__FAIL = -2, /* return error, do not fallback */
224+
DESERIALIZE_WAIT__BLOCK = -1, /* unlimited timeout */
225+
DESERIALIZE_WAIT__NO = 0, /* immediately fallback */
226+
/* any positive value is a timeout in tenths of a second */
227+
};
228+
220229
/*
221230
* Serialize computed status scan results using "version 1" format
222231
* to the given file.
@@ -231,7 +240,8 @@ void wt_status_serialize_v1(int fd, struct wt_status *s);
231240
* fields.
232241
*/
233242
int wt_status_deserialize(const struct wt_status *cmd_s,
234-
const char *path);
243+
const char *path,
244+
enum wt_status_deserialize_wait dw);
235245

236246
/*
237247
* A helper routine for serialize and deserialize to compute

0 commit comments

Comments
 (0)