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 "json-parser.h"
27+ #include "path.h"
28+
29+ static int is_unattended (void ) {
30+ return git_env_bool ("Scalar_UNATTENDED" , 0 );
31+ }
2332
2433static void setup_enlistment_directory (int argc , const char * * argv ,
2534 const char * const * usagestr ,
@@ -106,6 +115,19 @@ static int run_git(const char *arg, ...)
106115 return res ;
107116}
108117
118+ static const char * ensure_absolute_path (const char * path , char * * absolute )
119+ {
120+ struct strbuf buf = STRBUF_INIT ;
121+
122+ if (is_absolute_path (path ))
123+ return path ;
124+
125+ strbuf_realpath_forgiving (& buf , path , 1 );
126+ free (* absolute );
127+ * absolute = strbuf_detach (& buf , NULL );
128+ return * absolute ;
129+ }
130+
109131struct scalar_config {
110132 const char * key ;
111133 const char * value ;
@@ -435,6 +457,92 @@ static int supports_gvfs_protocol(const char *url, char **cache_server_url)
435457 return 0 ; /* error out quietly */
436458}
437459
460+ static char * default_cache_root (const char * root )
461+ {
462+ const char * env ;
463+
464+ if (is_unattended ()) {
465+ struct strbuf path = STRBUF_INIT ;
466+ strbuf_addstr (& path , root );
467+ strip_last_path_component (& path );
468+ strbuf_addstr (& path , "/.scalarCache" );
469+ return strbuf_detach (& path , NULL );
470+ }
471+
472+ #ifdef WIN32
473+ (void )env ;
474+ return xstrfmt ("%.*s.scalarCache" , offset_1st_component (root ), root );
475+ #elif defined(__APPLE__ )
476+ if ((env = getenv ("HOME" )) && * env )
477+ return xstrfmt ("%s/.scalarCache" , env );
478+ return NULL ;
479+ #else
480+ if ((env = getenv ("XDG_CACHE_HOME" )) && * env )
481+ return xstrfmt ("%s/scalar" , env );
482+ if ((env = getenv ("HOME" )) && * env )
483+ return xstrfmt ("%s/.cache/scalar" , env );
484+ return NULL ;
485+ #endif
486+ }
487+
488+ static int get_repository_id (struct json_iterator * it )
489+ {
490+ if (it -> type == JSON_STRING &&
491+ !strcasecmp (".repository.id" , it -> key .buf )) {
492+ * (char * * )it -> fn_data = strbuf_detach (& it -> string_value , NULL );
493+ return 1 ;
494+ }
495+
496+ return 0 ;
497+ }
498+
499+ /* Needs to run this in a worktree; gvfs-helper requires a Git repository */
500+ static char * get_cache_key (const char * url )
501+ {
502+ struct child_process cp = CHILD_PROCESS_INIT ;
503+ struct strbuf out = STRBUF_INIT ;
504+ char * cache_key = NULL ;
505+
506+ cp .git_cmd = 1 ;
507+ strvec_pushl (& cp .args , "gvfs-helper" , "--remote" , url ,
508+ "endpoint" , "vsts/info" , NULL );
509+ if (!pipe_command (& cp , NULL , 0 , & out , 512 , NULL , 0 )) {
510+ char * id = NULL ;
511+ struct json_iterator it =
512+ JSON_ITERATOR_INIT (out .buf , get_repository_id , & id );
513+
514+ if (iterate_json (& it ) < 0 )
515+ warning ("JSON parse error (%s)" , out .buf );
516+ else if (id )
517+ cache_key = xstrfmt ("id_%s" , id );
518+ free (id );
519+ }
520+
521+ if (!cache_key ) {
522+ struct strbuf downcased = STRBUF_INIT ;
523+ int hash_algo_index = hash_algo_by_name ("sha1" );
524+ const struct git_hash_algo * hash_algo = hash_algo_index < 0 ?
525+ the_hash_algo : & hash_algos [hash_algo_index ];
526+ struct git_hash_ctx ctx ;
527+ unsigned char hash [GIT_MAX_RAWSZ ];
528+
529+ strbuf_addstr (& downcased , url );
530+ strbuf_tolower (& downcased );
531+
532+ hash_algo -> init_fn (& ctx );
533+ hash_algo -> update_fn (& ctx , downcased .buf , downcased .len );
534+ hash_algo -> final_fn (hash , & ctx );
535+
536+ strbuf_release (& downcased );
537+
538+ cache_key = xstrfmt ("url_%s" ,
539+ hash_to_hex_algop (hash , hash_algo ));
540+ }
541+
542+ strbuf_release (& out );
543+ return cache_key ;
544+ }
545+
438546static char * remote_default_branch (const char * url )
439547{
440548 struct child_process cp = CHILD_PROCESS_INIT ;
@@ -529,14 +637,52 @@ void load_builtin_commands(const char *prefix UNUSED,
529637 die ("not implemented" );
530638}
531639
640+ static int init_shared_object_cache (const char * url ,
641+ const char * local_cache_root )
642+ {
643+ struct strbuf buf = STRBUF_INIT ;
644+ int res = 0 ;
645+ char * cache_key = NULL , * shared_cache_path = NULL , * alternates = NULL ;
646+
647+ if (!(cache_key = get_cache_key (url ))) {
648+ res = error (_ ("could not determine cache key for '%s'" ), url );
649+ goto cleanup ;
650+ }
651+
652+ shared_cache_path = xstrfmt ("%s/%s" , local_cache_root , cache_key );
653+ if (set_config ("gvfs.sharedCache=%s" , shared_cache_path )) {
654+ res = error (_ ("could not configure shared cache" ));
655+ goto cleanup ;
656+ }
657+
658+ strbuf_addf (& buf , "%s/pack" , shared_cache_path );
659+ switch (safe_create_leading_directories (the_repository , buf .buf )) {
660+ case SCLD_OK : case SCLD_EXISTS :
661+ break ; /* okay */
662+ default :
663+ res = error_errno (_ ("could not initialize '%s'" ), buf .buf );
664+ goto cleanup ;
665+ }
666+
667+ alternates = repo_git_path (the_repository , "objects/info/alternates" );
668+ write_file (alternates , "%s\n" , shared_cache_path );
669+
670+ cleanup :
671+ strbuf_release (& buf );
672+ free (shared_cache_path );
673+ free (cache_key );
674+ free (alternates );
675+ return res ;
676+ }
677+
532678static int cmd_clone (int argc , const char * * argv )
533679{
534680 const char * branch = NULL ;
535681 char * branch_to_free = NULL ;
536682 int full_clone = 0 , single_branch = 0 , show_progress = isatty (2 );
537683 int src = 1 , tags = 1 , maintenance = 1 ;
538- const char * cache_server_url = NULL ;
539- char * default_cache_server_url = NULL ;
684+ const char * cache_server_url = NULL , * local_cache_root = NULL ;
685+ char * default_cache_server_url = NULL , * local_cache_root_abs = NULL ;
540686 struct option clone_options [] = {
541687 OPT_STRING ('b' , "branch" , & branch , N_ ("<branch>" ),
542688 N_ ("branch to checkout after clone" )),
@@ -554,6 +700,9 @@ static int cmd_clone(int argc, const char **argv)
554700 OPT_STRING (0 , "cache-server-url" , & cache_server_url ,
555701 N_ ("<url>" ),
556702 N_ ("the url or friendly name of the cache server" )),
703+ OPT_STRING (0 , "local-cache-path" , & local_cache_root ,
704+ N_ ("<path>" ),
705+ N_ ("override the path for the local Scalar cache" )),
557706 OPT_END (),
558707 };
559708 const char * const clone_usage [] = {
@@ -595,11 +744,23 @@ static int cmd_clone(int argc, const char **argv)
595744 if (is_directory (enlistment ))
596745 die (_ ("directory '%s' exists already" ), enlistment );
597746
747+ ensure_absolute_path (enlistment , & enlistment );
748+
598749 if (src )
599750 dir = xstrfmt ("%s/src" , enlistment );
600751 else
601752 dir = xstrdup (enlistment );
602753
754+ if (!local_cache_root )
755+ local_cache_root = local_cache_root_abs =
756+ default_cache_root (enlistment );
757+ else
758+ local_cache_root = ensure_absolute_path (local_cache_root ,
759+ & local_cache_root_abs );
760+
761+ if (!local_cache_root )
762+ die (_ ("could not determine local cache root" ));
763+
603764 strbuf_reset (& buf );
604765 if (branch )
605766 strbuf_addf (& buf , "init.defaultBranch=%s" , branch );
@@ -619,8 +780,28 @@ static int cmd_clone(int argc, const char **argv)
619780
620781 setup_git_directory ();
621782
783+ repo_config (the_repository , git_default_config , NULL );
784+
785+ /*
786+ * This `dir_inside_of()` call relies on git_config() having parsed the
787+ * newly-initialized repository config's `core.ignoreCase` value.
788+ */
789+ if (dir_inside_of (local_cache_root , dir ) >= 0 ) {
790+ struct strbuf path = STRBUF_INIT ;
791+
792+ strbuf_addstr (& path , enlistment );
793+ if (chdir ("../.." ) < 0 ||
794+ remove_dir_recursively (& path , 0 ) < 0 )
795+ die (_ ("'--local-cache-path' cannot be inside the src "
796+ "folder;\nCould not remove '%s'" ), enlistment );
797+
798+ die (_ ("'--local-cache-path' cannot be inside the src folder" ));
799+ }
800+
622801 /* common-main already logs `argv` */
623802 trace2_def_repo (the_repository );
803+ trace2_data_intmax ("scalar" , the_repository , "unattended" ,
804+ is_unattended ());
624805
625806 if (!branch && !(branch = branch_to_free = remote_default_branch (url ))) {
626807 res = error (_ ("failed to get default branch for '%s'" ), url );
@@ -650,6 +831,8 @@ static int cmd_clone(int argc, const char **argv)
650831 supports_gvfs_protocol (url , & default_cache_server_url );
651832
652833 if (gvfs_protocol ) {
834+ if ((res = init_shared_object_cache (url , local_cache_root )))
835+ goto cleanup ;
653836 if (!cache_server_url )
654837 cache_server_url = default_cache_server_url ;
655838 if (set_config ("core.useGVFSHelper=true" ) ||
@@ -728,6 +911,7 @@ static int cmd_clone(int argc, const char **argv)
728911 free (dir );
729912 strbuf_release (& buf );
730913 free (default_cache_server_url );
914+ free (local_cache_root_abs );
731915 return res ;
732916}
733917
@@ -1162,6 +1346,12 @@ int cmd_main(int argc, const char **argv)
11621346 struct strbuf scalar_usage = STRBUF_INIT ;
11631347 int i ;
11641348
1349+ if (is_unattended ()) {
1350+ setenv ("GIT_ASKPASS" , "" , 0 );
1351+ setenv ("GIT_TERMINAL_PROMPT" , "false" , 0 );
1352+ git_config_push_parameter ("credential.interactive=false" );
1353+ }
1354+
11651355 while (argc > 1 && * argv [1 ] == '-' ) {
11661356 if (!strcmp (argv [1 ], "-C" )) {
11671357 if (argc < 3 )
0 commit comments