commit - ad7de6a5165442ec89b9daaadb18901a981befee
commit + 2e1f37b02955aef4d5bd0d0307e98da1e4b95463
blob - 180e1f604e161320c60fb88596033366830d1a36
blob + a279b6139588894ca1f2cb2ef36a76a68ab140a1
--- got/got.1
+++ got/got.1
.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
.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
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;
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)
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);
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,
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;
}
}
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();
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);
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)
__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);
}
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 */
"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");
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)
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
*/
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
}
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;
}
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;
}
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;
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)++;
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)
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,
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);
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,
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)
}
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)
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;
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
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 <<EOF
+-----------------------------------------------
+@@ -1,5 +1,5 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+-----------------------------------------------
+M numbers (change 1 of 3)
+unstage this change? [y/n/q] n
+-----------------------------------------------
+@@ -4,7 +4,7 @@
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+-----------------------------------------------
+M numbers (change 2 of 3)
+unstage this change? [y/n/q] n
+-----------------------------------------------
+@@ -13,4 +13,4 @@
+ 13
+ 14
+ 15
+-16
++c
+-----------------------------------------------
+M numbers (change 3 of 3)
+unstage this change? [y/n/q] n
+EOF
+ 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 " 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 <<EOF
+-----------------------------------------------
+@@ -1,5 +1,5 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+-----------------------------------------------
+M numbers (change 1 of 3)
+unstage this change? [y/n/q] n
+-----------------------------------------------
+@@ -4,7 +4,7 @@
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+-----------------------------------------------
+M numbers (change 2 of 3)
+unstage this change? [y/n/q] y
+-----------------------------------------------
+@@ -13,4 +13,4 @@
+ 13
+ 14
+ 15
+-16
++c
+-----------------------------------------------
+M numbers (change 3 of 3)
+unstage this change? [y/n/q] n
+G numbers
+EOF
+ 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 "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 <<EOF
+--- numbers
++++ numbers
+@@ -1,5 +1,5 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+@@ -13,4 +13,4 @@
+ 13
+ 14
+ 15
+-16
++c
+EOF
+ 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
+ (cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 \
+ >> $testroot/stdout.expected
+ echo "file + numbers" >> $testroot/stdout.expected
+ cat >> $testroot/stdout.expected <<EOF
+--- numbers
++++ numbers
+@@ -4,7 +4,7 @@ a
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+EOF
+ 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 stage >/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 <<EOF
+-----------------------------------------------
+@@ -1,5 +1,5 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+-----------------------------------------------
+M numbers (change 1 of 3)
+unstage this change? [y/n/q] n
+-----------------------------------------------
+@@ -4,7 +4,7 @@
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+-----------------------------------------------
+M numbers (change 2 of 3)
+unstage this change? [y/n/q] n
+-----------------------------------------------
+@@ -13,4 +13,4 @@
+ 13
+ 14
+ 15
+-16
++c
+-----------------------------------------------
+M numbers (change 3 of 3)
+unstage this change? [y/n/q] y
+G numbers
+EOF
+ 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 "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 <<EOF
+--- numbers
++++ numbers
+@@ -1,10 +1,10 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+EOF
+ 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
+ (cd $testroot/wt && got stage -l numbers) | cut -d' ' -f 1 \
+ >> $testroot/stdout.expected
+ echo "file + numbers" >> $testroot/stdout.expected
+ cat >> $testroot/stdout.expected <<EOF
+--- numbers
++++ numbers
+@@ -13,4 +13,4 @@ b
+ 13
+ 14
+ 15
+-16
++c
+EOF
+ 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 stage >/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 <<EOF
+-----------------------------------------------
+@@ -1,5 +1,5 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+-----------------------------------------------
+M numbers (change 1 of 3)
+unstage this change? [y/n/q] y
+-----------------------------------------------
+@@ -4,7 +4,7 @@
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+-----------------------------------------------
+M numbers (change 2 of 3)
+unstage this change? [y/n/q] y
+-----------------------------------------------
+@@ -13,4 +13,4 @@
+ 13
+ 14
+ 15
+-16
++c
+-----------------------------------------------
+M numbers (change 3 of 3)
+unstage this change? [y/n/q] y
+G numbers
+EOF
+ 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 "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 <<EOF
+--- numbers
++++ numbers
+@@ -1,10 +1,10 @@
+ 1
+-2
++a
+ 3
+ 4
+ 5
+ 6
+-7
++b
+ 8
+ 9
+ 10
+@@ -13,4 +13,4 @@
+ 13
+ 14
+ 15
+-16
++c
+EOF
+ 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_added {
+ local testroot=`test_init unstage_patch_added`
+ 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
+
+ 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