worktree.c 9.12 KB
Newer Older
1 2 3 4
#include "cache.h"
#include "refs.h"
#include "strbuf.h"
#include "worktree.h"
5
#include "dir.h"
6
#include "wt-status.h"
7

8 9 10 11 12 13
void free_worktrees(struct worktree **worktrees)
{
	int i = 0;

	for (i = 0; worktrees[i]; i++) {
		free(worktrees[i]->path);
14
		free(worktrees[i]->id);
15
		free(worktrees[i]->head_ref);
16
		free(worktrees[i]->lock_reason);
17 18 19 20 21
		free(worktrees[i]);
	}
	free (worktrees);
}

22 23
/*
 * read 'path_to_ref' into 'ref'.  Also if is_detached is not NULL,
24
 * set is_detached to 1 (0) if the ref is detached (is not detached).
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
 *
 * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
 * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
 * git_path). Parse the ref ourselves.
 *
 * return -1 if the ref is not a proper ref, 0 otherwise (success)
 */
static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
{
	if (is_detached)
		*is_detached = 0;
	if (!strbuf_readlink(ref, path_to_ref, 0)) {
		/* HEAD is symbolic link */
		if (!starts_with(ref->buf, "refs/") ||
				check_refname_format(ref->buf, 0))
			return -1;
	} else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
		/* textual symref or detached */
		if (!starts_with(ref->buf, "ref:")) {
			if (is_detached)
				*is_detached = 1;
		} else {
			strbuf_remove(ref, 0, strlen("ref:"));
			strbuf_trim(ref);
			if (check_refname_format(ref->buf, 0))
				return -1;
		}
	} else
		return -1;
	return 0;
}

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
/**
 * Add the head_sha1 and head_ref (if not detached) to the given worktree
 */
static void add_head_info(struct strbuf *head_ref, struct worktree *worktree)
{
	if (head_ref->len) {
		if (worktree->is_detached) {
			get_sha1_hex(head_ref->buf, worktree->head_sha1);
		} else {
			resolve_ref_unsafe(head_ref->buf, 0, worktree->head_sha1, NULL);
			worktree->head_ref = strbuf_detach(head_ref, NULL);
		}
	}
}

72 73 74 75
/**
 * get the main worktree
 */
static struct worktree *get_main_worktree(void)
76
{
77
	struct worktree *worktree = NULL;
78
	struct strbuf path = STRBUF_INIT;
79 80
	struct strbuf worktree_path = STRBUF_INIT;
	struct strbuf head_ref = STRBUF_INIT;
81 82
	int is_bare = 0;
	int is_detached = 0;
83

84
	strbuf_add_absolute_path(&worktree_path, get_git_common_dir());
85 86
	is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
	if (is_bare)
87 88 89 90
		strbuf_strip_suffix(&worktree_path, "/.");

	strbuf_addf(&path, "%s/HEAD", get_git_common_dir());

91
	worktree = xcalloc(1, sizeof(*worktree));
92 93 94
	worktree->path = strbuf_detach(&worktree_path, NULL);
	worktree->is_bare = is_bare;
	worktree->is_detached = is_detached;
95 96
	if (!parse_ref(path.buf, &head_ref, &is_detached))
		add_head_info(&head_ref, worktree);
97

98
	strbuf_release(&path);
99 100 101
	strbuf_release(&worktree_path);
	strbuf_release(&head_ref);
	return worktree;
102 103
}

104
static struct worktree *get_linked_worktree(const char *id)
105
{
106
	struct worktree *worktree = NULL;
107
	struct strbuf path = STRBUF_INIT;
108 109
	struct strbuf worktree_path = STRBUF_INIT;
	struct strbuf head_ref = STRBUF_INIT;
110
	int is_detached = 0;
111

112 113
	if (!id)
		die("Missing linked worktree name");
114

115
	strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
116 117
	if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
		/* invalid gitdir file */
118
		goto done;
119 120 121 122

	strbuf_rtrim(&worktree_path);
	if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
		strbuf_reset(&worktree_path);
123
		strbuf_add_absolute_path(&worktree_path, ".");
124 125 126
		strbuf_strip_suffix(&worktree_path, "/.");
	}

127
	strbuf_reset(&path);
128 129
	strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);

130 131 132
	if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
		goto done;

133
	worktree = xcalloc(1, sizeof(*worktree));
134
	worktree->path = strbuf_detach(&worktree_path, NULL);
135
	worktree->id = xstrdup(id);
136 137
	worktree->is_detached = is_detached;
	add_head_info(&head_ref, worktree);
138 139 140

done:
	strbuf_release(&path);
141 142 143
	strbuf_release(&worktree_path);
	strbuf_release(&head_ref);
	return worktree;
144 145
}

146 147
static void mark_current_worktree(struct worktree **worktrees)
{
148
	char *git_dir = xstrdup(absolute_path(get_git_dir()));
149 150 151 152
	int i;

	for (i = 0; worktrees[i]; i++) {
		struct worktree *wt = worktrees[i];
153 154 155 156
		const char *wt_git_dir = get_worktree_git_dir(wt);

		if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
			wt->is_current = 1;
157
			break;
158
		}
159
	}
160
	free(git_dir);
161 162
}

163 164 165 166 167 168 169
static int compare_worktree(const void *a_, const void *b_)
{
	const struct worktree *const *a = a_;
	const struct worktree *const *b = b_;
	return fspathcmp((*a)->path, (*b)->path);
}

170
struct worktree **get_worktrees(unsigned flags)
171
{
172
	struct worktree **list = NULL;
173 174 175
	struct strbuf path = STRBUF_INIT;
	DIR *dir;
	struct dirent *d;
176 177 178
	int counter = 0, alloc = 2;

	list = xmalloc(alloc * sizeof(struct worktree *));
179

180
	list[counter++] = get_main_worktree();
181 182 183 184

	strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
	dir = opendir(path.buf);
	strbuf_release(&path);
185 186 187
	if (dir) {
		while ((d = readdir(dir)) != NULL) {
			struct worktree *linked = NULL;
188
			if (is_dot_or_dotdot(d->d_name))
189
				continue;
190

Duy Nguyen's avatar
Duy Nguyen committed
191 192 193 194
			if ((linked = get_linked_worktree(d->d_name))) {
				ALLOC_GROW(list, counter + 1, alloc);
				list[counter++] = linked;
			}
195 196 197 198 199
		}
		closedir(dir);
	}
	ALLOC_GROW(list, counter + 1, alloc);
	list[counter] = NULL;
200

201 202 203 204 205 206 207
	if (flags & GWT_SORT_LINKED)
		/*
		 * don't sort the first item (main worktree), which will
		 * always be the first
		 */
		QSORT(list + 1, counter - 1, compare_worktree);

208
	mark_current_worktree(list);
209 210 211
	return list;
}

212 213 214 215 216 217 218 219 220 221
const char *get_worktree_git_dir(const struct worktree *wt)
{
	if (!wt)
		return get_git_dir();
	else if (!wt->id)
		return get_git_common_dir();
	else
		return git_common_path("worktrees/%s", wt->id);
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
static struct worktree *find_worktree_by_suffix(struct worktree **list,
						const char *suffix)
{
	struct worktree *found = NULL;
	int nr_found = 0, suffixlen;

	suffixlen = strlen(suffix);
	if (!suffixlen)
		return NULL;

	for (; *list && nr_found < 2; list++) {
		const char	*path	 = (*list)->path;
		int		 pathlen = strlen(path);
		int		 start	 = pathlen - suffixlen;

		/* suffix must start at directory boundary */
		if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
		    !fspathcmp(suffix, path + start)) {
			found = *list;
			nr_found++;
		}
	}
	return nr_found == 1 ? found : NULL;
}

247 248 249 250
struct worktree *find_worktree(struct worktree **list,
			       const char *prefix,
			       const char *arg)
{
251
	struct worktree *wt;
252 253
	char *path;

254 255 256
	if ((wt = find_worktree_by_suffix(list, arg)))
		return wt;

257 258 259 260 261 262 263 264 265
	arg = prefix_filename(prefix, strlen(prefix), arg);
	path = xstrdup(real_path(arg));
	for (; *list; list++)
		if (!fspathcmp(path, real_path((*list)->path)))
			break;
	free(path);
	return *list;
}

266 267 268 269 270
int is_main_worktree(const struct worktree *wt)
{
	return !wt->id;
}

271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
const char *is_worktree_locked(struct worktree *wt)
{
	assert(!is_main_worktree(wt));

	if (!wt->lock_reason_valid) {
		struct strbuf path = STRBUF_INIT;

		strbuf_addstr(&path, worktree_git_path(wt, "locked"));
		if (file_exists(path.buf)) {
			struct strbuf lock_reason = STRBUF_INIT;
			if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
				die_errno(_("failed to read '%s'"), path.buf);
			strbuf_trim(&lock_reason);
			wt->lock_reason = strbuf_detach(&lock_reason, NULL);
		} else
			wt->lock_reason = NULL;
		wt->lock_reason_valid = 1;
		strbuf_release(&path);
	}

	return wt->lock_reason;
}

294 295
int is_worktree_being_rebased(const struct worktree *wt,
			      const char *target)
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
{
	struct wt_status_state state;
	int found_rebase;

	memset(&state, 0, sizeof(state));
	found_rebase = wt_status_check_rebase(wt, &state) &&
		((state.rebase_in_progress ||
		  state.rebase_interactive_in_progress) &&
		 state.branch &&
		 starts_with(target, "refs/heads/") &&
		 !strcmp(state.branch, target + strlen("refs/heads/")));
	free(state.branch);
	free(state.onto);
	return found_rebase;
}

312 313
int is_worktree_being_bisected(const struct worktree *wt,
			       const char *target)
314
{
315 316 317 318 319 320 321 322 323 324 325 326
	struct wt_status_state state;
	int found_rebase;

	memset(&state, 0, sizeof(state));
	found_rebase = wt_status_check_bisect(wt, &state) &&
		state.branch &&
		starts_with(target, "refs/heads/") &&
		!strcmp(state.branch, target + strlen("refs/heads/"));
	free(state.branch);
	return found_rebase;
}

327 328 329 330 331 332
/*
 * note: this function should be able to detect shared symref even if
 * HEAD is temporarily detached (e.g. in the middle of rebase or
 * bisect). New commands that do similar things should update this
 * function as well.
 */
333 334
const struct worktree *find_shared_symref(const char *symref,
					  const char *target)
335
{
336
	const struct worktree *existing = NULL;
337 338
	struct strbuf path = STRBUF_INIT;
	struct strbuf sb = STRBUF_INIT;
339
	static struct worktree **worktrees;
340 341
	int i = 0;

342 343
	if (worktrees)
		free_worktrees(worktrees);
344
	worktrees = get_worktrees(0);
345

346
	for (i = 0; worktrees[i]; i++) {
347
		struct worktree *wt = worktrees[i];
348 349
		if (wt->is_bare)
			continue;
350

351 352 353 354 355
		if (wt->is_detached && !strcmp(symref, "HEAD")) {
			if (is_worktree_being_rebased(wt, target)) {
				existing = wt;
				break;
			}
356 357 358 359
			if (is_worktree_being_bisected(wt, target)) {
				existing = wt;
				break;
			}
360 361
		}

362 363
		strbuf_reset(&path);
		strbuf_reset(&sb);
364
		strbuf_addf(&path, "%s/%s",
365
			    get_worktree_git_dir(wt),
366
			    symref);
367 368

		if (parse_ref(path.buf, &sb, NULL)) {
369
			continue;
370 371 372
		}

		if (!strcmp(sb.buf, target)) {
373
			existing = wt;
374 375
			break;
		}
376
	}
377 378 379

	strbuf_release(&path);
	strbuf_release(&sb);
380 381 382

	return existing;
}