diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 52251a3331..4dc6fea129 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -164,7 +164,7 @@ phases: displayName: Windows condition: succeeded() queue: - name: Hosted VS2017 + name: Hosted timeoutInMinutes: 240 steps: - powershell: | @@ -215,7 +215,7 @@ phases: . ci/lib.sh make -j10 DEVELOPER=1 NO_PERL=1 || exit 1 - NO_PERL=1 NO_SVN_TESTS=1 GIT_TEST_OPTS=\"--quiet --write-junit-xml\" time make -j15 -k DEVELOPER=1 test || { + NO_PERL=1 NO_SVN_TESTS=1 GIT_TEST_OPTS=\"--no-chain-lint --no-bin-wrappers --quiet --write-junit-xml\" time make -j15 -k DEVELOPER=1 test || { NO_PERL=1 NO_SVN_TESTS=1 GIT_TEST_OPTS=\"-i -v -x\" make -k -C t failed; exit 1 } diff --git a/builtin/rebase.c b/builtin/rebase.c index 0b47846443..eef6b3836a 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -22,6 +22,7 @@ #include "wt-status.h" #include "revision.h" #include "rerere.h" +#include "branch.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -54,7 +55,7 @@ static int use_builtin_rebase(void) cp.git_cmd = 1; if (capture_command(&cp, &out, 6)) { strbuf_release(&out); - return 0; + return 1; } strbuf_trim(&out); @@ -281,8 +282,10 @@ static int apply_autostash(struct rebase_options *opts) if (!file_exists(path)) return 0; - if (read_one(state_dir_path("autostash", opts), &autostash)) + if (read_one(path, &autostash)) return error(_("Could not read '%s'"), path); + /* Ensure that the hash is not mistaken for a number */ + strbuf_addstr(&autostash, "^0"); argv_array_pushl(&stash_apply.args, "stash", "apply", autostash.buf, NULL); stash_apply.git_cmd = 1; @@ -364,9 +367,124 @@ N_("Resolve all conflicts manually, mark them as resolved with\n" "To abort and get back to the state before \"git rebase\", run " "\"git rebase --abort\"."); +#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" + +#define RESET_HEAD_DETACH (1<<0) +#define RESET_HEAD_HARD (1<<1) + static int reset_head(struct object_id *oid, const char *action, - const char *switch_to_branch, int detach_head, - const char *reflog_orig_head, const char *reflog_head); + const char *switch_to_branch, unsigned flags, + const char *reflog_orig_head, const char *reflog_head) +{ + unsigned detach_head = flags & RESET_HEAD_DETACH; + unsigned reset_hard = flags & RESET_HEAD_HARD; + struct object_id head_oid; + struct tree_desc desc[2] = { { NULL }, { NULL } }; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + const char *reflog_action; + struct strbuf msg = STRBUF_INIT; + size_t prefix_len; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; + int ret = 0, nr = 0; + + if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) + BUG("Not a fully qualified branch: '%s'", switch_to_branch); + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { + ret = -1; + goto leave_reset_head; + } + + if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { + ret = error(_("could not determine HEAD revision")); + goto leave_reset_head; + } + + if (!oid) + oid = &head_oid; + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = the_repository->index; + unpack_tree_opts.dst_index = the_repository->index; + unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; + unpack_tree_opts.update = 1; + unpack_tree_opts.merge = 1; + if (!detach_head) + unpack_tree_opts.reset = 1; + + if (read_index_unmerged(the_repository->index) < 0) { + ret = error(_("could not read index")); + goto leave_reset_head; + } + + if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) { + ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); + goto leave_reset_head; + } + + if (!fill_tree_descriptor(&desc[nr++], oid)) { + ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); + goto leave_reset_head; + } + + if (unpack_trees(nr, desc, &unpack_tree_opts)) { + ret = -1; + goto leave_reset_head; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(the_repository->index, tree); + + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) { + ret = error(_("could not write index")); + goto leave_reset_head; + } + + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); + prefix_len = msg.len; + + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + if (!reflog_head) { + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + reflog_head = msg.buf; + } + if (!switch_to_branch) + ret = update_ref(reflog_head, "HEAD", oid, orig, + detach_head ? REF_NO_DEREF : 0, + UPDATE_REFS_MSG_ON_ERR); + else { + ret = create_symref("HEAD", switch_to_branch, msg.buf); + if (!ret) + ret = update_ref(reflog_head, "HEAD", oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + } + +leave_reset_head: + strbuf_release(&msg); + rollback_lock_file(&lock); + while (nr) + free((void *)desc[--nr].buffer); + return ret; +} + static int move_to_original_branch(struct rebase_options *opts) { @@ -697,112 +815,6 @@ finished_rebase: return status ? -1 : 0; } -#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" - -static int reset_head(struct object_id *oid, const char *action, - const char *switch_to_branch, int detach_head, - const char *reflog_orig_head, const char *reflog_head) -{ - struct object_id head_oid; - struct tree_desc desc; - struct lock_file lock = LOCK_INIT; - struct unpack_trees_options unpack_tree_opts; - struct tree *tree; - const char *reflog_action; - struct strbuf msg = STRBUF_INIT; - size_t prefix_len; - struct object_id *orig = NULL, oid_orig, - *old_orig = NULL, oid_old_orig; - int ret = 0; - - if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) - BUG("Not a fully qualified branch: '%s'", switch_to_branch); - - if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) - return -1; - - if (!oid) { - if (get_oid("HEAD", &head_oid)) { - rollback_lock_file(&lock); - return error(_("could not determine HEAD revision")); - } - oid = &head_oid; - } - - memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); - setup_unpack_trees_porcelain(&unpack_tree_opts, action); - unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = the_repository->index; - unpack_tree_opts.dst_index = the_repository->index; - unpack_tree_opts.fn = oneway_merge; - unpack_tree_opts.update = 1; - unpack_tree_opts.merge = 1; - if (!detach_head) - unpack_tree_opts.reset = 1; - - if (read_index_unmerged(the_repository->index) < 0) { - rollback_lock_file(&lock); - return error(_("could not read index")); - } - - if (!fill_tree_descriptor(&desc, oid)) { - error(_("failed to find tree of %s"), oid_to_hex(oid)); - rollback_lock_file(&lock); - free((void *)desc.buffer); - return -1; - } - - if (unpack_trees(1, &desc, &unpack_tree_opts)) { - rollback_lock_file(&lock); - free((void *)desc.buffer); - return -1; - } - - tree = parse_tree_indirect(oid); - prime_cache_tree(the_repository->index, tree); - - if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) - ret = error(_("could not write index")); - free((void *)desc.buffer); - - if (ret) - return ret; - - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); - prefix_len = msg.len; - - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, - UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - if (!reflog_head) { - strbuf_setlen(&msg, prefix_len); - strbuf_addstr(&msg, "updating HEAD"); - reflog_head = msg.buf; - } - if (!switch_to_branch) - ret = update_ref(reflog_head, "HEAD", oid, orig, REF_NO_DEREF, - UPDATE_REFS_MSG_ON_ERR); - else { - ret = create_symref("HEAD", switch_to_branch, msg.buf); - if (!ret) - ret = update_ref(reflog_head, "HEAD", oid, NULL, 0, - UPDATE_REFS_MSG_ON_ERR); - } - - strbuf_release(&msg); - return ret; -} - static int rebase_config(const char *var, const char *value, void *data) { struct rebase_options *opts = data; @@ -1177,8 +1189,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(&merge_rr); string_list_clear(&merge_rr, 1); - if (reset_head(NULL, "reset", NULL, 0, NULL, NULL) < 0) + if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD, + NULL, NULL) < 0) die(_("could not discard worktree changes")); + remove_branch_state(); if (read_basic_state(&options)) exit(1); goto run_rebase; @@ -1193,9 +1207,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); if (reset_head(&options.orig_head, "reset", - options.head_name, 0, NULL, NULL) < 0) + options.head_name, RESET_HEAD_HARD, + NULL, NULL) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); + remove_branch_state(); ret = finish_rebase(&options); goto cleanup; } @@ -1395,15 +1411,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * git-rebase.txt caveats with "unless you know what you are doing" */ if (options.rebase_merges) - die(_("error: cannot combine '--preserve_merges' with " + die(_("error: cannot combine '--preserve-merges' with " "'--rebase-merges'")); if (options.rebase_merges) { if (strategy_options.nr) - die(_("error: cannot combine '--rebase_merges' with " + die(_("error: cannot combine '--rebase-merges' with " "'--strategy-option'")); if (options.strategy) - die(_("error: cannot combine '--rebase_merges' with " + die(_("error: cannot combine '--rebase-merges' with " "'--strategy'")); } @@ -1528,7 +1544,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) update_index_if_able(&the_index, &lock_file); rollback_lock_file(&lock_file); - if (has_unstaged_changes(0) || has_uncommitted_changes(0)) { + if (has_unstaged_changes(1) || has_uncommitted_changes(1)) { const char *autostash = state_dir_path("autostash", &options); struct child_process stash = CHILD_PROCESS_INIT; @@ -1554,10 +1570,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (safe_create_leading_directories_const(autostash)) die(_("Could not create directory for '%s'"), options.state_dir); - write_file(autostash, "%s", buf.buf); + write_file(autostash, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); if (reset_head(&head->object.oid, "reset --hard", - NULL, 0, NULL, NULL) < 0) + NULL, RESET_HEAD_HARD, NULL, NULL) < 0) die(_("could not reset --hard")); printf(_("HEAD is now at %s"), find_unique_abbrev(&head->object.oid, @@ -1677,8 +1693,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "it...\n")); strbuf_addf(&msg, "rebase: checkout %s", options.onto_name); - if (reset_head(&options.onto->object.oid, "checkout", NULL, 1, - NULL, msg.buf)) + if (reset_head(&options.onto->object.oid, "checkout", NULL, + RESET_HEAD_DETACH, NULL, msg.buf)) die(_("Could not detach HEAD")); strbuf_release(&msg); diff --git a/builtin/stash.c b/builtin/stash.c index 965e938ebd..29d04c9f2f 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -15,6 +15,8 @@ #include "diffcore.h" #include "exec-cmd.h" +#define INCLUDE_ALL_FILES 2 + static const char * const git_stash_usage[] = { N_("git stash list []"), N_("git stash show [] []"), @@ -70,11 +72,6 @@ static const char * const git_stash_store_usage[] = { NULL }; -static const char * const git_stash_create_usage[] = { - N_("git stash create []"), - NULL -}; - static const char * const git_stash_push_usage[] = { N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" @@ -89,7 +86,6 @@ static const char * const git_stash_save_usage[] = { }; static const char *ref_stash = "refs/stash"; -static int quiet; static struct strbuf stash_index_path = STRBUF_INIT; /* @@ -127,27 +123,25 @@ static void assert_stash_like(struct stash_info *info, const char *revision) if (get_oidf(&info->b_commit, "%s^1", revision) || get_oidf(&info->w_tree, "%s:", revision) || get_oidf(&info->b_tree, "%s^1:", revision) || - get_oidf(&info->i_tree, "%s^2:", revision)) { - free_stash_info(info); - error(_("'%s' is not a stash-like commit"), revision); - exit(128); - } + get_oidf(&info->i_tree, "%s^2:", revision)) + die(_("'%s' is not a stash-like commit"), revision); } static int get_stash_info(struct stash_info *info, int argc, const char **argv) { - struct strbuf symbolic = STRBUF_INIT; int ret; - const char *revision; - const char *commit = NULL; char *end_of_rev; char *expanded_ref; + const char *revision; + const char *commit = NULL; struct object_id dummy; + struct strbuf symbolic = STRBUF_INIT; if (argc > 1) { int i; struct strbuf refs_msg = STRBUF_INIT; - for (i = 0; i < argc; ++i) + + for (i = 0; i < argc; i++) strbuf_addf(&refs_msg, " '%s'", argv[i]); fprintf_ln(stderr, _("Too many revisions specified:%s"), @@ -227,15 +221,16 @@ static int clear_stash(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION); if (argc) - return error(_("git stash clear with parameters is unimplemented")); + return error(_("git stash clear with parameters is " + "unimplemented")); return do_clear_stash(); } static int reset_tree(struct object_id *i_tree, int update, int reset) { - struct unpack_trees_options opts; int nr_trees = 1; + struct unpack_trees_options opts; struct tree_desc t[MAX_UNPACK_TREES]; struct tree *tree; struct lock_file lock_file = LOCK_INIT; @@ -320,10 +315,16 @@ static void add_diff_to_buf(struct diff_queue_struct *q, void *data) { int i; + for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - strbuf_addstr(data, p->one->path); - strbuf_addch(data, 0); + strbuf_addstr(data, q->queue[i]->one->path); + + /* + * The reason we add "0" at the end of this strbuf + * is because we will pass the output further to + * "git update-index -z ...". + */ + strbuf_addch(data, '\0'); } } @@ -358,8 +359,8 @@ static int update_index(struct strbuf *out) static int restore_untracked(struct object_id *u_tree) { - struct child_process cp = CHILD_PROCESS_INIT; int res; + struct child_process cp = CHILD_PROCESS_INIT; /* * We need to run restore files from a given index, but without @@ -388,52 +389,53 @@ static int restore_untracked(struct object_id *u_tree) } static int do_apply_stash(const char *prefix, struct stash_info *info, - int index) + int index, int quiet) { + int ret; + int has_index = index; struct merge_options o; struct object_id c_tree; struct object_id index_tree; - const struct object_id *bases[1]; struct commit *result; - int ret; - int has_index = index; + const struct object_id *bases[1]; read_cache_preload(NULL); if (refresh_cache(REFRESH_QUIET)) return -1; if (write_cache_as_tree(&c_tree, 0, NULL) || reset_tree(&c_tree, 0, 0)) - return error(_("Cannot apply a stash in the middle of a merge")); + return error(_("cannot apply a stash in the middle of a merge")); if (index) { - if (!oidcmp(&info->b_tree, &info->i_tree) || !oidcmp(&c_tree, - &info->i_tree)) { + if (!oidcmp(&info->b_tree, &info->i_tree) || + !oidcmp(&c_tree, &info->i_tree)) { has_index = 0; } else { struct strbuf out = STRBUF_INIT; if (diff_tree_binary(&out, &info->w_commit)) { strbuf_release(&out); - return error(_("Could not generate diff %s^!."), + return error(_("could not generate diff %s^!."), oid_to_hex(&info->w_commit)); } ret = apply_cached(&out); strbuf_release(&out); if (ret) - return error(_("Conflicts in index. Try without --index.")); + return error(_("conflicts in index." + "Try without --index.")); discard_cache(); read_cache(); if (write_cache_as_tree(&index_tree, 0, NULL)) - return error(_("Could not save index tree")); + return error(_("could not save index tree")); reset_head(); } } if (info->has_u && restore_untracked(&info->u_tree)) - return error(_("Could not restore untracked files from stash")); + return error(_("could not restore untracked files from stash")); init_merge_options(&o); @@ -508,13 +510,14 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, static int apply_stash(int argc, const char **argv, const char *prefix) { + int ret; + int quiet = 0; int index = 0; struct stash_info info; - int ret; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, - N_("attempt to recreate the index")), + N_("attempt to recreate the index")), OPT_END() }; @@ -524,16 +527,16 @@ static int apply_stash(int argc, const char **argv, const char *prefix) if (get_stash_info(&info, argc, argv)) return -1; - ret = do_apply_stash(prefix, &info, index); + ret = do_apply_stash(prefix, &info, index, quiet); free_stash_info(&info); return ret; } -static int do_drop_stash(const char *prefix, struct stash_info *info) +static int do_drop_stash(const char *prefix, struct stash_info *info, int quiet) { + int ret; struct child_process cp_reflog = CHILD_PROCESS_INIT; struct child_process cp = CHILD_PROCESS_INIT; - int ret; /* * reflog does not provide a simple function for deleting refs. One will @@ -583,8 +586,9 @@ static void assert_stash_ref(struct stash_info *info) static int drop_stash(int argc, const char **argv, const char *prefix) { - struct stash_info info; int ret; + int quiet = 0; + struct stash_info info; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_END() @@ -598,19 +602,21 @@ static int drop_stash(int argc, const char **argv, const char *prefix) assert_stash_ref(&info); - ret = do_drop_stash(prefix, &info); + ret = do_drop_stash(prefix, &info, quiet); free_stash_info(&info); return ret; } static int pop_stash(int argc, const char **argv, const char *prefix) { - int index = 0, ret; + int ret; + int index = 0; + int quiet = 0; struct stash_info info; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, - N_("attempt to recreate the index")), + N_("attempt to recreate the index")), OPT_END() }; @@ -621,10 +627,11 @@ static int pop_stash(int argc, const char **argv, const char *prefix) return -1; assert_stash_ref(&info); - if ((ret = do_apply_stash(prefix, &info, index))) - printf_ln(_("The stash entry is kept in case you need it again.")); + if ((ret = do_apply_stash(prefix, &info, index, quiet))) + printf_ln(_("The stash entry is kept in case " + "you need it again.")); else - ret = do_drop_stash(prefix, &info); + ret = do_drop_stash(prefix, &info, quiet); free_stash_info(&info); return ret; @@ -632,10 +639,10 @@ static int pop_stash(int argc, const char **argv, const char *prefix) static int branch_stash(int argc, const char **argv, const char *prefix) { - const char *branch = NULL; int ret; - struct child_process cp = CHILD_PROCESS_INIT; + const char *branch = NULL; struct stash_info info; + struct child_process cp = CHILD_PROCESS_INIT; struct option options[] = { OPT_END() }; @@ -643,8 +650,10 @@ static int branch_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_branch_usage, 0); - if (!argc) - return error(_("No branch name specified")); + if (!argc) { + fprintf_ln(stderr, _("No branch name specified")); + return -1; + } branch = argv[0]; @@ -657,9 +666,9 @@ static int branch_stash(int argc, const char **argv, const char *prefix) argv_array_push(&cp.args, oid_to_hex(&info.b_commit)); ret = run_command(&cp); if (!ret) - ret = do_apply_stash(prefix, &info, 1); + ret = do_apply_stash(prefix, &info, 1, 0); if (!ret && info.is_stash_ref) - ret = do_drop_stash(prefix, &info); + ret = do_drop_stash(prefix, &info, 0); free_stash_info(&info); @@ -721,7 +730,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) git_config(git_diff_ui_config, NULL); init_revisions(&rev, prefix); - for (i = 1; i < argc; ++i) { + for (i = 1; i < argc; i++) { if (argv[i][0] != '-') argv_array_push(&stash_args, argv[i]); else @@ -766,39 +775,36 @@ static int show_stash(int argc, const char **argv, const char *prefix) return diff_result_code(&rev.diffopt, 0); } -static int do_store_stash(const char *w_commit, const char *stash_msg, +static int do_store_stash(const struct object_id *w_commit, const char *stash_msg, int quiet) { - int ret = 0; - int need_to_free = 0; - struct object_id obj; + if (!stash_msg) + stash_msg = "Created via \"git stash store\"."; - if (!stash_msg) { - need_to_free = 1; - stash_msg = xstrdup("Created via \"git stash store\"."); + if (update_ref(stash_msg, ref_stash, w_commit, NULL, + REF_FORCE_CREATE_REFLOG, + quiet ? UPDATE_REFS_QUIET_ON_ERR : + UPDATE_REFS_MSG_ON_ERR)) { + if (!quiet) { + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, oid_to_hex(w_commit)); + } + return -1; } - ret = get_oid(w_commit, &obj); - if (!ret) { - ret = update_ref(stash_msg, ref_stash, &obj, NULL, - REF_FORCE_CREATE_REFLOG, - quiet ? UPDATE_REFS_QUIET_ON_ERR : - UPDATE_REFS_MSG_ON_ERR); - } - if (ret && !quiet) - fprintf_ln(stderr, _("Cannot update %s with %s"), - ref_stash, w_commit); - if (need_to_free) - free((char *) stash_msg); - return ret; + return 0; } static int store_stash(int argc, const char **argv, const char *prefix) { + int quiet = 0; const char *stash_msg = NULL; + struct object_id obj; + struct object_context dummy; struct option options[] = { - OPT__QUIET(&quiet, N_("be quiet, only report errors")), - OPT_STRING('m', "message", &stash_msg, "message", N_("stash message")), + OPT__QUIET(&quiet, N_("be quiet")), + OPT_STRING('m', "message", &stash_msg, "message", + N_("stash message")), OPT_END() }; @@ -807,11 +813,29 @@ static int store_stash(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_UNKNOWN); if (argc != 1) { - fprintf_ln(stderr, _("\"git stash store\" requires one argument")); + if (!quiet) + fprintf_ln(stderr, _("\"git stash store\" requires one " + " argument")); return -1; } - return do_store_stash(argv[0], stash_msg, quiet); + if (get_oid_with_context(argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, + &dummy)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, argv[0]); + return -1; + } + + return do_store_stash(&obj, stash_msg, quiet); +} + +static void add_pathspecs(struct argv_array *args, + struct pathspec ps) { + int i; + + for (i = 0; i < ps.nr; i++) + argv_array_push(args, ps.items[i].match); } /* @@ -821,17 +845,17 @@ static int store_stash(int argc, const char **argv, const char *prefix) * = 0 if there are not any untracked files * > 0 if there are untracked files */ -static struct strbuf untracked_files = STRBUF_INIT; - -static int get_untracked_files(struct pathspec ps, int include_untracked) +static int get_untracked_files(struct pathspec ps, int include_untracked, + struct strbuf *untracked_files) { - int max_len; int i; + int max_len; + int found = 0; char *seen; struct dir_struct dir; memset(&dir, 0, sizeof(dir)); - if (include_untracked != 2) + if (include_untracked != INCLUDE_ALL_FILES) setup_standard_excludes(&dir); seen = xcalloc(ps.nr, 1); @@ -839,19 +863,20 @@ static int get_untracked_files(struct pathspec ps, int include_untracked) max_len = fill_directory(&dir, the_repository->index, &ps); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - if (!dir_path_match(&the_index, ent, &ps, max_len, seen)) { - free(ent); - continue; + if (dir_path_match(&the_index, ent, &ps, max_len, seen)) { + found++; + strbuf_addstr(untracked_files, ent->name); + /* NUL-terminate: will be fed to update-index -z */ + strbuf_addch(untracked_files, 0); } - strbuf_addf(&untracked_files, "%s%c", ent->name, '\0'); free(ent); } + free(seen); free(dir.entries); free(dir.ignored); clear_directory(&dir); - free(seen); - return untracked_files.len; + return found; } /* @@ -868,6 +893,13 @@ static int check_changes_tracked_files(struct pathspec ps) struct rev_info rev; struct object_id dummy; + /* No initial commit. */ + if (get_oid("HEAD", &dummy)) + return -1; + + if (read_cache() < 0) + return -1; + init_revisions(&rev, NULL); rev.prune_data = ps; @@ -875,15 +907,9 @@ static int check_changes_tracked_files(struct pathspec ps) rev.diffopt.flags.ignore_submodules = 1; rev.abbrev = 0; - /* No initial commit. */ - if (get_oid("HEAD", &dummy)) - return -1; - add_head_to_pending(&rev); diff_setup_done(&rev.diffopt); - if (read_cache() < 0) - return 1; result = run_diff_index(&rev, 1); if (diff_result_code(&rev.diffopt, result)) return 1; @@ -896,19 +922,27 @@ static int check_changes_tracked_files(struct pathspec ps) return 0; } -static int check_changes(struct pathspec ps, int include_untracked) +/* + * The function will fill `untracked_files` with the names of untracked files + * It will return 1 if there were any changes and 0 if there were not. + */ + +static int check_changes(struct pathspec ps, int include_untracked, + struct strbuf *untracked_files) { int ret = 0; if (check_changes_tracked_files(ps)) ret = 1; - if (include_untracked && get_untracked_files(ps, include_untracked)) + if (include_untracked && get_untracked_files(ps, include_untracked, + untracked_files)) ret = 1; return ret; } -static int save_untracked_files(struct stash_info *info, struct strbuf *msg) +static int save_untracked_files(struct stash_info *info, struct strbuf *msg, + struct strbuf files) { int ret = 0; struct strbuf untracked_msg = STRBUF_INIT; @@ -922,8 +956,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg) stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); - if (pipe_command(&cp_upd_index, untracked_files.buf, untracked_files.len, - NULL, 0, NULL, 0)) { + if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0, + NULL, 0)) { ret = -1; goto done; } @@ -947,11 +981,9 @@ done: return ret; } -static struct strbuf patch = STRBUF_INIT; - -static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) +static int stash_patch(struct stash_info *info, struct pathspec ps, + struct strbuf *out_patch, int quiet) { - int i; int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; struct child_process cp_add_i = CHILD_PROCESS_INIT; @@ -969,11 +1001,11 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) goto done; } + /* Find out what the user wants. */ cp_add_i.git_cmd = 1; argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", - "--", NULL); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp_add_i.args, ps.items[i].match); + "--", NULL); + add_pathspecs(&cp_add_i.args, ps); argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (run_command(&cp_add_i)) { @@ -981,6 +1013,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) goto done; } + /* State of the working tree. */ if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, NULL)) { ret = -1; @@ -990,12 +1023,12 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) cp_diff_tree.git_cmd = 1; argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); - if (pipe_command(&cp_diff_tree, NULL, 0, &patch, 0, NULL, 0)) { + if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; goto done; } - if (!patch.len) { + if (!out_patch->len) { if (!quiet) fprintf_ln(stderr, _("No changes selected")); ret = 1; @@ -1010,9 +1043,9 @@ done: static int stash_working_tree(struct stash_info *info, struct pathspec ps) { int ret = 0; + struct rev_info rev; struct child_process cp_upd_index = CHILD_PROCESS_INIT; struct strbuf diff_output = STRBUF_INIT; - struct rev_info rev; struct index_state istate = { NULL }; set_alternate_index_output(stash_index_path.buf); @@ -1022,7 +1055,6 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) } set_alternate_index_output(NULL); - git_config(git_diff_basic_config, NULL); init_revisions(&rev, NULL); rev.prune_data = ps; rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; @@ -1034,7 +1066,8 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) goto done; } - add_pending_object(&rev, parse_object(the_repository, &info->b_commit), ""); + add_pending_object(&rev, parse_object(the_repository, &info->b_commit), + ""); if (run_diff_index(&rev, 0)) { ret = -1; goto done; @@ -1067,13 +1100,14 @@ done: return ret; } -static int do_create_stash(struct pathspec ps, const char **stash_msg, +static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, int include_untracked, int patch_mode, - struct stash_info *info, int quiet) + struct stash_info *info, struct strbuf *patch, + int quiet) { - int untracked_commit_option = 0; int ret = 0; - int flags; + int flags = 0; + int untracked_commit_option = 0; const char *head_short_sha1 = NULL; const char *branch_ref = NULL; const char *branch_name = "(no branch)"; @@ -1081,21 +1115,26 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, struct commit_list *parents = NULL; struct strbuf msg = STRBUF_INIT; struct strbuf commit_tree_label = STRBUF_INIT; - struct strbuf stash_msg_buf = STRBUF_INIT; + struct strbuf untracked_files = STRBUF_INIT; read_cache_preload(NULL); refresh_cache(REFRESH_QUIET); if (get_oid("HEAD", &info->b_commit)) { if (!quiet) - fprintf_ln(stderr, _("You do not have the initial commit yet")); + fprintf_ln(stderr, _("You do not have " + "the initial commit yet")); ret = -1; - *stash_msg = NULL; goto done; } else { head_commit = lookup_commit(the_repository, &info->b_commit); } + if (!check_changes(ps, include_untracked, &untracked_files)) { + ret = 1; + goto done; + } + branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags); if (flags & REF_ISSYMREF) branch_name = strrchr(branch_ref, '/') + 1; @@ -1110,28 +1149,28 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, commit_tree(commit_tree_label.buf, commit_tree_label.len, &info->i_tree, parents, &info->i_commit, NULL, NULL)) { if (!quiet) - fprintf_ln(stderr, _("Cannot save the current index state")); + fprintf_ln(stderr, _("Cannot save the current " + "index state")); ret = -1; - *stash_msg = NULL; goto done; } if (include_untracked) { - if (save_untracked_files(info, &msg)) { + if (save_untracked_files(info, &msg, untracked_files)) { if (!quiet) - fprintf_ln(stderr, _("Cannot save the untracked files")); + fprintf_ln(stderr, _("Cannot save " + "the untracked files")); ret = -1; - *stash_msg = NULL; goto done; } untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, quiet); - *stash_msg = NULL; + ret = stash_patch(info, ps, patch, quiet); if (ret < 0) { if (!quiet) - fprintf_ln(stderr, _("Cannot save the current worktree state")); + fprintf_ln(stderr, _("Cannot save the current " + "worktree state")); goto done; } else if (ret > 0) { goto done; @@ -1139,19 +1178,23 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, } else { if (stash_working_tree(info, ps)) { if (!quiet) - fprintf_ln(stderr, _("Cannot save the current worktree state")); + fprintf_ln(stderr, _("Cannot save the current " + "worktree state")); ret = -1; - *stash_msg = NULL; goto done; } } - if (!*stash_msg || !strlen(*stash_msg)) - strbuf_addf(&stash_msg_buf, "WIP on %s", msg.buf); - else - strbuf_addf(&stash_msg_buf, "On %s: %s", branch_name, - *stash_msg); - *stash_msg = strbuf_detach(&stash_msg_buf, NULL); + if (!stash_msg_buf->len) { + strbuf_release(stash_msg_buf); + strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf); + } else { + struct strbuf temp_buf = STRBUF_INIT; + strbuf_addf(&temp_buf, "On %s: %s", branch_name, + stash_msg_buf->buf); + strbuf_release(stash_msg_buf); + *stash_msg_buf = temp_buf; + } /* * `parents` will be empty after calling `commit_tree()`, so there is @@ -1159,14 +1202,18 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, */ parents = NULL; if (untracked_commit_option) - commit_list_insert(lookup_commit(the_repository, &info->u_commit), &parents); - commit_list_insert(lookup_commit(the_repository, &info->i_commit), &parents); + commit_list_insert(lookup_commit(the_repository, + &info->u_commit), + &parents); + commit_list_insert(lookup_commit(the_repository, &info->i_commit), + &parents); commit_list_insert(head_commit, &parents); - if (commit_tree(*stash_msg, strlen(*stash_msg), &info->w_tree, + if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree, parents, &info->w_commit, NULL, NULL)) { if (!quiet) - fprintf_ln(stderr, _("Cannot record working tree state")); + fprintf_ln(stderr, _("Cannot record " + "working tree state")); ret = -1; goto done; } @@ -1174,62 +1221,48 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, done: strbuf_release(&commit_tree_label); strbuf_release(&msg); - strbuf_release(&stash_msg_buf); + strbuf_release(&untracked_files); return ret; } static int create_stash(int argc, const char **argv, const char *prefix) { - int i; int ret = 0; - char *to_free = NULL; - const char *stash_msg = NULL; + struct strbuf stash_msg_buf = STRBUF_INIT; struct stash_info info; struct pathspec ps; - struct strbuf stash_msg_buf = STRBUF_INIT; - struct option options[] = { - OPT_END() - }; - argc = parse_options(argc, argv, prefix, options, - git_stash_create_usage, - 0); + /* Starting with argv[1], since argv[0] is "create" */ + strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); memset(&ps, 0, sizeof(ps)); if (!check_changes_tracked_files(ps)) return 0; - for (i = 0; i < argc; ++i) - strbuf_addf(&stash_msg_buf, "%s ", argv[i]); - stash_msg = strbuf_detach(&stash_msg_buf, NULL); - to_free = (char *) stash_msg; - - if (!(ret = do_create_stash(ps, &stash_msg, 0, 0, &info, 0))) + if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0))) printf_ln("%s", oid_to_hex(&info.w_commit)); - free(to_free); - free((char *) stash_msg); + strbuf_release(&stash_msg_buf); return ret; } -static void add_ps_items_to_argv_array(struct argv_array *args, - struct pathspec ps) { - int i; - for (i = 0; i < ps.nr; ++i) - argv_array_push(args, ps.items[i].match); -} - static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, int keep_index, int patch_mode, int include_untracked) { int ret = 0; struct stash_info info; + struct strbuf patch = STRBUF_INIT; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct strbuf untracked_files = STRBUF_INIT; + if (patch_mode && keep_index == -1) keep_index = 1; if (patch_mode && include_untracked) { - fprintf_ln(stderr, _("Can't use --patch and --include-untracked or --all at the same time")); - return -1; + fprintf_ln(stderr, _("Can't use --patch and --include-untracked" + " or --all at the same time")); + ret = -1; + goto done; } read_cache_preload(NULL); @@ -1237,49 +1270,55 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, int i; char *ps_matched = xcalloc(ps.nr, 1); - for (i = 0; i < active_nr; ++i) { - const struct cache_entry *ce = active_cache[i]; - ce_path_match(&the_index, ce, &ps, ps_matched); - } + for (i = 0; i < active_nr; i++) + ce_path_match(&the_index, active_cache[i], &ps, + ps_matched); if (report_path_error(ps_matched, &ps, NULL)) { fprintf_ln(stderr, _("Did you forget to 'git add'?")); - return -1; + ret = -1; + free(ps_matched); + goto done; } free(ps_matched); } - if (refresh_cache(REFRESH_QUIET)) - return -1; - - if (!check_changes(ps, include_untracked)) { - if (!quiet) - printf_ln(_("No local changes to save")); - return 0; - } - - if (!reflog_exists(ref_stash) && do_clear_stash()) { - if (!quiet) - fprintf_ln(stderr, _("Cannot initialize stash")); - return -1; - } - - if (do_create_stash(ps, &stash_msg, include_untracked, patch_mode, - &info, quiet)) { + if (refresh_cache(REFRESH_QUIET)) { ret = -1; goto done; } - if (do_store_stash(oid_to_hex(&info.w_commit), stash_msg, 1)) { + if (!check_changes(ps, include_untracked, &untracked_files)) { + if (!quiet) + printf_ln(_("No local changes to save")); + goto done; + } + + if (!reflog_exists(ref_stash) && do_clear_stash()) { + ret = -1; + if (!quiet) + fprintf_ln(stderr, _("Cannot initialize stash")); + goto done; + } + + if (stash_msg) + strbuf_addstr(&stash_msg_buf, stash_msg); + if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, + &info, &patch, quiet)) { + ret = -1; + goto done; + } + + if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) { + ret = -1; if (!quiet) fprintf_ln(stderr, _("Cannot save the current status")); - ret = -1; goto done; } if (!quiet) printf_ln(_("Saved working directory and index state %s"), - stash_msg); + stash_msg_buf.buf); if (!patch_mode) { if (include_untracked && !ps.nr) { @@ -1288,7 +1327,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, cp.git_cmd = 1; argv_array_pushl(&cp.args, "clean", "--force", "--quiet", "-d", NULL); - if (include_untracked == 2) + if (include_untracked == INCLUDE_ALL_FILES) argv_array_push(&cp.args, "-x"); if (run_command(&cp)) { ret = -1; @@ -1296,39 +1335,39 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, } } if (ps.nr) { - struct child_process cp1 = CHILD_PROCESS_INIT; - struct child_process cp2 = CHILD_PROCESS_INIT; - struct child_process cp3 = CHILD_PROCESS_INIT; + struct child_process cp_add = CHILD_PROCESS_INIT; + struct child_process cp_diff = CHILD_PROCESS_INIT; + struct child_process cp_apply = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; - cp1.git_cmd = 1; - argv_array_push(&cp1.args, "add"); + cp_add.git_cmd = 1; + argv_array_push(&cp_add.args, "add"); if (!include_untracked) - argv_array_push(&cp1.args, "-u"); - if (include_untracked == 2) - argv_array_push(&cp1.args, "--force"); - argv_array_push(&cp1.args, "--"); - add_ps_items_to_argv_array(&cp1.args, ps); - if (run_command(&cp1)) { + argv_array_push(&cp_add.args, "-u"); + if (include_untracked == INCLUDE_ALL_FILES) + argv_array_push(&cp_add.args, "--force"); + argv_array_push(&cp_add.args, "--"); + add_pathspecs(&cp_add.args, ps); + if (run_command(&cp_add)) { ret = -1; goto done; } - cp2.git_cmd = 1; - argv_array_pushl(&cp2.args, "diff-index", "-p", + cp_diff.git_cmd = 1; + argv_array_pushl(&cp_diff.args, "diff-index", "-p", "--cached", "--binary", "HEAD", "--", NULL); - add_ps_items_to_argv_array(&cp2.args, ps); - if (pipe_command(&cp2, NULL, 0, &out, 0, NULL, 0)) { + add_pathspecs(&cp_diff.args, ps); + if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) { ret = -1; goto done; } - cp3.git_cmd = 1; - argv_array_pushl(&cp3.args, "apply", "--index", "-R", - NULL); - if (pipe_command(&cp3, out.buf, out.len, NULL, 0, NULL, - 0)) { + cp_apply.git_cmd = 1; + argv_array_pushl(&cp_apply.args, "apply", "--index", + "-R", NULL); + if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0, + NULL, 0)) { ret = -1; goto done; } @@ -1344,8 +1383,8 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, } if (keep_index == 1 && !is_null_oid(&info.i_tree)) { - struct child_process cp1 = CHILD_PROCESS_INIT; - struct child_process cp2 = CHILD_PROCESS_INIT; + struct child_process cp_ls = CHILD_PROCESS_INIT; + struct child_process cp_checkout = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; if (reset_tree(&info.i_tree, 0, 1)) { @@ -1353,24 +1392,26 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, goto done; } - cp1.git_cmd = 1; - argv_array_pushl(&cp1.args, "ls-files", "-z", + cp_ls.git_cmd = 1; + argv_array_pushl(&cp_ls.args, "ls-files", "-z", "--modified", "--", NULL); - add_ps_items_to_argv_array(&cp1.args, ps); - if (pipe_command(&cp1, NULL, 0, &out, 0, NULL, 0)) { + + add_pathspecs(&cp_ls.args, ps); + if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) { ret = -1; goto done; } - cp2.git_cmd = 1; - argv_array_pushl(&cp2.args, "checkout-index", "-z", - "--force", "--stdin", NULL); - if (pipe_command(&cp2, out.buf, out.len, NULL, 0, NULL, - 0)) { + cp_checkout.git_cmd = 1; + argv_array_pushl(&cp_checkout.args, "checkout-index", + "-z", "--force", "--stdin", NULL); + if (pipe_command(&cp_checkout, out.buf, out.len, NULL, + 0, NULL, 0)) { ret = -1; goto done; } } + goto done; } else { struct child_process cp = CHILD_PROCESS_INIT; @@ -1379,27 +1420,28 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { if (!quiet) - fprintf_ln(stderr, _("Cannot remove worktree changes")); + fprintf_ln(stderr, _("Cannot remove " + "worktree changes")); ret = -1; goto done; } if (keep_index < 1) { - int i; struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp.args, ps.items[i].match); + add_pathspecs(&cp.args, ps); if (run_command(&cp)) { ret = -1; goto done; } } + goto done; } + done: - free((char *) stash_msg); + strbuf_release(&stash_msg_buf); return ret; } @@ -1412,17 +1454,17 @@ static int push_stash(int argc, const char **argv, const char *prefix) const char *stash_msg = NULL; struct pathspec ps; struct option options[] = { - OPT_SET_INT('k', "keep-index", &keep_index, - N_("keep index"), 1), + OPT_BOOL('k', "keep-index", &keep_index, + N_("keep index")), OPT_BOOL('p', "patch", &patch_mode, - N_("stash in patch mode")), + N_("stash in patch mode")), OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), OPT_SET_INT('a', "all", &include_untracked, N_("include ignore files"), 2), OPT_STRING('m', "message", &stash_msg, N_("message"), - N_("stash message")), + N_("stash message")), OPT_END() }; @@ -1438,43 +1480,41 @@ static int push_stash(int argc, const char **argv, const char *prefix) static int save_stash(int argc, const char **argv, const char *prefix) { - int i; int keep_index = -1; int patch_mode = 0; int include_untracked = 0; int quiet = 0; int ret = 0; const char *stash_msg = NULL; - char *to_free = NULL; - struct strbuf stash_msg_buf = STRBUF_INIT; struct pathspec ps; + struct strbuf stash_msg_buf = STRBUF_INIT; struct option options[] = { - OPT_SET_INT('k', "keep-index", &keep_index, - N_("keep index"), 1), + OPT_BOOL('k', "keep-index", &keep_index, + N_("keep index")), OPT_BOOL('p', "patch", &patch_mode, - N_("stash in patch mode")), + N_("stash in patch mode")), OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), OPT_SET_INT('a', "all", &include_untracked, N_("include ignore files"), 2), + OPT_STRING('m', "message", &stash_msg, "message", + N_("stash message")), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_stash_save_usage, - 0); + PARSE_OPT_KEEP_DASHDASH); - for (i = 0; i < argc; ++i) - strbuf_addf(&stash_msg_buf, "%s ", argv[i]); - stash_msg = strbuf_detach(&stash_msg_buf, NULL); - to_free = (char *) stash_msg; + if (argc) + stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' '); memset(&ps, 0, sizeof(ps)); - ret = do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked); + ret = do_push_stash(ps, stash_msg, quiet, keep_index, + patch_mode, include_untracked); - free(to_free); + strbuf_release(&stash_msg_buf); return ret; } @@ -1489,7 +1529,7 @@ static int use_builtin_stash(void) cp.git_cmd = 1; if (capture_command(&cp, &out, 6)) { strbuf_release(&out); - return 0; + return 1; } strbuf_trim(&out); @@ -1523,7 +1563,7 @@ int cmd_stash(int argc, const char **argv, const char *prefix) trace_repo_setup(prefix); setup_work_tree(); - git_config(git_default_config, NULL); + git_config(git_diff_basic_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); @@ -1532,7 +1572,7 @@ int cmd_stash(int argc, const char **argv, const char *prefix) strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); - if (argc == 0) + if (!argc) return !!push_stash(0, NULL, prefix); else if (!strcmp(argv[0], "apply")) return !!apply_stash(argc, argv, prefix); @@ -1583,10 +1623,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix) * they need to be immediately followed by a string * (i.e.`-m"foobar"` or `--message="foobar"`). */ - if ((strlen(argv[i]) > 2 && - !strncmp(argv[i], "-m", 2)) || - (strlen(argv[i]) > 10 && - !strncmp(argv[i], "--message=", 10))) + if (starts_with(argv[i], "-m") || + starts_with(argv[i], "--message=")) continue; usage_with_options(git_stash_usage, options); diff --git a/compat/mingw.c b/compat/mingw.c index f43146ef18..1ecacb0fa7 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -8,9 +8,9 @@ #include "../cache.h" #include "win32/exit-process.h" #include "win32/lazyload.h" -#include "../config.h" #include "../string-list.h" #include "../attr.h" +#include "../config.h" #define HCAST(type, handle) ((type)(intptr_t)handle) @@ -819,13 +819,11 @@ static int current_directory_len = 0; int mingw_chdir(const char *dirname) { int result; - DECLARE_PROC_ADDR(kernel32.dll, DWORD, GetFinalPathNameByHandleW, - HANDLE, LPWSTR, DWORD, DWORD); wchar_t wdirname[MAX_LONG_PATH]; if (xutftowcs_long_path(wdirname, dirname) < 0) return -1; - if (has_symlinks && INIT_PROC_ADDR(GetFinalPathNameByHandleW)) { + if (has_symlinks) { HANDLE hnd = CreateFileW(wdirname, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); @@ -962,12 +960,15 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf) errno = err_win_to_posix(GetLastError()); return -1; } + buf->st_ino = 0; - buf->st_dev = buf->st_rdev = 0; /* not used by Git */ - buf->st_gid = buf->st_uid = 0; + buf->st_gid = 0; + buf->st_uid = 0; buf->st_nlink = 1; buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0, NULL); - buf->st_size = fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32); + buf->st_size = fdata.nFileSizeLow | + (((off_t)fdata.nFileSizeHigh)<<32); + buf->st_dev = buf->st_rdev = 0; /* not used by Git */ filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim)); filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim)); filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim)); @@ -1176,8 +1177,6 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) char *mingw_getcwd(char *pointer, int len) { wchar_t cwd[MAX_PATH], wpointer[MAX_PATH]; - DECLARE_PROC_ADDR(kernel32.dll, DWORD, GetFinalPathNameByHandleW, - HANDLE, LPWSTR, DWORD, DWORD); DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd); if (!ret || ret >= ARRAY_SIZE(cwd)) { @@ -1185,8 +1184,7 @@ char *mingw_getcwd(char *pointer, int len) return NULL; } ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer)); - if (!ret && GetLastError() == ERROR_ACCESS_DENIED && - INIT_PROC_ADDR(GetFinalPathNameByHandleW)) { + if (!ret && GetLastError() == ERROR_ACCESS_DENIED) { HANDLE hnd = CreateFileW(cwd, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); @@ -1209,8 +1207,8 @@ char *mingw_getcwd(char *pointer, int len) } /* - * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx - * (Parsing C++ Command-Line Arguments) + * See "Parsing C++ Command-Line Arguments" at Microsoft's Docs: + * https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */ static const char *quote_arg_msvc(const char *arg) { @@ -1452,34 +1450,37 @@ static char *path_lookup(const char *cmd, int exe_only) return prog; } -#if defined(_MSC_VER) - -/* We need a stable sort */ -#ifndef INTERNAL_QSORT -#include "qsort.c" -#endif +static const wchar_t *wcschrnul(const wchar_t *s, wchar_t c) +{ + while (*s && *s != c) + s++; + return s; +} /* Compare only keys */ static int wenvcmp(const void *a, const void *b) { wchar_t *p = *(wchar_t **)a, *q = *(wchar_t **)b; size_t p_len, q_len; - int ret; - /* Find end of keys */ - for (p_len = 0; p[p_len] && p[p_len] != L'='; p_len++) - ; /* do nothing */ - for (q_len = 0; q[q_len] && q[q_len] != L'='; q_len++) - ; /* do nothing */ + /* Find the keys */ + p_len = wcschrnul(p, L'=') - p; + q_len = wcschrnul(q, L'=') - q; - /* Are keys identical (modulo case)? */ - if (p_len == q_len && !_wcsnicmp(p, q, p_len)) - return 0; + /* If the length differs, include the shorter key's NUL */ + if (p_len < q_len) + p_len++; + else if (p_len > q_len) + p_len = q_len + 1; - ret = _wcsnicmp(p, q, p_len < q_len ? p_len : q_len); - return ret ? ret : (p_len < q_len ? -1 : +1); + return _wcsnicmp(p, q, p_len); } +/* We need a stable sort to convert the environment between UTF-16 <-> UTF-8 */ +#ifndef INTERNAL_QSORT +#include "qsort.c" +#endif + /* * Build an environment block combining the inherited environment * merged with the given list of settings. @@ -1494,25 +1495,15 @@ static int wenvcmp(const void *a, const void *b) */ static wchar_t *make_environment_block(char **deltaenv) { - /* - * The CRT (at least as of UCRT) secretly declares "_wenviron" - * as a function that returns a pointer to a mostly static table. - * Grab the pointer and cache it for the duration of our loop. - */ - const wchar_t *wenv = GetEnvironmentStringsW(), *p; - size_t delta_size = 0, size = 1; /* for extra NUL at the end */ + wchar_t *wenv = GetEnvironmentStringsW(), *wdeltaenv, *result, *p; + size_t wlen, s, delta_size, size; wchar_t **array = NULL; size_t alloc = 0, nr = 0, i; - const char *p2; - wchar_t *wdeltaenv; + size = 1; /* for extra NUL at the end */ - wchar_t *result, *p3; - - /* - * If there is no deltaenv to apply, simply return a copy - */ + /* If there is no deltaenv to apply, simply return a copy. */ if (!deltaenv || !*deltaenv) { for (p = wenv; p && *p; ) { size_t s = wcslen(p) + 1; @@ -1531,116 +1522,51 @@ static wchar_t *make_environment_block(char **deltaenv) * sort them using the stable git_qsort() and then copy, skipping * duplicate keys */ - for (p = wenv; p && *p; ) { - size_t s = wcslen(p) + 1; - size += s; ALLOC_GROW(array, nr + 1, alloc); + s = wcslen(p) + 1; array[nr++] = p; p += s; + size += s; } /* (over-)assess size needed for wchar version of deltaenv */ - for (i = 0; deltaenv[i]; i++) { - size_t s = strlen(deltaenv[i]) + 1; - delta_size += s; - } - + for (delta_size = 0, i = 0; deltaenv[i]; i++) + delta_size += strlen(deltaenv[i]) * 2 + 1; ALLOC_ARRAY(wdeltaenv, delta_size); /* convert the deltaenv, appending to array */ - for (i = 0, p3 = wdeltaenv; deltaenv[i]; i++) { - size_t s = strlen(deltaenv[i]) + 1, wlen; - wlen = xutftowcs(p3, deltaenv[i], s * 2); - + for (i = 0, p = wdeltaenv; deltaenv[i]; i++) { ALLOC_GROW(array, nr + 1, alloc); - array[nr++] = p3; - - p3 += wlen + 1; + wlen = xutftowcs(p, deltaenv[i], wdeltaenv + delta_size - p); + array[nr++] = p; + p += wlen + 1; } git_qsort(array, nr, sizeof(*array), wenvcmp); ALLOC_ARRAY(result, size + delta_size); - for (p3 = result, i = 0; i < nr; i++) { - wchar_t *equal = wcschr(array[i], L'=');; + for (p = result, i = 0; i < nr; i++) { + /* Skip any duplicate keys; last one wins */ + while (i + 1 < nr && !wenvcmp(array + i, array + i + 1)) + i++; /* Skip "to delete" entry */ - if (!equal) + if (!wcschr(array[i], L'=')) continue; - p = array[i]; - - /* Skip any duplicate */ - if (i + 1 < nr) { - wchar_t *next = array[i + 1]; - size_t n = equal - p; - - if (!_wcsnicmp(p, next, n) && (!next[n] || next[n] == L'=')) - continue; - } - - size = wcslen(p) + 1; - memcpy(p3, p, size * sizeof(*p)); - p3 += size; + size = wcslen(array[i]) + 1; + memcpy(p, array[i], size * sizeof(*p)); + p += size; } - *p3 = L'\0'; + *p = L'\0'; free(array); + free(wdeltaenv); FreeEnvironmentStringsW(wenv); return result; } -#else - -static int do_putenv(char **env, const char *name, int size, int free_old); - -/* used number of elements of environ array, including terminating NULL */ -static int environ_size = 0; -/* allocated size of environ array, in bytes */ -static int environ_alloc = 0; -/* used as a indicator when the environment has been changed outside mingw.c */ -static char **saved_environ; - -static void maybe_reinitialize_environ(void); - -/* - * Create environment block suitable for CreateProcess. Merges current - * process environment and the supplied environment changes. - */ -static wchar_t *make_environment_block(char **deltaenv) -{ - wchar_t *wenvblk = NULL; - char **tmpenv; - int i = 0, size, wenvsz = 0, wenvpos = 0; - - maybe_reinitialize_environ(); - size = environ_size; - - while (deltaenv && deltaenv[i] && *deltaenv[i]) - i++; - - /* copy the environment, leaving space for changes */ - ALLOC_ARRAY(tmpenv, size + i); - memcpy(tmpenv, environ, size * sizeof(char*)); - - /* merge supplied environment changes into the temporary environment */ - for (i = 0; deltaenv && deltaenv[i] && *deltaenv[i]; i++) - size = do_putenv(tmpenv, deltaenv[i], size, 0); - - /* create environment block from temporary environment */ - for (i = 0; tmpenv[i] && *tmpenv[i]; i++) { - size = 2 * strlen(tmpenv[i]) + 2; /* +2 for final \0 */ - ALLOC_GROW(wenvblk, (wenvpos + size) * sizeof(wchar_t), wenvsz); - wenvpos += xutftowcs(&wenvblk[wenvpos], tmpenv[i], size) + 1; - } - /* add final \0 terminator */ - wenvblk[wenvpos] = 0; - free(tmpenv); - return wenvblk; -} -#endif - static void do_unset_environment_variables(void) { static int done; @@ -2141,131 +2067,83 @@ int msc_putenv(const char *name) #else /* - * Compare environment entries by key (i.e. stopping at '=' or '\0'). + * UTF-8 versions of getenv(), putenv() and unsetenv(). + * Internally, they use the CRT's stock UNICODE routines + * to avoid data loss. */ -static int compareenv(const void *v1, const void *v2) -{ - const char *e1 = *(const char**)v1; - const char *e2 = *(const char**)v2; - - for (;;) { - int c1 = *e1++; - int c2 = *e2++; - c1 = (c1 == '=') ? 0 : tolower(c1); - c2 = (c2 == '=') ? 0 : tolower(c2); - if (c1 > c2) - return 1; - if (c1 < c2) - return -1; - if (c1 == 0) - return 0; - } -} - -/* - * Functions implemented outside Git are able to modify the environment, - * too. For example, cURL's curl_global_init() function sets the CHARSET - * environment variable (at least in certain circumstances). - * - * Therefore we need to be *really* careful *not* to assume that we have - * sole control over the environment and reinitialize it when necessary. - */ -static void maybe_reinitialize_environ(void) -{ - int i; - - if (!saved_environ) { - warning("MinGW environment not initialized yet"); - return; - } - - if (environ_size <= 0) - return; - - if (saved_environ != environ) - /* We have *no* idea how much space was allocated outside */ - environ_alloc = 0; - else if (!environ[environ_size - 1]) - return; /* still consistent */ - - for (i = 0; environ[i] && *environ[i]; i++) - ; /* continue counting */ - environ[i] = NULL; - environ_size = i + 1; - - /* sort environment for O(log n) getenv / putenv */ - qsort(environ, i, sizeof(char*), compareenv); -} - -static int bsearchenv(char **env, const char *name, size_t size) -{ - unsigned low = 0, high = size; - while (low < high) { - unsigned mid = low + ((high - low) >> 1); - int cmp = compareenv(&env[mid], &name); - if (cmp < 0) - low = mid + 1; - else if (cmp > 0) - high = mid; - else - return mid; - } - return ~low; /* not found, return 1's complement of insert position */ -} - -/* - * If name contains '=', then sets the variable, otherwise it unsets it - * Size includes the terminating NULL. Env must have room for size + 1 entries - * (in case of insert). Returns the new size. Optionally frees removed entries. - */ -static int do_putenv(char **env, const char *name, int size, int free_old) -{ - int i = size <= 0 ? -1 : bsearchenv(env, name, size - 1); - - /* optionally free removed / replaced entry */ - if (i >= 0 && free_old) - free(env[i]); - - if (strchr(name, '=')) { - /* if new value ('key=value') is specified, insert or replace entry */ - if (i < 0) { - i = ~i; - memmove(&env[i + 1], &env[i], (size - i) * sizeof(char*)); - size++; - } - env[i] = (char*) name; - } else if (i >= 0) { - /* otherwise ('key') remove existing entry */ - size--; - memmove(&env[i], &env[i + 1], (size - i) * sizeof(char*)); - } - return size; -} - char *mingw_getenv(const char *name) { +#define GETENV_MAX_RETAIN 30 + static char *values[GETENV_MAX_RETAIN]; + static int value_counter; + int len_key, len_value; + wchar_t *w_key; char *value; - int pos; + wchar_t w_value[32768]; - if (environ_size <= 0) + if (!name || !*name) return NULL; - maybe_reinitialize_environ(); - pos = bsearchenv(environ, name, environ_size - 1); - - if (pos < 0) + len_key = strlen(name) + 1; + /* We cannot use xcalloc() here because that uses getenv() itself */ + w_key = calloc(len_key, sizeof(wchar_t)); + if (!w_key) + die("Out of memory, (tried to allocate %u wchar_t's)", len_key); + xutftowcs(w_key, name, len_key); + len_value = GetEnvironmentVariableW(w_key, w_value, ARRAY_SIZE(w_value)); + if (!len_value && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + free(w_key); return NULL; - value = strchr(environ[pos], '='); - return value ? &value[1] : NULL; + } + free(w_key); + + len_value = len_value * 3 + 1; + /* We cannot use xcalloc() here because that uses getenv() itself */ + value = calloc(len_value, sizeof(char)); + if (!value) + die("Out of memory, (tried to allocate %u bytes)", len_value); + xwcstoutf(value, w_value, len_value); + + /* + * We return `value` which is an allocated value and the caller is NOT + * expecting to have to free it, so we keep a round-robin array, + * invalidating the buffer after GETENV_MAX_RETAIN getenv() calls. + */ + free(values[value_counter]); + values[value_counter++] = value; + if (value_counter >= ARRAY_SIZE(values)) + value_counter = 0; + + return value; } int mingw_putenv(const char *namevalue) { - maybe_reinitialize_environ(); - ALLOC_GROW(environ, (environ_size + 1) * sizeof(char*), environ_alloc); - saved_environ = environ; - environ_size = do_putenv(environ, namevalue, environ_size, 1); - return 0; + int size; + wchar_t *wide, *equal; + BOOL result; + + if (!namevalue || !*namevalue) + return 0; + + size = strlen(namevalue) * 2 + 1; + wide = calloc(size, sizeof(wchar_t)); + if (!wide) + die("Out of memory, (tried to allocate %u wchar_t's)", size); + xutftowcs(wide, namevalue, size); + equal = wcschr(wide, L'='); + if (!equal) + result = SetEnvironmentVariableW(wide, NULL); + else { + *equal = L'\0'; + result = SetEnvironmentVariableW(wide, equal + 1); + } + free(wide); + + if (!result) + errno = err_win_to_posix(GetLastError()); + + return result ? 0 : -1; } #endif @@ -3284,42 +3162,35 @@ static BOOL WINAPI handle_ctrl_c(DWORD ctrl_type) return TRUE; /* we did handle this */ } -#if defined(_MSC_VER) - +#ifdef _MSC_VER #ifdef _DEBUG #include #endif +#endif /* - * This routine sits between wmain() and "main" in git.exe. - * We receive UNICODE (wchar_t) values for argv and env. + * We implement wmain() and compile with -municode, which would + * normally ignore main(), but we call the latter from the former + * so that we can handle non-ASCII command-line parameters + * appropriately. * * To be more compatible with the core git code, we convert - * argv into UTF8 and pass them directly to the "main" routine. - * - * We don't bother converting the given UNICODE env vector, - * but rather leave them in the CRT. We replaced the various - * getenv/putenv routines to pull them directly from the CRT. - * - * This is unlike the MINGW version: - * [] It does the UNICODE-2-UTF8 conversion on both sets and - * stuffs the values back into the CRT using exported symbols. - * [] It also maintains a private copy of the environment and - * tries to track external changes to it. + * argv into UTF8 and pass them directly to main(). */ -int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env) +int wmain(int argc, const wchar_t **wargv) { - char **my_utf8_argv = NULL, **save = NULL; - char *buffer = NULL; - int maxlen; - int k, exit_status; + int i, maxlen, exit_status; + char *buffer, **save; + const char **argv; +#ifdef _MSC_VER #ifdef _DEBUG _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); #endif #ifdef USE_MSVC_CRTDBG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#endif #endif SetConsoleCtrlHandler(handle_ctrl_c, TRUE); @@ -3328,10 +3199,10 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env) adjust_symlink_flags(); fsync_object_files = 1; - /* determine size of argv conversion buffer */ - maxlen = wcslen(_wpgmptr); - for (k = 1; k < argc; k++) - maxlen = max(maxlen, wcslen(w_argv[k])); + /* determine size of argv and environ conversion buffer */ + maxlen = wcslen(wargv[0]); + for (i = 1; i < argc; i++) + maxlen = max(maxlen, wcslen(wargv[i])); /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */ maxlen = 3 * maxlen + 1; @@ -3342,13 +3213,11 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env) * to remember all the string pointers because parse_options() * will remove claimed items from the argv that we pass down. */ - ALLOC_ARRAY(my_utf8_argv, argc + 1); + ALLOC_ARRAY(argv, argc + 1); ALLOC_ARRAY(save, argc + 1); - save[0] = my_utf8_argv[0] = wcstoutfdup_startup(buffer, _wpgmptr, maxlen); - for (k = 1; k < argc; k++) - save[k] = my_utf8_argv[k] = wcstoutfdup_startup(buffer, w_argv[k], maxlen); - save[k] = my_utf8_argv[k] = NULL; - + for (i = 0; i < argc; i++) + argv[i] = save[i] = wcstoutfdup_startup(buffer, wargv[i], maxlen); + argv[i] = save[i] = NULL; free(buffer); /* fix Windows specific environment settings */ @@ -3373,102 +3242,16 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env) current_directory_len = GetCurrentDirectoryW(0, NULL); /* invoke the real main() using our utf8 version of argv. */ - exit_status = msc_main(argc, my_utf8_argv); + exit_status = main(argc, argv); - for (k = 0; k < argc; k++) - free(save[k]); + for (i = 0; i < argc; i++) + free(save[i]); free(save); - free(my_utf8_argv); + free(argv); return exit_status; } -#else - -void mingw_startup(void) -{ - int i, maxlen, argc; - char *buffer; - wchar_t **wenv, **wargv; - _startupinfo si; - - SetConsoleCtrlHandler(handle_ctrl_c, TRUE); - - maybe_redirect_std_handles(); - adjust_symlink_flags(); - fsync_object_files = 1; - - /* get wide char arguments and environment */ - si.newmode = 0; - if (__wgetmainargs(&argc, &wargv, &wenv, _CRT_glob, &si) < 0) - die_startup(); - - /* determine size of argv and environ conversion buffer */ - maxlen = wcslen(wargv[0]); - for (i = 1; i < argc; i++) - maxlen = max(maxlen, wcslen(wargv[i])); - for (i = 0; wenv[i]; i++) - maxlen = max(maxlen, wcslen(wenv[i])); - - /* - * nedmalloc can't free CRT memory, allocate resizable environment - * list. Note that xmalloc / xmemdupz etc. call getenv, so we cannot - * use it while initializing the environment itself. - */ - environ_size = i + 1; - environ_alloc = alloc_nr(environ_size * sizeof(char*)); - saved_environ = environ = malloc_startup(environ_alloc); - - /* allocate buffer (wchar_t encodes to max 3 UTF-8 bytes) */ - maxlen = 3 * maxlen + 1; - buffer = malloc_startup(maxlen); - - /* convert command line arguments and environment to UTF-8 */ - for (i = 0; i < argc; i++) - __argv[i] = wcstoutfdup_startup(buffer, wargv[i], maxlen); - for (i = 0; wenv[i]; i++) - environ[i] = wcstoutfdup_startup(buffer, wenv[i], maxlen); - environ[i] = NULL; - free(buffer); - - /* sort environment for O(log n) getenv / putenv */ - qsort(environ, i, sizeof(char*), compareenv); - - /* fix Windows specific environment settings */ - setup_windows_environment(); - - unset_environment_variables = xstrdup("PERL5LIB"); - - /* - * Avoid a segmentation fault when cURL tries to set the CHARSET - * variable and putenv() barfs at our nedmalloc'ed environment. - */ - if (!getenv("CHARSET")) { - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "cp%u", GetACP()); - setenv("CHARSET", buf.buf, 1); - strbuf_release(&buf); - } - - /* initialize critical section for waitpid pinfo_t list */ - InitializeCriticalSection(&pinfo_cs); - InitializeCriticalSection(&phantom_symlinks_cs); - - /* set up default file mode and file modes for stdin/out/err */ - _fmode = _O_BINARY; - _setmode(_fileno(stdin), _O_BINARY); - _setmode(_fileno(stdout), _O_BINARY); - _setmode(_fileno(stderr), _O_BINARY); - - /* initialize Unicode console */ - winansi_init(); - - /* init length of current directory for handle_long_path */ - current_directory_len = GetCurrentDirectoryW(0, NULL); -} - -#endif - int uname(struct utsname *buf) { unsigned v = (unsigned)GetVersion(); diff --git a/compat/mingw.h b/compat/mingw.h index 99976fe27a..ffa6831388 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -262,12 +262,11 @@ char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd #ifdef NO_UNSETENV -#error "NO_UNSETENV is incompatible with the MinGW startup code!" +#error "NO_UNSETENV is incompatible with the Windows-specific startup code!" #endif -#if defined(_MSC_VER) /* - * We bind *env() routines (even the mingw_ ones) to private msc_ versions. + * We bind *env() routines (even the mingw_ ones) to private mingw_ versions. * These talk to the CRT using UNICODE/wchar_t, but maintain the original * narrow-char API. * @@ -276,7 +275,7 @@ char *mingw_getcwd(char *pointer, int len); * (and secretly updates both when you set one or the other), but it uses CP_ACP * to do the conversion rather than CP_UTF8. * - * Since everything in the git code base is UTF8, we define the msc_ routines + * Since everything in the git code base is UTF8, we define the mingw_ routines * to access the CRT using the UNICODE routines and manually convert them to * UTF8. This also avoids round-trip problems. * @@ -284,33 +283,13 @@ char *mingw_getcwd(char *pointer, int len); * from the CRT. But to access "_environ" we would have to statically link * to the CRT (/MT). * - * We also use "wmain(argc,argv,env)" and get the initial UNICODE setup for us. - * This avoids the need for the msc_startup() to import and convert the - * inherited environment. - * - * We require NO_SETENV (and let gitsetenv() call our msc_putenv). + * We require NO_SETENV (and let gitsetenv() call our mingw_putenv). */ -#define getenv msc_getenv -#define putenv msc_putenv -#define unsetenv msc_putenv -#define mingw_getenv msc_getenv -#define mingw_putenv msc_putenv -char *msc_getenv(const char *name); -int msc_putenv(const char *name); - -#ifndef NO_SETENV -#error "NO_SETENV is required for MSC startup code!" -#endif - -#else - +#define getenv mingw_getenv +#define putenv mingw_putenv +#define unsetenv mingw_putenv char *mingw_getenv(const char *name); -#define getenv mingw_getenv -int mingw_putenv(const char *namevalue); -#define putenv mingw_putenv -#define unsetenv mingw_putenv - -#endif +int mingw_putenv(const char *name); int mingw_gethostname(char *host, int namelen); #define gethostname mingw_gethostname @@ -687,39 +666,18 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen); extern CRITICAL_SECTION pinfo_cs; /* - * A replacement of main() that adds win32 specific initialization. + * Git, like most portable C applications, implements a main() function. On + * Windows, this main() function would receive parameters encoded in the + * current locale, but Git for Windows would prefer UTF-8 encoded parameters. * - * Note that the end of these macros are unterminated so that the - * brace group following the use of the macro is the body of the - * function. + * To make that happen, we still declare main() here, and then declare and + * implement wmain() (which is the Unicode variant of main()) and compile with + * -municode. This wmain() function reencodes the parameters from UTF-16 to + * UTF-8 format, sets up a couple of other things as required on Windows, and + * then hands off to the main() function. */ -#if defined(_MSC_VER) - -int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env); -extern int msc_main(int argc, const char **argv); - -#define main(c,v) dummy_decl_msc_main(void); \ -int wmain(int my_argc, \ - wchar_t **my_w_argv, \ - wchar_t **my_w_env) \ -{ \ - return msc_startup(my_argc, my_w_argv, my_w_env); \ -} \ -int msc_main(c, v) - -#else - -void mingw_startup(void); -#define main(c,v) dummy_decl_mingw_main(void); \ -static int mingw_main(c,v); \ -int main(int argc, const char **argv) \ -{ \ - mingw_startup(); \ - return mingw_main(__argc, (void *)__argv); \ -} \ -static int mingw_main(c,v) - -#endif +int wmain(int argc, const wchar_t **w_argv); +int main(int argc, const char **argv); /* * For debugging: if a problem occurs, say, in a Git process that is spawned diff --git a/compat/vcbuild/README b/compat/vcbuild/README index 2ea3da97f4..81da36a93b 100644 --- a/compat/vcbuild/README +++ b/compat/vcbuild/README @@ -1,7 +1,7 @@ The Steps to Build Git with VS2015 or VS2017 from the command line. 1. Install the "vcpkg" open source package manager and build essential - third-party libaries. The steps for this have been captured in a + third-party libraries. The steps for this have been captured in a set of convenience scripts. These can be run from a stock Command Prompt or from an SDK bash window: @@ -45,7 +45,7 @@ Note that this will automatically add and commit the generated .sln and .vcxproj files to the repo. You may want to drop this commit before submitting a Pull Request.... -Or maybe we should put the .sln/.vcxproj files in the .gitignores +Or maybe we should put the .sln/.vcxproj files in the .gitignore file and not do this. I'm not sure. ================================================================ diff --git a/config.mak.uname b/config.mak.uname index fded5c2530..c820bc9045 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -398,12 +398,9 @@ ifeq ($(uname_S),Windows) NO_MKDTEMP = YesPlease NO_INTTYPES_H = YesPlease # VS2015 with UCRT claims that snprintf and friends are C99 compliant, - # so we don't need this. + # so we don't need this: # - # TODO If we want to support older compilers, we need to make this - # TODO conditional on the compiler version. - # - # SNPRINTF_RETURNS_BOGUS = YesPlease + # SNPRINTF_RETURNS_BOGUS = YesPlease NO_SVN_TESTS = YesPlease RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo @@ -587,6 +584,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) ETAGS_TARGET = ETAGS NO_POSIX_GOODIES = UnfortunatelyYes DEFAULT_HELP_FORMAT = html + BASIC_LDFLAGS += -municode COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index da08ee01d4..4bdb8008d1 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -70,7 +70,7 @@ sub createProject { } my $defines = join(";", sort(@{$$build_structure{"$prefix${name}_DEFINES"}})); my $includes= join(";", sort(map { s/^-I//; s/\//\\/g; File::Spec->file_name_is_absolute($_) ? $_ : "$rel_dir\\$_" } @{$$build_structure{"$prefix${name}_INCLUDES"}})); - my $cflags = join(" ", sort(map { s/^-[GLMOZ].*//; s/.* .*/"$&"/; $_; } @{$$build_structure{"$prefix${name}_CFLAGS"}})); + my $cflags = join(" ", sort(map { s/^-[GLMOWZ].*//; s/.* .*/"$&"/; $_; } @{$$build_structure{"$prefix${name}_CFLAGS"}})); $cflags =~ s//>/g; @@ -179,10 +179,10 @@ sub createProject { EOM if ($target eq 'libgit') { print F << "EOM"; - + Initialize VCPKG del "$cdup\\compat\\vcbuild\\vcpkg" - call "$cdup\\compat\\vcbuild\\vcpkg_install.bat" + call "$cdup\\compat\\vcbuild\\vcpkg_install.bat" EOM } diff --git a/sequencer.c b/sequencer.c index f312ce123e..a54470bb9b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -3184,10 +3184,6 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, } merge_commit = to_merge->item; - write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, - git_path_merge_head(the_repository), 0); - write_message("no-ff", 5, git_path_merge_mode(the_repository), 0); - bases = get_merge_bases(head_commit, merge_commit); if (bases && !oidcmp(&merge_commit->object.oid, &bases->item->object.oid)) { @@ -3196,6 +3192,10 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len, goto leave_merge; } + write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, + git_path_merge_head(the_repository), 0); + write_message("no-ff", 5, git_path_merge_mode(the_repository), 0); + for (j = bases; j; j = j->next) commit_list_insert(j->item, &reversed); free_commit_list(bases); @@ -3434,6 +3434,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); unlink(rebase_path_amend()); + unlink(git_path_merge_head(the_repository)); delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); } if (item->command <= TODO_SQUASH) { @@ -3790,6 +3791,7 @@ static int commit_staged_changes(struct replay_opts *opts, opts, flags)) return error(_("could not commit staged changes.")); unlink(rebase_path_amend()); + unlink(git_path_merge_head(the_repository)); if (final_fixup) { unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); diff --git a/strbuf.c b/strbuf.c index 9be7fe0ca1..b1f7219d1e 100644 --- a/strbuf.c +++ b/strbuf.c @@ -268,6 +268,21 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) strbuf_setlen(sb, sb->len + sb2->len); } +const char *strbuf_join_argv(struct strbuf *buf, + int argc, const char **argv, char delim) +{ + if (!argc) + return buf->buf; + + strbuf_addstr(buf, *argv); + while (--argc) { + strbuf_addch(buf, delim); + strbuf_addstr(buf, *(++argv)); + } + + return buf->buf; +} + void strbuf_addchars(struct strbuf *sb, int c, size_t n) { strbuf_grow(sb, n); diff --git a/strbuf.h b/strbuf.h index 9043fa17aa..9aee339a3b 100644 --- a/strbuf.h +++ b/strbuf.h @@ -287,6 +287,13 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) */ extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); +/** + * Join the arguments into a buffer. `delim` is put between every + * two arguments. + */ +const char *strbuf_join_argv(struct strbuf *buf, int argc, + const char **argv, char delim); + /** * This function can be used to expand a format string containing * placeholders. To that end, it parses the string and calls the specified diff --git a/t/README b/t/README index d174a38d91..24beceabdd 100644 --- a/t/README +++ b/t/README @@ -169,6 +169,15 @@ appropriately before running "make". implied by other options like --valgrind and GIT_TEST_INSTALLED. +--no-bin-wrappers:: + By default, the test suite uses the wrappers in + `../bin-wrappers/` to execute `git` and friends. With this option, + `../git` and friends are run directly. This is not recommended + in general, as the wrappers contain safeguards to ensure that no + files from an installed Git are used, but can speed up test runs + especially on platforms where running shell scripts is expensive + (most notably, Windows). + --root=:: Create "trash" directories used to store all temporary data during testing under , instead of the t/ directory. diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 7f19368fc1..b7a1c45881 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -486,7 +486,8 @@ test_expect_success MINGW 'redirect std handles' ' GIT_REDIRECT_STDERR="2>&1" \ git rev-parse --git-dir --verify refs/invalid && printf ".git\nfatal: Needed a single revision\n" >expect && - test_cmp expect output.txt + sort output.sorted && + test_cmp expect output.sorted ' test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 43ff4939ad..0215c96944 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -148,7 +148,8 @@ test_trace () { expect="$1" shift GIT_TRACE=1 test-tool run-command "$@" run-command true 2>&1 >/dev/null | \ - sed -e 's/.* run_command: //' -e '/trace: .*/d' >actual && + sed -e 's/.* run_command: //' -e '/trace: .*/d' \ + -e '/RUNTIME_PREFIX requested/d' >actual && echo "$expect true" >expect && test_cmp expect actual } diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index 0c4eefec76..4c7494cc8f 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -351,4 +351,22 @@ test_expect_success 'autostash is saved on editor failure with conflict' ' test_cmp expected file0 ' +test_expect_success 'autostash with dirty submodules' ' + test_when_finished "git reset --hard && git checkout master" && + git checkout -b with-submodule && + git submodule add ./ sub && + test_tick && + git commit -m add-submodule && + echo changed >sub/file0 && + git rebase -i --autostash HEAD +' + +test_expect_success 'branch is left alone when possible' ' + git checkout -b unchanged-branch && + echo changed >file0 && + git rebase --autostash unchanged-branch && + test changed = "$(cat file0)" && + test unchanged-branch = "$(git rev-parse --abbrev-ref HEAD)" +' + test_done diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index aa7bfc88ec..cc5646836f 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -396,4 +396,20 @@ test_expect_success 'with --autosquash and --exec' ' grep "G: +G" actual ' +test_expect_success '--continue after resolving conflicts after a merge' ' + git checkout -b already-has-g E && + git cherry-pick E..G && + test_commit H2 && + + git checkout -b conflicts-in-merge H && + test_commit H2 H2.t conflicts H2-conflict && + test_must_fail git rebase -r already-has-g && + grep conflicts H2.t && + echo resolved >H2.t && + git add -u && + git rebase --continue && + test_must_fail git rev-parse --verify HEAD^2 && + test_path_is_missing .git/MERGE_HEAD +' + test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 9e06494ba0..f1f6ca209e 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -36,7 +36,7 @@ EOF test_expect_success 'parents of stash' ' test $(git rev-parse stash^) = $(git rev-parse HEAD) && git diff stash^2..stash >output && - test_cmp output expect + test_cmp expect output ' test_expect_success 'applying bogus stash does nothing' ' @@ -210,9 +210,9 @@ test_expect_success 'stash branch' ' test refs/heads/stashbranch = $(git symbolic-ref HEAD) && test $(git rev-parse HEAD) = $(git rev-parse master^) && git diff --cached >output && - test_cmp output expect && + test_cmp expect output && git diff >output && - test_cmp output expect1 && + test_cmp expect1 output && git add file && git commit -m alternate\ second && git diff master..stashbranch >output && @@ -747,7 +747,7 @@ test_expect_success 'stash where working directory contains "HEAD" file' ' git diff-index --cached --quiet HEAD && test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" && git diff stash^..stash >output && - test_cmp output expect + test_cmp expect output ' test_expect_success 'store called with invalid commit' ' diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh index 8fe369c1a1..10914bba7b 100755 --- a/t/t3907-stash-show-config.sh +++ b/t/t3907-stash-show-config.sh @@ -15,26 +15,28 @@ test_expect_success 'setup' ' test_stat_and_patch () { if test "" = "$1" then - test_might_fail git config --unset stash.showStat + test_unconfig stash.showStat else test_config stash.showStat "$1" fi && if test "" = "$2" then - test_might_fail git config --unset stash.showPatch + test_unconfig stash.showPatch else test_config stash.showPatch "$2" fi && - shift && - shift && + shift 2 && echo 2 >file.t && - git diff "$@" >expect && + if test $# != 0 + then + git diff "$@" >expect + fi && git stash && git stash show >actual && - if test -z "$1" + if test $# = 0 then test_must_be_empty actual else diff --git a/t/test-lib.sh b/t/test-lib.sh index bf90aa500c..72604ec4fb 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -259,6 +259,8 @@ do test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; --with-dashes) with_dashes=t; shift ;; + --no-bin-wrappers) + no_bin_wrappers=t; shift ;; --no-color) color=; shift ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) @@ -1069,20 +1071,25 @@ then PATH=$GIT_TEST_INSTALLED$PATH_SEP$GIT_BUILD_DIR/t/helper$PATH_SEP$PATH GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} else # normal case, use ../bin-wrappers only unless $with_dashes: - git_bin_dir="$GIT_BUILD_DIR/bin-wrappers" - if ! test -x "$git_bin_dir/git" + if test -n "$no_bin_wrappers" then - if test -z "$with_dashes" - then - say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" - fi with_dashes=t + else + git_bin_dir="$GIT_BUILD_DIR/bin-wrappers" + if ! test -x "$git_bin_dir/git" + then + if test -z "$with_dashes" + then + say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" + fi + with_dashes=t + fi + PATH="$git_bin_dir$PATH_SEP$PATH" fi - PATH="$git_bin_dir$PATH_SEP$PATH" GIT_EXEC_PATH=$GIT_BUILD_DIR if test -n "$with_dashes" then - PATH="$GIT_BUILD_DIR$PATH_SEP$PATH" + PATH="$GIT_BUILD_DIR$PATH_SEP$GIT_BUILD_DIR/t/helper$PATH_SEP$PATH" fi fi GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt @@ -1106,7 +1113,7 @@ test -d "$GIT_BUILD_DIR"/templates/blt || { error "You haven't built things yet, have you?" } -if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool +if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool$X then echo >&2 'You need to build test-tool:' echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory' diff --git a/wt-status.c b/wt-status.c index 5ffab61015..30b81576a3 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1553,6 +1553,7 @@ void wt_status_get_state(struct wt_status_state *state, struct object_id oid; if (!stat(git_path_merge_head(the_repository), &st)) { + wt_status_check_rebase(NULL, state); state->merge_in_progress = 1; } else if (wt_status_check_rebase(NULL, state)) { ; /* all set */ @@ -1576,9 +1577,13 @@ static void wt_longstatus_print_state(struct wt_status *s, struct wt_status_state *state) { const char *state_color = color(WT_STATUS_HEADER, s); - if (state->merge_in_progress) + if (state->merge_in_progress) { + if (state->rebase_interactive_in_progress) { + show_rebase_information(s, state, state_color); + fputs("\n", s->fp); + } show_merge_in_progress(s, state, state_color); - else if (state->am_in_progress) + } else if (state->am_in_progress) show_am_in_progress(s, state, state_color); else if (state->rebase_in_progress || state->rebase_interactive_in_progress) show_rebase_in_progress(s, state, state_color);