wt-status.c 47.2 KB
Newer Older
1
#include "cache.h"
2 3 4 5 6 7 8
#include "wt-status.h"
#include "object.h"
#include "dir.h"
#include "commit.h"
#include "diff.h"
#include "revision.h"
#include "diffcore.h"
9
#include "quote.h"
10
#include "run-command.h"
11
#include "argv-array.h"
12
#include "remote.h"
13
#include "refs.h"
14
#include "submodule.h"
Duy Nguyen's avatar
Duy Nguyen committed
15
#include "column.h"
16
#include "strbuf.h"
17
#include "utf8.h"
18

19
static const char cut_line[] =
20 21
"------------------------ >8 ------------------------\n";

22
static char default_wt_status_colors[][COLOR_MAXLEN] = {
23 24 25 26 27
	GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
	GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
	GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
	GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
	GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
28
	GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
29 30
	GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
	GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
31
	GIT_COLOR_NIL,    /* WT_STATUS_ONBRANCH */
32
};
33

34
static const char *color(int slot, struct wt_status *s)
35
{
36 37 38
	const char *c = "";
	if (want_color(s->use_color))
		c = s->color_palette[slot];
39 40 41
	if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
		c = s->color_palette[WT_STATUS_HEADER];
	return c;
42 43
}

44 45 46 47 48 49 50 51 52
static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
		const char *fmt, va_list ap, const char *trail)
{
	struct strbuf sb = STRBUF_INIT;
	struct strbuf linebuf = STRBUF_INIT;
	const char *line, *eol;

	strbuf_vaddf(&sb, fmt, ap);
	if (!sb.len) {
53 54 55 56 57
		if (s->display_comment_prefix) {
			strbuf_addch(&sb, comment_line_char);
			if (!trail)
				strbuf_addch(&sb, ' ');
		}
58 59 60 61 62 63 64 65 66 67
		color_print_strbuf(s->fp, color, &sb);
		if (trail)
			fprintf(s->fp, "%s", trail);
		strbuf_release(&sb);
		return;
	}
	for (line = sb.buf; *line; line = eol + 1) {
		eol = strchr(line, '\n');

		strbuf_reset(&linebuf);
68
		if (at_bol && s->display_comment_prefix) {
69
			strbuf_addch(&linebuf, comment_line_char);
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
			if (*line != '\n' && *line != '\t')
				strbuf_addch(&linebuf, ' ');
		}
		if (eol)
			strbuf_add(&linebuf, line, eol - line);
		else
			strbuf_addstr(&linebuf, line);
		color_print_strbuf(s->fp, color, &linebuf);
		if (eol)
			fprintf(s->fp, "\n");
		else
			break;
		at_bol = 1;
	}
	if (trail)
		fprintf(s->fp, "%s", trail);
	strbuf_release(&linebuf);
	strbuf_release(&sb);
}

void status_printf_ln(struct wt_status *s, const char *color,
			const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	status_vprintf(s, 1, color, fmt, ap, "\n");
	va_end(ap);
}

void status_printf(struct wt_status *s, const char *color,
			const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	status_vprintf(s, 1, color, fmt, ap, NULL);
	va_end(ap);
}

110 111
static void status_printf_more(struct wt_status *s, const char *color,
			       const char *fmt, ...)
112 113 114 115 116 117 118 119
{
	va_list ap;

	va_start(ap, fmt);
	status_vprintf(s, 0, color, fmt, ap, NULL);
	va_end(ap);
}

120 121 122 123
void wt_status_prepare(struct wt_status *s)
{
	unsigned char sha1[20];

124
	memset(s, 0, sizeof(*s));
125 126
	memcpy(s->color_palette, default_wt_status_colors,
	       sizeof(default_wt_status_colors));
127 128 129
	s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
	s->use_color = -1;
	s->relative_paths = 1;
130
	s->branch = resolve_refdup("HEAD", 0, sha1, NULL);
131
	s->reference = "HEAD";
132
	s->fp = stdout;
133
	s->index_file = get_index_file();
134
	s->change.strdup_strings = 1;
135
	s->untracked.strdup_strings = 1;
136
	s->ignored.strdup_strings = 1;
137
	s->show_branch = -1;  /* unspecified */
138
	s->display_comment_prefix = 0;
139 140
}

141 142
static void wt_status_print_unmerged_header(struct wt_status *s)
{
143 144 145 146
	int i;
	int del_mod_conflict = 0;
	int both_deleted = 0;
	int not_deleted = 0;
147
	const char *c = color(WT_STATUS_HEADER, s);
148

149
	status_printf_ln(s, c, _("Unmerged paths:"));
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170

	for (i = 0; i < s->change.nr; i++) {
		struct string_list_item *it = &(s->change.items[i]);
		struct wt_status_change_data *d = it->util;

		switch (d->stagemask) {
		case 0:
			break;
		case 1:
			both_deleted = 1;
			break;
		case 3:
		case 5:
			del_mod_conflict = 1;
			break;
		default:
			not_deleted = 1;
			break;
		}
	}

171
	if (!s->hints)
172
		return;
173
	if (s->whence != FROM_COMMIT)
174 175
		;
	else if (!s->is_initial)
176
		status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
177
	else
178
		status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
179 180 181 182 183 184 185 186 187 188 189

	if (!both_deleted) {
		if (!del_mod_conflict)
			status_printf_ln(s, c, _("  (use \"git add <file>...\" to mark resolution)"));
		else
			status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
	} else if (!del_mod_conflict && !not_deleted) {
		status_printf_ln(s, c, _("  (use \"git rm <file>...\" to mark resolution)"));
	} else {
		status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
	}
190
	status_printf_ln(s, c, "%s", "");
191 192
}

193
static void wt_status_print_cached_header(struct wt_status *s)
194
{
195
	const char *c = color(WT_STATUS_HEADER, s);
196

197
	status_printf_ln(s, c, _("Changes to be committed:"));
198
	if (!s->hints)
199
		return;
200
	if (s->whence != FROM_COMMIT)
201 202
		; /* NEEDSWORK: use "git reset --unresolve"??? */
	else if (!s->is_initial)
203
		status_printf_ln(s, c, _("  (use \"git reset %s <file>...\" to unstage)"), s->reference);
204
	else
205
		status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
206
	status_printf_ln(s, c, "%s", "");
207 208
}

209
static void wt_status_print_dirty_header(struct wt_status *s,
210 211
					 int has_deleted,
					 int has_dirty_submodules)
212
{
213
	const char *c = color(WT_STATUS_HEADER, s);
214

215
	status_printf_ln(s, c, _("Changes not staged for commit:"));
216
	if (!s->hints)
217
		return;
218
	if (!has_deleted)
219
		status_printf_ln(s, c, _("  (use \"git add <file>...\" to update what will be committed)"));
220
	else
221 222
		status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" to update what will be committed)"));
	status_printf_ln(s, c, _("  (use \"git checkout -- <file>...\" to discard changes in working directory)"));
223
	if (has_dirty_submodules)
224
		status_printf_ln(s, c, _("  (commit or discard the untracked or modified content in submodules)"));
225
	status_printf_ln(s, c, "%s", "");
226 227
}

228 229 230
static void wt_status_print_other_header(struct wt_status *s,
					 const char *what,
					 const char *how)
231
{
232
	const char *c = color(WT_STATUS_HEADER, s);
Duy Nguyen's avatar
Duy Nguyen committed
233
	status_printf_ln(s, c, "%s:", what);
234
	if (!s->hints)
235
		return;
236
	status_printf_ln(s, c, _("  (use \"git %s <file>...\" to include in what will be committed)"), how);
237
	status_printf_ln(s, c, "%s", "");
238 239
}

240
static void wt_status_print_trailer(struct wt_status *s)
241
{
242
	status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
243 244
}

245
#define quote_path quote_path_relative
246

247
static const char *wt_status_unmerged_status_string(int stagemask)
248
{
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
	switch (stagemask) {
	case 1:
		return _("both deleted:");
	case 2:
		return _("added by us:");
	case 3:
		return _("deleted by them:");
	case 4:
		return _("added by them:");
	case 5:
		return _("deleted by us:");
	case 6:
		return _("both added:");
	case 7:
		return _("both modified:");
	default:
		die(_("bug: unhandled unmerged status %x"), stagemask);
266 267 268
	}
}

269 270 271 272
static const char *wt_status_diff_status_string(int status)
{
	switch (status) {
	case DIFF_STATUS_ADDED:
273
		return _("new file:");
274
	case DIFF_STATUS_COPIED:
275
		return _("copied:");
276
	case DIFF_STATUS_DELETED:
277
		return _("deleted:");
278
	case DIFF_STATUS_MODIFIED:
279
		return _("modified:");
280
	case DIFF_STATUS_RENAMED:
281
		return _("renamed:");
282
	case DIFF_STATUS_TYPE_CHANGED:
283
		return _("typechange:");
284
	case DIFF_STATUS_UNKNOWN:
285
		return _("unknown:");
286
	case DIFF_STATUS_UNMERGED:
287
		return _("unmerged:");
288 289 290 291 292
	default:
		return NULL;
	}
}

293 294 295 296 297 298 299 300 301 302 303 304 305
static int maxwidth(const char *(*label)(int), int minval, int maxval)
{
	int result = 0, i;

	for (i = minval; i <= maxval; i++) {
		const char *s = label(i);
		int len = s ? utf8_strwidth(s) : 0;
		if (len > result)
			result = len;
	}
	return result;
}

306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
static void wt_status_print_unmerged_data(struct wt_status *s,
					  struct string_list_item *it)
{
	const char *c = color(WT_STATUS_UNMERGED, s);
	struct wt_status_change_data *d = it->util;
	struct strbuf onebuf = STRBUF_INIT;
	static char *padding;
	static int label_width;
	const char *one, *how;
	int len;

	if (!padding) {
		label_width = maxwidth(wt_status_unmerged_status_string, 1, 7);
		label_width += strlen(" ");
		padding = xmallocz(label_width);
		memset(padding, ' ', label_width);
	}

	one = quote_path(it->string, s->prefix, &onebuf);
	status_printf(s, color(WT_STATUS_HEADER, s), "\t");

	how = wt_status_unmerged_status_string(d->stagemask);
	len = label_width - utf8_strwidth(how);
	status_printf_more(s, c, "%s%.*s%s\n", how, len, padding, one);
	strbuf_release(&onebuf);
}

333 334 335
static void wt_status_print_change_data(struct wt_status *s,
					int change_type,
					struct string_list_item *it)
336
{
337
	struct wt_status_change_data *d = it->util;
338
	const char *c = color(change_type, s);
339
	int status;
340 341
	char *one_name;
	char *two_name;
342
	const char *one, *two;
343
	struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
344
	struct strbuf extra = STRBUF_INIT;
345
	static char *padding;
346
	static int label_width;
347 348 349 350
	const char *what;
	int len;

	if (!padding) {
351 352
		/* If DIFF_STATUS_* uses outside the range [A..Z], we're in trouble */
		label_width = maxwidth(wt_status_diff_status_string, 'A', 'Z');
353 354 355
		label_width += strlen(" ");
		padding = xmallocz(label_width);
		memset(padding, ' ', label_width);
356
	}
357

358 359 360 361 362 363 364 365
	one_name = two_name = it->string;
	switch (change_type) {
	case WT_STATUS_UPDATED:
		status = d->index_status;
		if (d->head_path)
			one_name = d->head_path;
		break;
	case WT_STATUS_CHANGED:
366 367 368
		if (d->new_submodule_commits || d->dirty_submodule) {
			strbuf_addstr(&extra, " (");
			if (d->new_submodule_commits)
369
				strbuf_addf(&extra, _("new commits, "));
370
			if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
371
				strbuf_addf(&extra, _("modified content, "));
372
			if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
373
				strbuf_addf(&extra, _("untracked content, "));
374 375 376
			strbuf_setlen(&extra, extra.len - 2);
			strbuf_addch(&extra, ')');
		}
377 378
		status = d->worktree_status;
		break;
379 380 381
	default:
		die("BUG: unhandled change_type %d in wt_status_print_change_data",
		    change_type);
382 383
	}

384 385
	one = quote_path(one_name, s->prefix, &onebuf);
	two = quote_path(two_name, s->prefix, &twobuf);
386

387
	status_printf(s, color(WT_STATUS_HEADER, s), "\t");
388 389
	what = wt_status_diff_status_string(status);
	if (!what)
390
		die(_("bug: unhandled diff status %c"), status);
391
	len = label_width - utf8_strwidth(what);
392 393
	assert(len >= 0);
	if (status == DIFF_STATUS_COPIED || status == DIFF_STATUS_RENAMED)
394
		status_printf_more(s, c, "%s%.*s%s -> %s",
395 396
				   what, len, padding, one, two);
	else
397
		status_printf_more(s, c, "%s%.*s%s",
398
				   what, len, padding, one);
399
	if (extra.len) {
400
		status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
401 402
		strbuf_release(&extra);
	}
403
	status_printf_more(s, GIT_COLOR_NORMAL, "\n");
404 405
	strbuf_release(&onebuf);
	strbuf_release(&twobuf);
406 407
}

408 409 410
static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
					 struct diff_options *options,
					 void *data)
411 412 413
{
	struct wt_status *s = data;
	int i;
414 415 416 417

	if (!q->nr)
		return;
	s->workdir_dirty = 1;
418
	for (i = 0; i < q->nr; i++) {
419 420 421 422 423
		struct diff_filepair *p;
		struct string_list_item *it;
		struct wt_status_change_data *d;

		p = q->queue[i];
424
		it = string_list_insert(&s->change, p->one->path);
425 426 427 428
		d = it->util;
		if (!d) {
			d = xcalloc(1, sizeof(*d));
			it->util = d;
429
		}
430 431
		if (!d->worktree_status)
			d->worktree_status = p->status;
432 433 434
		d->dirty_submodule = p->two->dirty_submodule;
		if (S_ISGITLINK(p->two->mode))
			d->new_submodule_commits = !!hashcmp(p->one->sha1, p->two->sha1);
435 436 437
	}
}

438 439 440
static int unmerged_mask(const char *path)
{
	int pos, mask;
441
	const struct cache_entry *ce;
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457

	pos = cache_name_pos(path, strlen(path));
	if (0 <= pos)
		return 0;

	mask = 0;
	pos = -pos-1;
	while (pos < active_nr) {
		ce = active_cache[pos++];
		if (strcmp(ce->name, path) || !ce_stage(ce))
			break;
		mask |= (1 << (ce_stage(ce) - 1));
	}
	return mask;
}

458 459 460
static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
					 struct diff_options *options,
					 void *data)
461
{
462
	struct wt_status *s = data;
463
	int i;
464 465 466 467 468 469 470

	for (i = 0; i < q->nr; i++) {
		struct diff_filepair *p;
		struct string_list_item *it;
		struct wt_status_change_data *d;

		p = q->queue[i];
471
		it = string_list_insert(&s->change, p->two->path);
472 473 474 475 476 477 478 479 480 481 482 483
		d = it->util;
		if (!d) {
			d = xcalloc(1, sizeof(*d));
			it->util = d;
		}
		if (!d->index_status)
			d->index_status = p->status;
		switch (p->status) {
		case DIFF_STATUS_COPIED:
		case DIFF_STATUS_RENAMED:
			d->head_path = xstrdup(p->one->path);
			break;
484 485 486
		case DIFF_STATUS_UNMERGED:
			d->stagemask = unmerged_mask(p->two->path);
			break;
487
		}
488
	}
489 490
}

491
static void wt_status_collect_changes_worktree(struct wt_status *s)
492 493
{
	struct rev_info rev;
494 495 496 497

	init_revisions(&rev, NULL);
	setup_revisions(0, NULL, &rev, NULL);
	rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
498
	DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
499 500
	if (!s->show_untracked_files)
		DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
501 502
	if (s->ignore_submodule_arg) {
		DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
503
		handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
504
	}
505 506
	rev.diffopt.format_callback = wt_status_collect_changed_cb;
	rev.diffopt.format_callback_data = s;
507
	copy_pathspec(&rev.prune_data, &s->pathspec);
508 509 510 511 512 513
	run_diff_files(&rev, 0);
}

static void wt_status_collect_changes_index(struct wt_status *s)
{
	struct rev_info rev;
514
	struct setup_revision_opt opt;
515

516
	init_revisions(&rev, NULL);
517 518 519 520
	memset(&opt, 0, sizeof(opt));
	opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
	setup_revisions(0, NULL, &rev, &opt);

521
	DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
522
	if (s->ignore_submodule_arg) {
523
		handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
524 525 526 527 528 529 530 531 532 533
	} else {
		/*
		 * Unless the user did explicitly request a submodule ignore
		 * mode by passing a command line option we do not ignore any
		 * changed submodule SHA-1s when comparing index and HEAD, no
		 * matter what is configured. Otherwise the user won't be
		 * shown any submodules she manually added (and which are
		 * staged to be committed), which would be really confusing.
		 */
		handle_ignore_submodules_arg(&rev.diffopt, "dirty");
534
	}
535

536
	rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
537
	rev.diffopt.format_callback = wt_status_collect_updated_cb;
538 539
	rev.diffopt.format_callback_data = s;
	rev.diffopt.detect_rename = 1;
Jeff King's avatar
Jeff King committed
540
	rev.diffopt.rename_limit = 200;
541
	rev.diffopt.break_opt = 0;
542
	copy_pathspec(&rev.prune_data, &s->pathspec);
543 544 545
	run_diff_index(&rev, 1);
}

546 547 548 549 550 551 552
static void wt_status_collect_changes_initial(struct wt_status *s)
{
	int i;

	for (i = 0; i < active_nr; i++) {
		struct string_list_item *it;
		struct wt_status_change_data *d;
553
		const struct cache_entry *ce = active_cache[i];
554

555
		if (!ce_path_match(ce, &s->pathspec, NULL))
556
			continue;
557
		it = string_list_insert(&s->change, ce->name);
558 559 560 561 562
		d = it->util;
		if (!d) {
			d = xcalloc(1, sizeof(*d));
			it->util = d;
		}
563
		if (ce_stage(ce)) {
564
			d->index_status = DIFF_STATUS_UNMERGED;
565 566
			d->stagemask |= (1 << (ce_stage(ce) - 1));
		}
567 568 569 570 571
		else
			d->index_status = DIFF_STATUS_ADDED;
	}
}

572 573 574 575
static void wt_status_collect_untracked(struct wt_status *s)
{
	int i;
	struct dir_struct dir;
576
	uint64_t t_begin = getnanotime();
577 578 579

	if (!s->show_untracked_files)
		return;
580

581 582 583 584
	memset(&dir, 0, sizeof(dir));
	if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
		dir.flags |=
			DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
585 586
	if (s->show_ignored_files)
		dir.flags |= DIR_SHOW_IGNORED_TOO;
587 588
	else
		dir.untracked = the_index.untracked;
589 590
	setup_standard_excludes(&dir);

591
	fill_directory(&dir, &s->pathspec);
592

593
	for (i = 0; i < dir.nr; i++) {
594
		struct dir_entry *ent = dir.entries[i];
595
		if (cache_name_is_other(ent->name, ent->len) &&
596
		    dir_path_match(ent, &s->pathspec, 0, NULL))
597
			string_list_insert(&s->untracked, ent->name);
598
		free(ent);
599
	}
600

601 602 603
	for (i = 0; i < dir.ignored_nr; i++) {
		struct dir_entry *ent = dir.ignored[i];
		if (cache_name_is_other(ent->name, ent->len) &&
604
		    dir_path_match(ent, &s->pathspec, 0, NULL))
605 606
			string_list_insert(&s->ignored, ent->name);
		free(ent);
607 608
	}

609
	free(dir.entries);
610 611
	free(dir.ignored);
	clear_directory(&dir);
612

613 614
	if (advice_status_u_option)
		s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
615 616 617
}

void wt_status_collect(struct wt_status *s)
618 619 620 621 622 623 624
{
	wt_status_collect_changes_worktree(s);

	if (s->is_initial)
		wt_status_collect_changes_initial(s);
	else
		wt_status_collect_changes_index(s);
625
	wt_status_collect_untracked(s);
626 627
}

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
static void wt_status_print_unmerged(struct wt_status *s)
{
	int shown_header = 0;
	int i;

	for (i = 0; i < s->change.nr; i++) {
		struct wt_status_change_data *d;
		struct string_list_item *it;
		it = &(s->change.items[i]);
		d = it->util;
		if (!d->stagemask)
			continue;
		if (!shown_header) {
			wt_status_print_unmerged_header(s);
			shown_header = 1;
		}
		wt_status_print_unmerged_data(s, it);
	}
	if (shown_header)
		wt_status_print_trailer(s);

}

651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
static void wt_status_print_updated(struct wt_status *s)
{
	int shown_header = 0;
	int i;

	for (i = 0; i < s->change.nr; i++) {
		struct wt_status_change_data *d;
		struct string_list_item *it;
		it = &(s->change.items[i]);
		d = it->util;
		if (!d->index_status ||
		    d->index_status == DIFF_STATUS_UNMERGED)
			continue;
		if (!shown_header) {
			wt_status_print_cached_header(s);
			s->commitable = 1;
			shown_header = 1;
		}
		wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
	}
	if (shown_header)
		wt_status_print_trailer(s);
}

/*
 * -1 : has delete
 *  0 : no change
 *  1 : some change but no delete
 */
680 681
static int wt_status_check_worktree_changes(struct wt_status *s,
					     int *dirty_submodules)
682 683 684 685
{
	int i;
	int changes = 0;

686 687
	*dirty_submodules = 0;

688 689 690
	for (i = 0; i < s->change.nr; i++) {
		struct wt_status_change_data *d;
		d = s->change.items[i].util;
691 692
		if (!d->worktree_status ||
		    d->worktree_status == DIFF_STATUS_UNMERGED)
693
			continue;
694 695 696 697
		if (!changes)
			changes = 1;
		if (d->dirty_submodule)
			*dirty_submodules = 1;
698
		if (d->worktree_status == DIFF_STATUS_DELETED)
699
			changes = -1;
700 701 702 703
	}
	return changes;
}

704 705
static void wt_status_print_changed(struct wt_status *s)
{
706 707
	int i, dirty_submodules;
	int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
708 709 710 711

	if (!worktree_changes)
		return;

712
	wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
713 714 715 716 717 718

	for (i = 0; i < s->change.nr; i++) {
		struct wt_status_change_data *d;
		struct string_list_item *it;
		it = &(s->change.items[i]);
		d = it->util;
719 720
		if (!d->worktree_status ||
		    d->worktree_status == DIFF_STATUS_UNMERGED)
721 722 723 724
			continue;
		wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
	}
	wt_status_print_trailer(s);
725 726
}

727
static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
728
{
729
	struct child_process sm_summary = CHILD_PROCESS_INIT;
730 731 732
	struct strbuf cmd_stdout = STRBUF_INIT;
	struct strbuf summary = STRBUF_INIT;
	char *summary_content;
733

734 735
	argv_array_pushf(&sm_summary.env_array, "GIT_INDEX_FILE=%s",
			 s->index_file);
736

737 738 739 740 741 742
	argv_array_push(&sm_summary.args, "submodule");
	argv_array_push(&sm_summary.args, "summary");
	argv_array_push(&sm_summary.args, uncommitted ? "--files" : "--cached");
	argv_array_push(&sm_summary.args, "--for-status");
	argv_array_push(&sm_summary.args, "--summary-limit");
	argv_array_pushf(&sm_summary.args, "%d", s->submodule_summary);
743
	if (!uncommitted)
744
		argv_array_push(&sm_summary.args, s->amend ? "HEAD^" : "HEAD");
745

746 747
	sm_summary.git_cmd = 1;
	sm_summary.no_stdin = 1;
748

749
	capture_command(&sm_summary, &cmd_stdout, 1024);
750 751

	/* prepend header, only if there's an actual output */
752
	if (cmd_stdout.len) {
753 754 755 756 757 758 759 760 761
		if (uncommitted)
			strbuf_addstr(&summary, _("Submodules changed but not updated:"));
		else
			strbuf_addstr(&summary, _("Submodule changes to be committed:"));
		strbuf_addstr(&summary, "\n\n");
	}
	strbuf_addbuf(&summary, &cmd_stdout);
	strbuf_release(&cmd_stdout);

762
	if (s->display_comment_prefix) {
763
		size_t len;
764 765 766 767
		summary_content = strbuf_detach(&summary, &len);
		strbuf_add_commented_lines(&summary, summary_content, len);
		free(summary_content);
	}
768 769 770

	fputs(summary.buf, s->fp);
	strbuf_release(&summary);
771 772
}

773 774 775 776
static void wt_status_print_other(struct wt_status *s,
				  struct string_list *l,
				  const char *what,
				  const char *how)
777 778
{
	int i;
779
	struct strbuf buf = STRBUF_INIT;
Duy Nguyen's avatar
Duy Nguyen committed
780 781
	static struct string_list output = STRING_LIST_INIT_DUP;
	struct column_options copts;
782

783
	if (!l->nr)
784
		return;
785

786 787 788
	wt_status_print_other_header(s, what, how);

	for (i = 0; i < l->nr; i++) {
789
		struct string_list_item *it;
Duy Nguyen's avatar
Duy Nguyen committed
790
		const char *path;
791
		it = &(l->items[i]);
792
		path = quote_path(it->string, s->prefix, &buf);
Duy Nguyen's avatar
Duy Nguyen committed
793 794 795 796
		if (column_active(s->colopts)) {
			string_list_append(&output, path);
			continue;
		}
797 798
		status_printf(s, color(WT_STATUS_HEADER, s), "\t");
		status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
Duy Nguyen's avatar
Duy Nguyen committed
799
				   "%s\n", path);
800
	}
Duy Nguyen's avatar
Duy Nguyen committed
801 802 803

	strbuf_release(&buf);
	if (!column_active(s->colopts))
804
		goto conclude;
Duy Nguyen's avatar
Duy Nguyen committed
805

806
	strbuf_addf(&buf, "%s%s\t%s",
Duy Nguyen's avatar
Duy Nguyen committed
807
		    color(WT_STATUS_HEADER, s),
808
		    s->display_comment_prefix ? "#" : "",
Duy Nguyen's avatar
Duy Nguyen committed
809 810 811 812 813 814 815 816
		    color(WT_STATUS_UNTRACKED, s));
	memset(&copts, 0, sizeof(copts));
	copts.padding = 1;
	copts.indent = buf.buf;
	if (want_color(s->use_color))
		copts.nl = GIT_COLOR_RESET "\n";
	print_columns(&output, s->colopts, &copts);
	string_list_clear(&output, 0);
817
	strbuf_release(&buf);
818
conclude:
819
	status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
820 821
}

822 823 824 825 826
void wt_status_truncate_message_at_cut_line(struct strbuf *buf)
{
	const char *p;
	struct strbuf pattern = STRBUF_INIT;

827 828 829 830 831
	strbuf_addf(&pattern, "\n%c %s", comment_line_char, cut_line);
	if (starts_with(buf->buf, pattern.buf + 1))
		strbuf_setlen(buf, 0);
	else if ((p = strstr(buf->buf, pattern.buf)))
		strbuf_setlen(buf, p - buf->buf + 1);
832 833 834
	strbuf_release(&pattern);
}

835 836 837 838 839 840 841 842 843 844 845
void wt_status_add_cut_line(FILE *fp)
{
	const char *explanation = _("Do not touch the line above.\nEverything below will be removed.");
	struct strbuf buf = STRBUF_INIT;

	fprintf(fp, "%c %s", comment_line_char, cut_line);
	strbuf_add_commented_lines(&buf, explanation, strlen(explanation));
	fputs(buf.buf, fp);
	strbuf_release(&buf);
}

846 847 848
static void wt_status_print_verbose(struct wt_status *s)
{
	struct rev_info rev;
849
	struct setup_revision_opt opt;
850 851
	int dirty_submodules;
	const char *c = color(WT_STATUS_HEADER, s);
852

853
	init_revisions(&rev, NULL);
854
	DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
855 856 857 858 859

	memset(&opt, 0, sizeof(opt));
	opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
	setup_revisions(0, NULL, &rev, &opt);

860 861
	rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
	rev.diffopt.detect_rename = 1;
862 863
	rev.diffopt.file = s->fp;
	rev.diffopt.close_file = 0;
864 865 866 867
	/*
	 * If we're not going to stdout, then we definitely don't
	 * want color, since we are going to the commit message
	 * file (and even the "auto" setting won't work, since it
868 869 870
	 * will have checked isatty on stdout). But we then do want
	 * to insert the scissor line here to reliably remove the
	 * diff before committing.
871
	 */
872
	if (s->fp != stdout) {
873
		rev.diffopt.use_color = 0;
874
		wt_status_add_cut_line(s->fp);
875
	}
876 877 878 879 880 881 882 883
	if (s->verbose > 1 && s->commitable) {
		/* print_updated() printed a header, so do we */
		if (s->fp != stdout)
			wt_status_print_trailer(s);
		status_printf_ln(s, c, _("Changes to be committed:"));
		rev.diffopt.a_prefix = "c/";
		rev.diffopt.b_prefix = "i/";
	} /* else use prefix as per user config */
884
	run_diff_index(&rev, 1);
885 886 887 888 889 890 891 892 893 894
	if (s->verbose > 1 &&
	    wt_status_check_worktree_changes(s, &dirty_submodules)) {
		status_printf_ln(s, c,
			"--------------------------------------------------");
		status_printf_ln(s, c, _("Changes not staged for commit:"));
		setup_work_tree();
		rev.diffopt.a_prefix = "i/";
		rev.diffopt.b_prefix = "w/";
		run_diff_files(&rev, 0);
	}
895 896
}

897 898 899
static void wt_status_print_tracking(struct wt_status *s)
{
	struct strbuf sb = STRBUF_INIT;
900
	const char *cp, *ep, *branch_name;
901
	struct branch *branch;
902 903
	char comment_line_string[3];
	int i;
904 905

	assert(s->branch && !s->is_initial);
906
	if (!skip_prefix(s->branch, "refs/heads/", &branch_name))
907
		return;
908
	branch = branch_get(branch_name);
909 910 911
	if (!format_tracking_info(branch, &sb))
		return;

912 913 914 915 916 917 918
	i = 0;
	if (s->display_comment_prefix) {
		comment_line_string[i++] = comment_line_char;
		comment_line_string[i++] = ' ';
	}
	comment_line_string[i] = '\0';

919
	for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
920
		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
921
				 "%s%.*s", comment_line_string,
922
				 (int)(ep - cp), cp);
923 924 925 926
	if (s->display_comment_prefix)
		color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%c",
				 comment_line_char);
	else
927
		fputs("", s->fp);
928 929
}

930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
static int has_unmerged(struct wt_status *s)
{
	int i;

	for (i = 0; i < s->change.nr; i++) {
		struct wt_status_change_data *d;
		d = s->change.items[i].util;
		if (d->stagemask)
			return 1;
	}
	return 0;
}

static void show_merge_in_progress(struct wt_status *s,
				struct wt_status_state *state,
				const char *color)
{
	if (has_unmerged(s)) {
		status_printf_ln(s, color, _("You have unmerged paths."));
949
		if (s->hints)
950 951 952 953 954
			status_printf_ln(s, color,
				_("  (fix conflicts and run \"git commit\")"));
	} else {
		status_printf_ln(s, color,
			_("All conflicts fixed but you are still merging."));
955
		if (s->hints)
956 957 958 959 960 961 962 963 964 965 966 967 968 969 970
			status_printf_ln(s, color,
				_("  (use \"git commit\" to conclude merge)"));
	}
	wt_status_print_trailer(s);
}

static void show_am_in_progress(struct wt_status *s,
				struct wt_status_state *state,
				const char *color)
{
	status_printf_ln(s, color,
		_("You are in the middle of an am session."));
	if (state->am_empty_patch)
		status_printf_ln(s, color,
			_("The current patch is empty."));
971
	if (s->hints) {
972 973
		if (!state->am_empty_patch)
			status_printf_ln(s, color,
974
				_("  (fix conflicts and then run \"git am --continue\")"));
975 976 977 978 979 980 981 982
		status_printf_ln(s, color,
			_("  (use \"git am --skip\" to skip this patch)"));
		status_printf_ln(s, color,
			_("  (use \"git am --abort\" to restore the original branch)"));
	}
	wt_status_print_trailer(s);
}

983 984 985 986 987 988 989 990
static char *read_line_from_git_path(const char *filename)
{
	struct strbuf buf = STRBUF_INIT;
	FILE *fp = fopen(git_path("%s", filename), "r");
	if (!fp) {
		strbuf_release(&buf);
		return NULL;
	}
991
	strbuf_getline_lf(&buf, fp);
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
	if (!fclose(fp)) {
		return strbuf_detach(&buf, NULL);
	} else {
		strbuf_release(&buf);
		return NULL;
	}
}

static int split_commit_in_progress(struct wt_status *s)
{
	int split_in_progress = 0;
	char *head = read_line_from_git_path("HEAD");
	char *orig_head = read_line_from_git_path("ORIG_HEAD");
	char *rebase_amend = read_line_from_git_path("rebase-merge/amend");
	char *rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head");

	if (!head || !orig_head || !rebase_amend || !rebase_orig_head ||
	    !s->branch || strcmp(s->branch, "HEAD"))
		return split_in_progress;

	if (!strcmp(rebase_amend, rebase_orig_head)) {
		if (strcmp(head, rebase_amend))
			split_in_progress = 1;
	} else if (strcmp(orig_head, rebase_orig_head)) {
		split_in_progress = 1;
	}

	if (!s->amend && !s->nowarn && !s->workdir_dirty)
		split_in_progress = 0;

	free(head);
	free(orig_head);
	free(rebase_amend);
	free(rebase_orig_head);
	return split_in_progress;
}

1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
/*
 * Turn
 * "pick d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some message"
 * into
 * "pick d6a2f03 some message"
 *
 * The function assumes that the line does not contain useless spaces
 * before or after the command.
 */
static void abbrev_sha1_in_line(struct strbuf *line)
{
	struct strbuf **split;
	int i;

	if (starts_with(line->buf, "exec ") ||
	    starts_with(line->buf, "x "))
		return;

	split = strbuf_split_max(line, ' ', 3);
	if (split[0] && split[1]) {
		unsigned char sha1[20];
		const char *abbrev;

		/*
		 * strbuf_split_max left a space. Trim it and re-add
		 * it after abbreviation.
		 */
		strbuf_trim(split[1]);
		if (!get_sha1(split[1]->buf, sha1)) {
			abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
			strbuf_reset(split[1]);
			strbuf_addf(split[1], "%s ", abbrev);
			strbuf_reset(line);
			for (i = 0; split[i]; i++)
				strbuf_addf(line, "%s", split[i]->buf);
		}
	}
	for (i = 0; split[i]; i++)
		strbuf_release(split[i]);

}

static void read_rebase_todolist(const char *fname, struct string_list *lines)
{
	struct strbuf line = STRBUF_INIT;
	FILE *f = fopen(git_path("%s", fname), "r");

	if (!f)
		die_errno("Could not open file %s for reading",
			  git_path("%s", fname));
1079
	while (!strbuf_getline_lf(&line, f)) {
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
		if (line.len && line.buf[0] == comment_line_char)
			continue;
		strbuf_trim(&line);
		if (!line.len)
			continue;
		abbrev_sha1_in_line(&line);
		string_list_append(lines, line.buf);
	}
}

static void show_rebase_information(struct wt_status *s,
					struct wt_status_state *state,
					const char *color)
{
	if (state->rebase_interactive_in_progress) {
		int i;
		int nr_lines_to_show = 2;

		struct string_list have_done = STRING_LIST_INIT_DUP;
		struct string_list yet_to_do = STRING_LIST_INIT_DUP;

		read_rebase_todolist("rebase-merge/done", &have_done);
		read_rebase_todolist("rebase-merge/git-rebase-todo", &yet_to_do);

		if (have_done.nr == 0)
			status_printf_ln(s, color, _("No commands done."));
		else {
			status_printf_ln(s, color,
				Q_("Last command done (%d command done):",
					"Last commands done (%d commands done):",
					have_done.nr),
				have_done.nr);
			for (i = (have_done.nr > nr_lines_to_show)
				? have_done.nr - nr_lines_to_show : 0;
				i < have_done.nr;
				i++)
				status_printf_ln(s, color, "   %s", have_done.items[i].string);
			if (have_done.nr > nr_lines_to_show && s->hints)
				status_printf_ln(s, color,
					_("  (see more in file %s)"), git_path("rebase-merge/done"));
		}

		if (yet_to_do.nr == 0)
			status_printf_ln(s, color,
					 _("No commands remaining."));
		else {
			status_printf_ln(s, color,
				Q_("Next command to do (%d remaining command):",
					"Next commands to do (%d remaining commands):",
					yet_to_do.nr),
				yet_to_do.nr);
			for (i = 0; i < nr_lines_to_show && i < yet_to_do.nr; i++)
				status_printf_ln(s, color, "   %s", yet_to_do.items[i].string);
			if (s->hints)
				status_printf_ln(s, color,
					_("  (use \"git rebase --edit-todo\" to view and edit)"));
		}
		string_list_clear(&yet_to_do, 0);
		string_list_clear(&have_done, 0);
	}
}

1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
static void print_rebase_state(struct wt_status *s,
				struct wt_status_state *state,
				const char *color)
{
	if (state->branch)
		status_printf_ln(s, color,
				 _("You are currently rebasing branch '%s' on '%s'."),
				 state->branch,
				 state->onto);
	else
		status_printf_ln(s, color,
				 _("You are currently rebasing."));
}

1156 1157 1158 1159 1160 1161
static void show_rebase_in_progress(struct wt_status *s,
				struct wt_status_state *state,
				const char *color)
{
	struct stat st;

1162
	show_rebase_information(s, state, color);
1163
	if (has_unmerged(s)) {
1164
		print_rebase_state(s, state, color);
1165
		if (s->hints) {
1166 1167 1168 1169 1170 1171 1172
			status_printf_ln(s, color,
				_("  (fix conflicts and then run \"git rebase --continue\")"));
			status_printf_ln(s, color,
				_("  (use \"git rebase --skip\" to skip this patch)"));
			status_printf_ln(s, color,
				_("  (use \"git rebase --abort\" to check out the original branch)"));
		}
1173
	} else if (state->rebase_in_progress || !stat(git_path_merge_msg(), &st)) {
1174
		print_rebase_state(s, state, color);
1175
		if (s->hints)
1176 1177
			status_printf_ln(s, color,
				_("  (all conflicts fixed: run \"git rebase --continue\")"));
1178
	} else if (split_commit_in_progress(s)) {
1179 1180 1181 1182 1183 1184 1185 1186
		if (state->branch)
			status_printf_ln(s, color,
					 _("You are currently splitting a commit while rebasing branch '%s' on '%s'."),
					 state->branch,
					 state->onto);
		else
			status_printf_ln(s, color,
					 _("You are currently splitting a commit during a rebase."));
1187
		if (s->hints)
1188 1189
			status_printf_ln(s, color,
				_("  (Once your working directory is clean, run \"git rebase --continue\")"));
1190
	} else {
1191 1192 1193 1194 1195 1196 1197 1198
		if (state->branch)
			status_printf_ln(s, color,
					 _("You are currently editing a commit while rebasing branch '%s' on '%s'."),
					 state->branch,
					 state->onto);
		else
			status_printf_ln(s, color,
					 _("You are currently editing a commit during a rebase."));
1199
		if (s->hints && !s->amend) {
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
			status_printf_ln(s, color,
				_("  (use \"git commit --amend\" to amend the current commit)"));
			status_printf_ln(s, color,
				_("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
		}
	}
	wt_status_print_trailer(s);
}

static void show_cherry_pick_in_progress(struct wt_status *s,
					struct wt_status_state *state,
					const char *color)
{
1213 1214
	status_printf_ln(s, color, _("You are currently cherry-picking commit %s."),
			find_unique_abbrev(state->cherry_pick_head_sha1, DEFAULT_ABBREV));
1215
	if (s->hints) {
1216 1217
		if (has_unmerged(s))
			status_printf_ln(s, color,
1218
				_("  (fix conflicts and run \"git cherry-pick --continue\")"));
1219 1220
		else
			status_printf_ln(s, color,
1221 1222 1223
				_("  (all conflicts fixed: run \"git cherry-pick --continue\")"));
		status_printf_ln(s, color,
			_("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
1224 1225 1226 1227
	}
	wt_status_print_trailer(s);
}

1228 1229 1230 1231
static void show_revert_in_progress(struct wt_status *s,
					struct wt_status_state *state,
					const char *color)
{
1232 1233
	status_printf_ln(s, color, _("You are currently reverting commit %s."),
			 find_unique_abbrev(state->revert_head_sha1, DEFAULT_ABBREV));
1234
	if (s->hints) {
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246
		if (has_unmerged(s))
			status_printf_ln(s, color,
				_("  (fix conflicts and run \"git revert --continue\")"));
		else
			status_printf_ln(s, color,
				_("  (all conflicts fixed: run \"git revert --continue\")"));
		status_printf_ln(s, color,
			_("  (use \"git revert --abort\" to cancel the revert operation)"));
	}
	wt_status_print_trailer(s);
}

1247 1248 1249 1250
static void show_bisect_in_progress(struct wt_status *s,
				struct wt_status_state *state,
				const char *color)
{
1251 1252
	if (state->branch)
		status_printf_ln(s, color,
1253
				 _("You are currently bisecting, started from branch '%s'."),
1254 1255 1256 1257
				 state->branch);
	else
		status_printf_ln(s, color,
				 _("You are currently bisecting."));
1258
	if (s->hints)
1259 1260 1261 1262 1263
		status_printf_ln(s, color,
			_("  (use \"git bisect reset\" to get back to the original branch)"));
	wt_status_print_trailer(s);
}

1264 1265 1266
/*
 * Extract branch information from rebase/bisect
 */
1267
static char *read_and_strip_branch(const char *path)
1268
{
1269
	struct strbuf sb = STRBUF_INIT;
1270
	unsigned char sha1[20];
1271
	const char *branch_name;
1272

1273 1274
	if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
		goto got_nothing;
1275

1276
	while (sb.len && sb.buf[sb.len - 1] == '\n')
1277 1278 1279
		strbuf_setlen(&sb, sb.len - 1);
	if (!sb.len)
		goto got_nothing;
1280 1281
	if (skip_prefix(sb.buf, "refs/heads/", &branch_name))
		strbuf_remove(&sb, 0, branch_name - sb.buf);
1282
	else if (starts_with(sb.buf, "refs/"))
1283 1284
		;
	else if (!get_sha1_hex(sb.buf, sha1)) {
1285 1286
		const char *abbrev;
		abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
1287 1288 1289 1290
		strbuf_reset(&sb);
		strbuf_addstr(&sb, abbrev);
	} else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
		goto got_nothing;
1291
	else			/* bisect */
1292 1293 1294 1295 1296 1297
		;
	return strbuf_detach(&sb, NULL);

got_nothing:
	strbuf_release(&sb);
	return NULL;
1298 1299
}

1300 1301 1302 1303 1304 1305 1306 1307
struct grab_1st_switch_cbdata {
	struct strbuf buf;
	unsigned char nsha1[20];
};

static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
			   const char *email, unsigned long timestamp, int tz,
			   const char *message, void *cb_data)
1308
{
1309 1310
	struct grab_1st_switch_cbdata *cb = cb_data;
	const char *target = NULL, *end;
1311

1312
	if (!skip_prefix(message, "checkout: moving from ", &message))
1313 1314 1315 1316 1317 1318 1319
		return 0;
	target = strstr(message, " to ");
	if (!target)
		return 0;
	target += strlen(" to ");
	strbuf_reset(&cb->buf);
	hashcpy(cb->nsha1, nsha1);
1320 1321 1322
	end = strchrnul(target, '\n');
	strbuf_add(&cb->buf, target, end - target);
	if (!strcmp(cb->buf.buf, "HEAD")) {
1323
		/* HEAD is relative. Resolve it to the right reflog entry. */
1324
		strbuf_reset(&cb->buf);
1325 1326 1327
		strbuf_addstr(&cb->buf,
			      find_unique_abbrev(nsha1, DEFAULT_ABBREV));
	}
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348
	return 1;
}

static void wt_status_get_detached_from(struct wt_status_state *state)
{
	struct grab_1st_switch_cbdata cb;
	struct commit *commit;
	unsigned char sha1[20];
	char *ref = NULL;

	strbuf_init(&cb.buf, 0);
	if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
		strbuf_release(&cb.buf);
		return;
	}

	if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
	    /* sha1 is a commit? match without further lookup */
	    (!hashcmp(cb.nsha1, sha1) ||
	     /* perhaps sha1 is a tag, try to dereference to a commit */
	     ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
1349
	      !hashcmp(cb.nsha1, commit->object.oid.hash)))) {
1350 1351 1352 1353
		const char *from = ref;
		if (!skip_prefix(from, "refs/tags/", &from))
			skip_prefix(from, "refs/remotes/", &from);
		state->detached_from = xstrdup(from);
1354 1355 1356 1357
	} else
		state->detached_from =
			xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
	hashcpy(state->detached_sha1, cb.nsha1);
1358 1359
	state->detached_at = !get_sha1("HEAD", sha1) &&
			     !hashcmp(sha1, state->detached_sha1);
Duy Nguyen's avatar