Blob


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 */
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>
25 static const struct got_error *
26 cmd_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;
41 TAILQ_INIT(&ignores);
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 }
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();
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;
98 error = get_author(&author, repo, NULL);
99 if (error)
100 return error;
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);
110 if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) {
111 error = got_error_from_errno("asprintf");
112 goto done;
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;
125 path_dir = realpath(argv[0], NULL);
126 if (path_dir == NULL) {
127 error = got_error_from_errno2("realpath", argv[0]);
128 goto done;
130 got_path_strip_trailing_slashes(path_dir);
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;
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;
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;
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;
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;
180 error = got_ref_write(branch_ref, repo);
181 if (error) {
182 if (logmsg_path)
183 preserve_logmsg = 1;
184 goto done;
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;
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;
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;
210 error = got_ref_write(head_ref, repo);
211 if (error) {
212 if (logmsg_path)
213 preserve_logmsg = 1;
214 goto done;
218 printf("Created branch %s with commit %s\n",
219 got_ref_get_name(branch_ref), id_str);
220 done:
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;
242 __dead static void
243 usage_clone(void)
245 fprintf(stderr, "usage: %s clone [-a] [-b branch] [-l] [-m] [-q] [-v] "
246 "[-R reference] repository-url [directory]\n", getprogname());
247 exit(1);
250 static const struct got_error *
251 cmd_clone(int argc, char *argv[])
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;
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;
272 gotconfig_file = fopen(gotconfig_path, "a");
273 if (gotconfig_file == NULL) {
274 error = got_error_from_errno2("fopen", gotconfig_path);
275 goto done;
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;
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;