stash: convert create to builtin

Add stash create to the helper. Signed-off-by: Paul-Sebastian Ungureanu <ungureanupaulsebastian@gmail.com> Helped-by: Matthew Kraai <mkraai@its.jnj.com> Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paul-Sebastian Ungureanu committed Feb 25, 2019 at 23:16 UTC d4788af875cc5c027c42b85435b022edb00b9fda
2 files changed +449 -2
builtin/stash--helper.c
+448 -1
@@ -12,6 +12,9 @@
12 #include "rerere.h"
13 #include "revision.h"
14 #include "log-tree.h"
15 +#include "diffcore.h"
16 +
17 +#define INCLUDE_ALL_FILES 2
18
19 static const char * const git_stash_helper_usage[] = {
20 N_("git stash--helper list [<options>]"),
@@ -63,6 +66,11 @@ static const char * const git_stash_helper_store_usage[] = {
66 NULL
67 };
68
69 +static const char * const git_stash_helper_create_usage[] = {
70 + N_("git stash--helper create [<message>]"),
71 + NULL
72 +};
73 +
74 static const char *ref_stash = "refs/stash";
75 static struct strbuf stash_index_path = STRBUF_INIT;
76
@@ -287,6 +295,20 @@ static int reset_head(void)
295 return run_command(&cp);
296 }
297
298 +static void add_diff_to_buf(struct diff_queue_struct *q,
299 + struct diff_options *options,
300 + void *data)
301 +{
302 + int i;
303 +
304 + for (i = 0; i < q->nr; i++) {
305 + strbuf_addstr(data, q->queue[i]->one->path);
306 +
307 + /* NUL-terminate: will be fed to update-index -z */
308 + strbuf_addch(data, '\0');
309 + }
310 +}
311 +
312 static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
313 {
314 struct child_process cp = CHILD_PROCESS_INIT;
@@ -789,6 +811,429 @@ static int store_stash(int argc, const char **argv, const char *prefix)
811 return do_store_stash(&obj, stash_msg, quiet);
812 }
813
814 +static void add_pathspecs(struct argv_array *args,
815 + struct pathspec ps) {
816 + int i;
817 +
818 + for (i = 0; i < ps.nr; i++)
819 + argv_array_push(args, ps.items[i].match);
820 +}
821 +
822 +/*
823 + * `untracked_files` will be filled with the names of untracked files.
824 + * The return value is:
825 + *
826 + * = 0 if there are not any untracked files
827 + * > 0 if there are untracked files
828 + */
829 +static int get_untracked_files(struct pathspec ps, int include_untracked,
830 + struct strbuf *untracked_files)
831 +{
832 + int i;
833 + int max_len;
834 + int found = 0;
835 + char *seen;
836 + struct dir_struct dir;
837 +
838 + memset(&dir, 0, sizeof(dir));
839 + if (include_untracked != INCLUDE_ALL_FILES)
840 + setup_standard_excludes(&dir);
841 +
842 + seen = xcalloc(ps.nr, 1);
843 +
844 + max_len = fill_directory(&dir, the_repository->index, &ps);
845 + for (i = 0; i < dir.nr; i++) {
846 + struct dir_entry *ent = dir.entries[i];
847 + if (dir_path_match(&the_index, ent, &ps, max_len, seen)) {
848 + found++;
849 + strbuf_addstr(untracked_files, ent->name);
850 + /* NUL-terminate: will be fed to update-index -z */
851 + strbuf_addch(untracked_files, '\0');
852 + }
853 + free(ent);
854 + }
855 +
856 + free(seen);
857 + free(dir.entries);
858 + free(dir.ignored);
859 + clear_directory(&dir);
860 + return found;
861 +}
862 +
863 +/*
864 + * The return value of `check_changes()` can be:
865 + *
866 + * < 0 if there was an error
867 + * = 0 if there are no changes.
868 + * > 0 if there are changes.
869 + */
870 +static int check_changes(struct pathspec ps, int include_untracked)
871 +{
872 + int result;
873 + struct rev_info rev;
874 + struct object_id dummy;
875 + struct strbuf out = STRBUF_INIT;
876 +
877 + /* No initial commit. */
878 + if (get_oid("HEAD", &dummy))
879 + return -1;
880 +
881 + if (read_cache() < 0)
882 + return -1;
883 +
884 + init_revisions(&rev, NULL);
885 + rev.prune_data = ps;
886 +
887 + rev.diffopt.flags.quick = 1;
888 + rev.diffopt.flags.ignore_submodules = 1;
889 + rev.abbrev = 0;
890 +
891 + add_head_to_pending(&rev);
892 + diff_setup_done(&rev.diffopt);
893 +
894 + result = run_diff_index(&rev, 1);
895 + if (diff_result_code(&rev.diffopt, result))
896 + return 1;
897 +
898 + object_array_clear(&rev.pending);
899 + result = run_diff_files(&rev, 0);
900 + if (diff_result_code(&rev.diffopt, result))
901 + return 1;
902 +
903 + if (include_untracked && get_untracked_files(ps, include_untracked,
904 + &out)) {
905 + strbuf_release(&out);
906 + return 1;
907 + }
908 +
909 + strbuf_release(&out);
910 + return 0;
911 +}
912 +
913 +static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
914 + struct strbuf files)
915 +{
916 + int ret = 0;
917 + struct strbuf untracked_msg = STRBUF_INIT;
918 + struct strbuf out = STRBUF_INIT;
919 + struct child_process cp_upd_index = CHILD_PROCESS_INIT;
920 + struct child_process cp_write_tree = CHILD_PROCESS_INIT;
921 +
922 + cp_upd_index.git_cmd = 1;
923 + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
924 + "--remove", "--stdin", NULL);
925 + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
926 + stash_index_path.buf);
927 +
928 + strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf);
929 + if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0,
930 + NULL, 0)) {
931 + ret = -1;
932 + goto done;
933 + }
934 +
935 + cp_write_tree.git_cmd = 1;
936 + argv_array_push(&cp_write_tree.args, "write-tree");
937 + argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
938 + stash_index_path.buf);
939 + if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
940 + ret = -1;
941 + goto done;
942 + }
943 + get_oid_hex(out.buf, &info->u_tree);
944 +
945 + if (commit_tree(untracked_msg.buf, untracked_msg.len,
946 + &info->u_tree, NULL, &info->u_commit, NULL, NULL)) {
947 + ret = -1;
948 + goto done;
949 + }
950 +
951 +done:
952 + strbuf_release(&untracked_msg);
953 + strbuf_release(&out);
954 + remove_path(stash_index_path.buf);
955 + return ret;
956 +}
957 +
958 +static int stash_patch(struct stash_info *info, struct pathspec ps,
959 + struct strbuf *out_patch)
960 +{
961 + int ret = 0;
962 + struct strbuf out = STRBUF_INIT;
963 + struct child_process cp_read_tree = CHILD_PROCESS_INIT;
964 + struct child_process cp_add_i = CHILD_PROCESS_INIT;
965 + struct child_process cp_write_tree = CHILD_PROCESS_INIT;
966 + struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
967 +
968 + remove_path(stash_index_path.buf);
969 +
970 + cp_read_tree.git_cmd = 1;
971 + argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL);
972 + argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s",
973 + stash_index_path.buf);
974 + if (run_command(&cp_read_tree)) {
975 + ret = -1;
976 + goto done;
977 + }
978 +
979 + /* Find out what the user wants. */
980 + cp_add_i.git_cmd = 1;
981 + argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
982 + "--", NULL);
983 + add_pathspecs(&cp_add_i.args, ps);
984 + argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
985 + stash_index_path.buf);
986 + if (run_command(&cp_add_i)) {
987 + ret = -1;
988 + goto done;
989 + }
990 +
991 + /* State of the working tree. */
992 + cp_write_tree.git_cmd = 1;
993 + argv_array_push(&cp_write_tree.args, "write-tree");
994 + argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
995 + stash_index_path.buf);
996 + if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
997 + ret = -1;
998 + goto done;
999 + }
1000 +
1001 + get_oid_hex(out.buf, &info->w_tree);
1002 +
1003 + cp_diff_tree.git_cmd = 1;
1004 + argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
1005 + oid_to_hex(&info->w_tree), "--", NULL);
1006 + if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
1007 + ret = -1;
1008 + goto done;
1009 + }
1010 +
1011 + if (!out_patch->len) {
1012 + fprintf_ln(stderr, _("No changes selected"));
1013 + ret = 1;
1014 + }
1015 +
1016 +done:
1017 + strbuf_release(&out);
1018 + remove_path(stash_index_path.buf);
1019 + return ret;
1020 +}
1021 +
1022 +static int stash_working_tree(struct stash_info *info, struct pathspec ps)
1023 +{
1024 + int ret = 0;
1025 + struct rev_info rev;
1026 + struct child_process cp_upd_index = CHILD_PROCESS_INIT;
1027 + struct child_process cp_write_tree = CHILD_PROCESS_INIT;
1028 + struct strbuf out = STRBUF_INIT;
1029 + struct strbuf diff_output = STRBUF_INIT;
1030 +
1031 + init_revisions(&rev, NULL);
1032 +
1033 + set_alternate_index_output(stash_index_path.buf);
1034 + if (reset_tree(&info->i_tree, 0, 0)) {
1035 + ret = -1;
1036 + goto done;
1037 + }
1038 + set_alternate_index_output(NULL);
1039 +
1040 + rev.prune_data = ps;
1041 + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
1042 + rev.diffopt.format_callback = add_diff_to_buf;
1043 + rev.diffopt.format_callback_data = &diff_output;
1044 +
1045 + if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
1046 + ret = -1;
1047 + goto done;
1048 + }
1049 +
1050 + add_pending_object(&rev, parse_object(the_repository, &info->b_commit),
1051 + "");
1052 + if (run_diff_index(&rev, 0)) {
1053 + ret = -1;
1054 + goto done;
1055 + }
1056 +
1057 + cp_upd_index.git_cmd = 1;
1058 + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
1059 + "--remove", "--stdin", NULL);
1060 + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
1061 + stash_index_path.buf);
1062 +
1063 + if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len,
1064 + NULL, 0, NULL, 0)) {
1065 + ret = -1;
1066 + goto done;
1067 + }
1068 +
1069 + cp_write_tree.git_cmd = 1;
1070 + argv_array_push(&cp_write_tree.args, "write-tree");
1071 + argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s",
1072 + stash_index_path.buf);
1073 + if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) {
1074 + ret = -1;
1075 + goto done;
1076 + }
1077 +
1078 + get_oid_hex(out.buf, &info->w_tree);
1079 +
1080 +done:
1081 + UNLEAK(rev);
1082 + strbuf_release(&out);
1083 + object_array_clear(&rev.pending);
1084 + strbuf_release(&diff_output);
1085 + remove_path(stash_index_path.buf);
1086 + return ret;
1087 +}
1088 +
1089 +static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf,
1090 + int include_untracked, int patch_mode,
1091 + struct stash_info *info)
1092 +{
1093 + int ret = 0;
1094 + int flags = 0;
1095 + int untracked_commit_option = 0;
1096 + const char *head_short_sha1 = NULL;
1097 + const char *branch_ref = NULL;
1098 + const char *branch_name = "(no branch)";
1099 + struct commit *head_commit = NULL;
1100 + struct commit_list *parents = NULL;
1101 + struct strbuf msg = STRBUF_INIT;
1102 + struct strbuf commit_tree_label = STRBUF_INIT;
1103 + struct strbuf untracked_files = STRBUF_INIT;
1104 + struct strbuf patch = STRBUF_INIT;
1105 +
1106 + prepare_fallback_ident("git stash", "git@stash");
1107 +
1108 + read_cache_preload(NULL);
1109 + refresh_cache(REFRESH_QUIET);
1110 +
1111 + if (get_oid("HEAD", &info->b_commit)) {
1112 + fprintf_ln(stderr, _("You do not have the initial commit yet"));
1113 + ret = -1;
1114 + goto done;
1115 + } else {
1116 + head_commit = lookup_commit(the_repository, &info->b_commit);
1117 + }
1118 +
1119 + if (!check_changes(ps, include_untracked)) {
1120 + ret = 1;
1121 + goto done;
1122 + }
1123 +
1124 + branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
1125 + if (flags & REF_ISSYMREF)
1126 + branch_name = strrchr(branch_ref, '/') + 1;
1127 + head_short_sha1 = find_unique_abbrev(&head_commit->object.oid,
1128 + DEFAULT_ABBREV);
1129 + strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1);
1130 + pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg);
1131 +
1132 + strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf);
1133 + commit_list_insert(head_commit, &parents);
1134 + if (write_cache_as_tree(&info->i_tree, 0, NULL) ||
1135 + commit_tree(commit_tree_label.buf, commit_tree_label.len,
1136 + &info->i_tree, parents, &info->i_commit, NULL, NULL)) {
1137 + fprintf_ln(stderr, _("Cannot save the current index state"));
1138 + ret = -1;
1139 + goto done;
1140 + }
1141 +
1142 + if (include_untracked && get_untracked_files(ps, include_untracked,
1143 + &untracked_files)) {
1144 + if (save_untracked_files(info, &msg, untracked_files)) {
1145 + fprintf_ln(stderr, _("Cannot save "
1146 + "the untracked files"));
1147 + ret = -1;
1148 + goto done;
1149 + }
1150 + untracked_commit_option = 1;
1151 + }
1152 + if (patch_mode) {
1153 + ret = stash_patch(info, ps, &patch);
1154 + if (ret < 0) {
1155 + fprintf_ln(stderr, _("Cannot save the current "
1156 + "worktree state"));
1157 + goto done;
1158 + } else if (ret > 0) {
1159 + goto done;
1160 + }
1161 + } else {
1162 + if (stash_working_tree(info, ps)) {
1163 + fprintf_ln(stderr, _("Cannot save the current "
1164 + "worktree state"));
1165 + ret = -1;
1166 + goto done;
1167 + }
1168 + }
1169 +
1170 + if (!stash_msg_buf->len)
1171 + strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf);
1172 + else
1173 + strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name);
1174 +
1175 + /*
1176 + * `parents` will be empty after calling `commit_tree()`, so there is
1177 + * no need to call `free_commit_list()`
1178 + */
1179 + parents = NULL;
1180 + if (untracked_commit_option)
1181 + commit_list_insert(lookup_commit(the_repository,
1182 + &info->u_commit),
1183 + &parents);
1184 + commit_list_insert(lookup_commit(the_repository, &info->i_commit),
1185 + &parents);
1186 + commit_list_insert(head_commit, &parents);
1187 +
1188 + if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree,
1189 + parents, &info->w_commit, NULL, NULL)) {
1190 + fprintf_ln(stderr, _("Cannot record working tree state"));
1191 + ret = -1;
1192 + goto done;
1193 + }
1194 +
1195 +done:
1196 + strbuf_release(&commit_tree_label);
1197 + strbuf_release(&msg);
1198 + strbuf_release(&untracked_files);
1199 + return ret;
1200 +}
1201 +
1202 +static int create_stash(int argc, const char **argv, const char *prefix)
1203 +{
1204 + int include_untracked = 0;
1205 + int ret = 0;
1206 + const char *stash_msg = NULL;
1207 + struct strbuf stash_msg_buf = STRBUF_INIT;
1208 + struct stash_info info;
1209 + struct pathspec ps;
1210 + struct option options[] = {
1211 + OPT_BOOL('u', "include-untracked", &include_untracked,
1212 + N_("include untracked files in stash")),
1213 + OPT_STRING('m', "message", &stash_msg, N_("message"),
1214 + N_("stash message")),
1215 + OPT_END()
1216 + };
1217 +
1218 + argc = parse_options(argc, argv, prefix, options,
1219 + git_stash_helper_create_usage,
1220 + 0);
1221 +
1222 + memset(&ps, 0, sizeof(ps));
1223 + strbuf_addstr(&stash_msg_buf, stash_msg);
1224 + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info);
1225 + if (!ret)
1226 + printf_ln("%s", oid_to_hex(&info.w_commit));
1227 +
1228 + strbuf_release(&stash_msg_buf);
1229 +
1230 + /*
1231 + * ret can be 1 if there were no changes. In this case, we should
1232 + * not error out.
1233 + */
1234 + return ret < 0;
1235 +}
1236 +
1237 int cmd_stash__helper(int argc, const char **argv, const char *prefix)
1238 {
1239 pid_t pid = getpid();
@@ -798,7 +1243,7 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
1243 OPT_END()
1244 };
1245
801 - git_config(git_default_config, NULL);
1246 + git_config(git_diff_basic_config, NULL);
1247
1248 argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage,
1249 PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
@@ -825,6 +1270,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix)
1270 return !!show_stash(argc, argv, prefix);
1271 else if (!strcmp(argv[0], "store"))
1272 return !!store_stash(argc, argv, prefix);
1273 + else if (!strcmp(argv[0], "create"))
1274 + return !!create_stash(argc, argv, prefix);
1275
1276 usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
1277 git_stash_helper_usage, options);
git-stash.sh
+1 -1
@@ -442,7 +442,7 @@ clear)
442 ;;
443 create)
444 shift
445 - create_stash -m "$*" && echo "$w_commit"
445 + git stash--helper create --message "$*"
446 ;;
447 store)
448 shift