1/* 2 * Copyright (c) 2017 Martin Pieuchot <mpi@openbsd.org> 3 * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org> 4 * Copyright (c) 2020 Ori Bernstein <ori@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/queue.h> 20#include <sys/types.h> 21#include <sys/stat.h> 22#include <sys/param.h> 23#include <sys/wait.h> 24 25static const struct got_error * 26cmd_import(int argc, char *argv[]) 27{ 28 const struct got_error *error = NULL; 29 char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; 30 char *gitconfig_path = NULL, *editor = NULL, *author = NULL; 31 const char *branch_name = "main"; 32 char *refname = NULL, *id_str = NULL, *logmsg_path = NULL; 33 struct got_repository *repo = NULL; 34 struct got_reference *branch_ref = NULL, *head_ref = NULL; 35 struct got_object_id *new_commit_id = NULL; 36 int ch; 37 struct got_pathlist_head ignores; 38 struct got_pathlist_entry *pe; 39 int preserve_logmsg = 0; 40 41 TAILQ_INIT(&ignores); 42 43 while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { 44 switch (ch) { 45 case 'b': 46 branch_name = optarg; 47 break; 48 case 'm': 49 logmsg = strdup(optarg); 50 if (logmsg == NULL) { 51 error = got_error_from_errno("strdup"); 52 goto done; 53 } 54 break; 55 case 'r': 56 repo_path = realpath(optarg, NULL); 57 if (repo_path == NULL) { 58 error = got_error_from_errno2("realpath", 59 optarg); 60 goto done; 61 } 62 break; 63 case 'I': 64 if (optarg[0] == '\0') 65 break; 66 error = got_pathlist_insert(&pe, &ignores, optarg, 67 NULL); 68 if (error) 69 goto done; 70 break; 71 default: 72 usage_import(); 73 /* NOTREACHED */ 74 } 75 } 76 77#ifndef PROFILE 78 if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " 79 "unveil", 80 NULL) == -1) 81 err(1, "pledge"); 82#endif 83 if (argc != 1) 84 usage_import(); 85 86 if (repo_path == NULL) { 87 repo_path = getcwd(NULL, 0); 88 if (repo_path == NULL) 89 return got_error_from_errno("getcwd"); 90 } 91 error = get_gitconfig_path(&gitconfig_path); 92 if (error) 93 goto done; 94 error = got_repo_open(&repo, repo_path, gitconfig_path); 95 if (error) 96 goto done; 97 98 error = get_author(&author, repo, NULL); 99 if (error) 100 return error; 101 102 /* 103 * Don't let the user create a branch name with a leading '-'. 104 * While technically a valid reference name, this case is usually 105 * an unintended typo. 106 */ 107 if (branch_name[0] == '-') 108 return got_error_path(branch_name, GOT_ERR_REF_NAME_MINUS); 109 110 if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { 111 error = got_error_from_errno("asprintf"); 112 goto done; 113 } 114 115 error = got_ref_open(&branch_ref, repo, refname, 0); 116 if (error) { 117 if (error->code != GOT_ERR_NOT_REF) 118 goto done; 119 } else { 120 error = got_error_msg(GOT_ERR_BRANCH_EXISTS, 121 "import target branch already exists"); 122 goto done; 123 } 124 125 path_dir = realpath(argv[0], NULL); 126 if (path_dir == NULL) { 127 error = got_error_from_errno2("realpath", argv[0]); 128 goto done; 129 } 130 got_path_strip_trailing_slashes(path_dir); 131 132 /* 133 * unveil(2) traverses exec(2); if an editor is used we have 134 * to apply unveil after the log message has been written. 135 */ 136 if (logmsg == NULL || strlen(logmsg) == 0) { 137 error = get_editor(&editor); 138 if (error) 139 goto done; 140 free(logmsg); 141 error = collect_import_msg(&logmsg, &logmsg_path, editor, 142 path_dir, refname); 143 if (error) { 144 if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && 145 logmsg_path != NULL) 146 preserve_logmsg = 1; 147 goto done; 148 } 149 } 150 151 if (unveil(path_dir, "r") != 0) { 152 error = got_error_from_errno2("unveil", path_dir); 153 if (logmsg_path) 154 preserve_logmsg = 1; 155 goto done; 156 } 157 158 error = apply_unveil(got_repo_get_path(repo), 0, NULL); 159 if (error) { 160 if (logmsg_path) 161 preserve_logmsg = 1; 162 goto done; 163 } 164 165 error = got_repo_import(&new_commit_id, path_dir, logmsg, 166 author, &ignores, repo, import_progress, NULL); 167 if (error) { 168 if (logmsg_path) 169 preserve_logmsg = 1; 170 goto done; 171 } 172 173 error = got_ref_alloc(&branch_ref, refname, new_commit_id); 174 if (error) { 175 if (logmsg_path) 176 preserve_logmsg = 1; 177 goto done; 178 } 179 180 error = got_ref_write(branch_ref, repo); 181 if (error) { 182 if (logmsg_path) 183 preserve_logmsg = 1; 184 goto done; 185 } 186 187 error = got_object_id_str(&id_str, new_commit_id); 188 if (error) { 189 if (logmsg_path) 190 preserve_logmsg = 1; 191 goto done; 192 } 193 194 error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); 195 if (error) { 196 if (error->code != GOT_ERR_NOT_REF) { 197 if (logmsg_path) 198 preserve_logmsg = 1; 199 goto done; 200 } 201 202 error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, 203 branch_ref); 204 if (error) { 205 if (logmsg_path) 206 preserve_logmsg = 1; 207 goto done; 208 } 209 210 error = got_ref_write(head_ref, repo); 211 if (error) { 212 if (logmsg_path) 213 preserve_logmsg = 1; 214 goto done; 215 } 216 } 217 218 printf("Created branch %s with commit %s\n", 219 got_ref_get_name(branch_ref), id_str); 220done: 221 if (preserve_logmsg) { 222 fprintf(stderr, "%s: log message preserved in %s\n", 223 getprogname(), logmsg_path); 224 } else if (logmsg_path && unlink(logmsg_path) == -1 && error == NULL) 225 error = got_error_from_errno2("unlink", logmsg_path); 226 free(logmsg); 227 free(logmsg_path); 228 free(repo_path); 229 free(editor); 230 free(refname); 231 free(new_commit_id); 232 free(id_str); 233 free(author); 234 free(gitconfig_path); 235 if (branch_ref) 236 got_ref_close(branch_ref); 237 if (head_ref) 238 got_ref_close(head_ref); 239 return error; 240} 241 242__dead static void 243usage_clone(void) 244{ 245 fprintf(stderr, "usage: %s clone [-a] [-b branch] [-l] [-m] [-q] [-v] " 246 "[-R reference] repository-url [directory]\n", getprogname()); 247 exit(1); 248} 249 250static const struct got_error * 251cmd_clone(int argc, char *argv[]) 252{ 253 const struct got_error *error = NULL; 254 const char *uri, *dirname; 255 char *proto, *host, *port, *repo_name, *server_path; 256 char *default_destdir = NULL, *id_str = NULL; 257 const char *repo_path; 258 struct got_repository *repo = NULL; 259 struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs; 260 struct got_pathlist_entry *pe; 261 struct got_object_id *pack_hash = NULL; 262 int ch, fetchfd = -1, fetchstatus; 263 pid_t fetchpid = -1; 264 x 265 266 /* Create got.conf(5). */ 267 gotconfig_path = got_repo_get_path_gotconfig(repo); 268 if (gotconfig_path == NULL) { 269 error = got_error_from_errno("got_repo_get_path_gotconfig"); 270 goto done; 271 } 272 gotconfig_file = fopen(gotconfig_path, "a"); 273 if (gotconfig_file == NULL) { 274 error = got_error_from_errno2("fopen", gotconfig_path); 275 goto done; 276 } 277 got_path_strip_trailing_slashes(server_path); 278 if (asprintf(&gotconfig, 279 "remote \"%s\" {\n" 280 "\tserver %s\n" 281 "\tprotocol %s\n" 282 "%s%s%s" 283 "\trepository \"%s\"\n" 284 "%s" 285 "}\n", 286 GOT_FETCH_DEFAULT_REMOTE_NAME, host, proto, 287 port ? "\tport " : "", port ? port : "", port ? "\n" : "", 288 server_path, 289 mirror_references ? "\tmirror-references yes\n" : "") == -1) { 290 error = got_error_from_errno("asprintf"); 291 goto done; 292 } 293 n = fwrite(gotconfig, 1, strlen(gotconfig), gotconfig_file); 294 if (n != strlen(gotconfig)) { 295 error = got_ferror(gotconfig_file, GOT_ERR_IO); 296 goto done; 297 } 298} 299