commit 4e759de49807996b58bace13158d54df47e7717e from: Stefan Sperling date: Wed Jun 26 07:03:12 2019 UTC add 'got branch' command 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" }, }; /*