commit 2822a3526b8c61302ec86e0871e724c07a4b078d from: Stefan Sperling date: Tue Oct 15 11:38:27 2019 UTC add 'got integrate' command commit - 13add98862efe69781a0693d9af2c45d235f1f43 commit + 2822a3526b8c61302ec86e0871e724c07a4b078d blob - c34b1bcb69fd3e41f6c82be87c06d624f89bf175 blob + 841f4902e5bee78beec97707fdf93aec1087eab9 --- got/got.1 +++ got/got.1 @@ -1042,6 +1042,58 @@ If this option is used, no other command-line argument .It Cm he Short alias for .Cm histedit . +.It Cm integrate Ar branch +Integrate the specified +.Ar branch +into the work tree's current branch. +Files in the work tree are updated to match the contents on the integrated +.Ar branch , +and the reference of the work tree's branch is changed to point at the +head commit of the integrated +.Ar branch . +.Pp +Both branches can be considered equivalent after integration since they +will be pointing at the same commit. +Both branches remain available for future work, if desired. +In case the integrated +.Ar branch +is no longer needed it may be deleted with +.Cm got branch -d . +.Pp +Show the status of each affected file, using the following status codes: +.Bl -column YXZ description +.It U Ta file was updated +.It D Ta file was deleted +.It A Ta new file was added +.It \(a~ Ta versioned file is obstructed by a non-regular file +.It ! Ta a missing versioned file was restored +.El +.Pp +.Cm got integrate +will refuse to run if certain preconditions are not met. +Most importantly, the +.Ar branch +must have been rebased onto the work tree's current branch with +.Cm got rebase +before it can be integrated, in order to linearize commit history and +resolve merge conflicts. +If the work tree contains multiple base commits it must first be updated +to a single base commit with +.Cm got update . +If changes have been staged with +.Cm got stage , +these changes must first be committed with +.Cm got commit +or unstaged with +.Cm got unstage . +If the work tree contains local changes, these changes must first be +committed with +.Cm got commit +or reverted with +.Cm got revert . +.It Cm ig +Short alias for +.Cm integrate . .It Cm stage Oo Fl l Oc Oo Fl p Oc Oo Fl F Ar response-script Oc Op Ar path ... Stage local changes for inclusion in the next commit. If no blob - 206285202f7c507b7a0760eda2c197cf1053c16e blob + c2246a9a4a1eb2d49378c3096a50bcbf472739e2 --- got/got.c +++ got/got.c @@ -97,6 +97,7 @@ __dead static void usage_cherrypick(void); __dead static void usage_backout(void); __dead static void usage_rebase(void); __dead static void usage_histedit(void); +__dead static void usage_integrate(void); __dead static void usage_stage(void); __dead static void usage_unstage(void); __dead static void usage_cat(void); @@ -121,6 +122,7 @@ static const struct got_error* cmd_cherrypick(int, ch static const struct got_error* cmd_backout(int, char *[]); static const struct got_error* cmd_rebase(int, char *[]); static const struct got_error* cmd_histedit(int, char *[]); +static const struct got_error* cmd_integrate(int, char *[]); static const struct got_error* cmd_stage(int, char *[]); static const struct got_error* cmd_unstage(int, char *[]); static const struct got_error* cmd_cat(int, char *[]); @@ -146,6 +148,7 @@ static struct got_cmd got_commands[] = { { "backout", cmd_backout, usage_backout, "bo" }, { "rebase", cmd_rebase, usage_rebase, "rb" }, { "histedit", cmd_histedit, usage_histedit, "he" }, + { "integrate", cmd_integrate, usage_integrate,"ig" }, { "stage", cmd_stage, usage_stage, "sg" }, { "unstage", cmd_unstage, usage_unstage, "ug" }, { "cat", cmd_cat, usage_cat, "" }, @@ -6452,8 +6455,141 @@ done: got_ref_close(tmp_branch); if (worktree) got_worktree_close(worktree); + if (repo) + got_repo_close(repo); + return error; +} + +__dead static void +usage_integrate(void) +{ + fprintf(stderr, "usage: %s integrate branch\n", getprogname()); + exit(1); +} + +static const struct got_error * +cmd_integrate(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *cwd = NULL, *refname = NULL, *base_refname = NULL; + const char *branch_arg = NULL; + struct got_reference *branch_ref = NULL, *base_branch_ref = NULL; + struct got_fileindex *fileindex = NULL; + struct got_object_id *commit_id = NULL, *base_commit_id = NULL; + int ch, did_something = 0; + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + usage_integrate(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) + usage_integrate(); + branch_arg = argv[0]; + + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " + "unveil", NULL) == -1) + err(1, "pledge"); + + cwd = getcwd(NULL, 0); + if (cwd == NULL) { + error = got_error_from_errno("getcwd"); + goto done; + } + + error = got_worktree_open(&worktree, cwd); + if (error) + goto done; + + error = check_rebase_or_histedit_in_progress(worktree); + if (error) + goto done; + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), + NULL); + if (error != NULL) + goto done; + + error = apply_unveil(got_repo_get_path(repo), 0, + got_worktree_get_root_path(worktree)); + if (error) + goto done; + + if (asprintf(&refname, "refs/heads/%s", branch_arg) == -1) { + error = got_error_from_errno("asprintf"); + goto done; + } + + error = got_worktree_integrate_prepare(&fileindex, &branch_ref, + &base_branch_ref, worktree, refname, repo); + if (error) + goto done; + + refname = strdup(got_ref_get_name(branch_ref)); + if (refname == NULL) { + error = got_error_from_errno("strdup"); + got_worktree_integrate_abort(worktree, fileindex, repo, + branch_ref, base_branch_ref); + goto done; + } + base_refname = strdup(got_ref_get_name(base_branch_ref)); + if (base_refname == NULL) { + error = got_error_from_errno("strdup"); + got_worktree_integrate_abort(worktree, fileindex, repo, + branch_ref, base_branch_ref); + goto done; + } + + error = got_ref_resolve(&commit_id, repo, branch_ref); + if (error) + goto done; + + error = got_ref_resolve(&base_commit_id, repo, base_branch_ref); + if (error) + goto done; + + if (got_object_id_cmp(commit_id, base_commit_id) == 0) { + error = got_error_msg(GOT_ERR_SAME_BRANCH, + "specified branch has already been integrated"); + got_worktree_integrate_abort(worktree, fileindex, repo, + branch_ref, base_branch_ref); + goto done; + } + + error = check_linear_ancestry(commit_id, base_commit_id, repo); + if (error) { + if (error->code == GOT_ERR_ANCESTRY) + error = got_error(GOT_ERR_REBASE_REQUIRED); + got_worktree_integrate_abort(worktree, fileindex, repo, + branch_ref, base_branch_ref); + goto done; + } + + error = got_worktree_integrate_continue(worktree, fileindex, repo, + branch_ref, base_branch_ref, update_progress, &did_something, + check_cancelled, NULL); + if (error) + goto done; + + printf("Integrated %s into %s\n", refname, base_refname); +done: if (repo) got_repo_close(repo); + if (worktree) + got_worktree_close(worktree); + free(cwd); + free(base_commit_id); + free(commit_id); + free(refname); + free(base_refname); return error; } blob - f8e9822b47d297dd837ffbfe9f018be2143c3b44 blob + f1c654737061914c1e6789930e5d84be5117070c --- include/got_error.h +++ include/got_error.h @@ -124,6 +124,7 @@ #define GOT_ERR_COMMIT_NO_EMAIL 108 #define GOT_ERR_TAG_EXISTS 109 #define GOT_ERR_GIT_REPO_FORMAT 110 +#define GOT_ERR_REBASE_REQUIRED 111 static const struct got_error { int code; @@ -254,6 +255,7 @@ static const struct got_error { "with Git" }, { GOT_ERR_TAG_EXISTS,"specified tag already exists" }, { GOT_ERR_GIT_REPO_FORMAT,"unknown git repository format version" }, + { GOT_ERR_REBASE_REQUIRED,"specified branch must be rebased first" }, }; /* blob - 096c4e4ceac795c28e6da4fba2b9be6fd783015b blob + add6c8aa88f249f82d6451270fc7c7c6952abc9b --- include/got_worktree.h +++ include/got_worktree.h @@ -382,8 +382,31 @@ const struct got_error *got_worktree_histedit_abort(st const struct got_error *got_worktree_get_histedit_script_path(char **, struct got_worktree *); +/* + * Prepare a work tree for integrating a branch. + * Return pointers to a fileindex and locked references which must be + * passed back to other integrate-related functions. + */ +const struct got_error * +got_worktree_integrate_prepare(struct got_fileindex **, + struct got_reference **, struct got_reference **, + struct got_worktree *, const char *, struct got_repository *); /* + * Carry out a prepared branch integration operation. + * Report affected files via the specified progress callback. + */ +const struct got_error *got_worktree_integrate_continue( + struct got_worktree *, struct got_fileindex *, struct got_repository *, + struct got_reference *, struct got_reference *, + got_worktree_checkout_cb, void *, got_cancel_cb, void *); + +/* Abort a prepared branch integration operation. */ +const struct got_error *got_worktree_integrate_abort(struct got_worktree *, + struct got_fileindex *, struct got_repository *, + struct got_reference *, struct got_reference *); + +/* * Stage the specified paths for commit. * If the patch callback is not NULL, call it to select patch hunks for * staging. Otherwise, stage the full file content found at each path. blob - d7dc1224936d7d6588be61fac9a25737e3bc8965 blob + 35d6705944c9ba45ca47f53a0109f5cc466e5957 --- lib/worktree.c +++ lib/worktree.c @@ -5610,9 +5610,146 @@ got_worktree_histedit_skip_commit(struct got_worktree err = delete_ref(commit_ref_name, repo); done: free(commit_ref_name); + return err; +} + +const struct got_error * +got_worktree_integrate_prepare(struct got_fileindex **fileindex, + struct got_reference **branch_ref, struct got_reference **base_branch_ref, + struct got_worktree *worktree, const char *refname, + struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *fileindex_path = NULL; + struct check_rebase_ok_arg ok_arg; + + *fileindex = NULL; + *branch_ref = NULL; + *base_branch_ref = NULL; + + err = lock_worktree(worktree, LOCK_EX); + if (err) + return err; + + if (strcmp(refname, got_worktree_get_head_ref_name(worktree)) == 0) { + err = got_error_msg(GOT_ERR_SAME_BRANCH, + "cannot integrate a branch into itself; " + "update -b or different branch name required"); + goto done; + } + + err = open_fileindex(fileindex, &fileindex_path, worktree); + if (err) + goto done; + + /* Preconditions are the same as for rebase. */ + ok_arg.worktree = worktree; + ok_arg.repo = repo; + err = got_fileindex_for_each_entry_safe(*fileindex, check_rebase_ok, + &ok_arg); + if (err) + goto done; + + err = got_ref_open(branch_ref, repo, refname, 1); + if (err) + goto done; + + err = got_ref_open(base_branch_ref, repo, + got_worktree_get_head_ref_name(worktree), 1); +done: + if (err) { + if (*branch_ref) { + got_ref_close(*branch_ref); + *branch_ref = NULL; + } + if (*base_branch_ref) { + got_ref_close(*base_branch_ref); + *base_branch_ref = NULL; + } + if (*fileindex) { + got_fileindex_free(*fileindex); + *fileindex = NULL; + } + lock_worktree(worktree, LOCK_SH); + } return err; } +const struct got_error * +got_worktree_integrate_continue(struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_repository *repo, + struct got_reference *branch_ref, struct got_reference *base_branch_ref, + got_worktree_checkout_cb progress_cb, void *progress_arg, + got_cancel_cb cancel_cb, void *cancel_arg) +{ + const struct got_error *err = NULL, *sync_err, *unlockerr; + char *fileindex_path = NULL; + struct got_object_id *tree_id = NULL, *commit_id = NULL; + + err = get_fileindex_path(&fileindex_path, worktree); + if (err) + goto done; + + err = got_ref_resolve(&commit_id, repo, branch_ref); + if (err) + goto done; + + err = got_object_id_by_path(&tree_id, repo, commit_id, + worktree->path_prefix); + if (err) + goto done; + + err = got_worktree_set_base_commit_id(worktree, repo, commit_id); + if (err) + goto done; + + err = checkout_files(worktree, fileindex, "", tree_id, NULL, repo, + progress_cb, progress_arg, cancel_cb, cancel_arg); + if (err) + goto sync; + + err = got_ref_change_ref(base_branch_ref, commit_id); + if (err) + goto sync; + + err = got_ref_write(base_branch_ref, repo); +sync: + sync_err = sync_fileindex(fileindex, fileindex_path); + if (sync_err && err == NULL) + err = sync_err; + +done: + unlockerr = got_ref_unlock(branch_ref); + if (unlockerr && err == NULL) + err = unlockerr; + got_ref_close(branch_ref); + + unlockerr = got_ref_unlock(base_branch_ref); + if (unlockerr && err == NULL) + err = unlockerr; + got_ref_close(base_branch_ref); + + got_fileindex_free(fileindex); + free(fileindex_path); + free(tree_id); + + unlockerr = lock_worktree(worktree, LOCK_SH); + if (unlockerr && err == NULL) + err = unlockerr; + return err; +} + +const struct got_error * +got_worktree_integrate_abort(struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_repository *repo, + struct got_reference *branch_ref, struct got_reference *base_branch_ref) +{ + got_ref_close(branch_ref); + got_ref_close(base_branch_ref); + got_fileindex_free(fileindex); + return lock_worktree(worktree, LOCK_SH); +} + struct check_stage_ok_arg { struct got_object_id *head_commit_id; struct got_worktree *worktree; blob - 535e0b8450f2e076c694aaae464ec319797b783f blob + 65f8a62feba0842c2e9667e380c20958719414c8 --- regress/cmdline/Makefile +++ regress/cmdline/Makefile @@ -1,6 +1,6 @@ REGRESS_TARGETS=checkout update status log add rm diff blame branch tag \ - ref commit revert cherrypick backout rebase import histedit stage \ - unstage cat + ref commit revert cherrypick backout rebase import histedit \ + integrate stage unstage cat NOOBJ=Yes checkout: @@ -57,6 +57,9 @@ import: histedit: ./histedit.sh +integrate: + ./integrate.sh + stage: ./stage.sh blob - /dev/null blob + 86d2fa9ed1614f01bad8d546d2b87b57266c3147 (mode 644) --- /dev/null +++ regress/cmdline/integrate.sh @@ -0,0 +1,297 @@ +#!/bin/sh +# +# Copyright (c) 2019 Stefan Sperling +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +. ./common.sh + +function test_integrate_basic { + local testroot=`test_init integrate_basic` + + (cd $testroot/repo && git checkout -q -b newbranch) + echo "modified delta on branch" > $testroot/repo/gamma/delta + git_commit $testroot/repo -m "committing to delta on newbranch" + + echo "modified alpha on branch" > $testroot/repo/alpha + (cd $testroot/repo && git rm -q beta) + echo "new file on branch" > $testroot/repo/epsilon/new + (cd $testroot/repo && git add epsilon/new) + git_commit $testroot/repo -m "committing more changes on newbranch" + + local orig_commit1=`git_show_parent_commit $testroot/repo` + local orig_commit2=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q master) + echo "modified zeta on master" > $testroot/repo/epsilon/zeta + git_commit $testroot/repo -m "committing to zeta on master" + local master_commit=`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 rebase newbranch > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got rebase failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && git checkout -q newbranch) + local new_commit1=`git_show_parent_commit $testroot/repo` + local new_commit2=`git_show_head $testroot/repo` + + (cd $testroot/wt && got update -b master > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got update failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got integrate newbranch > $testroot/stdout) + + echo "U alpha" > $testroot/stdout.expected + echo "D beta" >> $testroot/stdout.expected + echo "A epsilon/new" >> $testroot/stdout.expected + echo "U gamma/delta" >> $testroot/stdout.expected + echo "Integrated refs/heads/newbranch into refs/heads/master" \ + >> $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 + + echo "modified delta on branch" > $testroot/content.expected + cat $testroot/wt/gamma/delta > $testroot/content + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + echo "modified alpha on branch" > $testroot/content.expected + cat $testroot/wt/alpha > $testroot/content + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + if [ -e $testroot/wt/beta ]; then + echo "removed file beta still exists on disk" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "new file on branch" > $testroot/content.expected + cat $testroot/wt/epsilon/new > $testroot/content + cmp -s $testroot/content.expected $testroot/content + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/content.expected $testroot/content + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got status > $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 log -l3 | grep ^commit > $testroot/stdout) + echo "commit $new_commit2 (master, newbranch)" \ + > $testroot/stdout.expected + echo "commit $new_commit1" >> $testroot/stdout.expected + echo "commit $master_commit" >> $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_integrate_requires_rebase_first { + local testroot=`test_init integrate_requires_rebase_first` + local init_commit=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q -b newbranch) + echo "modified delta on branch" > $testroot/repo/gamma/delta + git_commit $testroot/repo -m "committing to delta on newbranch" + + echo "modified alpha on branch" > $testroot/repo/alpha + (cd $testroot/repo && git rm -q beta) + echo "new file on branch" > $testroot/repo/epsilon/new + (cd $testroot/repo && git add epsilon/new) + git_commit $testroot/repo -m "committing more changes on newbranch" + + local orig_commit1=`git_show_parent_commit $testroot/repo` + local orig_commit2=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q master) + echo "modified zeta on master" > $testroot/repo/epsilon/zeta + git_commit $testroot/repo -m "committing to zeta on master" + local master_commit=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q newbranch) + local new_commit1=`git_show_parent_commit $testroot/repo` + local new_commit2=`git_show_head $testroot/repo` + + got checkout -b master $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got integrate newbranch \ + > $testroot/stdout 2> $testroot/stderr) + ret="$?" + if [ "$ret" == "0" ]; then + echo "got integrate succeeded unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + 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 + + echo "got: specified branch must be rebased first" \ + > $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/stderr.expected $testroot/stderr + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && got log -c master | \ + grep ^commit > $testroot/stdout) + echo "commit $master_commit (master)" > $testroot/stdout.expected + echo "commit $init_commit" >> $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/repo && got log -c newbranch | \ + grep ^commit > $testroot/stdout) + echo "commit $new_commit2 (newbranch)" \ + > $testroot/stdout.expected + echo "commit $new_commit1" >> $testroot/stdout.expected + echo "commit $init_commit" >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $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_integrate_path_prefix { + local testroot=`test_init integrate_path_prefix` + + (cd $testroot/repo && git checkout -q -b newbranch) + echo "modified delta on branch" > $testroot/repo/gamma/delta + git_commit $testroot/repo -m "committing to delta on newbranch" + + echo "modified alpha on branch" > $testroot/repo/alpha + (cd $testroot/repo && git rm -q beta) + echo "new file on branch" > $testroot/repo/epsilon/new + (cd $testroot/repo && git add epsilon/new) + git_commit $testroot/repo -m "committing more changes on newbranch" + + local orig_commit1=`git_show_parent_commit $testroot/repo` + local orig_commit2=`git_show_head $testroot/repo` + + (cd $testroot/repo && git checkout -q master) + echo "modified zeta on master" > $testroot/repo/epsilon/zeta + git_commit $testroot/repo -m "committing to zeta on master" + local master_commit=`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 rebase newbranch > /dev/null) + ret="$?" + if [ "$ret" != "0" ]; then + echo "got rebase failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && git checkout -q newbranch) + local new_commit1=`git_show_parent_commit $testroot/repo` + local new_commit2=`git_show_head $testroot/repo` + + rm -r $testroot/wt + got checkout -b master -p epsilon $testroot/repo $testroot/wt \ + > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + echo "got checkout failed unexpectedly" + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/wt && got integrate newbranch > $testroot/stdout) + + echo "A new" > $testroot/stdout.expected + echo "Integrated refs/heads/newbranch into refs/heads/master" \ + >> $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_integrate_basic +run_test test_integrate_requires_rebase_first +run_test test_integrate_path_prefix