Raw
1 #include "git-compat-util.h"
2 #include "advice.h"
3 #include "config.h"
4 #include "color.h"
5 #include "environment.h"
6 #include "gettext.h"
7 #include "help.h"
8 #include "string-list.h"
9
10 static enum git_colorbool advice_use_color = GIT_COLOR_UNKNOWN;
11 static char advice_colors[][COLOR_MAXLEN] = {
12 GIT_COLOR_RESET,
13 GIT_COLOR_YELLOW, /* HINT */
14 };
15
16 enum color_advice {
17 ADVICE_COLOR_RESET = 0,
18 ADVICE_COLOR_HINT = 1,
19 };
20
21 static int parse_advise_color_slot(const char *slot)
22 {
23 if (!strcasecmp(slot, "reset"))
24 return ADVICE_COLOR_RESET;
25 if (!strcasecmp(slot, "hint"))
26 return ADVICE_COLOR_HINT;
27 return -1;
28 }
29
30 static const char *advise_get_color(enum color_advice ix)
31 {
32 if (want_color_stderr(advice_use_color))
33 return advice_colors[ix];
34 return "";
35 }
36
37 enum advice_level {
38 ADVICE_LEVEL_NONE = 0,
39 ADVICE_LEVEL_DISABLED,
40 ADVICE_LEVEL_ENABLED,
41 };
42
43 static struct {
44 const char *key;
45 enum advice_level level;
46 } advice_setting[] = {
47 [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo" },
48 [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec" },
49 [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile" },
50 [ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec" },
51 [ADVICE_AM_WORK_DIR] = { "amWorkDir" },
52 [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" },
53 [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
54 [ADVICE_DEFAULT_BRANCH_NAME] = { "defaultBranchName" },
55 [ADVICE_DETACHED_HEAD] = { "detachedHead" },
56 [ADVICE_DIVERGING] = { "diverging" },
57 [ADVICE_FETCH_SET_HEAD_WARN] = { "fetchRemoteHEADWarn" },
58 [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates" },
59 [ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch" },
60 [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated" },
61 [ADVICE_HISTORY_UPDATE_REFS] = { "historyUpdateRefs" },
62 [ADVICE_IGNORED_HOOK] = { "ignoredHook" },
63 [ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity" },
64 [ADVICE_MERGE_CONFLICT] = { "mergeConflict" },
65 [ADVICE_NESTED_TAG] = { "nestedTag" },
66 [ADVICE_OBJECT_NAME_WARNING] = { "objectNameWarning" },
67 [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists" },
68 [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst" },
69 [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce" },
70 [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent" },
71 [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching" },
72 [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate" },
73 [ADVICE_PUSH_REPO_LOOKS_LIKE_REF] = { "pushRepoLooksLikeRef" },
74 [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" },
75 [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" },
76 [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward" }, /* backwards compatibility */
77 [ADVICE_REBASE_TODO_ERROR] = { "rebaseTodoError" },
78 [ADVICE_REF_SYNTAX] = { "refSyntax" },
79 [ADVICE_RESET_NO_REFRESH_WARNING] = { "resetNoRefresh" },
80 [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict" },
81 [ADVICE_RM_HINTS] = { "rmHints" },
82 [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse" },
83 [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure" },
84 [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks" },
85 [ADVICE_SPARSE_INDEX_EXPANDED] = { "sparseIndexExpanded" },
86 [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning" },
87 [ADVICE_STATUS_HINTS] = { "statusHints" },
88 [ADVICE_STATUS_U_OPTION] = { "statusUoption" },
89 [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated" },
90 [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie" },
91 [ADVICE_SUBMODULE_MERGE_CONFLICT] = { "submoduleMergeConflict" },
92 [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead" },
93 [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath" },
94 [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor" },
95 [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan" },
96 };
97
98 static const char turn_off_instructions[] =
99 N_("\n"
100 "Disable this message with \"git config set advice.%s false\"");
101
102 static void vadvise(const char *advice, int display_instructions,
103 const char *key, va_list params)
104 {
105 struct strbuf buf = STRBUF_INIT;
106 const char *cp, *np;
107
108 strbuf_vaddf(&buf, advice, params);
109
110 if (display_instructions)
111 strbuf_addf(&buf, turn_off_instructions, key);
112
113 for (cp = buf.buf; *cp; cp = np) {
114 np = strchrnul(cp, '\n');
115 fprintf(stderr, _("%shint:%s%.*s%s\n"),
116 advise_get_color(ADVICE_COLOR_HINT),
117 (np == cp) ? "" : " ",
118 (int)(np - cp), cp,
119 advise_get_color(ADVICE_COLOR_RESET));
120 if (*np)
121 np++;
122 }
123 strbuf_release(&buf);
124 }
125
126 void advise(const char *advice, ...)
127 {
128 va_list params;
129 va_start(params, advice);
130 vadvise(advice, 0, "", params);
131 va_end(params);
132 }
133
134 int advice_enabled(enum advice_type type)
135 {
136 int enabled = advice_setting[type].level != ADVICE_LEVEL_DISABLED;
137 static int globally_enabled = -1;
138
139 if (globally_enabled < 0)
140 globally_enabled = git_env_bool(GIT_ADVICE_ENVIRONMENT, 1);
141 if (!globally_enabled)
142 return 0;
143
144 if (type == ADVICE_PUSH_UPDATE_REJECTED)
145 return enabled &&
146 advice_enabled(ADVICE_PUSH_UPDATE_REJECTED_ALIAS);
147
148 return enabled;
149 }
150
151 void advise_if_enabled(enum advice_type type, const char *advice, ...)
152 {
153 va_list params;
154
155 if (!advice_enabled(type))
156 return;
157
158 va_start(params, advice);
159 vadvise(advice, !advice_setting[type].level, advice_setting[type].key,
160 params);
161 va_end(params);
162 }
163
164 int git_default_advice_config(const char *var, const char *value)
165 {
166 const char *k, *slot_name;
167
168 if (!strcmp(var, "color.advice")) {
169 advice_use_color = git_config_colorbool(var, value);
170 return 0;
171 }
172
173 if (skip_prefix(var, "color.advice.", &slot_name)) {
174 int slot = parse_advise_color_slot(slot_name);
175 if (slot < 0)
176 return 0;
177 if (!value)
178 return config_error_nonbool(var);
179 return color_parse(value, advice_colors[slot]);
180 }
181
182 if (!skip_prefix(var, "advice.", &k))
183 return 0;
184
185 for (size_t i = 0; i < ARRAY_SIZE(advice_setting); i++) {
186 if (strcasecmp(k, advice_setting[i].key))
187 continue;
188 advice_setting[i].level = git_config_bool(var, value)
189 ? ADVICE_LEVEL_ENABLED
190 : ADVICE_LEVEL_DISABLED;
191 return 0;
192 }
193
194 return 0;
195 }
196
197 void list_config_advices(struct string_list *list, const char *prefix)
198 {
199 for (size_t i = 0; i < ARRAY_SIZE(advice_setting); i++)
200 list_config_item(list, prefix, advice_setting[i].key);
201 }
202
203 int error_resolve_conflict(const char *me)
204 {
205 if (!strcmp(me, "cherry-pick"))
206 error(_("Cherry-picking is not possible because you have unmerged files."));
207 else if (!strcmp(me, "commit"))
208 error(_("Committing is not possible because you have unmerged files."));
209 else if (!strcmp(me, "merge"))
210 error(_("Merging is not possible because you have unmerged files."));
211 else if (!strcmp(me, "pull"))
212 error(_("Pulling is not possible because you have unmerged files."));
213 else if (!strcmp(me, "revert"))
214 error(_("Reverting is not possible because you have unmerged files."));
215 else if (!strcmp(me, "rebase"))
216 error(_("Rebasing is not possible because you have unmerged files."));
217 else
218 BUG("Unhandled conflict reason '%s'", me);
219
220 if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
221 /*
222 * Message used both when 'git commit' fails and when
223 * other commands doing a merge do.
224 */
225 advise(_("Fix them up in the work tree, and then use 'git add/rm <file>'\n"
226 "as appropriate to mark resolution and make a commit."));
227 return -1;
228 }
229
230 void NORETURN die_resolve_conflict(const char *me)
231 {
232 error_resolve_conflict(me);
233 die(_("Exiting because of an unresolved conflict."));
234 }
235
236 void NORETURN die_conclude_merge(void)
237 {
238 error(_("You have not concluded your merge (MERGE_HEAD exists)."));
239 if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
240 advise(_("Please, commit your changes before merging."));
241 die(_("Exiting because of unfinished merge."));
242 }
243
244 void NORETURN die_ff_impossible(void)
245 {
246 advise_if_enabled(ADVICE_DIVERGING,
247 _("Diverging branches can't be fast-forwarded, you need to either:\n"
248 "\n"
249 "\tgit merge --no-ff\n"
250 "\n"
251 "or:\n"
252 "\n"
253 "\tgit rebase\n"));
254 die(_("Not possible to fast-forward, aborting."));
255 }
256
257 void advise_on_updating_sparse_paths(struct string_list *pathspec_list)
258 {
259 struct string_list_item *item;
260
261 if (!pathspec_list->nr)
262 return;
263
264 fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n"
265 "outside of your sparse-checkout definition, so will not be\n"
266 "updated in the index:\n"));
267 for_each_string_list_item(item, pathspec_list)
268 fprintf(stderr, "%s\n", item->string);
269
270 advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
271 _("If you intend to update such entries, try one of the following:\n"
272 "* Use the --sparse option.\n"
273 "* Disable or modify the sparsity rules."));
274 }
275
276 void detach_advice(const char *new_name)
277 {
278 const char *fmt =
279 _("Note: switching to '%s'.\n"
280 "\n"
281 "You are in 'detached HEAD' state. You can look around, make experimental\n"
282 "changes and commit them, and you can discard any commits you make in this\n"
283 "state without impacting any branches by switching back to a branch.\n"
284 "\n"
285 "If you want to create a new branch to retain commits you create, you may\n"
286 "do so (now or later) by using -c with the switch command. Example:\n"
287 "\n"
288 " git switch -c <new-branch-name>\n"
289 "\n"
290 "Or undo this operation with:\n"
291 "\n"
292 " git switch -\n"
293 "\n"
294 "Turn off this advice by setting config variable advice.detachedHead to false\n\n");
295
296 fprintf(stderr, fmt, new_name);
297 }
298
299 void advise_on_moving_dirty_path(struct string_list *pathspec_list)
300 {
301 struct string_list_item *item;
302
303 if (!pathspec_list->nr)
304 return;
305
306 fprintf(stderr, _("The following paths have been moved outside the\n"
307 "sparse-checkout definition but are not sparse due to local\n"
308 "modifications.\n"));
309 for_each_string_list_item(item, pathspec_list)
310 fprintf(stderr, "%s\n", item->string);
311
312 advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH,
313 _("To correct the sparsity of these paths, do the following:\n"
314 "* Use \"git add --sparse <paths>\" to update the index\n"
315 "* Use \"git sparse-checkout reapply\" to apply the sparsity rules"));
316 }