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"
1213#include "run-command.h"
1516#include "fsmonitor-settings.h"
1617#include "refs.h"
1718#include "dir.h"
19+ #include "object-file.h"
1820#include "packfile.h"
1921#include "help.h"
2022#include "setup.h"
23+ #include "wrapper.h"
2124#include "trace2.h"
2225#include "json-parser.h"
26+ #include "path.h"
27+
28+ static int is_unattended (void ) {
29+ return git_env_bool ("Scalar_UNATTENDED" , 0 );
30+ }
2331
2432static void setup_enlistment_directory (int argc , const char * * argv ,
2533 const char * const * usagestr ,
@@ -106,6 +114,19 @@ static int run_git(const char *arg, ...)
106114 return res ;
107115}
108116
117+ static const char * ensure_absolute_path (const char * path , char * * absolute )
118+ {
119+ struct strbuf buf = STRBUF_INIT ;
120+
121+ if (is_absolute_path (path ))
122+ return path ;
123+
124+ strbuf_realpath_forgiving (& buf , path , 1 );
125+ free (* absolute );
126+ * absolute = strbuf_detach (& buf , NULL );
127+ return * absolute ;
128+ }
129+
109130struct scalar_config {
110131 const char * key ;
111132 const char * value ;
@@ -418,6 +439,87 @@ static int supports_gvfs_protocol(const char *url, char **cache_server_url)
418439 return 0 ; /* error out quietly */
419440}
420441
442+ static char * default_cache_root (const char * root )
443+ {
444+ const char * env ;
445+
446+ if (is_unattended ())
447+ return xstrfmt ("%s/.scalarCache" , root );
448+
449+ #ifdef WIN32
450+ (void )env ;
451+ return xstrfmt ("%.*s.scalarCache" , offset_1st_component (root ), root );
452+ #elif defined(__APPLE__ )
453+ if ((env = getenv ("HOME" )) && * env )
454+ return xstrfmt ("%s/.scalarCache" , env );
455+ return NULL ;
456+ #else
457+ if ((env = getenv ("XDG_CACHE_HOME" )) && * env )
458+ return xstrfmt ("%s/scalar" , env );
459+ if ((env = getenv ("HOME" )) && * env )
460+ return xstrfmt ("%s/.cache/scalar" , env );
461+ return NULL ;
462+ #endif
463+ }
464+
465+ static int get_repository_id (struct json_iterator * it )
466+ {
467+ if (it -> type == JSON_STRING &&
468+ !strcasecmp (".repository.id" , it -> key .buf )) {
469+ * (char * * )it -> fn_data = strbuf_detach (& it -> string_value , NULL );
470+ return 1 ;
471+ }
472+
473+ return 0 ;
474+ }
475+
476+ /* Needs to run this in a worktree; gvfs-helper requires a Git repository */
477+ static char * get_cache_key (const char * url )
478+ {
479+ struct child_process cp = CHILD_PROCESS_INIT ;
480+ struct strbuf out = STRBUF_INIT ;
481+ char * cache_key = NULL ;
482+
483+ cp .git_cmd = 1 ;
484+ strvec_pushl (& cp .args , "gvfs-helper" , "--remote" , url ,
485+ "endpoint" , "vsts/info" , NULL );
486+ if (!pipe_command (& cp , NULL , 0 , & out , 512 , NULL , 0 )) {
487+ char * id = NULL ;
488+ struct json_iterator it =
489+ JSON_ITERATOR_INIT (out .buf , get_repository_id , & id );
490+
491+ if (iterate_json (& it ) < 0 )
492+ warning ("JSON parse error (%s)" , out .buf );
493+ else if (id )
494+ cache_key = xstrfmt ("id_%s" , id );
495+ free (id );
496+ }
497+
498+ if (!cache_key ) {
499+ struct strbuf downcased = STRBUF_INIT ;
500+ int hash_algo_index = hash_algo_by_name ("sha1" );
501+ const struct git_hash_algo * hash_algo = hash_algo_index < 0 ?
502+ the_hash_algo : & hash_algos [hash_algo_index ];
503+ git_hash_ctx ctx ;
504+ unsigned char hash [GIT_MAX_RAWSZ ];
505+
506+ strbuf_addstr (& downcased , url );
507+ strbuf_tolower (& downcased );
508+
509+ hash_algo -> init_fn (& ctx );
510+ hash_algo -> update_fn (& ctx , downcased .buf , downcased .len );
511+ hash_algo -> final_fn (hash , & ctx );
512+
513+ strbuf_release (& downcased );
514+
515+ cache_key = xstrfmt ("url_%s" ,
516+ hash_to_hex_algop (hash , hash_algo ));
517+ }
518+
519+ strbuf_release (& out );
520+ return cache_key ;
521+ }
522+
421523static char * remote_default_branch (const char * url )
422524{
423525 struct child_process cp = CHILD_PROCESS_INIT ;
@@ -512,13 +614,49 @@ void load_builtin_commands(const char *prefix UNUSED,
512614 die ("not implemented" );
513615}
514616
617+ static int init_shared_object_cache (const char * url ,
618+ const char * local_cache_root )
619+ {
620+ struct strbuf buf = STRBUF_INIT ;
621+ int res = 0 ;
622+ char * cache_key = NULL , * shared_cache_path = NULL ;
623+
624+ if (!(cache_key = get_cache_key (url ))) {
625+ res = error (_ ("could not determine cache key for '%s'" ), url );
626+ goto cleanup ;
627+ }
628+
629+ shared_cache_path = xstrfmt ("%s/%s" , local_cache_root , cache_key );
630+ if (set_config ("gvfs.sharedCache=%s" , shared_cache_path )) {
631+ res = error (_ ("could not configure shared cache" ));
632+ goto cleanup ;
633+ }
634+
635+ strbuf_addf (& buf , "%s/pack" , shared_cache_path );
636+ switch (safe_create_leading_directories (buf .buf )) {
637+ case SCLD_OK : case SCLD_EXISTS :
638+ break ; /* okay */
639+ default :
640+ res = error_errno (_ ("could not initialize '%s'" ), buf .buf );
641+ goto cleanup ;
642+ }
643+
644+ write_file (git_path ("objects/info/alternates" ),"%s\n" , shared_cache_path );
645+
646+ cleanup :
647+ strbuf_release (& buf );
648+ free (shared_cache_path );
649+ free (cache_key );
650+ return res ;
651+ }
652+
515653static int cmd_clone (int argc , const char * * argv )
516654{
517655 const char * branch = NULL ;
518656 int full_clone = 0 , single_branch = 0 , show_progress = isatty (2 );
519657 int src = 1 , tags = 1 ;
520- const char * cache_server_url = NULL ;
521- char * default_cache_server_url = NULL ;
658+ const char * cache_server_url = NULL , * local_cache_root = NULL ;
659+ char * default_cache_server_url = NULL , * local_cache_root_abs = NULL ;
522660 struct option clone_options [] = {
523661 OPT_STRING ('b' , "branch" , & branch , N_ ("<branch>" ),
524662 N_ ("branch to checkout after clone" )),
@@ -534,6 +672,9 @@ static int cmd_clone(int argc, const char **argv)
534672 OPT_STRING (0 , "cache-server-url" , & cache_server_url ,
535673 N_ ("<url>" ),
536674 N_ ("the url or friendly name of the cache server" )),
675+ OPT_STRING (0 , "local-cache-path" , & local_cache_root ,
676+ N_ ("<path>" ),
677+ N_ ("override the path for the local Scalar cache" )),
537678 OPT_END (),
538679 };
539680 const char * const clone_usage [] = {
@@ -575,11 +716,23 @@ static int cmd_clone(int argc, const char **argv)
575716 if (is_directory (enlistment ))
576717 die (_ ("directory '%s' exists already" ), enlistment );
577718
719+ ensure_absolute_path (enlistment , & enlistment );
720+
578721 if (src )
579722 dir = xstrfmt ("%s/src" , enlistment );
580723 else
581724 dir = xstrdup (enlistment );
582725
726+ if (!local_cache_root )
727+ local_cache_root = local_cache_root_abs =
728+ default_cache_root (enlistment );
729+ else
730+ local_cache_root = ensure_absolute_path (local_cache_root ,
731+ & local_cache_root_abs );
732+
733+ if (!local_cache_root )
734+ die (_ ("could not determine local cache root" ));
735+
583736 strbuf_reset (& buf );
584737 if (branch )
585738 strbuf_addf (& buf , "init.defaultBranch=%s" , branch );
@@ -599,8 +752,28 @@ static int cmd_clone(int argc, const char **argv)
599752
600753 setup_git_directory ();
601754
755+ git_config (git_default_config , NULL );
756+
757+ /*
758+ * This `dir_inside_of()` call relies on git_config() having parsed the
759+ * newly-initialized repository config's `core.ignoreCase` value.
760+ */
761+ if (dir_inside_of (local_cache_root , dir ) >= 0 ) {
762+ struct strbuf path = STRBUF_INIT ;
763+
764+ strbuf_addstr (& path , enlistment );
765+ if (chdir ("../.." ) < 0 ||
766+ remove_dir_recursively (& path , 0 ) < 0 )
767+ die (_ ("'--local-cache-path' cannot be inside the src "
768+ "folder;\nCould not remove '%s'" ), enlistment );
769+
770+ die (_ ("'--local-cache-path' cannot be inside the src folder" ));
771+ }
772+
602773 /* common-main already logs `argv` */
603774 trace2_def_repo (the_repository );
775+ trace2_data_intmax ("scalar" , the_repository , "unattended" ,
776+ is_unattended ());
604777
605778 if (!branch && !(branch = remote_default_branch (url ))) {
606779 res = error (_ ("failed to get default branch for '%s'" ), url );
@@ -630,6 +803,8 @@ static int cmd_clone(int argc, const char **argv)
630803 supports_gvfs_protocol (url , & default_cache_server_url );
631804
632805 if (gvfs_protocol ) {
806+ if ((res = init_shared_object_cache (url , local_cache_root )))
807+ goto cleanup ;
633808 if (!cache_server_url )
634809 cache_server_url = default_cache_server_url ;
635810 if (set_config ("core.useGVFSHelper=true" ) ||
@@ -706,6 +881,7 @@ static int cmd_clone(int argc, const char **argv)
706881 free (dir );
707882 strbuf_release (& buf );
708883 free (default_cache_server_url );
884+ free (local_cache_root_abs );
709885 return res ;
710886}
711887
@@ -1117,6 +1293,12 @@ int cmd_main(int argc, const char **argv)
11171293 struct strbuf scalar_usage = STRBUF_INIT ;
11181294 int i ;
11191295
1296+ if (is_unattended ()) {
1297+ setenv ("GIT_ASKPASS" , "" , 0 );
1298+ setenv ("GIT_TERMINAL_PROMPT" , "false" , 0 );
1299+ git_config_push_parameter ("credential.interactive=false" );
1300+ }
1301+
11201302 while (argc > 1 && * argv [1 ] == '-' ) {
11211303 if (!strcmp (argv [1 ], "-C" )) {
11221304 if (argc < 3 )
0 commit comments