Commit Diff


commit - d01904d4bba62d3608e53c5f4940a408d3353370
commit + 4e759de49807996b58bace13158d54df47e7717e
blob - d7cc1a04da9d814d794c71616fa488d5edd96dfb
blob + faa6c4d2707477f3e5482355a69ab2f68951a36d
--- got/got.1
+++ got/got.1
@@ -309,6 +309,42 @@ work tree, use the repository path associated with thi
 List all existing references in the repository.
 .It Fl d Ar name
 Delete the reference with the specified name from the repository.
+.El
+.It Cm branch [ Fl r Ar repository-path ] [ Fl l ] [ Fl d Ar name ] [ Ar name [ Ar base-branch ] ]
+Manage branches in a repository.
+.Pp
+Branches are managed via references which live in the
+.Dq refs/heads/
+reference namespace.
+The
+.Cm got branch
+command operates on references in this namespace only.
+.Pp
+If no options are passed, expect one or two arguments and attempt to create
+a branch with the given
+.Ar name ,
+and make it point at the given
+.Ar base-branch .
+If no
+.Ar base-branch
+is specified, default to the work tree's current branch if invoked in a
+work tree, or to the repository's HEAD reference.
+.Pp
+The options for
+.Cm got branch
+are as follows:
+.Bl -tag -width Ds
+.It Fl r Ar repository-path
+Use the repository at the specified path.
+If not specified, assume the repository is located at or above the current
+working directory.
+If this directory is a
+.Nm
+work tree, use the repository path associated with this work tree.
+.It Fl l
+List all existing branches in the repository.
+.It Fl d Ar name
+Delete the branch with the specified name from the repository.
 .El
 .It Cm add Ar file-path ...
 Schedule unversioned files in a work tree for addition to the
@@ -489,7 +525,7 @@ View local changes in a work tree directory:
 .Pp
 In a work tree or a git repository directory, list all branch references:
 .Pp
-.Dl $ got ref -l | grep ^refs/heads
+.Dl $ got branch -l
 .Pp
 In a work tree or a git repository directory, create a new branch called
 .Dq unified-buffer-cache
@@ -497,7 +533,7 @@ which is forked off the
 .Dq master
 branch:
 .Pp
-.Dl $ got ref refs/heads/unified-buffer-cache refs/heads/master
+.Dl $ got branch unified-buffer-cache master
 .Pp
 Switch an existing work tree to the branch
 .Dq unified-buffer-cache .
blob - 8787eff3dc915827732a1e6e2f48996dbab376a6
blob + a4975d670ed06aa6ed6696642cd7e23b2e0a0ee9
--- got/got.c
+++ got/got.c
@@ -82,6 +82,7 @@ __dead static void	usage_blame(void);
 __dead static void	usage_tree(void);
 __dead static void	usage_status(void);
 __dead static void	usage_ref(void);
+__dead static void	usage_branch(void);
 __dead static void	usage_add(void);
 __dead static void	usage_rm(void);
 __dead static void	usage_revert(void);
@@ -98,6 +99,7 @@ static const struct got_error*		cmd_blame(int, char *[
 static const struct got_error*		cmd_tree(int, char *[]);
 static const struct got_error*		cmd_status(int, char *[]);
 static const struct got_error*		cmd_ref(int, char *[]);
+static const struct got_error*		cmd_branch(int, char *[]);
 static const struct got_error*		cmd_add(int, char *[]);
 static const struct got_error*		cmd_rm(int, char *[]);
 static const struct got_error*		cmd_revert(int, char *[]);
@@ -115,6 +117,7 @@ static struct cmd got_commands[] = {
 	{ "tree",	cmd_tree,	usage_tree },
 	{ "status",	cmd_status,	usage_status },
 	{ "ref",	cmd_ref,	usage_ref },
+	{ "branch",	cmd_branch,	usage_branch },
 	{ "add",	cmd_add,	usage_add },
 	{ "rm",		cmd_rm,		usage_rm },
 	{ "revert",	cmd_revert,	usage_revert },
@@ -2155,7 +2158,232 @@ cmd_ref(int argc, char *argv[])
 		error = delete_ref(repo, delref);
 	else
 		error = add_ref(repo, argv[0], argv[1]);
+done:
+	if (repo)
+		got_repo_close(repo);
+	if (worktree)
+		got_worktree_close(worktree);
+	free(cwd);
+	free(repo_path);
+	return error;
+}
+
+__dead static void
+usage_branch(void)
+{
+	fprintf(stderr,
+	    "usage: %s branch [-r repository] -l | -d name | "
+	    "name [base-branch]\n", getprogname());
+	exit(1);
+}
+
+static const struct got_error *
+list_branches(struct got_repository *repo)
+{
+	static const struct got_error *err = NULL;
+	struct got_reflist_head refs;
+	struct got_reflist_entry *re;
+
+	SIMPLEQ_INIT(&refs);
+	err = got_ref_list(&refs, repo);
+	if (err)
+		return err;
+
+	SIMPLEQ_FOREACH(re, &refs, entry) {
+		const char *refname;
+		char *refstr;
+		refname = got_ref_get_name(re->ref);
+		if (strncmp(refname, "refs/heads/", 11) != 0)
+			continue;
+		refname += 11;
+		refstr = got_ref_to_str(re->ref);
+		if (refstr == NULL)
+			return got_error_from_errno("got_ref_to_str");
+		printf("%s: %s\n", refname, refstr);
+		free(refstr);
+	}
+
+	got_ref_list_free(&refs);
+	return NULL;
+}
+
+static const struct got_error *
+delete_branch(struct got_repository *repo, const char *branch_name)
+{
+	const struct got_error *err = NULL;
+	struct got_reference *ref;
+	char *refname;
+
+	if (asprintf(&refname, "refs/heads/%s", branch_name) == -1)
+		return got_error_from_errno("asprintf");
+
+	err = got_ref_open(&ref, repo, refname, 0);
+	if (err)
+		goto done;
+
+	err = got_ref_delete(ref, repo);
+	got_ref_close(ref);
+done:
+	free(refname);
+	return err;
+}
+
+static const struct got_error *
+add_branch(struct got_repository *repo, const char *branch_name,
+    const char *base_branch)
+{
+	const struct got_error *err = NULL;
+	struct got_object_id *id = NULL;
+	struct got_reference *ref = NULL;
+	char *base_refname = NULL, *refname = NULL;
+	struct got_reference *base_ref;
+
+	if (strcmp(GOT_REF_HEAD, base_branch) == 0) {
+		base_refname = strdup(GOT_REF_HEAD);
+		if (base_refname == NULL)
+			return got_error_from_errno("strdup");
+	} else if (asprintf(&base_refname, "refs/heads/%s", base_branch) == -1)
+		return got_error_from_errno("asprintf");
+
+	err = got_ref_open(&base_ref, repo, base_refname, 0);
+	if (err)
+		goto done;
+	err = got_ref_resolve(&id, repo, base_ref);
+	got_ref_close(base_ref);
+	if (err)
+		goto done;
+
+	if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) {
+		 err = got_error_from_errno("asprintf");
+		 goto done;
+	}
+
+	err = got_ref_open(&ref, repo, refname, 0);
+	if (err == NULL) {
+		err = got_error(GOT_ERR_BRANCH_EXISTS);
+		goto done;
+	} else if (err->code != GOT_ERR_NOT_REF)
+		goto done;
+
+	err = got_ref_alloc(&ref, refname, id);
+	if (err)
+		goto done;
+
+	err = got_ref_write(ref, repo);
 done:
+	if (ref)
+		got_ref_close(ref);
+	free(id);
+	free(base_refname);
+	free(refname);
+	return err;
+}
+
+static const struct got_error *
+cmd_branch(int argc, char *argv[])
+{
+	const struct got_error *error = NULL;
+	struct got_repository *repo = NULL;
+	struct got_worktree *worktree = NULL;
+	char *cwd = NULL, *repo_path = NULL;
+	int ch, do_list = 0;
+	const char *delref = NULL;
+
+	while ((ch = getopt(argc, argv, "d:r:l")) != -1) {
+		switch (ch) {
+		case 'd':
+			delref = optarg;
+			break;
+		case 'r':
+			repo_path = realpath(optarg, NULL);
+			if (repo_path == NULL)
+				err(1, "-r option");
+			got_path_strip_trailing_slashes(repo_path);
+			break;
+		case 'l':
+			do_list = 1;
+			break;
+		default:
+			usage_branch();
+			/* NOTREACHED */
+		}
+	}
+
+	if (do_list && delref)
+		errx(1, "-l and -d options are mutually exclusive\n");
+
+	argc -= optind;
+	argv += optind;
+
+	if (do_list || delref) {
+		if (argc > 0)
+			usage_branch();
+	} else if (argc < 1 || argc > 2)
+		usage_branch();
+
+#ifndef PROFILE
+	if (do_list) {
+		if (pledge("stdio rpath wpath flock proc exec sendfd unveil",
+		    NULL) == -1)
+			err(1, "pledge");
+	} else {
+		if (pledge("stdio rpath wpath cpath fattr flock proc exec "
+		    "sendfd unveil", NULL) == -1)
+			err(1, "pledge");
+	}
+#endif
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
+		error = got_error_from_errno("getcwd");
+		goto done;
+	}
+
+	if (repo_path == NULL) {
+		error = got_worktree_open(&worktree, cwd);
+		if (error && error->code != GOT_ERR_NOT_WORKTREE)
+			goto done;
+		else
+			error = NULL;
+		if (worktree) {
+			repo_path =
+			    strdup(got_worktree_get_repo_path(worktree));
+			if (repo_path == NULL)
+				error = got_error_from_errno("strdup");
+			if (error)
+				goto done;
+		} else {
+			repo_path = strdup(cwd);
+			if (repo_path == NULL) {
+				error = got_error_from_errno("strdup");
+				goto done;
+			}
+		}
+	}
+
+	error = got_repo_open(&repo, repo_path);
+	if (error != NULL)
+		goto done;
+
+	error = apply_unveil(got_repo_get_path(repo), do_list,
+	    worktree ? got_worktree_get_root_path(worktree) : NULL, 0);
+	if (error)
+		goto done;
+
+	if (do_list)
+		error = list_branches(repo);
+	else if (delref)
+		error = delete_branch(repo, delref);
+	else {
+		const char *base_branch;
+		if (argc == 1) {
+			base_branch = worktree ?
+			    got_worktree_get_head_ref_name(worktree) :
+			    GOT_REF_HEAD;
+		} else
+			base_branch = argv[1];
+		error = add_branch(repo, argv[0], base_branch);
+	}
+done:
 	if (repo)
 		got_repo_close(repo);
 	if (worktree)
blob - 0186e720548b6da8fde30b6fd002a9076c3adc16
blob + c05ac929d21262fe260f6843312f25459fc4a06d
--- include/got_error.h
+++ include/got_error.h
@@ -96,6 +96,7 @@
 #define GOT_ERR_ROOT_COMMIT	80
 #define GOT_ERR_MIXED_COMMITS	81
 #define GOT_ERR_CONFLICTS	82
+#define GOT_ERR_BRANCH_EXISTS	83
 
 static const struct got_error {
 	int code;
@@ -187,6 +188,7 @@ static const struct got_error {
 	    "base commits; the entire work tree must be updated first" },
 	{ GOT_ERR_CONFLICTS,	"work tree contains conflicted files; these "
 	    "conflicts must be resolved first" },
+	{ GOT_ERR_BRANCH_EXISTS,"specified branch already exists" },
 };
 
 /*