@@ -67,37 +67,43 @@ static void delete_worktrees_dir_if_empty(void)
67
67
rmdir (git_path ("worktrees" )); /* ignore failed removal */
68
68
}
69
69
70
- static int prune_worktree (const char * id , struct strbuf * reason )
70
+ /*
71
+ * Return true if worktree entry should be pruned, along with the reason for
72
+ * pruning. Otherwise, return false and the worktree's path, or NULL if it
73
+ * cannot be determined. Caller is responsible for freeing returned path.
74
+ */
75
+ static int should_prune_worktree (const char * id , struct strbuf * reason , char * * wtpath )
71
76
{
72
77
struct stat st ;
73
78
char * path ;
74
79
int fd ;
75
80
size_t len ;
76
81
ssize_t read_result ;
77
82
83
+ * wtpath = NULL ;
78
84
if (!is_directory (git_path ("worktrees/%s" , id ))) {
79
- strbuf_addf (reason , _ ("Removing worktrees/%s: not a valid directory" ), id );
85
+ strbuf_addstr (reason , _ ("not a valid directory" ));
80
86
return 1 ;
81
87
}
82
88
if (file_exists (git_path ("worktrees/%s/locked" , id )))
83
89
return 0 ;
84
90
if (stat (git_path ("worktrees/%s/gitdir" , id ), & st )) {
85
- strbuf_addf (reason , _ ("Removing worktrees/%s: gitdir file does not exist" ), id );
91
+ strbuf_addstr (reason , _ ("gitdir file does not exist" ));
86
92
return 1 ;
87
93
}
88
94
fd = open (git_path ("worktrees/%s/gitdir" , id ), O_RDONLY );
89
95
if (fd < 0 ) {
90
- strbuf_addf (reason , _ ("Removing worktrees/%s: unable to read gitdir file (%s)" ),
91
- id , strerror (errno ));
96
+ strbuf_addf (reason , _ ("unable to read gitdir file (%s)" ),
97
+ strerror (errno ));
92
98
return 1 ;
93
99
}
94
100
len = xsize_t (st .st_size );
95
101
path = xmallocz (len );
96
102
97
103
read_result = read_in_full (fd , path , len );
98
104
if (read_result < 0 ) {
99
- strbuf_addf (reason , _ ("Removing worktrees/%s: unable to read gitdir file (%s)" ),
100
- id , strerror (errno ));
105
+ strbuf_addf (reason , _ ("unable to read gitdir file (%s)" ),
106
+ strerror (errno ));
101
107
close (fd );
102
108
free (path );
103
109
return 1 ;
@@ -106,53 +112,103 @@ static int prune_worktree(const char *id, struct strbuf *reason)
106
112
107
113
if (read_result != len ) {
108
114
strbuf_addf (reason ,
109
- _ ("Removing worktrees/%s: short read (expected %" PRIuMAX " bytes, read %" PRIuMAX ")" ),
110
- id , (uintmax_t )len , (uintmax_t )read_result );
115
+ _ ("short read (expected %" PRIuMAX " bytes, read %" PRIuMAX ")" ),
116
+ (uintmax_t )len , (uintmax_t )read_result );
111
117
free (path );
112
118
return 1 ;
113
119
}
114
120
while (len && (path [len - 1 ] == '\n' || path [len - 1 ] == '\r' ))
115
121
len -- ;
116
122
if (!len ) {
117
- strbuf_addf (reason , _ ("Removing worktrees/%s: invalid gitdir file" ), id );
123
+ strbuf_addstr (reason , _ ("invalid gitdir file" ));
118
124
free (path );
119
125
return 1 ;
120
126
}
121
127
path [len ] = '\0' ;
122
128
if (!file_exists (path )) {
123
- free (path );
124
129
if (stat (git_path ("worktrees/%s/index" , id ), & st ) ||
125
130
st .st_mtime <= expire ) {
126
- strbuf_addf (reason , _ ("Removing worktrees/%s: gitdir file points to non-existent location" ), id );
131
+ strbuf_addstr (reason , _ ("gitdir file points to non-existent location" ));
132
+ free (path );
127
133
return 1 ;
128
134
} else {
135
+ * wtpath = path ;
129
136
return 0 ;
130
137
}
131
138
}
132
- free ( path ) ;
139
+ * wtpath = path ;
133
140
return 0 ;
134
141
}
135
142
143
+ static void prune_worktree (const char * id , const char * reason )
144
+ {
145
+ if (show_only || verbose )
146
+ printf_ln (_ ("Removing %s/%s: %s" ), "worktrees" , id , reason );
147
+ if (!show_only )
148
+ delete_git_dir (id );
149
+ }
150
+
151
+ static int prune_cmp (const void * a , const void * b )
152
+ {
153
+ const struct string_list_item * x = a ;
154
+ const struct string_list_item * y = b ;
155
+ int c ;
156
+
157
+ if ((c = fspathcmp (x -> string , y -> string )))
158
+ return c ;
159
+ /*
160
+ * paths same; prune_dupes() removes all but the first worktree entry
161
+ * having the same path, so sort main worktree ('util' is NULL) above
162
+ * linked worktrees ('util' not NULL) since main worktree can't be
163
+ * removed
164
+ */
165
+ if (!x -> util )
166
+ return -1 ;
167
+ if (!y -> util )
168
+ return 1 ;
169
+ /* paths same; sort by .git/worktrees/<id> */
170
+ return strcmp (x -> util , y -> util );
171
+ }
172
+
173
+ static void prune_dups (struct string_list * l )
174
+ {
175
+ int i ;
176
+
177
+ QSORT (l -> items , l -> nr , prune_cmp );
178
+ for (i = 1 ; i < l -> nr ; i ++ ) {
179
+ if (!fspathcmp (l -> items [i ].string , l -> items [i - 1 ].string ))
180
+ prune_worktree (l -> items [i ].util , "duplicate entry" );
181
+ }
182
+ }
183
+
136
184
static void prune_worktrees (void )
137
185
{
138
186
struct strbuf reason = STRBUF_INIT ;
187
+ struct strbuf main_path = STRBUF_INIT ;
188
+ struct string_list kept = STRING_LIST_INIT_NODUP ;
139
189
DIR * dir = opendir (git_path ("worktrees" ));
140
190
struct dirent * d ;
141
191
if (!dir )
142
192
return ;
143
193
while ((d = readdir (dir )) != NULL ) {
194
+ char * path ;
144
195
if (is_dot_or_dotdot (d -> d_name ))
145
196
continue ;
146
197
strbuf_reset (& reason );
147
- if (!prune_worktree (d -> d_name , & reason ))
148
- continue ;
149
- if (show_only || verbose )
150
- printf ("%s\n" , reason .buf );
151
- if (show_only )
152
- continue ;
153
- delete_git_dir (d -> d_name );
198
+ if (should_prune_worktree (d -> d_name , & reason , & path ))
199
+ prune_worktree (d -> d_name , reason .buf );
200
+ else if (path )
201
+ string_list_append (& kept , path )-> util = xstrdup (d -> d_name );
154
202
}
155
203
closedir (dir );
204
+
205
+ strbuf_add_absolute_path (& main_path , get_git_common_dir ());
206
+ /* massage main worktree absolute path to match 'gitdir' content */
207
+ strbuf_strip_suffix (& main_path , "/." );
208
+ string_list_append (& kept , strbuf_detach (& main_path , NULL ));
209
+ prune_dups (& kept );
210
+ string_list_clear (& kept , 1 );
211
+
156
212
if (!show_only )
157
213
delete_worktrees_dir_if_empty ();
158
214
strbuf_release (& reason );
@@ -224,34 +280,33 @@ static const char *worktree_basename(const char *path, int *olen)
224
280
return name ;
225
281
}
226
282
227
- static void validate_worktree_add (const char * path , const struct add_opts * opts )
283
+ /* check that path is viable location for worktree */
284
+ static void check_candidate_path (const char * path ,
285
+ int force ,
286
+ struct worktree * * worktrees ,
287
+ const char * cmd )
228
288
{
229
- struct worktree * * worktrees ;
230
289
struct worktree * wt ;
231
290
int locked ;
232
291
233
292
if (file_exists (path ) && !is_empty_dir (path ))
234
293
die (_ ("'%s' already exists" ), path );
235
294
236
- worktrees = get_worktrees (0 );
237
295
wt = find_worktree_by_path (worktrees , path );
238
296
if (!wt )
239
- goto done ;
297
+ return ;
240
298
241
299
locked = !!worktree_lock_reason (wt );
242
- if ((!locked && opts -> force ) || (locked && opts -> force > 1 )) {
300
+ if ((!locked && force ) || (locked && force > 1 )) {
243
301
if (delete_git_dir (wt -> id ))
244
- die (_ ("unable to re-add worktree '%s'" ), path );
245
- goto done ;
302
+ die (_ ("unusable worktree destination '%s'" ), path );
303
+ return ;
246
304
}
247
305
248
306
if (locked )
249
- die (_ ("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear" ), path );
307
+ die (_ ("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear" ), cmd , path );
250
308
else
251
- die (_ ("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear" ), path );
252
-
253
- done :
254
- free_worktrees (worktrees );
309
+ die (_ ("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear" ), cmd , path );
255
310
}
256
311
257
312
static int add_worktree (const char * path , const char * refname ,
@@ -268,8 +323,12 @@ static int add_worktree(const char *path, const char *refname,
268
323
struct commit * commit = NULL ;
269
324
int is_branch = 0 ;
270
325
struct strbuf sb_name = STRBUF_INIT ;
326
+ struct worktree * * worktrees ;
271
327
272
- validate_worktree_add (path , opts );
328
+ worktrees = get_worktrees (0 );
329
+ check_candidate_path (path , opts -> force , worktrees , "add" );
330
+ free_worktrees (worktrees );
331
+ worktrees = NULL ;
273
332
274
333
/* is 'refname' a branch or commit? */
275
334
if (!opts -> detach && !strbuf_check_branch_ref (& symref , refname ) &&
@@ -804,8 +863,7 @@ static int move_worktree(int ac, const char **av, const char *prefix)
804
863
strbuf_trim_trailing_dir_sep (& dst );
805
864
strbuf_addstr (& dst , sep );
806
865
}
807
- if (file_exists (dst .buf ))
808
- die (_ ("target '%s' already exists" ), dst .buf );
866
+ check_candidate_path (dst .buf , force , worktrees , "move" );
809
867
810
868
validate_no_submodules (wt );
811
869
0 commit comments