Skip to content

Commit ab82508

Browse files
Merge pull request #7 from jeffhostetler/gvfs-status-serialize-wait
status: deserialization wait
2 parents 54396ce + ea5f297 commit ab82508

File tree

5 files changed

+245
-13
lines changed

5 files changed

+245
-13
lines changed

Documentation/config.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3378,6 +3378,22 @@ status.deserializePath::
33783378
`--deserialize=<path>` on the command line. If the cache file is
33793379
invalid or stale, git will fall-back and compute status normally.
33803380

3381+
status.deserializeWait::
3382+
EXPERIMENTAL, Specifies what `git status --deserialize` should do
3383+
if the serialization cache file is stale and whether it should
3384+
fall-back and compute status normally. This will be overridden by
3385+
`--deserialize-wait=<value>` on the command line.
3386+
+
3387+
--
3388+
* `fail` - cause git to exit with an error when the status cache file
3389+
is stale; this is intended for testing and debugging.
3390+
* `block` - cause git to spin and periodically retry the cache file
3391+
every 100 ms; this is intended to help coordinate with another git
3392+
instance concurrently computing the cache file.
3393+
* `no` - to immediately fall-back if cache file is stale. This is the default.
3394+
* `<timeout>` - time (in tenths of a second) to spin and retry.
3395+
--
3396+
33813397
stash.showPatch::
33823398
If this is set to true, the `git stash show` command without an
33833399
option will show the stash entry in patch form. Defaults to false.

builtin/commit.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ static int do_implicit_deserialize = 0;
134134
static int do_explicit_deserialize = 0;
135135
static char *deserialize_path = NULL;
136136

137+
static enum wt_status_deserialize_wait implicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
138+
static enum wt_status_deserialize_wait explicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
139+
137140
/*
138141
* --serialize | --serialize=<path>
139142
*
@@ -194,6 +197,40 @@ static int opt_parse_deserialize(const struct option *opt, const char *arg, int
194197
return 0;
195198
}
196199

200+
static enum wt_status_deserialize_wait parse_dw(const char *arg)
201+
{
202+
int tenths;
203+
204+
if (!strcmp(arg, "fail"))
205+
return DESERIALIZE_WAIT__FAIL;
206+
else if (!strcmp(arg, "block"))
207+
return DESERIALIZE_WAIT__BLOCK;
208+
else if (!strcmp(arg, "no"))
209+
return DESERIALIZE_WAIT__NO;
210+
211+
/*
212+
* Otherwise, assume it is a timeout in tenths of a second.
213+
* If it contains a bogus value, atol() will return zero
214+
* which is OK.
215+
*/
216+
tenths = atol(arg);
217+
if (tenths < 0)
218+
tenths = DESERIALIZE_WAIT__NO;
219+
return tenths;
220+
}
221+
222+
static int opt_parse_deserialize_wait(const struct option *opt,
223+
const char *arg,
224+
int unset)
225+
{
226+
if (unset)
227+
explicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
228+
else
229+
explicit_deserialize_wait = parse_dw(arg);
230+
231+
return 0;
232+
}
233+
197234
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
198235
{
199236
struct strbuf *buf = opt->value;
@@ -1355,6 +1392,13 @@ static int git_status_config(const char *k, const char *v, void *cb)
13551392
}
13561393
return 0;
13571394
}
1395+
if (!strcmp(k, "status.deserializewait")) {
1396+
if (!v || !*v)
1397+
implicit_deserialize_wait = DESERIALIZE_WAIT__UNSET;
1398+
else
1399+
implicit_deserialize_wait = parse_dw(v);
1400+
return 0;
1401+
}
13581402
if (!strcmp(k, "status.showuntrackedfiles")) {
13591403
if (!v)
13601404
return config_error_nonbool(k);
@@ -1418,6 +1462,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
14181462
{ OPTION_CALLBACK, 0, "deserialize", NULL,
14191463
N_("path"), N_("deserialize raw status data from file"),
14201464
PARSE_OPT_OPTARG, opt_parse_deserialize },
1465+
{ OPTION_CALLBACK, 0, "deserialize-wait", NULL,
1466+
N_("fail|block|no"), N_("how to wait if status cache file is invalid"),
1467+
PARSE_OPT_OPTARG, opt_parse_deserialize_wait },
14211468
OPT_SET_INT(0, "long", &status_format,
14221469
N_("show status in long format (default)"),
14231470
STATUS_FORMAT_LONG),
@@ -1525,11 +1572,21 @@ int cmd_status(int argc, const char **argv, const char *prefix)
15251572
}
15261573

15271574
if (try_deserialize) {
1575+
int result;
1576+
enum wt_status_deserialize_wait dw = implicit_deserialize_wait;
1577+
if (explicit_deserialize_wait != DESERIALIZE_WAIT__UNSET)
1578+
dw = explicit_deserialize_wait;
1579+
if (dw == DESERIALIZE_WAIT__UNSET)
1580+
dw = DESERIALIZE_WAIT__NO;
1581+
15281582
if (s.relative_paths)
15291583
s.prefix = prefix;
15301584

1531-
if (wt_status_deserialize(&s, deserialize_path) == DESERIALIZE_OK)
1585+
result = wt_status_deserialize(&s, deserialize_path, dw);
1586+
if (result == DESERIALIZE_OK)
15321587
return 0;
1588+
if (dw == DESERIALIZE_WAIT__FAIL)
1589+
die(_("Rejected status serialization cache"));
15331590

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

t/t7524-serialized-status.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,58 @@ test_expect_success 'verify new --serialize=path mode' '
186186
test_i18ncmp expect output.2
187187
'
188188

189+
test_expect_success 'try deserialize-wait feature' '
190+
test_when_finished "rm -f serialized_status.dat dirt expect.* output.* trace.*" &&
191+
192+
git status --serialize=serialized_status.dat >output.1 &&
193+
194+
# make status cache stale by updating the mtime on the index. confirm that
195+
# deserialize fails when requested.
196+
sleep 1 &&
197+
touch .git/index &&
198+
test_must_fail git status --deserialize=serialized_status.dat --deserialize-wait=fail &&
199+
test_must_fail git -c status.deserializeWait=fail status --deserialize=serialized_status.dat &&
200+
201+
cat >expect.1 <<-\EOF &&
202+
? expect.1
203+
? output.1
204+
? serialized_status.dat
205+
? untracked/
206+
? untracked_1.txt
207+
EOF
208+
209+
# refresh the status cache.
210+
git status --porcelain=v2 --serialize=serialized_status.dat >output.1 &&
211+
test_cmp expect.1 output.1 &&
212+
213+
# create some dirt. confirm deserialize used the existing status cache.
214+
echo x >dirt &&
215+
git status --porcelain=v2 --deserialize=serialized_status.dat >output.2 &&
216+
test_cmp output.1 output.2 &&
217+
218+
# make the cache stale and try the timeout feature and wait upto
219+
# 2 tenths of a second. confirm deserialize timed out and rejected
220+
# the status cache and did a normal scan.
221+
222+
cat >expect.2 <<-\EOF &&
223+
? dirt
224+
? expect.1
225+
? expect.2
226+
? output.1
227+
? output.2
228+
? serialized_status.dat
229+
? trace.2
230+
? untracked/
231+
? untracked_1.txt
232+
EOF
233+
234+
sleep 1 &&
235+
touch .git/index &&
236+
GIT_TRACE_DESERIALIZE=1 git status --porcelain=v2 --deserialize=serialized_status.dat --deserialize-wait=2 >output.2 2>trace.2 &&
237+
test_cmp expect.2 output.2 &&
238+
grep "wait polled=2 result=1" trace.2 >trace.2g
239+
'
240+
189241
test_expect_success 'merge conflicts' '
190242
191243
# 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;
@@ -545,6 +546,8 @@ static inline int my_strcmp_null(const char *a, const char *b)
545546

546547
static int wt_deserialize_fd(const struct wt_status *cmd_s, struct wt_status *des_s, int fd)
547548
{
549+
memset(des_s, 0, sizeof(*des_s));
550+
548551
/*
549552
* Check the path spec on the current command
550553
*/
@@ -668,33 +671,127 @@ static int wt_deserialize_fd(const struct wt_status *cmd_s, struct wt_status *de
668671
return DESERIALIZE_OK;
669672
}
670673

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

687786
if (path && *path && strcmp(path, "0")) {
688-
int fd = xopen(path, O_RDONLY);
689-
if (fd == -1) {
690-
trace_printf_key(&trace_deserialize, "could not read '%s'", path);
691-
return DESERIALIZE_ERR;
692-
}
693-
trace_printf_key(&trace_deserialize, "reading serialization file '%s'", path);
694-
result = wt_deserialize_fd(cmd_s, &des_s, fd);
695-
close(fd);
787+
result = try_deserialize_read_from_file(cmd_s, path, dw, &des_s);
696788
} else {
697789
trace_printf_key(&trace_deserialize, "reading stdin");
790+
791+
/*
792+
* Read status cache data from stdin. Ignore the deserialize-wait
793+
* term, since we cannot read stdin multiple times.
794+
*/
698795
result = wt_deserialize_fd(cmd_s, &des_s, 0);
699796
}
700797

wt-status.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ struct wt_status_serialize_data
182182
- sizeof(struct wt_status_serialize_data_fixed)];
183183
};
184184

185+
enum wt_status_deserialize_wait
186+
{
187+
DESERIALIZE_WAIT__UNSET = -3,
188+
DESERIALIZE_WAIT__FAIL = -2, /* return error, do not fallback */
189+
DESERIALIZE_WAIT__BLOCK = -1, /* unlimited timeout */
190+
DESERIALIZE_WAIT__NO = 0, /* immediately fallback */
191+
/* any positive value is a timeout in tenths of a second */
192+
};
193+
185194
/*
186195
* Serialize computed status scan results using "version 1" format
187196
* to the given file.
@@ -196,7 +205,8 @@ void wt_status_serialize_v1(int fd, struct wt_status *s);
196205
* fields.
197206
*/
198207
int wt_status_deserialize(const struct wt_status *cmd_s,
199-
const char *path);
208+
const char *path,
209+
enum wt_status_deserialize_wait dw);
200210

201211
/*
202212
* A helper routine for serialize and deserialize to compute

0 commit comments

Comments
 (0)