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