commit 024e9686ae5fdfe2c0699649dc600c3dd39397f0 from: Stefan Sperling date: Tue May 14 17:22:16 2019 UTC add branch support to 'got update' commit - 08573d5b25f67f49eebace9318c417f8d384ab10 commit + 024e9686ae5fdfe2c0699649dc600c3dd39397f0 blob - fe652787be00cc913d2c8b2aea7b0a18025407f1 blob + 9ecb75494a1c2fb158597ddb5c771284fa29c6a2 --- got/got.1 +++ got/got.1 @@ -91,9 +91,8 @@ Only files beneath the specified .Ar path-prefix will be checked out. .El -.It Cm update [ Fl c Ar commit ] [ Ar path ] -Update an existing work tree to another commit on the current branch. -By default, the latest commit on the current branch is assumed. +.It Cm update [ Fl b Ar branch ] [ Fl c Ar commit ] [ Ar path ] +Update an existing work tree to a different commit. Show the status of each affected file, using the following status codes: .Bl -column YXZ description .It U Ta file was updated and contained no local changes @@ -118,15 +117,27 @@ multiple base commits. The base commit of such a work tree can be made consistent by running .Cm got update across the entire work tree. +Specifying a +.Ar path +is incompatible with the +.Fl b +option. .Pp The options for .Cm got update are as follows: .Bl -tag -width Ds +.It Fl b Ar branch +Switch the work tree's branch reference to the specified +.Ar branch +before updating the work tree. +This option requires that all paths in the work tree are updated. .It Fl c Ar commit Update the work tree to the specified .Ar commit . The expected argument is a SHA1 hash which corresponds to a commit object. +If this option is not specified, the most recent commit on the work tree's +branch will be used. .El .It Cm status [ Ar path ] Show the current modification status of files in a work tree, blob - 6196ec8e1aee73ae1e808d8789afab3b6a6a8f03 blob + 9da47c9e14b7718c02ba130a82ba75db680fab64 --- got/got.c +++ got/got.c @@ -296,13 +296,12 @@ check_cancelled(void *arg) } static const struct got_error * -check_linear_ancestry(struct got_worktree *worktree, - struct got_object_id *commit_id, struct got_repository *repo) +check_linear_ancestry(struct got_object_id *commit_id, + struct got_object_id *base_commit_id, struct got_repository *repo) { const struct got_error *err = NULL; - struct got_object_id *yca_id, *base_commit_id; + struct got_object_id *yca_id; - base_commit_id = got_worktree_get_base_commit_id(worktree); err = got_commit_graph_find_youngest_common_ancestor(&yca_id, commit_id, base_commit_id, repo); if (err) @@ -465,7 +464,8 @@ cmd_checkout(int argc, char *argv[]) commit_id_str); if (error != NULL) goto done; - error = check_linear_ancestry(worktree, commit_id, repo); + error = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), repo); if (error != NULL) { free(commit_id); goto done; @@ -494,7 +494,7 @@ done: __dead static void usage_update(void) { - fprintf(stderr, "usage: %s update [-c commit] [path]\n", + fprintf(stderr, "usage: %s update [-b branch] [-c commit] [path]\n", getprogname()); exit(1); } @@ -522,10 +522,15 @@ cmd_update(int argc, char *argv[]) char *worktree_path = NULL, *path = NULL; struct got_object_id *commit_id = NULL; char *commit_id_str = NULL; + const char *branch_name = NULL; + struct got_reference *head_ref = NULL; int ch, did_something = 0; - while ((ch = getopt(argc, argv, "c:")) != -1) { + while ((ch = getopt(argc, argv, "b:c:")) != -1) { switch (ch) { + case 'b': + branch_name = optarg; + break; case 'c': commit_id_str = strdup(optarg); if (commit_id_str == NULL) @@ -576,11 +581,12 @@ cmd_update(int argc, char *argv[]) if (error) goto done; + if (branch_name == NULL) + branch_name = got_worktree_get_head_ref_name(worktree); + error = got_ref_open(&head_ref, repo, branch_name, 0); + if (error != NULL) + goto done; if (commit_id_str == NULL) { - struct got_reference *head_ref; - error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); - if (error != NULL) - goto done; error = got_ref_resolve(&commit_id, repo, head_ref); if (error != NULL) goto done; @@ -594,9 +600,32 @@ cmd_update(int argc, char *argv[]) goto done; } - error = check_linear_ancestry(worktree, commit_id, repo); - if (error != NULL) - goto done; + if (strcmp(got_ref_get_name(head_ref), + got_worktree_get_head_ref_name(worktree)) != 0) { + struct got_object_id *head_commit_id; + if (strlen(path) != 0) { + fprintf(stderr, "%s: switching to a different " + "branch requires that the entire work tree " + "gets updated, not just '%s'\n", + getprogname(), path); + error = got_error(GOT_ERR_BAD_PATH); + goto done; + } + error = got_ref_resolve(&head_commit_id, repo, head_ref); + if (error) + goto done; + error = check_linear_ancestry(commit_id, head_commit_id, repo); + if (error != NULL) + goto done; + error = got_worktree_set_head_ref(worktree, head_ref); + if (error) + goto done; + } else { + error = check_linear_ancestry(commit_id, + got_worktree_get_base_commit_id(worktree), repo); + if (error != NULL) + goto done; + } if (got_object_id_cmp(got_worktree_get_base_commit_id(worktree), commit_id) != 0) { blob - 204c0b97c37f8f8c73bf17fbfc12436c416b62a3 blob + 38554a89939443afe975a7d181c7e5528eb8d42f --- include/got_worktree.h +++ include/got_worktree.h @@ -88,6 +88,12 @@ const struct got_error *got_worktree_match_path_prefix const char *got_worktree_get_head_ref_name(struct got_worktree *); /* + * Set the branch head reference of the work tree. + */ +const struct got_error *got_worktree_set_head_ref(struct got_worktree *, + struct got_reference *); + +/* * Get the current base commit ID of a worktree. */ struct got_object_id *got_worktree_get_base_commit_id(struct got_worktree *); blob - 674f852d118ec5c1e19ba14a93ab7f8731b9a8a4 blob + 7c7732c5ffc9aabbc1c18f30cbaaa200279534c7 --- lib/worktree.c +++ lib/worktree.c @@ -195,6 +195,26 @@ done: return err; } +static const struct got_error * +write_head_ref(const char *path_got, struct got_reference *head_ref) +{ + const struct got_error *err = NULL; + char *refstr = NULL; + + if (got_ref_is_symbolic(head_ref)) { + refstr = got_ref_to_str(head_ref); + if (refstr == NULL) + return got_error_from_errno("got_ref_to_str"); + } else { + refstr = strdup(got_ref_get_name(head_ref)); + if (refstr == NULL) + return got_error_from_errno("strdup"); + } + err = update_meta_file(path_got, GOT_WORKTREE_HEAD_REF, refstr); + free(refstr); + return err; +} + const struct got_error * got_worktree_init(const char *path, struct got_reference *head_ref, const char *prefix, struct got_repository *repo) @@ -205,7 +225,6 @@ got_worktree_init(const char *path, struct got_referen uint32_t uuid_status; int obj_type; char *path_got = NULL; - char *refstr = NULL; char *formatstr = NULL; char *absprefix = NULL; char *basestr = NULL; @@ -257,20 +276,7 @@ got_worktree_init(const char *path, struct got_referen goto done; /* Write the HEAD reference. */ - if (got_ref_is_symbolic(head_ref)) { - refstr = got_ref_to_str(head_ref); - if (refstr == NULL) { - err = got_error_from_errno("got_ref_to_str"); - goto done; - } - } else { - refstr = strdup(got_ref_get_name(head_ref)); - if (refstr == NULL) { - err = got_error_from_errno("strdup"); - goto done; - } - } - err = create_meta_file(path_got, GOT_WORKTREE_HEAD_REF, refstr); + err = write_head_ref(path_got, head_ref); if (err) goto done; @@ -322,7 +328,6 @@ done: free(commit_id); free(path_got); free(formatstr); - free(refstr); free(absprefix); free(basestr); free(uuidstr); @@ -519,6 +524,39 @@ const char * got_worktree_get_head_ref_name(struct got_worktree *worktree) { return worktree->head_ref_name; +} + +const struct got_error * +got_worktree_set_head_ref(struct got_worktree *worktree, + struct got_reference *head_ref) +{ + const struct got_error *err = NULL; + char *path_got = NULL, *head_ref_name = NULL; + + if (asprintf(&path_got, "%s/%s", worktree->root_path, + GOT_WORKTREE_GOT_DIR) == -1) { + err = got_error_from_errno("asprintf"); + path_got = NULL; + goto done; + } + + head_ref_name = strdup(got_ref_get_name(head_ref)); + if (head_ref_name == NULL) { + err = got_error_from_errno("strdup"); + goto done; + } + + err = write_head_ref(path_got, head_ref); + if (err) + goto done; + + free(worktree->head_ref_name); + worktree->head_ref_name = head_ref_name; +done: + free(path_got); + if (err) + free(head_ref_name); + return err; } struct got_object_id * blob - 3c6429593ca295ae154c301420e7717af1201161 blob + 6d4a991bc7cf8d3edfaf88297fdbf43787ce5f9f --- regress/cmdline/update.sh +++ regress/cmdline/update.sh @@ -1283,8 +1283,77 @@ function test_update_moved_branch_ref { fi test_done "$testroot" "$ret" } + +function test_update_to_another_branch { + local testroot=`test_init update_to_another_branch` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret="$?" + if [ "$ret" != "0" ]; then + test_done "$testroot" "$ret" + return 1 + fi + + echo 'refs/heads/master'> $testroot/head-ref.expected + cmp -s $testroot/head-ref.expected $testroot/wt/.got/head-ref + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/head-ref.expected $testroot/wt/.got/head-ref + test_done "$testroot" "$ret" + return 1 + fi + + (cd $testroot/repo && git checkout -q -b newbranch) + echo "modified alpha on new branch" > $testroot/repo/alpha + git_commit $testroot/repo -m "modified alpha on new branch" + + echo "modified alpha in work tree" > $testroot/wt/alpha + + echo "C alpha" > $testroot/stdout.expected + echo -n "Updated to commit " >> $testroot/stdout.expected + git_show_head $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected + + (cd $testroot/wt && got update -b newbranch > $testroot/stdout) + + 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 -n "<<<<<<< commit " > $testroot/content.expected + git_show_head $testroot/repo >> $testroot/content.expected + echo >> $testroot/content.expected + echo "modified alpha on new branch" >> $testroot/content.expected + echo "=======" >> $testroot/content.expected + echo "modified alpha in work tree" >> $testroot/content.expected + 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 'refs/heads/newbranch'> $testroot/head-ref.expected + cmp -s $testroot/head-ref.expected $testroot/wt/.got/head-ref + ret="$?" + if [ "$ret" != "0" ]; then + diff -u $testroot/head-ref.expected $testroot/wt/.got/head-ref + test_done "$testroot" "$ret" + return 1 + fi + + test_done "$testroot" "$ret" +} + run_test test_update_basic run_test test_update_adds_file run_test test_update_deletes_file @@ -1311,3 +1380,4 @@ run_test test_update_partial_add run_test test_update_partial_rm run_test test_update_partial_dir run_test test_update_moved_branch_ref +run_test test_update_to_another_branch