77#include "git-compat-util.h"
88#include "abspath.h"
99#include "gettext.h"
10+ #include "hex.h"
1011#include "parse-options.h"
1112#include "config.h"
13+ #include "environment.h"
1214#include "run-command.h"
1315#include "simple-ipc.h"
1416#include "fsmonitor-ipc.h"
1517#include "fsmonitor-settings.h"
1618#include "refs.h"
1719#include "dir.h"
20+ #include "object-file.h"
1821#include "packfile.h"
1922#include "help.h"
2023#include "setup.h"
24+ #include "wrapper.h"
2125#include "trace2.h"
2226#include "path.h"
2327#include "json-parser.h"
28+ #include "path.h"
29+
30+ static int is_unattended (void ) {
31+ return git_env_bool ("Scalar_UNATTENDED" , 0 );
32+ }
2433
2534static void setup_enlistment_directory (int argc , const char * * argv ,
2635 const char * const * usagestr ,
@@ -107,6 +116,19 @@ static int run_git(const char *arg, ...)
107116 return res ;
108117}
109118
119+ static const char * ensure_absolute_path (const char * path , char * * absolute )
120+ {
121+ struct strbuf buf = STRBUF_INIT ;
122+
123+ if (is_absolute_path (path ))
124+ return path ;
125+
126+ strbuf_realpath_forgiving (& buf , path , 1 );
127+ free (* absolute );
128+ * absolute = strbuf_detach (& buf , NULL );
129+ return * absolute ;
130+ }
131+
110132struct scalar_config {
111133 const char * key ;
112134 const char * value ;
@@ -444,6 +466,92 @@ static int supports_gvfs_protocol(const char *url, char **cache_server_url)
444466 return 0 ; /* error out quietly */
445467}
446468
469+ static char * default_cache_root (const char * root )
470+ {
471+ const char * env ;
472+
473+ if (is_unattended ()) {
474+ struct strbuf path = STRBUF_INIT ;
475+ strbuf_addstr (& path , root );
476+ strip_last_path_component (& path );
477+ strbuf_addstr (& path , "/.scalarCache" );
478+ return strbuf_detach (& path , NULL );
479+ }
480+
481+ #ifdef WIN32
482+ (void )env ;
483+ return xstrfmt ("%.*s.scalarCache" , offset_1st_component (root ), root );
484+ #elif defined(__APPLE__ )
485+ if ((env = getenv ("HOME" )) && * env )
486+ return xstrfmt ("%s/.scalarCache" , env );
487+ return NULL ;
488+ #else
489+ if ((env = getenv ("XDG_CACHE_HOME" )) && * env )
490+ return xstrfmt ("%s/scalar" , env );
491+ if ((env = getenv ("HOME" )) && * env )
492+ return xstrfmt ("%s/.cache/scalar" , env );
493+ return NULL ;
494+ #endif
495+ }
496+
497+ static int get_repository_id (struct json_iterator * it )
498+ {
499+ if (it -> type == JSON_STRING &&
500+ !strcasecmp (".repository.id" , it -> key .buf )) {
501+ * (char * * )it -> fn_data = strbuf_detach (& it -> string_value , NULL );
502+ return 1 ;
503+ }
504+
505+ return 0 ;
506+ }
507+
508+ /* Needs to run this in a worktree; gvfs-helper requires a Git repository */
509+ static char * get_cache_key (const char * url )
510+ {
511+ struct child_process cp = CHILD_PROCESS_INIT ;
512+ struct strbuf out = STRBUF_INIT ;
513+ char * cache_key = NULL ;
514+
515+ cp .git_cmd = 1 ;
516+ strvec_pushl (& cp .args , "gvfs-helper" , "--remote" , url ,
517+ "endpoint" , "vsts/info" , NULL );
518+ if (!pipe_command (& cp , NULL , 0 , & out , 512 , NULL , 0 )) {
519+ char * id = NULL ;
520+ struct json_iterator it =
521+ JSON_ITERATOR_INIT (out .buf , get_repository_id , & id );
522+
523+ if (iterate_json (& it ) < 0 )
524+ warning ("JSON parse error (%s)" , out .buf );
525+ else if (id )
526+ cache_key = xstrfmt ("id_%s" , id );
527+ free (id );
528+ }
529+
530+ if (!cache_key ) {
531+ struct strbuf downcased = STRBUF_INIT ;
532+ int hash_algo_index = hash_algo_by_name ("sha1" );
533+ const struct git_hash_algo * hash_algo = hash_algo_index < 0 ?
534+ the_hash_algo : & hash_algos [hash_algo_index ];
535+ struct git_hash_ctx ctx ;
536+ unsigned char hash [GIT_MAX_RAWSZ ];
537+
538+ strbuf_addstr (& downcased , url );
539+ strbuf_tolower (& downcased );
540+
541+ hash_algo -> init_fn (& ctx );
542+ hash_algo -> update_fn (& ctx , downcased .buf , downcased .len );
543+ hash_algo -> final_fn (hash , & ctx );
544+
545+ strbuf_release (& downcased );
546+
547+ cache_key = xstrfmt ("url_%s" ,
548+ hash_to_hex_algop (hash , hash_algo ));
549+ }
550+
551+ strbuf_release (& out );
552+ return cache_key ;
553+ }
554+
447555static char * remote_default_branch (const char * url )
448556{
449557 struct child_process cp = CHILD_PROCESS_INIT ;
@@ -538,14 +646,52 @@ void load_builtin_commands(const char *prefix UNUSED,
538646 die ("not implemented" );
539647}
540648
649+ static int init_shared_object_cache (const char * url ,
650+ const char * local_cache_root )
651+ {
652+ struct strbuf buf = STRBUF_INIT ;
653+ int res = 0 ;
654+ char * cache_key = NULL , * shared_cache_path = NULL , * alternates = NULL ;
655+
656+ if (!(cache_key = get_cache_key (url ))) {
657+ res = error (_ ("could not determine cache key for '%s'" ), url );
658+ goto cleanup ;
659+ }
660+
661+ shared_cache_path = xstrfmt ("%s/%s" , local_cache_root , cache_key );
662+ if (set_config ("gvfs.sharedCache=%s" , shared_cache_path )) {
663+ res = error (_ ("could not configure shared cache" ));
664+ goto cleanup ;
665+ }
666+
667+ strbuf_addf (& buf , "%s/pack" , shared_cache_path );
668+ switch (safe_create_leading_directories (the_repository , buf .buf )) {
669+ case SCLD_OK : case SCLD_EXISTS :
670+ break ; /* okay */
671+ default :
672+ res = error_errno (_ ("could not initialize '%s'" ), buf .buf );
673+ goto cleanup ;
674+ }
675+
676+ alternates = repo_git_path (the_repository , "objects/info/alternates" );
677+ write_file (alternates , "%s\n" , shared_cache_path );
678+
679+ cleanup :
680+ strbuf_release (& buf );
681+ free (shared_cache_path );
682+ free (cache_key );
683+ free (alternates );
684+ return res ;
685+ }
686+
541687static int cmd_clone (int argc , const char * * argv )
542688{
543689 const char * branch = NULL ;
544690 char * branch_to_free = NULL ;
545691 int full_clone = 0 , single_branch = 0 , show_progress = isatty (2 );
546692 int src = 1 , tags = 1 , maintenance = 1 ;
547- const char * cache_server_url = NULL ;
548- char * default_cache_server_url = NULL ;
693+ const char * cache_server_url = NULL , * local_cache_root = NULL ;
694+ char * default_cache_server_url = NULL , * local_cache_root_abs = NULL ;
549695 struct option clone_options [] = {
550696 OPT_STRING ('b' , "branch" , & branch , N_ ("<branch>" ),
551697 N_ ("branch to checkout after clone" )),
@@ -563,6 +709,9 @@ static int cmd_clone(int argc, const char **argv)
563709 OPT_STRING (0 , "cache-server-url" , & cache_server_url ,
564710 N_ ("<url>" ),
565711 N_ ("the url or friendly name of the cache server" )),
712+ OPT_STRING (0 , "local-cache-path" , & local_cache_root ,
713+ N_ ("<path>" ),
714+ N_ ("override the path for the local Scalar cache" )),
566715 OPT_END (),
567716 };
568717 const char * const clone_usage [] = {
@@ -604,11 +753,23 @@ static int cmd_clone(int argc, const char **argv)
604753 if (is_directory (enlistment ))
605754 die (_ ("directory '%s' exists already" ), enlistment );
606755
756+ ensure_absolute_path (enlistment , & enlistment );
757+
607758 if (src )
608759 dir = xstrfmt ("%s/src" , enlistment );
609760 else
610761 dir = xstrdup (enlistment );
611762
763+ if (!local_cache_root )
764+ local_cache_root = local_cache_root_abs =
765+ default_cache_root (enlistment );
766+ else
767+ local_cache_root = ensure_absolute_path (local_cache_root ,
768+ & local_cache_root_abs );
769+
770+ if (!local_cache_root )
771+ die (_ ("could not determine local cache root" ));
772+
612773 strbuf_reset (& buf );
613774 if (branch )
614775 strbuf_addf (& buf , "init.defaultBranch=%s" , branch );
@@ -628,8 +789,28 @@ static int cmd_clone(int argc, const char **argv)
628789
629790 setup_git_directory ();
630791
792+ repo_config (the_repository , git_default_config , NULL );
793+
794+ /*
795+ * This `dir_inside_of()` call relies on git_config() having parsed the
796+ * newly-initialized repository config's `core.ignoreCase` value.
797+ */
798+ if (dir_inside_of (local_cache_root , dir ) >= 0 ) {
799+ struct strbuf path = STRBUF_INIT ;
800+
801+ strbuf_addstr (& path , enlistment );
802+ if (chdir ("../.." ) < 0 ||
803+ remove_dir_recursively (& path , 0 ) < 0 )
804+ die (_ ("'--local-cache-path' cannot be inside the src "
805+ "folder;\nCould not remove '%s'" ), enlistment );
806+
807+ die (_ ("'--local-cache-path' cannot be inside the src folder" ));
808+ }
809+
631810 /* common-main already logs `argv` */
632811 trace2_def_repo (the_repository );
812+ trace2_data_intmax ("scalar" , the_repository , "unattended" ,
813+ is_unattended ());
633814
634815 if (!branch && !(branch = branch_to_free = remote_default_branch (url ))) {
635816 res = error (_ ("failed to get default branch for '%s'" ), url );
@@ -659,6 +840,8 @@ static int cmd_clone(int argc, const char **argv)
659840 supports_gvfs_protocol (url , & default_cache_server_url );
660841
661842 if (gvfs_protocol ) {
843+ if ((res = init_shared_object_cache (url , local_cache_root )))
844+ goto cleanup ;
662845 if (!cache_server_url )
663846 cache_server_url = default_cache_server_url ;
664847 if (set_config ("core.useGVFSHelper=true" ) ||
@@ -737,6 +920,7 @@ static int cmd_clone(int argc, const char **argv)
737920 free (dir );
738921 strbuf_release (& buf );
739922 free (default_cache_server_url );
923+ free (local_cache_root_abs );
740924 return res ;
741925}
742926
@@ -1171,6 +1355,12 @@ int cmd_main(int argc, const char **argv)
11711355 struct strbuf scalar_usage = STRBUF_INIT ;
11721356 int i ;
11731357
1358+ if (is_unattended ()) {
1359+ setenv ("GIT_ASKPASS" , "" , 0 );
1360+ setenv ("GIT_TERMINAL_PROMPT" , "false" , 0 );
1361+ git_config_push_parameter ("credential.interactive=false" );
1362+ }
1363+
11741364 while (argc > 1 && * argv [1 ] == '-' ) {
11751365 if (!strcmp (argv [1 ], "-C" )) {
11761366 if (argc < 3 )
0 commit comments