commit - 03415a1a67d78f2decd6e46d82288a224bd4454d
commit + 5ef14e636366c988d1453d9fb6f4a2b9ff127141
blob - c120894f1b10c13a0d054b4f168f3adbb37416f9
blob + ccaccdbe1c46e978d4dd5c5c97878cd641408429
--- got/got.1
+++ got/got.1
.Cm got update .
If the work tree already contains files with merge conflicts, these
conflicts must be resolved first.
+.It Cm backout Ar commit
+Reverse-merge changes from a single
+.Ar commit
+into the work tree.
+The specified
+.Ar commit
+must be on the same branch as the work tree's base commit.
+The expected argument is a reference or a SHA1 hash which corresponds to
+a commit object.
+.Pp
+Show the status of each affected file, using the following status codes:
+.Bl -column YXZ description
+.It G Ta file was merged
+.It C Ta file was merged and conflicts occurred during merge
+.It ! Ta changes destined for a missing file were not merged
+.It D Ta file was deleted
+.It d Ta file's deletion was obstructed by local modifications
+.It A Ta new file was added
+.It ~ Ta changes destined for a non-regular file were not merged
.El
+.Pp
+The reverse-merged changes will appear as local changes in the work tree,
+which may be viewed with
+.Cm got diff ,
+amended manually or with further
+.Cm got cherrypick
+comands,
+committed with
+.Cm got commit ,
+or discarded again with
+.Cm got revert .
+.Pp
+.El
.Sh ENVIRONMENT
.Bl -tag -width GOT_AUTHOR
.It Ev GOT_AUTHOR
blob - 19de68852ed6874ce2254f35bf04bb2454d29ae6
blob + fd6712a8a82ee0d50d4a511136d1704b36783aa5
--- got/got.c
+++ got/got.c
__dead static void usage_revert(void);
__dead static void usage_commit(void);
__dead static void usage_cherrypick(void);
+__dead static void usage_backout(void);
static const struct got_error* cmd_checkout(int, char *[]);
static const struct got_error* cmd_update(int, char *[]);
static const struct got_error* cmd_revert(int, char *[]);
static const struct got_error* cmd_commit(int, char *[]);
static const struct got_error* cmd_cherrypick(int, char *[]);
+static const struct got_error* cmd_backout(int, char *[]);
static struct cmd got_commands[] = {
{ "checkout", cmd_checkout, usage_checkout,
"write changes from work tree to repository" },
{ "cherrypick", cmd_cherrypick, usage_cherrypick,
"merge a single commit from another branch into a work tree" },
+ { "backout", cmd_backout, usage_backout,
+ "reverse-merge changes from a commit into a work tree" },
};
int
got_repo_close(repo);
return error;
}
+
+__dead static void
+usage_backout(void)
+{
+ fprintf(stderr, "usage: %s backout commit-id\n", getprogname());
+ exit(1);
+}
+
+static const struct got_error *
+cmd_backout(int argc, char *argv[])
+{
+ const struct got_error *error = NULL;
+ struct got_worktree *worktree = NULL;
+ struct got_repository *repo = NULL;
+ char *cwd = NULL, *commit_id_str = NULL;
+ struct got_object_id *commit_id = NULL;
+ struct got_commit_object *commit = NULL;
+ struct got_object_qid *pid;
+ struct got_reference *head_ref = NULL;
+ int ch, did_something = 0;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ usage_backout();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage_backout();
+
+ 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 = got_repo_open(&repo, got_worktree_get_repo_path(worktree));
+ if (error != NULL)
+ goto done;
+
+ error = apply_unveil(got_repo_get_path(repo), 0,
+ got_worktree_get_root_path(worktree), 0);
+ if (error)
+ goto done;
+
+ error = got_object_resolve_id_str(&commit_id, repo, argv[0]);
+ if (error != NULL) {
+ struct got_reference *ref;
+ if (error->code != GOT_ERR_BAD_OBJ_ID_STR)
+ goto done;
+ error = got_ref_open(&ref, repo, argv[0], 0);
+ if (error != NULL)
+ goto done;
+ error = got_ref_resolve(&commit_id, repo, ref);
+ got_ref_close(ref);
+ if (error != NULL)
+ goto done;
+ }
+ error = got_object_id_str(&commit_id_str, commit_id);
+ if (error)
+ goto done;
+
+ error = got_ref_open(&head_ref, repo,
+ got_worktree_get_head_ref_name(worktree), 0);
+ if (error != NULL)
+ goto done;
+
+ error = check_same_branch(commit_id, head_ref, repo);
+ if (error)
+ goto done;
+
+ error = got_object_open_as_commit(&commit, repo, commit_id);
+ if (error)
+ goto done;
+ pid = SIMPLEQ_FIRST(got_object_commit_get_parent_ids(commit));
+ if (pid == NULL) {
+ error = got_error(GOT_ERR_ROOT_COMMIT);
+ goto done;
+ }
+
+ error = got_worktree_merge_files(worktree, commit_id, pid->id, repo,
+ update_progress, &did_something, check_cancelled, NULL);
+ if (error != NULL)
+ goto done;
+
+ if (did_something)
+ printf("backed out commit %s\n", commit_id_str);
+done:
+ if (commit)
+ got_object_commit_close(commit);
+ free(commit_id_str);
+ if (head_ref)
+ got_ref_close(head_ref);
+ if (worktree)
+ got_worktree_close(worktree);
+ if (repo)
+ got_repo_close(repo);
+ return error;
+}
blob - e19e3956f955247d86d4c8d6910b38709f202a31
blob + 0186e720548b6da8fde30b6fd002a9076c3adc16
--- include/got_error.h
+++ include/got_error.h
#define GOT_ERR_BRANCH_MOVED 77
#define GOT_ERR_OBJ_TOO_LARGE 78
#define GOT_ERR_SAME_BRANCH 79
-/* 80 is currently free for re-use */
+#define GOT_ERR_ROOT_COMMIT 80
#define GOT_ERR_MIXED_COMMITS 81
#define GOT_ERR_CONFLICTS 82
"different branch; new head reference and/or update -b required" },
{ GOT_ERR_OBJ_TOO_LARGE, "object too large" },
{ GOT_ERR_SAME_BRANCH, "commit is already contained in this branch" },
- { 80, "unused error code" },
+ { GOT_ERR_ROOT_COMMIT, "specified commit has no parent commit" },
{ GOT_ERR_MIXED_COMMITS,"work tree contains files from multiple "
"base commits; the entire work tree must be updated first" },
{ GOT_ERR_CONFLICTS, "work tree contains conflicted files; these "
blob - 5776af5d999314a63da955a325813c1315eb8bfa
blob + a8340495b91e2f1614460a1b9c12ed746be6921f
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
cherrypick:
./cherrypick.sh
+backout:
+ ./backout.sh
+
.include <bsd.regress.mk>
blob - /dev/null
blob + d6140ca8eb2f4465f26784c06d824446c3fda162 (mode 755)
--- /dev/null
+++ regress/cmdline/backout.sh
+#!/bin/sh
+#
+# Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+#
+# 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_backout_basic {
+ local testroot=`test_init backout_basic`
+
+ got checkout $testroot/repo $testroot/wt > /dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ echo "modified alpha" > $testroot/wt/alpha
+ (cd $testroot/wt && got commit -m "changing alpha" > /dev/null)
+
+ local bad_commit=`git_show_head $testroot/repo`
+
+
+ (cd $testroot/wt && got update > /dev/null)
+
+ echo "modified beta" > $testroot/wt/beta
+ (cd $testroot/wt && got commit -m "changing beta" > /dev/null)
+
+ (cd $testroot/wt && got update > /dev/null)
+
+ (cd $testroot/wt && got backout $bad_commit > $testroot/stdout)
+
+ echo "G alpha" > $testroot/stdout.expected
+ echo "backed out commit $bad_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
+
+ echo "alpha" > $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
+
+ echo 'M alpha' > $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"
+}
+
+run_test test_backout_basic