@@ -48,6 +48,8 @@ struct fsmonitor_daemon_backend_data
48
48
#define LISTENER_HAVE_DATA_WORKTREE 1
49
49
#define LISTENER_HAVE_DATA_GITDIR 2
50
50
int nr_listener_handles ;
51
+
52
+ struct strbuf dot_git_shortname ;
51
53
};
52
54
53
55
/*
@@ -258,6 +260,62 @@ static void cancel_rdcw_watch(struct one_watch *watch)
258
260
watch -> is_active = FALSE;
259
261
}
260
262
263
+ /*
264
+ * Process a single relative pathname event.
265
+ * Return 1 if we should shutdown.
266
+ */
267
+ static int process_1_worktree_event (
268
+ FILE_NOTIFY_INFORMATION * info ,
269
+ struct string_list * cookie_list ,
270
+ struct fsmonitor_batch * * batch ,
271
+ const struct strbuf * path ,
272
+ enum fsmonitor_path_type t )
273
+ {
274
+ const char * slash ;
275
+
276
+ switch (t ) {
277
+ case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX :
278
+ /* special case cookie files within .git */
279
+
280
+ /* Use just the filename of the cookie file. */
281
+ slash = find_last_dir_sep (path -> buf );
282
+ string_list_append (cookie_list ,
283
+ slash ? slash + 1 : path -> buf );
284
+ break ;
285
+
286
+ case IS_INSIDE_DOT_GIT :
287
+ /* ignore everything inside of "<worktree>/.git/" */
288
+ break ;
289
+
290
+ case IS_DOT_GIT :
291
+ /* "<worktree>/.git" was deleted (or renamed away) */
292
+ if ((info -> Action == FILE_ACTION_REMOVED ) ||
293
+ (info -> Action == FILE_ACTION_RENAMED_OLD_NAME )) {
294
+ trace2_data_string ("fsmonitor" , NULL ,
295
+ "fsm-listen/dotgit" ,
296
+ "removed" );
297
+ return 1 ;
298
+ }
299
+ break ;
300
+
301
+ case IS_WORKDIR_PATH :
302
+ /* queue normal pathname */
303
+ if (!* batch )
304
+ * batch = fsmonitor_batch__new ();
305
+ fsmonitor_batch__add_path (* batch , path -> buf );
306
+ break ;
307
+
308
+ case IS_GITDIR :
309
+ case IS_INSIDE_GITDIR :
310
+ case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX :
311
+ default :
312
+ BUG ("unexpected path classification '%d' for '%s'" ,
313
+ t , path -> buf );
314
+ }
315
+
316
+ return 0 ;
317
+ }
318
+
261
319
/*
262
320
* Process filesystem events that happen anywhere (recursively) under the
263
321
* <worktree> root directory. For a normal working directory, this includes
@@ -302,7 +360,6 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
302
360
*/
303
361
for (;;) {
304
362
FILE_NOTIFY_INFORMATION * info = (void * )p ;
305
- const char * slash ;
306
363
enum fsmonitor_path_type t ;
307
364
308
365
strbuf_reset (& path );
@@ -311,45 +368,45 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)
311
368
312
369
t = fsmonitor_classify_path_workdir_relative (path .buf );
313
370
314
- switch ( t ) {
315
- case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX :
316
- /* special case cookie files within .git */
317
-
318
- /* Use just the filename of the cookie file. */
319
- slash = find_last_dir_sep ( path . buf );
320
- string_list_append ( & cookie_list ,
321
- slash ? slash + 1 : path . buf );
322
- break ;
323
-
324
- case IS_INSIDE_DOT_GIT :
325
- /* ignore everything inside of "<worktree>/.git/" */
326
- break ;
327
-
328
- case IS_DOT_GIT :
329
- /* "<worktree>/.git" was deleted (or renamed away) */
330
- if (( info -> Action == FILE_ACTION_REMOVED ) ||
331
- ( info -> Action == FILE_ACTION_RENAMED_OLD_NAME )) {
332
- trace2_data_string ( "fsmonitor" , NULL ,
333
- "fsm-listen/dotgit" ,
334
- "removed" );
335
- goto force_shutdown ;
336
- }
337
- break ;
338
-
339
- case IS_WORKDIR_PATH :
340
- /* queue normal pathname */
341
- if (! batch )
342
- batch = fsmonitor_batch__new ();
343
- fsmonitor_batch__add_path ( batch , path . buf );
344
- break ;
345
-
346
- case IS_GITDIR :
347
- case IS_INSIDE_GITDIR :
348
- case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX :
349
- default :
350
- BUG ( "unexpected path classification '%d' for '%s'" ,
351
- t , path . buf );
352
- }
371
+ if ( process_1_worktree_event ( info , & cookie_list , & batch ,
372
+ & path , t ))
373
+ goto force_shutdown ;
374
+
375
+ /*
376
+ * NEEDSWORK: If ` path` contains a shortname (that is,
377
+ * if any component within it is a shortname), we
378
+ * should expand it to a longname (See
379
+ * `GetLongPathNameW()`) and re-normalize, classify,
380
+ * and process it because our client is probably
381
+ * expecting "normal" paths.
382
+ *
383
+ * HOWEVER, if our process has called `chdir()` to get
384
+ * us out of the root of the worktree (so that the
385
+ * root directory is not busy), then we have to be
386
+ * careful to convert the paths in the INFO array
387
+ * (which are relative to the directory of the RDCW
388
+ * watch and not the CWD) into absolute paths before
389
+ * calling GetLongPathNameW() and then convert the
390
+ * computed value back to a RDCW-relative pathname
391
+ * (which is what we and the client expect).
392
+ *
393
+ * FOR NOW, just handle case (1) exactly so that we
394
+ * shutdown properly when ".git" is deleted via the
395
+ * shortname alias.
396
+ *
397
+ * We might see case (2) events for cookie files, but
398
+ * we can ignore them.
399
+ *
400
+ * FOR LATER, handle case (3) where the worktree
401
+ * events contain shortnames. We should convert
402
+ * them to longnames to avoid confusing the client.
403
+ */
404
+ if ( data -> dot_git_shortname . len &&
405
+ ! strcmp ( path . buf , data -> dot_git_shortname . buf ) &&
406
+ process_1_worktree_event ( info , & cookie_list , & batch ,
407
+ & data -> dot_git_shortname ,
408
+ IS_DOT_GIT ))
409
+ goto force_shutdown ;
353
410
354
411
skip_this_path :
355
412
if (!info -> NextEntryOffset )
@@ -423,6 +480,14 @@ static int process_gitdir_events(struct fsmonitor_daemon_state *state)
423
480
t , path .buf );
424
481
}
425
482
483
+ /*
484
+ * WRT shortnames, this external gitdir will not see
485
+ * case (1) nor case (3) events.
486
+ *
487
+ * We might see case (2) events for cookie files, but
488
+ * we can ignore them.
489
+ */
490
+
426
491
skip_this_path :
427
492
if (!info -> NextEntryOffset )
428
493
break ;
@@ -524,6 +589,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)
524
589
int fsm_listen__ctor (struct fsmonitor_daemon_state * state )
525
590
{
526
591
struct fsmonitor_daemon_backend_data * data ;
592
+ char shortname [16 ]; /* a padded 8.3 buffer */
527
593
528
594
CALLOC_ARRAY (data , 1 );
529
595
@@ -554,6 +620,52 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
554
620
data -> nr_listener_handles ++ ;
555
621
}
556
622
623
+ /*
624
+ * NEEDSWORK: Properly handle 8.3 shortnames. RDCW events can
625
+ * contain a shortname (if another application uses a
626
+ * shortname in a system call). We care about aliasing and
627
+ * the use of shortnames for:
628
+ *
629
+ * (1) ".git",
630
+ * -- if an external process deletes ".git" using "GIT~1",
631
+ * we need to catch that and shutdown.
632
+ *
633
+ * (2) our cookie files,
634
+ * -- if an external process deletes one of our cookie
635
+ * files using a shortname, we will get a shortname
636
+ * event for it. However, we should have already
637
+ * gotten a longname event for it when we created the
638
+ * cookie, so we can safely discard the shortname
639
+ * events for cookie files.
640
+ *
641
+ * (3) the spelling of modified files that we report to clients.
642
+ * -- we need to report the longname to the client because
643
+ * that is what they are expecting. Presumably, the
644
+ * client is going to lookup the paths that we report
645
+ * in their index and untracked-cache, so we should
646
+ * normalize the data for them. (Technically, they
647
+ * could adapt, so we could relax this maybe.)
648
+ *
649
+ * FOR NOW, while our CWD is at the root of the worktree we
650
+ * can easily get the spelling of the shortname of ".git" (if
651
+ * the volume has shortnames enabled). For most worktrees
652
+ * this value will be "GIT~1", but we don't want to assume
653
+ * that.
654
+ *
655
+ * Capture this so that we can handle (1).
656
+ *
657
+ * We leave (3) for a future effort.
658
+ */
659
+ strbuf_init (& data -> dot_git_shortname , 0 );
660
+ GetShortPathNameA (".git" , shortname , sizeof (shortname ));
661
+ if (!strcmp (".git" , shortname ))
662
+ trace_printf_key (& trace_fsmonitor , "No shortname for '.git'" );
663
+ else {
664
+ trace_printf_key (& trace_fsmonitor ,
665
+ "Shortname of '.git' is '%s'" , shortname );
666
+ strbuf_addstr (& data -> dot_git_shortname , shortname );
667
+ }
668
+
557
669
state -> backend_data = data ;
558
670
return 0 ;
559
671
0 commit comments