3030
3131#define BUILTIN_WORKTREE_ADD_USAGE \
3232 N_("git worktree add [-f] [--detach] [--checkout] [--lock [--reason <string>]]\n" \
33- " [--orphan] [(-b | -B) <new-branch>] <path> [<commit-ish>]")
33+ " [--orphan] [--reflink] [ (-b | -B) <new-branch>] <path> [<commit-ish>]")
3434
3535#define BUILTIN_WORKTREE_LIST_USAGE \
3636 N_("git worktree list [-v | --porcelain [-z]]")
4747#define BUILTIN_WORKTREE_UNLOCK_USAGE \
4848 N_("git worktree unlock <worktree>")
4949
50+ /* values for add_opts.reflink and the worktree.reflink config */
51+ #define REFLINK_OFF 0
52+ #define REFLINK_ON 1 /* warn and fall back if unsupported */
53+ #define REFLINK_AUTO 2 /* silently fall back if unsupported */
54+
5055#define WORKTREE_ADD_DWIM_ORPHAN_INFER_TEXT \
5156 _("No possible source branch, inferring '--orphan'")
5257
@@ -123,13 +128,15 @@ struct add_opts {
123128 int checkout ;
124129 int orphan ;
125130 int relative_paths ;
131+ int reflink ;
126132 const char * keep_locked ;
127133};
128134
129135static int show_only ;
130136static int verbose ;
131137static int guess_remote ;
132138static int use_relative_paths ;
139+ static int reflink_config = REFLINK_OFF ;
133140static timestamp_t expire ;
134141
135142static int git_worktree_config (const char * var , const char * value ,
@@ -141,6 +148,13 @@ static int git_worktree_config(const char *var, const char *value,
141148 } else if (!strcmp (var , "worktree.userelativepaths" )) {
142149 use_relative_paths = git_config_bool (var , value );
143150 return 0 ;
151+ } else if (!strcmp (var , "worktree.reflink" )) {
152+ if (value && !strcmp (value , "auto" ))
153+ reflink_config = REFLINK_AUTO ;
154+ else
155+ reflink_config = git_config_bool (var , value ) ?
156+ REFLINK_ON : REFLINK_OFF ;
157+ return 0 ;
144158 }
145159
146160 return git_default_config (var , value , ctx , cb );
@@ -397,6 +411,182 @@ static void copy_filtered_worktree_config(const char *worktree_git_dir)
397411 free (to_file );
398412}
399413
414+ /*
415+ * Probe whether the filesystem backing "dir" supports reflinks. We do this
416+ * once up front so that, on filesystems without copy-on-write support (or on
417+ * platforms such as Windows that lack a reflink primitive entirely), we can
418+ * fall back to a normal checkout instead of byte-copying the whole source
419+ * working tree -- which would include untracked files and be slower than the
420+ * checkout we are trying to avoid.
421+ */
422+ static int reflink_supported (const char * dir )
423+ {
424+ struct strbuf src = STRBUF_INIT , dst = STRBUF_INIT ;
425+ int fd , ok = 0 ;
426+
427+ strbuf_addf (& src , "%s/.git-reflink-probe-src" , dir );
428+ strbuf_addf (& dst , "%s/.git-reflink-probe-dst" , dir );
429+
430+ fd = open (src .buf , O_WRONLY | O_CREAT | O_TRUNC , 0600 );
431+ if (fd >= 0 ) {
432+ write_in_full (fd , "x" , 1 );
433+ close (fd );
434+ if (!reflink_file (dst .buf , src .buf , 0600 ))
435+ ok = 1 ;
436+ unlink (dst .buf );
437+ }
438+ unlink (src .buf );
439+
440+ strbuf_release (& src );
441+ strbuf_release (& dst );
442+ return ok ;
443+ }
444+
445+ /*
446+ * Reflink a single regular file, falling back to a regular copy when the
447+ * clone fails for this particular file (for example across mount points).
448+ */
449+ static int reflink_or_copy (const char * dst , const char * src , int mode )
450+ {
451+ if (!reflink_file (dst , src , mode ))
452+ return 0 ;
453+ return copy_file (dst , src , mode );
454+ }
455+
456+ /*
457+ * Recursively copy the working-tree directory "src" into "dst" using reflinks
458+ * for regular files. Directory entries that resolve to the destination
459+ * worktree itself (identified by skip_dev/skip_ino, which matters when the new
460+ * worktree lives inside the source one) and the top-level ".git" gitfile are
461+ * skipped.
462+ */
463+ static int reflink_tree (const char * src , const char * dst ,
464+ dev_t skip_dev , ino_t skip_ino , int top )
465+ {
466+ struct strbuf s = STRBUF_INIT , d = STRBUF_INIT ;
467+ DIR * dir ;
468+ struct dirent * de ;
469+ int ret = 0 ;
470+
471+ dir = opendir (src );
472+ if (!dir )
473+ return error_errno (_ ("could not open directory '%s'" ), src );
474+
475+ while (!ret && (de = readdir (dir ))) {
476+ struct stat st ;
477+
478+ if (is_dot_or_dotdot (de -> d_name ))
479+ continue ;
480+ if (top && !strcmp (de -> d_name , ".git" ))
481+ continue ;
482+
483+ strbuf_reset (& s );
484+ strbuf_addf (& s , "%s/%s" , src , de -> d_name );
485+ strbuf_reset (& d );
486+ strbuf_addf (& d , "%s/%s" , dst , de -> d_name );
487+
488+ if (lstat (s .buf , & st )) {
489+ ret = error_errno (_ ("could not stat '%s'" ), s .buf );
490+ break ;
491+ }
492+
493+ if (S_ISDIR (st .st_mode )) {
494+ /* never recurse into the new worktree itself */
495+ if (st .st_dev == skip_dev && st .st_ino == skip_ino )
496+ continue ;
497+ if (mkdir (d .buf , st .st_mode & 07777 ) && errno != EEXIST ) {
498+ ret = error_errno (_ ("could not create directory '%s'" ), d .buf );
499+ break ;
500+ }
501+ ret = reflink_tree (s .buf , d .buf , skip_dev , skip_ino , 0 );
502+ } else if (S_ISLNK (st .st_mode )) {
503+ struct strbuf link = STRBUF_INIT ;
504+
505+ if (strbuf_readlink (& link , s .buf , st .st_size ))
506+ ret = error_errno (_ ("could not read symlink '%s'" ), s .buf );
507+ else if (symlink (link .buf , d .buf ))
508+ ret = error_errno (_ ("could not create symlink '%s'" ), d .buf );
509+ strbuf_release (& link );
510+ } else if (S_ISREG (st .st_mode )) {
511+ if (reflink_or_copy (d .buf , s .buf , st .st_mode ))
512+ ret = error_errno (_ ("could not copy '%s' to '%s'" ),
513+ s .buf , d .buf );
514+ }
515+ /* silently skip fifos, sockets and device nodes */
516+ }
517+
518+ closedir (dir );
519+ strbuf_release (& s );
520+ strbuf_release (& d );
521+ return ret ;
522+ }
523+
524+ /*
525+ * Populate the new worktree at "path" by reflinking the current worktree's
526+ * files and index. The subsequent "git reset --hard" then only has to rewrite
527+ * the paths that actually differ between the source and <commit-ish>, leaving
528+ * everything else sharing storage with the source. Returns 1 when reflinks are
529+ * unavailable so the caller can fall back to a plain checkout.
530+ */
531+ static int reflink_worktree (const char * path , const char * wt_git_dir ,
532+ const struct add_opts * opts , struct strvec * child_env )
533+ {
534+ const char * src_wt = the_repository -> worktree ;
535+ char * src_index = NULL , * dst_index = NULL ;
536+ struct stat dst_st ;
537+ int ret = 0 ;
538+
539+ if (!src_wt )
540+ return error (_ ("--reflink needs a source working tree, but this "
541+ "repository does not have one" ));
542+
543+ if (!reflink_supported (path )) {
544+ /* In auto mode the fallback is expected, so stay quiet. */
545+ if (!opts -> quiet && opts -> reflink != REFLINK_AUTO )
546+ warning (_ ("the filesystem at '%s' does not support reflinks; "
547+ "falling back to a regular checkout" ), path );
548+ return 1 ;
549+ }
550+
551+ if (stat (path , & dst_st ))
552+ return error_errno (_ ("could not stat '%s'" ), path );
553+
554+ if ((ret = reflink_tree (src_wt , path , dst_st .st_dev , dst_st .st_ino , 1 )))
555+ return ret ;
556+
557+ /*
558+ * Clone the source index so the following reset sees the source's
559+ * state and only materializes the differences to <commit-ish>.
560+ */
561+ src_index = repo_git_path (the_repository , "index" );
562+ dst_index = xstrfmt ("%s/index" , wt_git_dir );
563+ if (!access (src_index , F_OK ) &&
564+ reflink_or_copy (dst_index , src_index , 0666 )) {
565+ ret = error_errno (_ ("could not copy index to '%s'" ), dst_index );
566+ goto out ;
567+ }
568+
569+ /*
570+ * The cloned index still carries the source files' stat information.
571+ * Refresh it against the freshly reflinked files so that "git reset"
572+ * recognizes unchanged paths as up to date and leaves them sharing
573+ * storage instead of rewriting (and thus un-sharing) them.
574+ */
575+ if (!access (dst_index , F_OK )) {
576+ struct child_process cp = CHILD_PROCESS_INIT ;
577+ cp .git_cmd = 1 ;
578+ strvec_pushl (& cp .args , "update-index" , "-q" , "--refresh" , NULL );
579+ strvec_pushv (& cp .env , child_env -> v );
580+ /* a dirty working tree is not an error here */
581+ run_command (& cp );
582+ }
583+
584+ out :
585+ free (src_index );
586+ free (dst_index );
587+ return ret ;
588+ }
589+
400590static int checkout_worktree (const struct add_opts * opts ,
401591 struct strvec * child_env )
402592{
@@ -589,6 +779,20 @@ static int add_worktree(const char *path, const char *refname,
589779 (ret = make_worktree_orphan (refname , opts , & child_env )))
590780 goto done ;
591781
782+ /*
783+ * When --reflink is requested and the filesystem supports it, copy the
784+ * current worktree (and its index) into the new one using copy-on-write
785+ * clones. checkout_worktree() then only rewrites the paths that differ
786+ * from <commit-ish>. reflink_worktree() returns 1 when reflinks are not
787+ * available, in which case we just do an ordinary checkout below.
788+ */
789+ if (opts -> checkout && opts -> reflink ) {
790+ ret = reflink_worktree (path , sb_repo .buf , opts , & child_env );
791+ if (ret < 0 )
792+ goto done ;
793+ ret = 0 ;
794+ }
795+
592796 if (opts -> checkout &&
593797 (ret = checkout_worktree (opts , & child_env )))
594798 goto done ;
@@ -801,6 +1005,7 @@ static int add(int ac, const char **av, const char *prefix,
8011005 const char * lock_reason = NULL ;
8021006 int keep_locked = 0 ;
8031007 int used_new_branch_options ;
1008+ int reflink_cli = -1 ;
8041009 struct option options [] = {
8051010 OPT__FORCE (& opts .force ,
8061011 N_ ("checkout <branch> even if already checked out in other worktree" ),
@@ -823,6 +1028,8 @@ static int add(int ac, const char **av, const char *prefix,
8231028 N_ ("try to match the new branch name with a remote-tracking branch" )),
8241029 OPT_BOOL (0 , "relative-paths" , & opts .relative_paths ,
8251030 N_ ("use relative paths for worktrees" )),
1031+ OPT_BOOL (0 , "reflink" , & reflink_cli ,
1032+ N_ ("populate the worktree using copy-on-write clones when supported" )),
8261033 OPT_END ()
8271034 };
8281035 int ret ;
@@ -842,6 +1049,24 @@ static int add(int ac, const char **av, const char *prefix,
8421049 if (opts .orphan && !opts .checkout )
8431050 die (_ ("options '%s' and '%s' cannot be used together" ),
8441051 "--orphan" , "--no-checkout" );
1052+
1053+ /*
1054+ * Resolve whether to reflink: an explicit --reflink/--no-reflink on
1055+ * the command line wins, otherwise fall back to the worktree.reflink
1056+ * configuration (which may select the "auto" mode).
1057+ */
1058+ opts .reflink = (reflink_cli != -1 ) ? reflink_cli : reflink_config ;
1059+ if (opts .reflink && (opts .orphan || !opts .checkout )) {
1060+ /*
1061+ * Reflinking is incompatible with these; only complain when it
1062+ * was explicitly requested, otherwise quietly do a plain
1063+ * checkout so a configured default does not break these modes.
1064+ */
1065+ if (reflink_cli == REFLINK_ON )
1066+ die (_ ("options '%s' and '%s' cannot be used together" ),
1067+ "--reflink" , opts .orphan ? "--orphan" : "--no-checkout" );
1068+ opts .reflink = REFLINK_OFF ;
1069+ }
8451070 if (opts .orphan && ac == 2 )
8461071 die (_ ("option '%s' and commit-ish cannot be used together" ),
8471072 "--orphan" );
0 commit comments