Commit Diff


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