commit 5ef14e636366c988d1453d9fb6f4a2b9ff127141 from: Stefan Sperling date: Sun Jun 02 18:50:34 2019 UTC add 'got backout' command commit - 03415a1a67d78f2decd6e46d82288a224bd4454d commit + 5ef14e636366c988d1453d9fb6f4a2b9ff127141 blob - c120894f1b10c13a0d054b4f168f3adbb37416f9 blob + ccaccdbe1c46e978d4dd5c5c97878cd641408429 --- got/got.1 +++ got/got.1 @@ -412,7 +412,39 @@ to a single base commit with .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 @@ -87,6 +87,7 @@ __dead static void usage_rm(void); __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 *[]); @@ -101,6 +102,7 @@ static const struct got_error* cmd_rm(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, @@ -129,6 +131,8 @@ static struct cmd got_commands[] = { "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 @@ -2719,3 +2723,110 @@ done: 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 @@ -93,7 +93,7 @@ #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 @@ -182,7 +182,7 @@ static const struct got_error { "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 @@ -28,4 +28,7 @@ commit: cherrypick: ./cherrypick.sh +backout: + ./backout.sh + .include blob - /dev/null blob + d6140ca8eb2f4465f26784c06d824446c3fda162 (mode 755) --- /dev/null +++ regress/cmdline/backout.sh @@ -0,0 +1,74 @@ +#!/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_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