@@ -26,6 +26,7 @@ static char term = '\n';
2626
2727static int use_global_config , use_system_config , use_local_config ;
2828static int use_worktree_config ;
29+ static struct git_config_source move_source ;
2930static struct git_config_source given_config_source ;
3031static int actions , type ;
3132static char * default_value ;
@@ -50,6 +51,9 @@ static int show_origin;
5051#define ACTION_GET_COLOR (1<<13)
5152#define ACTION_GET_COLORBOOL (1<<14)
5253#define ACTION_GET_URLMATCH (1<<15)
54+ #define ACTION_MOVE (1<<16)
55+ #define ACTION_MOVE_REGEXP (1<<17)
56+ #define ACTION_MOVE_GLOB (1<<18)
5357
5458/*
5559 * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than
@@ -71,6 +75,64 @@ static int show_origin;
7175
7276static NORETURN void usage_builtin_config (void );
7377
78+ static void set_config_source_file (void )
79+ {
80+ int nongit = !startup_info -> have_repository ;
81+
82+ if (use_global_config + use_system_config + use_local_config +
83+ use_worktree_config +
84+ !!given_config_source .file + !!given_config_source .blob > 1 )
85+ die (_ ("only one config file at a time" ));
86+
87+ if (use_local_config && nongit )
88+ die (_ ("--local can only be used inside a git repository" ));
89+
90+ if (given_config_source .blob && nongit )
91+ die (_ ("--blob can only be used inside a git repository" ));
92+
93+ if (given_config_source .file &&
94+ !strcmp (given_config_source .file , "-" )) {
95+ given_config_source .file = NULL ;
96+ given_config_source .use_stdin = 1 ;
97+ }
98+
99+ if (use_global_config ) {
100+ char * user_config = expand_user_path ("~/.gitconfig" , 0 );
101+ char * xdg_config = xdg_config_home ("config" );
102+
103+ if (!user_config )
104+ /*
105+ * It is unknown if HOME/.gitconfig exists, so
106+ * we do not know if we should write to XDG
107+ * location; error out even if XDG_CONFIG_HOME
108+ * is set and points at a sane location.
109+ */
110+ die (_ ("$HOME not set" ));
111+
112+ if (access_or_warn (user_config , R_OK , 0 ) &&
113+ xdg_config && !access_or_warn (xdg_config , R_OK , 0 )) {
114+ given_config_source .file = xdg_config ;
115+ free (user_config );
116+ } else {
117+ given_config_source .file = user_config ;
118+ free (xdg_config );
119+ }
120+ }
121+ else if (use_system_config )
122+ given_config_source .file = git_etc_gitconfig ();
123+ else if (use_local_config )
124+ given_config_source .file = git_pathdup ("config" );
125+ else if (use_worktree_config ) {
126+ given_config_source .file = get_worktree_config (the_repository );
127+ if (!given_config_source .file )
128+ die (_ ("--worktree cannot be used with multiple "
129+ "working trees unless the config\n"
130+ "extension worktreeConfig is enabled. "
131+ "Please read \"CONFIGURATION FILE\"\n"
132+ "section in \"git help worktree\" for details" ));
133+ }
134+ }
135+
74136static int option_parse_type (const struct option * opt , const char * arg ,
75137 int unset )
76138{
@@ -120,13 +182,32 @@ static int option_parse_type(const struct option *opt, const char *arg,
120182 return 0 ;
121183}
122184
185+ static int option_move_cb (const struct option * opt ,
186+ const char * arg , int unset )
187+ {
188+ BUG_ON_OPT_NEG (unset );
189+ BUG_ON_OPT_ARG (arg );
190+
191+ set_config_source_file ();
192+ memcpy (& move_source , & given_config_source , sizeof (move_source ));
193+
194+ memset (& given_config_source , 0 , sizeof (given_config_source ));
195+ use_global_config = 0 ;
196+ use_system_config = 0 ;
197+ use_local_config = 0 ;
198+ use_worktree_config = 0 ;
199+
200+ actions = opt -> defval ;
201+ return 0 ;
202+ }
203+
123204static struct option builtin_config_options [] = {
124205 OPT_GROUP (N_ ("Config file location" )),
125206 OPT_BOOL (0 , "global" , & use_global_config , N_ ("use global config file" )),
126207 OPT_BOOL (0 , "system" , & use_system_config , N_ ("use system config file" )),
127208 OPT_BOOL (0 , "local" , & use_local_config , N_ ("use repository config file" )),
128209 OPT_BOOL (0 , "worktree" , & use_worktree_config , N_ ("use per-worktree config file" )),
129- OPT_STRING ('f' , "file" , & given_config_source .file , N_ ( "file" ) , N_ ("use given config file" )),
210+ OPT_FILENAME ('f' , "file" , & given_config_source .file , N_ ("use given config file" )),
130211 OPT_STRING (0 , "blob" , & given_config_source .blob , N_ ("blob-id" ), N_ ("read config from given blob object" )),
131212 OPT_GROUP (N_ ("Action" )),
132213 OPT_BIT (0 , "get" , & actions , N_ ("get value: name [value-regex]" ), ACTION_GET ),
@@ -139,6 +220,18 @@ static struct option builtin_config_options[] = {
139220 OPT_BIT (0 , "unset-all" , & actions , N_ ("remove all matches: name [value-regex]" ), ACTION_UNSET_ALL ),
140221 OPT_BIT (0 , "rename-section" , & actions , N_ ("rename section: old-name new-name" ), ACTION_RENAME_SECTION ),
141222 OPT_BIT (0 , "remove-section" , & actions , N_ ("remove a section: name" ), ACTION_REMOVE_SECTION ),
223+ { OPTION_CALLBACK , 0 , "move-to" , NULL , NULL ,
224+ N_ ("move a variable to a different config file" ),
225+ PARSE_OPT_NONEG | PARSE_OPT_NOARG ,
226+ option_move_cb , ACTION_MOVE },
227+ { OPTION_CALLBACK , 0 , "move-regexp-to" , NULL , NULL ,
228+ N_ ("move matching variables to a different config file" ),
229+ PARSE_OPT_NONEG | PARSE_OPT_NOARG ,
230+ option_move_cb , ACTION_MOVE_REGEXP },
231+ { OPTION_CALLBACK , 0 , "move-glob-to" , NULL , NULL ,
232+ N_ ("move matching variables to a different config file" ),
233+ PARSE_OPT_NONEG | PARSE_OPT_NOARG ,
234+ option_move_cb , ACTION_MOVE_GLOB },
142235 OPT_BIT ('l' , "list" , & actions , N_ ("list all" ), ACTION_LIST ),
143236 OPT_BIT ('e' , "edit" , & actions , N_ ("open an editor" ), ACTION_EDIT ),
144237 OPT_BIT (0 , "get-color" , & actions , N_ ("find the color configured: slot [default]" ), ACTION_GET_COLOR ),
@@ -369,6 +462,84 @@ static int get_value(const char *key_, const char *regex_)
369462 return ret ;
370463}
371464
465+ struct move_config_cb {
466+ struct string_list keys ;
467+ const char * key ;
468+ regex_t key_re ;
469+ };
470+
471+ static int collect_move_config (const char * key , const char * value , void * cb )
472+ {
473+ struct move_config_cb * data = cb ;
474+
475+ switch (actions ) {
476+ case ACTION_MOVE :
477+ if (strcasecmp (data -> key , key ))
478+ return 0 ;
479+ break ;
480+ case ACTION_MOVE_REGEXP :
481+ if (regexec (& data -> key_re , key , 0 , NULL , 0 ))
482+ return 0 ;
483+ break ;
484+ case ACTION_MOVE_GLOB :
485+ if (wildmatch (data -> key , key , WM_CASEFOLD ))
486+ return 0 ;
487+ break ;
488+ default :
489+ BUG ("action %d cannot get here" , actions );
490+ }
491+
492+ string_list_append (& data -> keys , key )-> util = xstrdup (value );
493+ return 0 ;
494+ }
495+
496+ static int move_config (const char * key )
497+ {
498+ struct move_config_cb cb ;
499+ int i , ret = 0 ;
500+
501+ config_options .respect_includes = 0 ;
502+ if (!move_source .file && !move_source .use_stdin && !move_source .blob )
503+ die (_ ("unknown config source" ));
504+
505+ string_list_init (& cb .keys , 1 );
506+ cb .key = key ;
507+ if (actions == ACTION_MOVE_REGEXP &&
508+ regcomp (& cb .key_re , key , REG_EXTENDED | REG_ICASE ))
509+ die (_ ("invalid key pattern: %s" ), key );
510+
511+ config_with_options (collect_move_config , & cb ,
512+ & move_source , & config_options );
513+
514+ for (i = 0 ; i < cb .keys .nr && !ret ; i ++ ) {
515+ const char * key = cb .keys .items [i ].string ;
516+ const char * value = cb .keys .items [i ].util ;
517+ const char * dest = given_config_source .file ;
518+
519+ ret = git_config_set_multivar_in_file_gently (
520+ dest , key , value , CONFIG_REGEX_NONE , 0 );
521+ }
522+
523+ /*
524+ * OK all keys have been copied successfully, time to delete
525+ * old ones
526+ */
527+ if (!ret && move_source .file ) {
528+ for (i = 0 ; i < cb .keys .nr ; i ++ ) {
529+ const char * key = cb .keys .items [i ].string ;
530+ const char * src = move_source .file ;
531+
532+ git_config_set_multivar_in_file_gently (
533+ src , key , NULL , NULL , 1 );
534+ }
535+ }
536+
537+ string_list_clear (& cb .keys , 1 );
538+ if (actions == ACTION_MOVE_REGEXP )
539+ regfree (& cb .key_re );
540+ return ret ;
541+ }
542+
372543static char * normalize_value (const char * key , const char * value )
373544{
374545 if (!value )
@@ -605,69 +776,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
605776 builtin_config_usage ,
606777 PARSE_OPT_STOP_AT_NON_OPTION );
607778
608- if (use_global_config + use_system_config + use_local_config +
609- use_worktree_config +
610- !!given_config_source .file + !!given_config_source .blob > 1 ) {
611- error (_ ("only one config file at a time" ));
612- usage_builtin_config ();
613- }
614-
615- if (use_local_config && nongit )
616- die (_ ("--local can only be used inside a git repository" ));
617-
618- if (given_config_source .blob && nongit )
619- die (_ ("--blob can only be used inside a git repository" ));
620-
621- if (given_config_source .file &&
622- !strcmp (given_config_source .file , "-" )) {
623- given_config_source .file = NULL ;
624- given_config_source .use_stdin = 1 ;
625- }
626-
627- if (use_global_config ) {
628- char * user_config = expand_user_path ("~/.gitconfig" , 0 );
629- char * xdg_config = xdg_config_home ("config" );
630-
631- if (!user_config )
632- /*
633- * It is unknown if HOME/.gitconfig exists, so
634- * we do not know if we should write to XDG
635- * location; error out even if XDG_CONFIG_HOME
636- * is set and points at a sane location.
637- */
638- die (_ ("$HOME not set" ));
639-
640- if (access_or_warn (user_config , R_OK , 0 ) &&
641- xdg_config && !access_or_warn (xdg_config , R_OK , 0 )) {
642- given_config_source .file = xdg_config ;
643- free (user_config );
644- } else {
645- given_config_source .file = user_config ;
646- free (xdg_config );
647- }
648- }
649- else if (use_system_config )
650- given_config_source .file = git_etc_gitconfig ();
651- else if (use_local_config )
652- given_config_source .file = git_pathdup ("config" );
653- else if (use_worktree_config ) {
654- struct worktree * * worktrees = get_worktrees (0 );
655- if (repository_format_worktree_config )
656- given_config_source .file = git_pathdup ("config.worktree" );
657- else if (worktrees [0 ] && worktrees [1 ])
658- die (_ ("--worktree cannot be used with multiple "
659- "working trees unless the config\n"
660- "extension worktreeConfig is enabled. "
661- "Please read \"CONFIGURATION FILE\"\n"
662- "section in \"git help worktree\" for details" ));
663- else
664- given_config_source .file = git_pathdup ("config" );
665- free_worktrees (worktrees );
666- } else if (given_config_source .file ) {
667- if (!is_absolute_path (given_config_source .file ) && prefix )
668- given_config_source .file =
669- prefix_filename (prefix , given_config_source .file );
670- }
779+ set_config_source_file ();
671780
672781 if (respect_includes_opt == -1 )
673782 config_options .respect_includes = !given_config_source .file ;
@@ -867,6 +976,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
867976 color_stdout_is_tty = git_config_bool ("command line" , argv [1 ]);
868977 return get_colorbool (argv [0 ], argc == 2 );
869978 }
979+ else if (actions == ACTION_MOVE ||
980+ actions == ACTION_MOVE_REGEXP ||
981+ actions == ACTION_MOVE_GLOB ) {
982+ check_write ();
983+ check_argc (argc , 1 , 1 );
984+ return move_config (argv [0 ]);
985+ }
870986
871987 return 0 ;
872988}
0 commit comments