summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/am.c4
-rw-r--r--builtin/blame.c48
-rw-r--r--builtin/commit-graph.c6
-rw-r--r--builtin/fast-export.c22
-rw-r--r--builtin/for-each-repo.c7
-rw-r--r--builtin/gc.c422
-rw-r--r--builtin/index-pack.c2
-rw-r--r--builtin/log.c8
-rw-r--r--builtin/name-rev.c4
-rw-r--r--builtin/pack-objects.c1
-rw-r--r--builtin/pull.c70
-rw-r--r--builtin/rebase.c4
-rw-r--r--builtin/rev-parse.c106
-rw-r--r--builtin/shortlog.c10
-rw-r--r--builtin/stash.c165
-rw-r--r--builtin/submodule--helper.c24
-rw-r--r--builtin/worktree.c2
17 files changed, 723 insertions, 182 deletions
diff --git a/builtin/am.c b/builtin/am.c
index f22c73a05b..8355e3566f 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2284,10 +2284,10 @@ int cmd_am(int argc, const char **argv, const char *prefix)
N_("skip the current patch"),
RESUME_SKIP),
OPT_CMDMODE(0, "abort", &resume.mode,
- N_("restore the original branch and abort the patching operation."),
+ N_("restore the original branch and abort the patching operation"),
RESUME_ABORT),
OPT_CMDMODE(0, "quit", &resume.mode,
- N_("abort the patching operation but keep HEAD where it is."),
+ N_("abort the patching operation but keep HEAD where it is"),
RESUME_QUIT),
{ OPTION_CALLBACK, 0, "show-current-patch", &resume.mode,
"(diff|raw)",
diff --git a/builtin/blame.c b/builtin/blame.c
index 6f7e32411a..2c1c02c455 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -866,33 +866,33 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
const char *revs_file = NULL;
const char *contents_from = NULL;
const struct option options[] = {
- OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
- OPT_BOOL('b', NULL, &blank_boundary, N_("Do not show object names of boundary commits (Default: off)")),
- OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
- OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
- OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")),
- OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
- OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
- OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
- OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
- OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
- OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
- OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
- OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
- OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
- OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
- OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
- OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")),
- OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")),
+ OPT_BOOL(0, "incremental", &incremental, N_("show blame entries as we find them, incrementally")),
+ OPT_BOOL('b', NULL, &blank_boundary, N_("do not show object names of boundary commits (Default: off)")),
+ OPT_BOOL(0, "root", &show_root, N_("do not treat root commits as boundaries (Default: off)")),
+ OPT_BOOL(0, "show-stats", &show_stats, N_("show work cost statistics")),
+ OPT_BOOL(0, "progress", &show_progress, N_("force progress reporting")),
+ OPT_BIT(0, "score-debug", &output_option, N_("show output score for blame entries"), OUTPUT_SHOW_SCORE),
+ OPT_BIT('f', "show-name", &output_option, N_("show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
+ OPT_BIT('n', "show-number", &output_option, N_("show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
+ OPT_BIT('p', "porcelain", &output_option, N_("show in a format designed for machine consumption"), OUTPUT_PORCELAIN),
+ OPT_BIT(0, "line-porcelain", &output_option, N_("show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
+ OPT_BIT('c', NULL, &output_option, N_("use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT),
+ OPT_BIT('t', NULL, &output_option, N_("show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP),
+ OPT_BIT('l', NULL, &output_option, N_("show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME),
+ OPT_BIT('s', NULL, &output_option, N_("suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
+ OPT_BIT('e', "show-email", &output_option, N_("show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
+ OPT_BIT('w', NULL, &xdl_opts, N_("ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+ OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("ignore <rev> when blaming")),
+ OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("ignore revisions from <file>")),
OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
- OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL),
- OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")),
- OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
- OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback),
- OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback),
+ OPT_BIT(0, "minimal", &xdl_opts, N_("spend extra cycles to find better match"), XDF_NEED_MINIMAL),
+ OPT_STRING('S', NULL, &revs_file, N_("file"), N_("use revisions from <file> instead of calling git-rev-list")),
+ OPT_STRING(0, "contents", &contents_from, N_("file"), N_("use <file>'s contents as the final image")),
+ OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback),
+ OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback),
OPT_STRING_LIST('L', NULL, &range_list, N_("range"),
- N_("Process only line range <start>,<end> or function :<funcname>")),
+ N_("process only line range <start>,<end> or function :<funcname>")),
OPT__ABBREV(&abbrev),
OPT_END()
};
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 78fa08f43a..cd86315221 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -78,7 +78,7 @@ static int graph_verify(int argc, const char **argv)
static struct option builtin_commit_graph_verify_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
- N_("The object directory to store the graph")),
+ N_("the object directory to store the graph")),
OPT_BOOL(0, "shallow", &opts.shallow,
N_("if the commit-graph is split, only verify the tip file")),
OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")),
@@ -208,7 +208,7 @@ static int graph_write(int argc, const char **argv)
static struct option builtin_commit_graph_write_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
- N_("The object directory to store the graph")),
+ N_("the object directory to store the graph")),
OPT_BOOL(0, "reachable", &opts.reachable,
N_("start walk at all refs")),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
@@ -314,7 +314,7 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix)
static struct option builtin_commit_graph_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
N_("dir"),
- N_("The object directory to store the graph")),
+ N_("the object directory to store the graph")),
OPT_END(),
};
diff --git a/builtin/fast-export.c b/builtin/fast-export.c
index 0a60356b06..85a76e0ef8 100644
--- a/builtin/fast-export.c
+++ b/builtin/fast-export.c
@@ -1205,32 +1205,32 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
N_("select handling of commit messages in an alternate encoding"),
parse_opt_reencode_mode),
OPT_STRING(0, "export-marks", &export_filename, N_("file"),
- N_("Dump marks to this file")),
+ N_("dump marks to this file")),
OPT_STRING(0, "import-marks", &import_filename, N_("file"),
- N_("Import marks from this file")),
+ N_("import marks from this file")),
OPT_STRING(0, "import-marks-if-exists",
&import_filename_if_exists,
N_("file"),
- N_("Import marks from this file if it exists")),
+ N_("import marks from this file if it exists")),
OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger,
- N_("Fake a tagger when tags lack one")),
+ N_("fake a tagger when tags lack one")),
OPT_BOOL(0, "full-tree", &full_tree,
- N_("Output full tree for each commit")),
+ N_("output full tree for each commit")),
OPT_BOOL(0, "use-done-feature", &use_done_feature,
- N_("Use the done feature to terminate the stream")),
- OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")),
+ N_("use the done feature to terminate the stream")),
+ OPT_BOOL(0, "no-data", &no_data, N_("skip output of blob data")),
OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"),
- N_("Apply refspec to exported refs")),
+ N_("apply refspec to exported refs")),
OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")),
OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"),
N_("convert <from> to <to> in anonymized output"),
PARSE_OPT_NONEG, parse_opt_anonymize_map),
OPT_BOOL(0, "reference-excluded-parents",
- &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")),
+ &reference_excluded_commits, N_("reference parents which are not in fast-export stream by object id")),
OPT_BOOL(0, "show-original-ids", &show_original_ids,
- N_("Show original object ids of blobs/commits")),
+ N_("show original object ids of blobs/commits")),
OPT_BOOL(0, "mark-tags", &mark_tags,
- N_("Label tags with mark ids")),
+ N_("label tags with mark ids")),
OPT_END()
};
diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c
index 5bba623ff1..52be64a437 100644
--- a/builtin/for-each-repo.c
+++ b/builtin/for-each-repo.c
@@ -51,6 +51,13 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix)
values = repo_config_get_value_multi(the_repository,
config_key);
+ /*
+ * Do nothing on an empty list, which is equivalent to the case
+ * where the config variable does not exist at all.
+ */
+ if (!values)
+ return 0;
+
for (i = 0; !result && i < values->nr; i++)
result = run_command_on_repo(values->items[i].string, &args);
diff --git a/builtin/gc.c b/builtin/gc.c
index 4c24f41852..b315b2ad58 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1493,38 +1493,368 @@ static int maintenance_unregister(void)
return run_command(&config_unset);
}
+static const char *get_frequency(enum schedule_priority schedule)
+{
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ return "hourly";
+ case SCHEDULE_DAILY:
+ return "daily";
+ case SCHEDULE_WEEKLY:
+ return "weekly";
+ default:
+ BUG("invalid schedule %d", schedule);
+ }
+}
+
+static char *launchctl_service_name(const char *frequency)
+{
+ struct strbuf label = STRBUF_INIT;
+ strbuf_addf(&label, "org.git-scm.git.%s", frequency);
+ return strbuf_detach(&label, NULL);
+}
+
+static char *launchctl_service_filename(const char *name)
+{
+ char *expanded;
+ struct strbuf filename = STRBUF_INIT;
+ strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name);
+
+ expanded = expand_user_path(filename.buf, 1);
+ if (!expanded)
+ die(_("failed to expand path '%s'"), filename.buf);
+
+ strbuf_release(&filename);
+ return expanded;
+}
+
+static char *launchctl_get_uid(void)
+{
+ return xstrfmt("gui/%d", getuid());
+}
+
+static int launchctl_boot_plist(int enable, const char *filename, const char *cmd)
+{
+ int result;
+ struct child_process child = CHILD_PROCESS_INIT;
+ char *uid = launchctl_get_uid();
+
+ strvec_split(&child.args, cmd);
+ if (enable)
+ strvec_push(&child.args, "bootstrap");
+ else
+ strvec_push(&child.args, "bootout");
+ strvec_push(&child.args, uid);
+ strvec_push(&child.args, filename);
+
+ child.no_stderr = 1;
+ child.no_stdout = 1;
+
+ if (start_command(&child))
+ die(_("failed to start launchctl"));
+
+ result = finish_command(&child);
+
+ free(uid);
+ return result;
+}
+
+static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd)
+{
+ const char *frequency = get_frequency(schedule);
+ char *name = launchctl_service_name(frequency);
+ char *filename = launchctl_service_filename(name);
+ int result = launchctl_boot_plist(0, filename, cmd);
+ unlink(filename);
+ free(filename);
+ free(name);
+ return result;
+}
+
+static int launchctl_remove_plists(const char *cmd)
+{
+ return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) ||
+ launchctl_remove_plist(SCHEDULE_DAILY, cmd) ||
+ launchctl_remove_plist(SCHEDULE_WEEKLY, cmd);
+}
+
+static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+{
+ FILE *plist;
+ int i;
+ const char *preamble, *repeat;
+ const char *frequency = get_frequency(schedule);
+ char *name = launchctl_service_name(frequency);
+ char *filename = launchctl_service_filename(name);
+
+ if (safe_create_leading_directories(filename))
+ die(_("failed to create directories for '%s'"), filename);
+ plist = xfopen(filename, "w");
+
+ preamble = "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+ "<plist version=\"1.0\">"
+ "<dict>\n"
+ "<key>Label</key><string>%s</string>\n"
+ "<key>ProgramArguments</key>\n"
+ "<array>\n"
+ "<string>%s/git</string>\n"
+ "<string>--exec-path=%s</string>\n"
+ "<string>for-each-repo</string>\n"
+ "<string>--config=maintenance.repo</string>\n"
+ "<string>maintenance</string>\n"
+ "<string>run</string>\n"
+ "<string>--schedule=%s</string>\n"
+ "</array>\n"
+ "<key>StartCalendarInterval</key>\n"
+ "<array>\n";
+ fprintf(plist, preamble, name, exec_path, exec_path, frequency);
+
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ repeat = "<dict>\n"
+ "<key>Hour</key><integer>%d</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n";
+ for (i = 1; i <= 23; i++)
+ fprintf(plist, repeat, i);
+ break;
+
+ case SCHEDULE_DAILY:
+ repeat = "<dict>\n"
+ "<key>Day</key><integer>%d</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n";
+ for (i = 1; i <= 6; i++)
+ fprintf(plist, repeat, i);
+ break;
+
+ case SCHEDULE_WEEKLY:
+ fprintf(plist,
+ "<dict>\n"
+ "<key>Day</key><integer>0</integer>\n"
+ "<key>Hour</key><integer>0</integer>\n"
+ "<key>Minute</key><integer>0</integer>\n"
+ "</dict>\n");
+ break;
+
+ default:
+ /* unreachable */
+ break;
+ }
+ fprintf(plist, "</array>\n</dict>\n</plist>\n");
+ fclose(plist);
+
+ /* bootout might fail if not already running, so ignore */
+ launchctl_boot_plist(0, filename, cmd);
+ if (launchctl_boot_plist(1, filename, cmd))
+ die(_("failed to bootstrap service %s"), filename);
+
+ free(filename);
+ free(name);
+ return 0;
+}
+
+static int launchctl_add_plists(const char *cmd)
+{
+ const char *exec_path = git_exec_path();
+
+ return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) ||
+ launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd);
+}
+
+static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd)
+{
+ if (run_maintenance)
+ return launchctl_add_plists(cmd);
+ else
+ return launchctl_remove_plists(cmd);
+}
+
+static char *schtasks_task_name(const char *frequency)
+{
+ struct strbuf label = STRBUF_INIT;
+ strbuf_addf(&label, "Git Maintenance (%s)", frequency);
+ return strbuf_detach(&label, NULL);
+}
+
+static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd)
+{
+ int result;
+ struct strvec args = STRVEC_INIT;
+ const char *frequency = get_frequency(schedule);
+ char *name = schtasks_task_name(frequency);
+
+ strvec_split(&args, cmd);
+ strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL);
+
+ result = run_command_v_opt(args.v, 0);
+
+ strvec_clear(&args);
+ free(name);
+ return result;
+}
+
+static int schtasks_remove_tasks(const char *cmd)
+{
+ return schtasks_remove_task(SCHEDULE_HOURLY, cmd) ||
+ schtasks_remove_task(SCHEDULE_DAILY, cmd) ||
+ schtasks_remove_task(SCHEDULE_WEEKLY, cmd);
+}
+
+static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd)
+{
+ int result;
+ struct child_process child = CHILD_PROCESS_INIT;
+ const char *xml;
+ struct tempfile *tfile;
+ const char *frequency = get_frequency(schedule);
+ char *name = schtasks_task_name(frequency);
+ struct strbuf tfilename = STRBUF_INIT;
+
+ strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
+ get_git_common_dir(), frequency);
+ tfile = xmks_tempfile(tfilename.buf);
+ strbuf_release(&tfilename);
+
+ if (!fdopen_tempfile(tfile, "w"))
+ die(_("failed to create temp xml file"));
+
+ xml = "<?xml version=\"1.0\" ?>\n"
+ "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
+ "<Triggers>\n"
+ "<CalendarTrigger>\n";
+ fputs(xml, tfile->fp);
+
+ switch (schedule) {
+ case SCHEDULE_HOURLY:
+ fprintf(tfile->fp,
+ "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n"
+ "<Enabled>true</Enabled>\n"
+ "<ScheduleByDay>\n"
+ "<DaysInterval>1</DaysInterval>\n"
+ "</ScheduleByDay>\n"
+ "<Repetition>\n"
+ "<Interval>PT1H</Interval>\n"
+ "<Duration>PT23H</Duration>\n"
+ "<StopAtDurationEnd>false</StopAtDurationEnd>\n"
+ "</Repetition>\n");
+ break;
+
+ case SCHEDULE_DAILY:
+ fprintf(tfile->fp,
+ "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n"
+ "<Enabled>true</Enabled>\n"
+ "<ScheduleByWeek>\n"
+ "<DaysOfWeek>\n"
+ "<Monday />\n"
+ "<Tuesday />\n"
+ "<Wednesday />\n"
+ "<Thursday />\n"
+ "<Friday />\n"
+ "<Saturday />\n"
+ "</DaysOfWeek>\n"
+ "<WeeksInterval>1</WeeksInterval>\n"
+ "</ScheduleByWeek>\n");
+ break;
+
+ case SCHEDULE_WEEKLY:
+ fprintf(tfile->fp,
+ "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n"
+ "<Enabled>true</Enabled>\n"
+ "<ScheduleByWeek>\n"
+ "<DaysOfWeek>\n"
+ "<Sunday />\n"
+ "</DaysOfWeek>\n"
+ "<WeeksInterval>1</WeeksInterval>\n"
+ "</ScheduleByWeek>\n");
+ break;
+
+ default:
+ break;
+ }
+
+ xml = "</CalendarTrigger>\n"
+ "</Triggers>\n"
+ "<Principals>\n"
+ "<Principal id=\"Author\">\n"
+ "<LogonType>InteractiveToken</LogonType>\n"
+ "<RunLevel>LeastPrivilege</RunLevel>\n"
+ "</Principal>\n"
+ "</Principals>\n"
+ "<Settings>\n"
+ "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n"
+ "<Enabled>true</Enabled>\n"
+ "<Hidden>true</Hidden>\n"
+ "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n"
+ "<WakeToRun>false</WakeToRun>\n"
+ "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n"
+ "<Priority>7</Priority>\n"
+ "</Settings>\n"
+ "<Actions Context=\"Author\">\n"
+ "<Exec>\n"
+ "<Command>\"%s\\git.exe\"</Command>\n"
+ "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
+ "</Exec>\n"
+ "</Actions>\n"
+ "</Task>\n";
+ fprintf(tfile->fp, xml, exec_path, exec_path, frequency);
+ strvec_split(&child.args, cmd);
+ strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml",
+ get_tempfile_path(tfile), NULL);
+ close_tempfile_gently(tfile);
+
+ child.no_stdout = 1;
+ child.no_stderr = 1;
+
+ if (start_command(&child))
+ die(_("failed to start schtasks"));
+ result = finish_command(&child);
+
+ delete_tempfile(&tfile);
+ free(name);
+ return result;
+}
+
+static int schtasks_schedule_tasks(const char *cmd)
+{
+ const char *exec_path = git_exec_path();
+
+ return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) ||
+ schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd);
+}
+
+static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd)
+{
+ if (run_maintenance)
+ return schtasks_schedule_tasks(cmd);
+ else
+ return schtasks_remove_tasks(cmd);
+}
+
#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE"
#define END_LINE "# END GIT MAINTENANCE SCHEDULE"
-static int update_background_schedule(int run_maintenance)
+static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd)
{
int result = 0;
int in_old_region = 0;
struct child_process crontab_list = CHILD_PROCESS_INIT;
struct child_process crontab_edit = CHILD_PROCESS_INIT;
FILE *cron_list, *cron_in;
- const char *crontab_name;
struct strbuf line = STRBUF_INIT;
- struct lock_file lk;
- char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
- if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0)
- return error(_("another process is scheduling background maintenance"));
-
- crontab_name = getenv("GIT_TEST_CRONTAB");
- if (!crontab_name)
- crontab_name = "crontab";
-
- strvec_split(&crontab_list.args, crontab_name);
+ strvec_split(&crontab_list.args, cmd);
strvec_push(&crontab_list.args, "-l");
crontab_list.in = -1;
- crontab_list.out = dup(lk.tempfile->fd);
+ crontab_list.out = dup(fd);
crontab_list.git_cmd = 0;
- if (start_command(&crontab_list)) {
- result = error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
- goto cleanup;
- }
+ if (start_command(&crontab_list))
+ return error(_("failed to run 'crontab -l'; your system might not support 'cron'"));
/* Ignore exit code, as an empty crontab will return error. */
finish_command(&crontab_list);
@@ -1533,17 +1863,15 @@ static int update_background_schedule(int run_maintenance)
* Read from the .lock file, filtering out the old
* schedule while appending the new schedule.
*/
- cron_list = fdopen(lk.tempfile->fd, "r");
+ cron_list = fdopen(fd, "r");
rewind(cron_list);
- strvec_split(&crontab_edit.args, crontab_name);
+ strvec_split(&crontab_edit.args, cmd);
crontab_edit.in = -1;
crontab_edit.git_cmd = 0;
- if (start_command(&crontab_edit)) {
- result = error(_("failed to run 'crontab'; your system might not support 'cron'"));
- goto cleanup;
- }
+ if (start_command(&crontab_edit))
+ return error(_("failed to run 'crontab'; your system might not support 'cron'"));
cron_in = fdopen(crontab_edit.in, "w");
if (!cron_in) {
@@ -1587,14 +1915,54 @@ static int update_background_schedule(int run_maintenance)
close(crontab_edit.in);
done_editing:
- if (finish_command(&crontab_edit)) {
+ if (finish_command(&crontab_edit))
result = error(_("'crontab' died"));
- goto cleanup;
+ else
+ fclose(cron_list);
+ return result;
+}
+
+#if defined(__APPLE__)
+static const char platform_scheduler[] = "launchctl";
+#elif defined(GIT_WINDOWS_NATIVE)
+static const char platform_scheduler[] = "schtasks";
+#else
+static const char platform_scheduler[] = "crontab";
+#endif
+
+static int update_background_schedule(int enable)
+{
+ int result;
+ const char *scheduler = platform_scheduler;
+ const char *cmd = scheduler;
+ char *testing;
+ struct lock_file lk;
+ char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path);
+
+ testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER"));
+ if (testing) {
+ char *sep = strchr(testing, ':');
+ if (!sep)
+ die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing);
+ *sep = '\0';
+ scheduler = testing;
+ cmd = sep + 1;
}
- fclose(cron_list);
-cleanup:
+ if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0)
+ return error(_("another process is scheduling background maintenance"));
+
+ if (!strcmp(scheduler, "launchctl"))
+ result = launchctl_update_schedule(enable, lk.tempfile->fd, cmd);
+ else if (!strcmp(scheduler, "schtasks"))
+ result = schtasks_update_schedule(enable, lk.tempfile->fd, cmd);
+ else if (!strcmp(scheduler, "crontab"))
+ result = crontab_update_schedule(enable, lk.tempfile->fd, cmd);
+ else
+ die("unknown background scheduler: %s", scheduler);
+
rollback_lock_file(&lk);
+ free(testing);
return result;
}
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 4b8d86e0ad..557bd2f348 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1641,7 +1641,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
/*
* Get rid of the idx file as we do not need it anymore.
* NEEDSWORK: extract this bit from free_pack_by_name() in
- * sha1-file.c, perhaps? It shouldn't matter very much as we
+ * object-file.c, perhaps? It shouldn't matter very much as we
* know we haven't installed this pack (hence we never have
* read anything from it).
*/
diff --git a/builtin/log.c b/builtin/log.c
index bd6ff4f9f9..f23ccdbec3 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -177,7 +177,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
const struct option builtin_log_options[] = {
OPT__QUIET(&quiet, N_("suppress diff output")),
OPT_BOOL(0, "source", &source, N_("show source")),
- OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
+ OPT_BOOL(0, "use-mailmap", &mailmap, N_("use mail map file")),
OPT_ALIAS(0, "mailmap", "use-mailmap"),
OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include,
N_("pattern"), N_("only decorate refs that match <pattern>")),
@@ -186,7 +186,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback),
OPT_CALLBACK('L', NULL, &line_cb, "range:file",
- N_("Trace the evolution of line range <start>,<end> or function :<funcname> in <file>"),
+ N_("trace the evolution of line range <start>,<end> or function :<funcname> in <file>"),
log_line_range_callback),
OPT_END()
};
@@ -1757,13 +1757,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max,
N_("max length of output filename")),
OPT_CALLBACK_F(0, "rfc", &rev, NULL,
- N_("Use [RFC PATCH] instead of [PATCH]"),
+ N_("use [RFC PATCH] instead of [PATCH]"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback),
OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
N_("cover-from-description-mode"),
N_("generate parts of a cover letter based on a branch's description")),
OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"),
- N_("Use [<prefix>] instead of [PATCH]"),
+ N_("use [<prefix>] instead of [PATCH]"),
PARSE_OPT_NONEG, subject_prefix_callback),
OPT_CALLBACK_F('o', "output-directory", &output_directory,
N_("dir"), N_("store resulting files in <dir>"),
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 725dd04519..3fe71a8c01 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -7,7 +7,7 @@
#include "refs.h"
#include "parse-options.h"
#include "prio-queue.h"
-#include "sha1-lookup.h"
+#include "hash-lookup.h"
#include "commit-slab.h"
/*
@@ -408,7 +408,7 @@ static const char *get_exact_ref_match(const struct object *o)
tip_table.sorted = 1;
}
- found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr,
+ found = hash_pos(o->oid.hash, tip_table.table, tip_table.nr,
nth_tip_table_ent);
if (0 <= found)
return tip_table.table[found].refname;
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 5617c01b5a..2a00358f34 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -1104,7 +1104,6 @@ static void write_pack_file(void)
stop_progress(&progress_state);
bitmap_writer_show_progress(progress);
- bitmap_writer_reuse_bitmaps(&to_pack);
bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
bitmap_writer_build(&to_pack);
bitmap_writer_finish(written_list, nr_written,
diff --git a/builtin/pull.c b/builtin/pull.c
index aa56ebcdd0..e8927fc2ff 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -324,7 +324,7 @@ static const char *config_get_ff(void)
* looks for the value of "pull.rebase". If both configuration keys do not
* exist, returns REBASE_FALSE.
*/
-static enum rebase_type config_get_rebase(void)
+static enum rebase_type config_get_rebase(int *rebase_unspecified)
{
struct branch *curr_branch = branch_get("HEAD");
const char *value;
@@ -344,20 +344,7 @@ static enum rebase_type config_get_rebase(void)
if (!git_config_get_value("pull.rebase", &value))
return parse_config_rebase("pull.rebase", value, 1);
- if (opt_verbosity >= 0 && !opt_ff) {
- advise(_("Pulling without specifying how to reconcile divergent branches is\n"
- "discouraged. You can squelch this message by running one of the following\n"
- "commands sometime before your next pull:\n"
- "\n"
- " git config pull.rebase false # merge (the default strategy)\n"
- " git config pull.rebase true # rebase\n"
- " git config pull.ff only # fast-forward only\n"
- "\n"
- "You can replace \"git config\" with \"git config --global\" to set a default\n"
- "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
- "or --ff-only on the command line to override the configured default per\n"
- "invocation.\n"));
- }
+ *rebase_unspecified = 1;
return REBASE_FALSE;
}
@@ -924,6 +911,36 @@ static int run_rebase(const struct object_id *newbase,
return ret;
}
+static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)
+{
+ int ret;
+ struct commit_list *list = NULL;
+ struct commit *merge_head, *head;
+
+ head = lookup_commit_reference(the_repository, orig_head);
+ commit_list_insert(head, &list);
+ merge_head = lookup_commit_reference(the_repository, orig_merge_head);
+ ret = repo_is_descendant_of(the_repository, merge_head, list);
+ free_commit_list(list);
+ return ret;
+}
+
+static void show_advice_pull_non_ff(void)
+{
+ advise(_("Pulling without specifying how to reconcile divergent branches is\n"
+ "discouraged. You can squelch this message by running one of the following\n"
+ "commands sometime before your next pull:\n"
+ "\n"
+ " git config pull.rebase false # merge (the default strategy)\n"
+ " git config pull.rebase true # rebase\n"
+ " git config pull.ff only # fast-forward only\n"
+ "\n"
+ "You can replace \"git config\" with \"git config --global\" to set a default\n"
+ "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
+ "or --ff-only on the command line to override the configured default per\n"
+ "invocation.\n"));
+}
+
int cmd_pull(int argc, const char **argv, const char *prefix)
{
const char *repo, **refspecs;
@@ -931,6 +948,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
struct object_id orig_head, curr_head;
struct object_id rebase_fork_point;
int autostash;
+ int rebase_unspecified = 0;
+ int can_ff;
if (!getenv("GIT_REFLOG_ACTION"))
set_reflog_message(argc, argv);
@@ -952,7 +971,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
opt_ff = xstrdup_or_null(config_get_ff());
if (opt_rebase < 0)
- opt_rebase = config_get_rebase();
+ opt_rebase = config_get_rebase(&rebase_unspecified);
if (read_cache_unmerged())
die_resolve_conflict("pull");
@@ -1026,6 +1045,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
if (opt_rebase && merge_heads.nr > 1)
die(_("Cannot rebase onto multiple branches."));
+ can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
+
+ if (rebase_unspecified && !opt_ff && !can_ff) {
+ if (opt_verbosity >= 0)
+ show_advice_pull_non_ff();
+ }
+
if (opt_rebase) {
int ret = 0;
int ran_ff = 0;
@@ -1040,22 +1066,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
submodule_touches_in_range(the_repository, &upstream, &curr_head))
die(_("cannot rebase with locally recorded submodule modifications"));
if (!autostash) {
- struct commit_list *list = NULL;
- struct commit *merge_head, *head;
-
- head = lookup_commit_reference(the_repository,
- &orig_head);
- commit_list_insert(head, &list);
- merge_head = lookup_commit_reference(the_repository,
- &merge_heads.oid[0]);
- if (repo_is_descendant_of(the_repository,
- merge_head, list)) {
+ if (can_ff) {
/* we can fast-forward this without invoking rebase */
opt_ff = "--ff-only";
ran_ff = 1;
ret = run_merge();
}
- free_commit_list(list);
}
if (!ran_ff)
ret = run_rebase(&newbase, &upstream);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 19c7b377aa..840dbd7eb7 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1917,7 +1917,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die_if_checked_out(buf.buf, 1);
options.head_name = xstrdup(buf.buf);
/* If not is it a valid ref (branch or commit)? */
- } else if (!get_oid(branch_name, &options.orig_head))
+ } else if (!get_oid(branch_name, &options.orig_head) &&
+ lookup_commit_reference(the_repository,
+ &options.orig_head))
options.head_name = NULL;
else
die(_("fatal: no such branch/commit '%s'"),
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 69ba7326cf..85bad9052e 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -583,6 +583,75 @@ static void handle_ref_opt(const char *pattern, const char *prefix)
clear_ref_exclusion(&ref_excludes);
}
+enum format_type {
+ /* We would like a relative path. */
+ FORMAT_RELATIVE,
+ /* We would like a canonical absolute path. */
+ FORMAT_CANONICAL,
+ /* We would like the default behavior. */
+ FORMAT_DEFAULT,
+};
+
+enum default_type {
+ /* Our default is a relative path. */
+ DEFAULT_RELATIVE,
+ /* Our default is a relative path if there's a shared root. */
+ DEFAULT_RELATIVE_IF_SHARED,
+ /* Our default is a canonical absolute path. */
+ DEFAULT_CANONICAL,
+ /* Our default is not to modify the item. */
+ DEFAULT_UNMODIFIED,
+};
+
+static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+{
+ char *cwd = NULL;
+ /*
+ * We don't ever produce a relative path if prefix is NULL, so set the
+ * prefix to the current directory so that we can produce a relative
+ * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then
+ * we want an absolute path unless the two share a common prefix, so don't
+ * set it in that case, since doing so causes a relative path to always
+ * be produced if possible.
+ */
+ if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
+ prefix = cwd = xgetcwd();
+ if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
+ puts(path);
+ } else if (format == FORMAT_RELATIVE ||
+ (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
+ /*
+ * In order for relative_path to work as expected, we need to
+ * make sure that both paths are absolute paths. If we don't,
+ * we can end up with an unexpected absolute path that the user
+ * didn't want.
+ */
+ struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
+ if (!is_absolute_path(path)) {
+ strbuf_realpath_forgiving(&realbuf, path, 1);
+ path = realbuf.buf;
+ }
+ if (!is_absolute_path(prefix)) {
+ strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
+ prefix = prefixbuf.buf;
+ }
+ puts(relative_path(path, prefix, &buf));
+ strbuf_release(&buf);
+ strbuf_release(&realbuf);
+ strbuf_release(&prefixbuf);
+ } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
+ struct strbuf buf = STRBUF_INIT;
+ puts(relative_path(path, prefix, &buf));
+ strbuf_release(&buf);
+ } else {
+ struct strbuf buf = STRBUF_INIT;
+ strbuf_realpath_forgiving(&buf, path, 1);
+ puts(buf.buf);
+ strbuf_release(&buf);
+ }
+ free(cwd);
+}
+
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
@@ -596,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
struct strbuf buf = STRBUF_INIT;
const int hexsz = the_hash_algo->hexsz;
int seen_end_of_options = 0;
+ enum format_type format = FORMAT_DEFAULT;
if (argc > 1 && !strcmp("--parseopt", argv[1]))
return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -668,8 +738,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!argv[i + 1])
die("--git-path requires an argument");
strbuf_reset(&buf);
- puts(relative_path(git_path("%s", argv[i + 1]),
- prefix, &buf));
+ print_path(git_path("%s", argv[i + 1]), prefix,
+ format,
+ DEFAULT_RELATIVE_IF_SHARED);
i++;
continue;
}
@@ -687,6 +758,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
show(arg);
continue;
}
+ if (opt_with_value(arg, "--path-format", &arg)) {
+ if (!strcmp(arg, "absolute")) {
+ format = FORMAT_CANONICAL;
+ } else if (!strcmp(arg, "relative")) {
+ format = FORMAT_RELATIVE;
+ } else {
+ die("unknown argument to --path-format: %s", arg);
+ }
+ continue;
+ }
if (!strcmp(arg, "--default")) {
def = argv[++i];
if (!def)
@@ -807,7 +888,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--show-toplevel")) {
const char *work_tree = get_git_work_tree();
if (work_tree)
- puts(work_tree);
+ print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
else
die("this operation must be run in a work tree");
continue;
@@ -815,7 +896,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--show-superproject-working-tree")) {
struct strbuf superproject = STRBUF_INIT;
if (get_superproject_working_tree(&superproject))
- puts(superproject.buf);
+ print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);
strbuf_release(&superproject);
continue;
}
@@ -850,16 +931,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
char *cwd;
int len;
+ enum format_type wanted = format;
if (arg[2] == 'g') { /* --git-dir */
if (gitdir) {
- puts(gitdir);
+ print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
continue;
}
if (!prefix) {
- puts(".git");
+ print_path(".git", prefix, format, DEFAULT_UNMODIFIED);
continue;
}
} else { /* --absolute-git-dir */
+ wanted = FORMAT_CANONICAL;
if (!gitdir && !prefix)
gitdir = ".git";
if (gitdir) {
@@ -872,14 +955,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
}
cwd = xgetcwd();
len = strlen(cwd);
- printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : "");
free(cwd);
+ print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);
continue;
}
if (!strcmp(arg, "--git-common-dir")) {
- strbuf_reset(&buf);
- puts(relative_path(get_git_common_dir(),
- prefix, &buf));
+ print_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED);
continue;
}
if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -909,8 +992,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
if (the_index.split_index) {
const struct object_id *oid = &the_index.split_index->base_oid;
const char *path = git_path("sharedindex.%s", oid_to_hex(oid));
- strbuf_reset(&buf);
- puts(relative_path(path, prefix, &buf));
+ print_path(path, prefix, format, DEFAULT_RELATIVE);
}
continue;
}
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index c52e4ccd19..1c0b3a9b05 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -360,19 +360,19 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
const struct option options[] = {
OPT_BIT('c', "committer", &log.groups,
- N_("Group by committer rather than author"),
+ N_("group by committer rather than author"),
SHORTLOG_GROUP_COMMITTER),
OPT_BOOL('n', "numbered", &log.sort_by_number,
N_("sort output according to the number of commits per author")),
OPT_BOOL('s', "summary", &log.summary,
- N_("Suppress commit descriptions, only provides commit count")),
+ N_("suppress commit descriptions, only provides commit count")),
OPT_BOOL('e', "email", &log.email,
- N_("Show the email address of each author")),
+ N_("show the email address of each author")),
OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"),
- N_("Linewrap output"), PARSE_OPT_OPTARG,
+ N_("linewrap output"), PARSE_OPT_OPTARG,
&parse_wrap_args),
OPT_CALLBACK(0, "group", &log, N_("field"),
- N_("Group by field"), parse_group_option),
+ N_("group by field"), parse_group_option),
OPT_END(),
};
diff --git a/builtin/stash.c b/builtin/stash.c
index e1f8235fdd..9bc85f91cd 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -325,35 +325,6 @@ static void add_diff_to_buf(struct diff_queue_struct *q,
}
}
-static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
- const char *c_tree_hex = oid_to_hex(c_tree);
-
- /*
- * diff-index is very similar to diff-tree above, and should be
- * converted together with update_index.
- */
- cp.git_cmd = 1;
- strvec_pushl(&cp.args, "diff-index", "--cached", "--name-only",
- "--diff-filter=A", NULL);
- strvec_push(&cp.args, c_tree_hex);
- return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
-}
-
-static int update_index(struct strbuf *out)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
-
- /*
- * Update-index is very complicated and may need to have a public
- * function exposed in order to remove this forking.
- */
- cp.git_cmd = 1;
- strvec_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
- return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
-}
-
static int restore_untracked(struct object_id *u_tree)
{
int res;
@@ -385,6 +356,121 @@ static int restore_untracked(struct object_id *u_tree)
return res;
}
+static void unstage_changes_unless_new(struct object_id *orig_tree)
+{
+ /*
+ * When we enter this function, there has been a clean merge of
+ * relevant trees, and the merge logic always stages whatever merges
+ * cleanly. We want to unstage those changes, unless it corresponds
+ * to a file that didn't exist as of orig_tree.
+ *
+ * However, if any SKIP_WORKTREE path is modified relative to
+ * orig_tree, then we want to clear the SKIP_WORKTREE bit and write
+ * it to the worktree before unstaging.
+ */
+
+ struct checkout state = CHECKOUT_INIT;
+ struct diff_options diff_opts;
+ struct lock_file lock = LOCK_INIT;
+ int i;
+
+ /* If any entries have skip_worktree set, we'll have to check 'em out */
+ state.force = 1;
+ state.quiet = 1;
+ state.refresh_cache = 1;
+ state.istate = &the_index;
+
+ /*
+ * Step 1: get a difference between orig_tree (which corresponding
+ * to the index before a merge was run) and the current index
+ * (reflecting the changes brought in by the merge).
+ */
+ diff_setup(&diff_opts);
+ diff_opts.flags.recursive = 1;
+ diff_opts.detect_rename = 0;
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_setup_done(&diff_opts);
+
+ do_diff_cache(orig_tree, &diff_opts);
+ diffcore_std(&diff_opts);
+
+ /* Iterate over the paths that changed due to the merge... */
+ for (i = 0; i < diff_queued_diff.nr; i++) {
+ struct diff_filepair *p;
+ struct cache_entry *ce;
+ int pos;
+
+ /* Look up the path's position in the current index. */
+ p = diff_queued_diff.queue[i];
+ pos = index_name_pos(&the_index, p->two->path,
+ strlen(p->two->path));
+
+ /*
+ * Step 2: Place changes in the working tree
+ *
+ * Stash is about restoring changes *to the working tree*.
+ * So if the merge successfully got a new version of some
+ * path, but left it out of the working tree, then clear the
+ * SKIP_WORKTREE bit and write it to the working tree.
+ */
+ if (pos >= 0 && ce_skip_worktree(active_cache[pos])) {
+ struct stat st;
+
+ ce = active_cache[pos];
+ if (!lstat(ce->name, &st)) {
+ /* Conflicting path present; relocate it */
+ struct strbuf new_path = STRBUF_INIT;
+ int fd;
+
+ strbuf_addf(&new_path,
+ "%s.stash.XXXXXX", ce->name);
+ fd = xmkstemp(new_path.buf);
+ close(fd);
+ printf(_("WARNING: Untracked file in way of "
+ "tracked file! Renaming\n "
+ " %s -> %s\n"
+ " to make room.\n"),
+ ce->name, new_path.buf);
+ if (rename(ce->name, new_path.buf))
+ die("Failed to move %s to %s\n",
+ ce->name, new_path.buf);
+ strbuf_release(&new_path);
+ }
+ checkout_entry(ce, &state, NULL, NULL);
+ ce->ce_flags &= ~CE_SKIP_WORKTREE;
+ }
+
+ /*
+ * Step 3: "unstage" changes, as long as they are still tracked
+ */
+ if (p->one->oid_valid) {
+ /*
+ * Path existed in orig_tree; restore index entry
+ * from that tree in order to "unstage" the changes.
+ */
+ int option = ADD_CACHE_OK_TO_REPLACE;
+ if (pos < 0)
+ option = ADD_CACHE_OK_TO_ADD;
+
+ ce = make_cache_entry(&the_index,
+ p->one->mode,
+ &p->one->oid,
+ p->one->path,
+ 0, 0);
+ add_index_entry(&the_index, ce, option);
+ }
+ }
+ diff_flush(&diff_opts);
+
+ /*
+ * Step 4: write the new index to disk
+ */
+ repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR);
+ if (write_locked_index(&the_index, &lock,
+ COMMIT_LOCK | SKIP_IF_UNCHANGED))
+ die(_("Unable to write index."));
+}
+
static int do_apply_stash(const char *prefix, struct stash_info *info,
int index, int quiet)
{
@@ -467,26 +553,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
if (reset_tree(&index_tree, 0, 0))
return -1;
} else {
- struct strbuf out = STRBUF_INIT;
-
- if (get_newly_staged(&out, &c_tree)) {
- strbuf_release(&out);
- return -1;
- }
-
- if (reset_tree(&c_tree, 0, 1)) {
- strbuf_release(&out);
- return -1;
- }
-
- ret = update_index(&out);
- strbuf_release(&out);
- if (ret)
- return -1;
-
- /* read back the result of update_index() back from the disk */
- discard_cache();
- read_cache();
+ unstage_changes_unless_new(&c_tree);
}
if (!quiet) {
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c30896c897..c2bd882d17 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -562,9 +562,9 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
struct module_list list = MODULE_LIST_INIT;
struct option module_foreach_options[] = {
- OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")),
+ OPT__QUIET(&info.quiet, N_("suppress output of entering each submodule command")),
OPT_BOOL(0, "recursive", &info.recursive,
- N_("Recurse into nested submodules")),
+ N_("recurse into nested submodules")),
OPT_END()
};
@@ -706,7 +706,7 @@ static int module_init(int argc, const char **argv, const char *prefix)
int quiet = 0;
struct option module_init_options[] = {
- OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")),
+ OPT__QUIET(&quiet, N_("suppress output for initializing a submodule")),
OPT_END()
};
@@ -883,8 +883,8 @@ static int module_status(int argc, const char **argv, const char *prefix)
int quiet = 0;
struct option module_status_options[] = {
- OPT__QUIET(&quiet, N_("Suppress submodule status output")),
- OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED),
+ OPT__QUIET(&quiet, N_("suppress submodule status output")),
+ OPT_BIT(0, "cached", &info.flags, N_("use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED),
OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE),
OPT_END()
};
@@ -1482,9 +1482,9 @@ static int module_sync(int argc, const char **argv, const char *prefix)
int recursive = 0;
struct option module_sync_options[] = {
- OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")),
+ OPT__QUIET(&quiet, N_("suppress output of synchronizing submodule url")),
OPT_BOOL(0, "recursive", &recursive,
- N_("Recurse into nested submodules")),
+ N_("recurse into nested submodules")),
OPT_END()
};
@@ -1620,9 +1620,9 @@ static int module_deinit(int argc, const char **argv, const char *prefix)
int all = 0;
struct option module_deinit_options[] = {
- OPT__QUIET(&quiet, N_("Suppress submodule status output")),
- OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes"), 0),
- OPT_BOOL(0, "all", &all, N_("Unregister all submodules")),
+ OPT__QUIET(&quiet, N_("suppress submodule status output")),
+ OPT__FORCE(&force, N_("remove submodule working trees even if they contain local changes"), 0),
+ OPT_BOOL(0, "all", &all, N_("unregister all submodules")),
OPT_END()
};
@@ -2337,7 +2337,7 @@ static int update_clone(int argc, const char **argv, const char *prefix)
OPT_BOOL(0, "dissociate", &suc.dissociate,
N_("use --reference only while cloning")),
OPT_STRING(0, "depth", &suc.depth, "<depth>",
- N_("Create a shallow clone truncated to the "
+ N_("create a shallow clone truncated to the "
"specified number of revisions")),
OPT_INTEGER('j', "jobs", &suc.max_jobs,
N_("parallel jobs")),
@@ -2678,7 +2678,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix)
char *config_name;
struct option options[] = {
- OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")),
+ OPT__QUIET(&quiet, N_("suppress output for setting url of a submodule")),
OPT_END()
};
const char *const usage[] = {
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 197fd24a55..71287b2da6 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -1052,10 +1052,10 @@ static int repair(int ac, const char **av, const char *prefix)
int rc = 0;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
- repair_worktrees(report_repair, &rc);
p = ac > 0 ? av : self;
for (; *p; p++)
repair_worktree_at_path(*p, report_repair, &rc);
+ repair_worktrees(report_repair, &rc);
return rc;
}