commit 2e1f37b02955aef4d5bd0d0307e98da1e4b95463 from: Stefan Sperling date: Thu Aug 08 10:04:29 2019 UTC implement 'got unstage -p' commit - ad7de6a5165442ec89b9daaadb18901a981befee commit + 2e1f37b02955aef4d5bd0d0307e98da1e4b95463 blob - 180e1f604e161320c60fb88596033366830d1a36 blob + a279b6139588894ca1f2cb2ef36a76a68ab140a1 --- got/got.1 +++ got/got.1 @@ -971,7 +971,7 @@ and may then be staged again if necessary. .It Cm sg Short alias for .Cm stage . -.It Cm unstage [ Ar path ... ] +.It Cm unstage [ Fl p ] [ Fl F Ar response-script ] [ Ar path ... ] Merge staged changes back into the work tree and put affected paths back into non-staged status. If no @@ -988,6 +988,35 @@ Show the status of each affected file, using the follo .It d Ta file's deletion was obstructed by local modifications .It ~ Ta changes destined for a non-regular file were not merged .El +.Pp +The options for +.Cm got unstage +are as follows: +.Bl -tag -width Ds +.It Fl p +Instead of unstaging the entire content of a changed file, interactively +select or reject changes for unstaging based on +.Dq y +(unstage change), +.Dq n +(keep change staged), and +.Dq q +(quit unstaging this file) responses. +If a file is staged in modified status, individual patches derived from the +staged file content can be unstaged. +Files staged in added or deleted status may only be unstaged in their entirety. +.It Fl F Ar response-script +With the +.Fl p +option, read +.Dq y , +.Dq n , +and +.Dq q +responses line-by-line from the specified +.Ar response-script +file instead of prompting interactively. +.El .It Cm ug Short alias for .Cm unstage . blob - af3c2e76e6aa26242c5c90e65a3a35249dc0f83e blob + c140d3621c853710a48a9125b23fc43d77e61798 --- got/got.c +++ got/got.c @@ -5194,7 +5194,7 @@ print_stage(void *arg, unsigned char status, unsigned static const struct got_error * show_change(unsigned char status, const char *path, FILE *patch_file, int n, - int nchanges) + int nchanges, const char *action) { char *line = NULL; size_t linesize = 0; @@ -5202,10 +5202,10 @@ show_change(unsigned char status, const char *path, FI switch (status) { case GOT_STATUS_ADD: - printf("A %s\nstage this addition? [y/n] ", path); + printf("A %s\n%s this addition? [y/n] ", path, action); break; case GOT_STATUS_DELETE: - printf("D %s\nstage this deletion? [y/n] ", path); + printf("D %s\n%s this deletion? [y/n] ", path, action); break; case GOT_STATUS_MODIFY: if (fseek(patch_file, 0L, SEEK_SET) == -1) @@ -5216,8 +5216,8 @@ show_change(unsigned char status, const char *path, FI if (ferror(patch_file)) return got_error_from_errno("getline"); printf(GOT_COMMIT_SEP_STR); - printf("M %s (change %d of %d)\nstage this change? [y/n/q] ", - path, n, nchanges); + printf("M %s (change %d of %d)\n%s this change? [y/n/q] ", + path, n, nchanges, action); break; default: return got_error_path(path, GOT_ERR_FILE_STATUS); @@ -5225,6 +5225,11 @@ show_change(unsigned char status, const char *path, FI return NULL; } + +struct choose_patch_arg { + FILE *patch_script_file; + const char *action; +}; static const struct got_error * choose_patch(int *choice, void *arg, unsigned char status, const char *path, @@ -5235,18 +5240,19 @@ choose_patch(int *choice, void *arg, unsigned char sta size_t linesize = 0; ssize_t linelen; int resp = ' '; - FILE *patch_script_file = arg; + struct choose_patch_arg *a = arg; *choice = GOT_PATCH_CHOICE_NONE; - if (patch_script_file) { + if (a->patch_script_file) { char *nl; - err = show_change(status, path, patch_file, n, nchanges); + err = show_change(status, path, patch_file, n, nchanges, + a->action); if (err) return err; - linelen = getline(&line, &linesize, patch_script_file); + linelen = getline(&line, &linesize, a->patch_script_file); if (linelen == -1) { - if (ferror(patch_script_file)) + if (ferror(a->patch_script_file)) return got_error_from_errno("getline"); return NULL; } @@ -5270,7 +5276,8 @@ choose_patch(int *choice, void *arg, unsigned char sta } while (resp != 'y' && resp != 'n' && resp != 'q') { - err = show_change(status, path, patch_file, n, nchanges); + err = show_change(status, path, patch_file, n, nchanges, + a->action); if (err) return err; resp = getchar(); @@ -5307,8 +5314,9 @@ cmd_stage(int argc, char *argv[]) struct got_pathlist_head paths; struct got_pathlist_entry *pe; int ch, list_stage = 0, pflag = 0; - const char *patch_script_path = NULL; FILE *patch_script_file = NULL; + const char *patch_script_path = NULL; + struct choose_patch_arg cpa; TAILQ_INIT(&paths); @@ -5376,11 +5384,17 @@ cmd_stage(int argc, char *argv[]) if (list_stage) error = got_worktree_status(worktree, &paths, repo, print_stage, NULL, check_cancelled, NULL); - else + else { + cpa.patch_script_file = patch_script_file; + cpa.action = "stage"; error = got_worktree_stage(worktree, &paths, pflag ? NULL : print_status, NULL, - pflag ? choose_patch : NULL, patch_script_file, repo); + pflag ? choose_patch : NULL, &cpa, repo); + } done: + if (patch_script_file && fclose(patch_script_file) == EOF && + error == NULL) + error = got_error_from_errno2("fclose", patch_script_path); if (repo) got_repo_close(repo); if (worktree) @@ -5395,7 +5409,8 @@ done: __dead static void usage_unstage(void) { - fprintf(stderr, "usage: %s unstage [file-path ...]\n", + fprintf(stderr, "usage: %s unstage [-p] [-F response-script] " + "[file-path ...]\n", getprogname()); exit(1); } @@ -5410,12 +5425,21 @@ cmd_unstage(int argc, char *argv[]) char *cwd = NULL; struct got_pathlist_head paths; struct got_pathlist_entry *pe; - int ch, did_something = 0; + int ch, did_something = 0, pflag = 0; + FILE *patch_script_file = NULL; + const char *patch_script_path = NULL; + struct choose_patch_arg cpa; TAILQ_INIT(&paths); - while ((ch = getopt(argc, argv, "")) != -1) { + while ((ch = getopt(argc, argv, "pF:")) != -1) { switch (ch) { + case 'p': + pflag = 1; + break; + case 'F': + patch_script_path = optarg; + break; default: usage_unstage(); /* NOTREACHED */ @@ -5430,6 +5454,9 @@ cmd_unstage(int argc, char *argv[]) "unveil", NULL) == -1) err(1, "pledge"); #endif + if (patch_script_path && !pflag) + errx(1, "-F option can only be used together with -p option"); + cwd = getcwd(NULL, 0); if (cwd == NULL) { error = got_error_from_errno("getcwd"); @@ -5444,6 +5471,15 @@ cmd_unstage(int argc, char *argv[]) if (error != NULL) goto done; + if (patch_script_path) { + patch_script_file = fopen(patch_script_path, "r"); + if (patch_script_file == NULL) { + error = got_error_from_errno2("fopen", + patch_script_path); + goto done; + } + } + error = apply_unveil(got_repo_get_path(repo), 1, got_worktree_get_root_path(worktree)); if (error) @@ -5453,9 +5489,14 @@ cmd_unstage(int argc, char *argv[]) if (error) goto done; + cpa.patch_script_file = patch_script_file; + cpa.action = "unstage"; error = got_worktree_unstage(worktree, &paths, update_progress, - &did_something, repo); + &did_something, pflag ? choose_patch : NULL, &cpa, repo); done: + if (patch_script_file && fclose(patch_script_file) == EOF && + error == NULL) + error = got_error_from_errno2("fclose", patch_script_path); if (repo) got_repo_close(repo); if (worktree) blob - b07be0075e7e1ea62cda811facea1131cf1463c0 blob + ff51861f2e2d1f238ff5c2e4b183ecf93de910ae --- include/got_worktree.h +++ include/got_worktree.h @@ -398,4 +398,4 @@ const struct got_error *got_worktree_stage(struct got_ */ const struct got_error *got_worktree_unstage(struct got_worktree *, struct got_pathlist_head *, got_worktree_checkout_cb, void *, - struct got_repository *); + got_worktree_patch_cb, void *, struct got_repository *); blob - 04dedb5234881a9d6726e1e8da27d6eb707ceaaa blob + bd8465018eb5fe3c7e15d2992d4119c5a58f1089 --- lib/worktree.c +++ lib/worktree.c @@ -5168,7 +5168,7 @@ done: } static const struct got_error * -copy_one_line(FILE *infile, FILE *outfile) +copy_one_line(FILE *infile, FILE *outfile, FILE *rejectfile) { const struct got_error *err = NULL; char *line = NULL; @@ -5183,9 +5183,18 @@ copy_one_line(FILE *infile, FILE *outfile) } return NULL; } - n = fwrite(line, 1, linelen, outfile); - if (n != linelen) - err = got_ferror(outfile, GOT_ERR_IO); + if (outfile) { + n = fwrite(line, 1, linelen, outfile); + if (n != linelen) { + err = got_ferror(outfile, GOT_ERR_IO); + goto done; + } + } + if (rejectfile) { + n = fwrite(line, 1, linelen, rejectfile); + if (n != linelen) + err = got_ferror(outfile, GOT_ERR_IO); + } done: free(line); return err; @@ -5206,11 +5215,63 @@ skip_one_line(FILE *f) } static const struct got_error * +copy_change(FILE *f1, FILE *f2, int *line_cur1, int *line_cur2, + int start_old, int end_old, int start_new, int end_new, + FILE *outfile, FILE *rejectfile) + { + const struct got_error *err; + + /* Copy old file's lines leading up to patch. */ + while (!feof(f1) && *line_cur1 < start_old) { + err = copy_one_line(f1, outfile, NULL); + if (err) + return err; + (*line_cur1)++; + } + /* Skip new file's lines leading up to patch. */ + while (!feof(f2) && *line_cur2 < start_new) { + if (rejectfile) + err = copy_one_line(f2, NULL, rejectfile); + else + err = skip_one_line(f2); + if (err) + return err; + (*line_cur2)++; + } + /* Copy patched lines. */ + while (!feof(f2) && *line_cur2 <= end_new) { + err = copy_one_line(f2, outfile, NULL); + if (err) + return err; + (*line_cur2)++; + } + /* Skip over old file's replaced lines. */ + while (!feof(f1) && *line_cur1 <= end_new) { + if (rejectfile) + err = copy_one_line(f1, NULL, rejectfile); + else + err = skip_one_line(f1); + if (err) + return err; + (*line_cur1)++; + } + /* Copy old file's lines after patch. */ + while (!feof(f1) && *line_cur1 <= end_old) { + err = copy_one_line(f1, outfile, rejectfile); + if (err) + return err; + (*line_cur1)++; + } + + return NULL; +} + +static const struct got_error * apply_or_reject_change(int *choice, struct got_diff_change *change, int n, int nchanges, struct got_diff_state *ds, struct got_diff_args *args, int diff_flags, const char *relpath, FILE *f1, FILE *f2, int *line_cur1, - int *line_cur2, FILE *outfile, got_worktree_patch_cb patch_cb, - void *patch_arg) + int *line_cur2, FILE *outfile, FILE *rejectfile, + got_worktree_patch_cb patch_cb, void *patch_arg) { const struct got_error *err = NULL; int start_old = change->cv.a; @@ -5252,62 +5313,17 @@ apply_or_reject_change(int *choice, struct got_diff_ch switch (*choice) { case GOT_PATCH_CHOICE_YES: - /* Copy old file's lines leading up to patch. */ - while (!feof(f1) && *line_cur1 < start_old) { - err = copy_one_line(f1, outfile); - if (err) - goto done; - (*line_cur1)++; - } - /* Skip new file's lines leading up to patch. */ - while (!feof(f2) && *line_cur2 < start_new) { - err = skip_one_line(f2); - if (err) - goto done; - (*line_cur2)++; - } - /* Copy patched lines. */ - while (!feof(f2) && *line_cur2 <= end_new) { - err = copy_one_line(f2, outfile); - if (err) - goto done; - (*line_cur2)++; - } - /* Skip over old file's replaced lines. */ - while (!feof(f1) && *line_cur1 <= end_new) { - err = skip_one_line(f1); - if (err) - goto done; - (*line_cur1)++; - } - /* Copy old file's lines after patch. */ - while (!feof(f1) && *line_cur1 <= end_old) { - err = skip_one_line(f1); - if (err) - goto done; - (*line_cur1)++; - } + err = copy_change(f1, f2, line_cur1, line_cur2, start_old, + end_old, start_new, end_new, outfile, rejectfile); break; case GOT_PATCH_CHOICE_NO: - /* Copy old file's lines. */ - while (!feof(f1) && *line_cur1 <= end_old) { - err = copy_one_line(f1, outfile); - if (err) - goto done; - (*line_cur1)++; - } - /* Skip over new file's lines. */ - while (!feof(f2) && *line_cur2 <= end_new) { - err = skip_one_line(f2); - if (err) - goto done; - (*line_cur2)++; - } + err = copy_change(f1, f2, line_cur1, line_cur2, start_old, + end_old, start_new, end_new, rejectfile, outfile); break; case GOT_PATCH_CHOICE_QUIT: /* Copy old file's lines until EOF. */ while (!feof(f1)) { - err = copy_one_line(f1, outfile); + err = copy_one_line(f1, outfile, rejectfile); if (err) goto done; (*line_cur1)++; @@ -5391,7 +5407,7 @@ create_staged_content(char **path_outfile, struct got_ err = apply_or_reject_change(&choice, change, ++n, changes->nchanges, ds, args, diff_flags, relpath, f1, f2, &line_cur1, &line_cur2, - outfile, patch_cb, patch_arg); + outfile, NULL, patch_cb, patch_arg); if (err) goto done; if (choice == GOT_PATCH_CHOICE_YES) @@ -5620,8 +5636,152 @@ struct unstage_path_arg { struct got_repository *repo; got_worktree_checkout_cb progress_cb; void *progress_arg; + got_worktree_patch_cb patch_cb; + void *patch_arg; }; + +static const struct got_error * +create_unstaged_content(char **path_unstaged_content, + char **path_new_staged_content, struct got_object_id *blob_id, + struct got_object_id *staged_blob_id, const char *relpath, + struct got_repository *repo, + got_worktree_patch_cb patch_cb, void *patch_arg) +{ + const struct got_error *err; + struct got_blob_object *blob = NULL, *staged_blob = NULL; + FILE *f1 = NULL, *f2 = NULL, *outfile = NULL, *rejectfile = NULL; + char *path1 = NULL, *path2 = NULL, *label1 = NULL; + struct stat sb1, sb2; + struct got_diff_changes *changes = NULL; + struct got_diff_state *ds = NULL; + struct got_diff_args *args = NULL; + struct got_diff_change *change; + int diff_flags = 0, line_cur1 = 1, line_cur2 = 1, n = 0; + int have_content = 0, have_rejected_content = 0; + + *path_unstaged_content = NULL; + *path_new_staged_content = NULL; + + err = got_object_id_str(&label1, blob_id); + if (err) + return err; + err = got_object_open_as_blob(&blob, repo, blob_id, 8192); + if (err) + goto done; + + err = got_opentemp_named(&path1, &f1, "got-unstage-blob-base"); + if (err) + goto done; + + err = got_object_blob_dump_to_file(NULL, NULL, NULL, f1, blob); + if (err) + goto done; + + err = got_object_open_as_blob(&staged_blob, repo, staged_blob_id, 8192); + if (err) + goto done; + + err = got_opentemp_named(&path2, &f2, "got-unstage-blob-staged"); + if (err) + goto done; + err = got_object_blob_dump_to_file(NULL, NULL, NULL, f2, staged_blob); + if (err) + goto done; + + if (stat(path1, &sb1) == -1) { + err = got_error_from_errno2("stat", path1); + goto done; + } + + if (stat(path2, &sb2) == -1) { + err = got_error_from_errno2("stat", path2); + goto done; + } + + err = got_diff_files(&changes, &ds, &args, &diff_flags, + f1, sb1.st_size, label1, f2, sb2.st_size, path2, 3, NULL); + if (err) + goto done; + + err = got_opentemp_named(path_unstaged_content, &outfile, + "got-unstaged-content"); + if (err) + goto done; + err = got_opentemp_named(path_new_staged_content, &rejectfile, + "got-new-staged-content"); + if (err) + goto done; + + if (fseek(f1, 0L, SEEK_SET) == -1) { + err = got_ferror(f1, GOT_ERR_IO); + goto done; + } + if (fseek(f2, 0L, SEEK_SET) == -1) { + err = got_ferror(f2, GOT_ERR_IO); + goto done; + } + SIMPLEQ_FOREACH(change, &changes->entries, entry) { + int choice; + err = apply_or_reject_change(&choice, change, ++n, + changes->nchanges, ds, args, diff_flags, relpath, + f1, f2, &line_cur1, &line_cur2, + outfile, rejectfile, patch_cb, patch_arg); + if (err) + goto done; + if (choice == GOT_PATCH_CHOICE_YES) + have_content = 1; + if (choice == GOT_PATCH_CHOICE_NO) + have_rejected_content = 1; + if (choice == GOT_PATCH_CHOICE_QUIT) + break; + } +done: + free(label1); + if (blob) + got_object_blob_close(blob); + if (staged_blob) + got_object_blob_close(staged_blob); + if (f1 && fclose(f1) == EOF && err == NULL) + err = got_error_from_errno2("fclose", path1); + if (f2 && fclose(f2) == EOF && err == NULL) + err = got_error_from_errno2("fclose", path2); + if (outfile && fclose(outfile) == EOF && err == NULL) + err = got_error_from_errno2("fclose", *path_unstaged_content); + if (rejectfile && fclose(rejectfile) == EOF && err == NULL) + err = got_error_from_errno2("fclose", *path_new_staged_content); + if (path1 && unlink(path1) == -1 && err == NULL) + err = got_error_from_errno2("unlink", path1); + if (path2 && unlink(path2) == -1 && err == NULL) + err = got_error_from_errno2("unlink", path2); + if (err || !have_content) { + if (*path_unstaged_content && + unlink(*path_unstaged_content) == -1 && err == NULL) + err = got_error_from_errno2("unlink", + *path_unstaged_content); + free(*path_unstaged_content); + *path_unstaged_content = NULL; + } + if (err || !have_rejected_content) { + if (*path_new_staged_content && + unlink(*path_new_staged_content) == -1 && err == NULL) + err = got_error_from_errno2("unlink", + *path_new_staged_content); + free(*path_new_staged_content); + *path_new_staged_content = NULL; + } + free(args); + if (ds) { + got_diff_state_free(ds); + free(ds); + } + if (changes) + got_diff_free_changes(changes); + free(path1); + free(path2); + return err; +} + static const struct got_error * unstage_path(void *arg, unsigned char status, unsigned char staged_status, const char *relpath, @@ -5632,10 +5792,16 @@ unstage_path(void *arg, unsigned char status, struct unstage_path_arg *a = arg; struct got_fileindex_entry *ie; struct got_blob_object *blob_base = NULL, *blob_staged = NULL; - char *ondisk_path = NULL; + char *ondisk_path = NULL, *path_unstaged_content = NULL; + char *path_new_staged_content = NULL; int local_changes_subsumed; struct stat sb; + if (staged_status != GOT_STATUS_ADD && + staged_status != GOT_STATUS_MODIFY && + staged_status != GOT_STATUS_DELETE) + return NULL; + ie = got_fileindex_entry_get(a->fileindex, relpath, strlen(relpath)); if (ie == NULL) return got_error_path(relpath, GOT_ERR_BAD_PATH); @@ -5652,12 +5818,50 @@ unstage_path(void *arg, unsigned char status, break; /* fall through */ case GOT_STATUS_ADD: + if (a->patch_cb) { + if (staged_status == GOT_STATUS_ADD) { + int choice = GOT_PATCH_CHOICE_NONE; + err = (*a->patch_cb)(&choice, a->patch_arg, + staged_status, ie->path, NULL, 1, 1); + if (err) + break; + if (choice != GOT_PATCH_CHOICE_YES) + break; + } else { + err = create_unstaged_content( + &path_unstaged_content, + &path_new_staged_content, blob_id, + staged_blob_id, ie->path, a->repo, + a->patch_cb, a->patch_arg); + if (err || path_unstaged_content == NULL) + break; + if (path_new_staged_content) { + err = got_object_blob_create( + &staged_blob_id, + path_new_staged_content, + a->repo); + if (err) + break; + memcpy(ie->staged_blob_sha1, + staged_blob_id->sha1, + SHA1_DIGEST_LENGTH); + } + err = merge_file(&local_changes_subsumed, + a->worktree, blob_base, ondisk_path, + relpath, got_fileindex_perms_to_st(ie), + path_unstaged_content, "unstaged", + a->repo, a->progress_cb, a->progress_arg); + if (err == NULL && + path_new_staged_content == NULL) + got_fileindex_entry_stage_set(ie, + GOT_FILEIDX_STAGE_NONE); + break; /* Done with this file. */ + } + } err = got_object_open_as_blob(&blob_staged, a->repo, staged_blob_id, 8192); if (err) break; - - err = merge_blob(&local_changes_subsumed, a->worktree, blob_base, ondisk_path, relpath, got_fileindex_perms_to_st(ie), blob_staged, @@ -5668,6 +5872,19 @@ unstage_path(void *arg, unsigned char status, GOT_FILEIDX_STAGE_NONE); break; case GOT_STATUS_DELETE: + if (a->patch_cb) { + int choice = GOT_PATCH_CHOICE_NONE; + err = (*a->patch_cb)(&choice, a->patch_arg, + staged_status, ie->path, NULL, 1, 1); + if (err) + break; + if (choice == GOT_PATCH_CHOICE_NO) + break; + if (choice != GOT_PATCH_CHOICE_YES) { + err = got_error(GOT_ERR_PATCH_CHOICE); + break; + } + } got_fileindex_entry_stage_set(ie, GOT_FILEIDX_STAGE_NONE); err = get_file_status(&status, &sb, ie, ondisk_path, a->repo); if (err) @@ -5677,6 +5894,14 @@ unstage_path(void *arg, unsigned char status, } free(ondisk_path); + if (path_unstaged_content && + unlink(path_unstaged_content) == -1 && err == NULL) + err = got_error_from_errno2("unlink", path_unstaged_content); + if (path_new_staged_content && + unlink(path_new_staged_content) == -1 && err == NULL) + err = got_error_from_errno2("unlink", path_new_staged_content); + free(path_unstaged_content); + free(path_new_staged_content); if (blob_base) got_object_blob_close(blob_base); if (blob_staged) @@ -5688,6 +5913,7 @@ const struct got_error * got_worktree_unstage(struct got_worktree *worktree, struct got_pathlist_head *paths, got_worktree_checkout_cb progress_cb, void *progress_arg, + got_worktree_patch_cb patch_cb, void *patch_arg, struct got_repository *repo) { const struct got_error *err = NULL, *sync_err, *unlockerr; @@ -5709,6 +5935,8 @@ got_worktree_unstage(struct got_worktree *worktree, upa.repo = repo; upa.progress_cb = progress_cb; upa.progress_arg = progress_arg; + upa.patch_cb = patch_cb; + upa.patch_arg = patch_arg; TAILQ_FOREACH(pe, paths, entry) { err = worktree_status(worktree, pe->path, fileindex, repo, unstage_path, &upa, NULL, NULL); blob - 1377492b96f7e6c3e31976b4e40366a8f951e7e7 blob + aa8c840bc317b2439e162d8a7510982c44639132 --- regress/cmdline/unstage.sh +++ regress/cmdline/unstage.sh @@ -58,13 +58,633 @@ function test_unstage_basic { echo 'M alpha' > $testroot/stdout.expected echo 'D beta' >> $testroot/stdout.expected echo 'A foo' >> $testroot/stdout.expected + (cd $testroot/wt && got status > $testroot/stdout) + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + +function test_unstage_patch { + local testroot=`test_init unstage_patch` + + jot 16 > $testroot/repo/numbers + (cd $testroot/repo && git add numbers) + git_commit $testroot/repo -m "added numbers file" + local commit_id=`git_show_head $testroot/repo` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + sed -i -e 's/^2$/a/' $testroot/wt/numbers + sed -i -e 's/^7$/b/' $testroot/wt/numbers + sed -i -e 's/^16$/c/' $testroot/wt/numbers + + (cd $testroot/wt && got stage > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got stage command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + # don't unstage any hunks + printf "n\nn\nn\n" > $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + numbers > $testroot/stdout) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got stage command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + cat > $testroot/stdout.expected < $testroot/stdout) + echo " M numbers" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # unstage middle hunk + printf "n\ny\nn\n" > $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + numbers > $testroot/stdout) + + cat > $testroot/stdout.expected < $testroot/stdout) + echo "MM numbers" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff -s > $testroot/stdout) + + echo "diff $commit_id $testroot/wt (staged changes)" \ + > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i -c $commit_id \ + | grep 'numbers$' | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + cat >> $testroot/stdout.expected < $testroot/stdout) + echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo "file + numbers" >> $testroot/stdout.expected + cat >> $testroot/stdout.expected </dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got stage command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + (cd $testroot/wt && got status > $testroot/stdout) + echo " M numbers" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + # unstage last hunk + printf "n\nn\ny\n" > $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + numbers > $testroot/stdout) + + cat > $testroot/stdout.expected < $testroot/stdout) + echo "MM numbers" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff -s > $testroot/stdout) + + echo "diff $commit_id $testroot/wt (staged changes)" \ + > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i -c $commit_id \ + | grep 'numbers$' | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo -n 'blob + ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + cat >> $testroot/stdout.expected < $testroot/stdout) + echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + (cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo "file + numbers" >> $testroot/stdout.expected + cat >> $testroot/stdout.expected </dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got stage command failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + (cd $testroot/wt && got status > $testroot/stdout) + echo " M numbers" > $testroot/stdout.expected cmp -s $testroot/stdout.expected $testroot/stdout ret="$?" if [ "$ret" != "0" ]; then diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 fi + + # unstage all hunks + printf "y\ny\ny\n" > $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + numbers > $testroot/stdout) + + cat > $testroot/stdout.expected < $testroot/stdout) + echo "M numbers" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff -s > $testroot/stdout) + echo -n > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff > $testroot/stdout) + + echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i -c $commit_id \ + | grep 'numbers$' | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo 'file + numbers' >> $testroot/stdout.expected + cat >> $testroot/stdout.expected < /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo "new" > $testroot/wt/epsilon/new + (cd $testroot/wt && got add epsilon/new > /dev/null) + + (cd $testroot/wt && got stage > /dev/null) + + printf "y\n" > $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + epsilon/new > $testroot/stdout) + + echo "A epsilon/new" > $testroot/stdout.expected + echo "unstage this addition? [y/n] y" >> $testroot/stdout.expected + echo "G epsilon/new" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got status > $testroot/stdout) + echo "A epsilon/new" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff -s > $testroot/stdout) + echo -n > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff > $testroot/stdout) + + echo "diff $commit_id $testroot/wt" > $testroot/stdout.expected + echo 'blob - /dev/null' >> $testroot/stdout.expected + echo 'file + epsilon/new' >> $testroot/stdout.expected + echo "--- epsilon/new" >> $testroot/stdout.expected + echo "+++ epsilon/new" >> $testroot/stdout.expected + echo "@@ -0,0 +1 @@" >> $testroot/stdout.expected + echo "+new" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + +function test_unstage_patch_removed { + local testroot=`test_init unstage_patch_removed` + local commit_id=`git_show_head $testroot/repo` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got rm beta > /dev/null) + (cd $testroot/wt && got stage > /dev/null) + + printf "y\n" > $testroot/patchscript + (cd $testroot/wt && got unstage -F $testroot/patchscript -p \ + beta > $testroot/stdout) + + echo "D beta" > $testroot/stdout.expected + echo "unstage this deletion? [y/n] y" >> $testroot/stdout.expected + echo "D beta" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got status > $testroot/stdout) + echo "D beta" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff -s > $testroot/stdout) + echo -n > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got diff > $testroot/stdout) + + echo "diff $commit_id $testroot/wt" \ + > $testroot/stdout.expected + echo -n 'blob - ' >> $testroot/stdout.expected + got tree -r $testroot/repo -i | grep 'beta$' | cut -d' ' -f 1 \ + >> $testroot/stdout.expected + echo 'file + /dev/null' >> $testroot/stdout.expected + echo "--- beta" >> $testroot/stdout.expected + echo "+++ beta" >> $testroot/stdout.expected + echo "@@ -1 +0,0 @@" >> $testroot/stdout.expected + echo "-beta" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done "$testroot" "$ret" +} + run_test test_unstage_basic +run_test test_unstage_patch +run_test test_unstage_patch_added +run_test test_unstage_patch_removed