Coverage Report

Created: 2026-03-31 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/wt-status.c
Line
Count
Source
1
#define USE_THE_REPOSITORY_VARIABLE
2
#define DISABLE_SIGN_COMPARE_WARNINGS
3
4
#include "git-compat-util.h"
5
#include "advice.h"
6
#include "wt-status.h"
7
#include "object.h"
8
#include "dir.h"
9
#include "commit.h"
10
#include "diff.h"
11
#include "environment.h"
12
#include "gettext.h"
13
#include "hash.h"
14
#include "hex.h"
15
#include "object-name.h"
16
#include "path.h"
17
#include "revision.h"
18
#include "diffcore.h"
19
#include "quote.h"
20
#include "repository.h"
21
#include "run-command.h"
22
#include "strvec.h"
23
#include "remote.h"
24
#include "refs.h"
25
#include "submodule.h"
26
#include "column.h"
27
#include "read-cache.h"
28
#include "setup.h"
29
#include "strbuf.h"
30
#include "trace.h"
31
#include "trace2.h"
32
#include "tree.h"
33
#include "utf8.h"
34
#include "worktree.h"
35
#include "lockfile.h"
36
#include "sequencer.h"
37
#include "fsmonitor-settings.h"
38
39
0
#define AB_DELAY_WARNING_IN_MS (2 * 1000)
40
0
#define UF_DELAY_WARNING_IN_MS (2 * 1000)
41
42
static const char cut_line[] =
43
"------------------------ >8 ------------------------\n";
44
45
static char default_wt_status_colors[][COLOR_MAXLEN] = {
46
  GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
47
  GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
48
  GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
49
  GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
50
  GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
51
  GIT_COLOR_RED,    /* WT_STATUS_UNMERGED */
52
  GIT_COLOR_GREEN,  /* WT_STATUS_LOCAL_BRANCH */
53
  GIT_COLOR_RED,    /* WT_STATUS_REMOTE_BRANCH */
54
  GIT_COLOR_NIL,    /* WT_STATUS_ONBRANCH */
55
};
56
57
static const char *color(int slot, struct wt_status *s)
58
0
{
59
0
  const char *c = "";
60
0
  if (want_color(s->use_color))
61
0
    c = s->color_palette[slot];
62
0
  if (slot == WT_STATUS_ONBRANCH && color_is_nil(c))
63
0
    c = s->color_palette[WT_STATUS_HEADER];
64
0
  return c;
65
0
}
66
67
static void status_vprintf(struct wt_status *s, int at_bol, const char *color,
68
    const char *fmt, va_list ap, const char *trail)
69
0
{
70
0
  struct strbuf sb = STRBUF_INIT;
71
0
  struct strbuf linebuf = STRBUF_INIT;
72
0
  const char *line, *eol;
73
74
0
  strbuf_vaddf(&sb, fmt, ap);
75
0
  if (!sb.len) {
76
0
    if (s->display_comment_prefix) {
77
0
      strbuf_addstr(&sb, comment_line_str);
78
0
      if (!trail)
79
0
        strbuf_addch(&sb, ' ');
80
0
    }
81
0
    color_print_strbuf(s->fp, color, &sb);
82
0
    if (trail)
83
0
      fprintf(s->fp, "%s", trail);
84
0
    strbuf_release(&sb);
85
0
    return;
86
0
  }
87
0
  for (line = sb.buf; *line; line = eol + 1) {
88
0
    eol = strchr(line, '\n');
89
90
0
    strbuf_reset(&linebuf);
91
0
    if (at_bol && s->display_comment_prefix) {
92
0
      strbuf_addstr(&linebuf, comment_line_str);
93
0
      if (*line != '\n' && *line != '\t')
94
0
        strbuf_addch(&linebuf, ' ');
95
0
    }
96
0
    if (eol)
97
0
      strbuf_add(&linebuf, line, eol - line);
98
0
    else
99
0
      strbuf_addstr(&linebuf, line);
100
0
    color_print_strbuf(s->fp, color, &linebuf);
101
0
    if (eol)
102
0
      fprintf(s->fp, "\n");
103
0
    else
104
0
      break;
105
0
    at_bol = 1;
106
0
  }
107
0
  if (trail)
108
0
    fprintf(s->fp, "%s", trail);
109
0
  strbuf_release(&linebuf);
110
0
  strbuf_release(&sb);
111
0
}
112
113
void status_printf_ln(struct wt_status *s, const char *color,
114
      const char *fmt, ...)
115
0
{
116
0
  va_list ap;
117
118
0
  va_start(ap, fmt);
119
0
  status_vprintf(s, 1, color, fmt, ap, "\n");
120
0
  va_end(ap);
121
0
}
122
123
void status_printf(struct wt_status *s, const char *color,
124
      const char *fmt, ...)
125
0
{
126
0
  va_list ap;
127
128
0
  va_start(ap, fmt);
129
0
  status_vprintf(s, 1, color, fmt, ap, NULL);
130
0
  va_end(ap);
131
0
}
132
133
__attribute__((format (printf, 3, 4)))
134
static void status_printf_more(struct wt_status *s, const char *color,
135
             const char *fmt, ...)
136
0
{
137
0
  va_list ap;
138
139
0
  va_start(ap, fmt);
140
0
  status_vprintf(s, 0, color, fmt, ap, NULL);
141
0
  va_end(ap);
142
0
}
143
144
void wt_status_prepare(struct repository *r, struct wt_status *s)
145
0
{
146
0
  memset(s, 0, sizeof(*s));
147
0
  s->repo = r;
148
0
  memcpy(s->color_palette, default_wt_status_colors,
149
0
         sizeof(default_wt_status_colors));
150
0
  s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
151
0
  s->use_color = GIT_COLOR_UNKNOWN;
152
0
  s->relative_paths = 1;
153
0
  s->branch = refs_resolve_refdup(get_main_ref_store(r),
154
0
          "HEAD", 0, NULL, NULL);
155
0
  s->reference = "HEAD";
156
0
  s->fp = stdout;
157
0
  s->index_file = repo_get_index_file(r);
158
0
  s->change.strdup_strings = 1;
159
0
  s->untracked.strdup_strings = 1;
160
0
  s->ignored.strdup_strings = 1;
161
0
  s->show_branch = -1;  /* unspecified */
162
0
  s->show_stash = 0;
163
0
  s->ahead_behind_flags = AHEAD_BEHIND_UNSPECIFIED;
164
0
  s->display_comment_prefix = 0;
165
0
  s->detect_rename = -1;
166
0
  s->rename_score = -1;
167
0
  s->rename_limit = -1;
168
0
}
169
170
static void wt_longstatus_print_unmerged_header(struct wt_status *s)
171
0
{
172
0
  int i;
173
0
  int del_mod_conflict = 0;
174
0
  int both_deleted = 0;
175
0
  int not_deleted = 0;
176
0
  const char *c = color(WT_STATUS_HEADER, s);
177
178
0
  status_printf_ln(s, c, _("Unmerged paths:"));
179
180
0
  for (i = 0; i < s->change.nr; i++) {
181
0
    struct string_list_item *it = &(s->change.items[i]);
182
0
    struct wt_status_change_data *d = it->util;
183
184
0
    switch (d->stagemask) {
185
0
    case 0:
186
0
      break;
187
0
    case 1:
188
0
      both_deleted = 1;
189
0
      break;
190
0
    case 3:
191
0
    case 5:
192
0
      del_mod_conflict = 1;
193
0
      break;
194
0
    default:
195
0
      not_deleted = 1;
196
0
      break;
197
0
    }
198
0
  }
199
200
0
  if (!s->hints)
201
0
    return;
202
0
  if (s->whence != FROM_COMMIT)
203
0
    ;
204
0
  else if (!s->is_initial) {
205
0
    if (!strcmp(s->reference, "HEAD"))
206
0
      status_printf_ln(s, c,
207
0
           _("  (use \"git restore --staged <file>...\" to unstage)"));
208
0
    else
209
0
      status_printf_ln(s, c,
210
0
           _("  (use \"git restore --source=%s --staged <file>...\" to unstage)"),
211
0
           s->reference);
212
0
  } else
213
0
    status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
214
215
0
  if (!both_deleted) {
216
0
    if (!del_mod_conflict)
217
0
      status_printf_ln(s, c, _("  (use \"git add <file>...\" to mark resolution)"));
218
0
    else
219
0
      status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
220
0
  } else if (!del_mod_conflict && !not_deleted) {
221
0
    status_printf_ln(s, c, _("  (use \"git rm <file>...\" to mark resolution)"));
222
0
  } else {
223
0
    status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" as appropriate to mark resolution)"));
224
0
  }
225
0
}
226
227
static void wt_longstatus_print_cached_header(struct wt_status *s)
228
0
{
229
0
  const char *c = color(WT_STATUS_HEADER, s);
230
231
0
  status_printf_ln(s, c, _("Changes to be committed:"));
232
0
  if (!s->hints)
233
0
    return;
234
0
  if (s->whence != FROM_COMMIT)
235
0
    ; /* NEEDSWORK: use "git reset --unresolve"??? */
236
0
  else if (!s->is_initial) {
237
0
    if (!strcmp(s->reference, "HEAD"))
238
0
      status_printf_ln(s, c
239
0
           , _("  (use \"git restore --staged <file>...\" to unstage)"));
240
0
    else
241
0
      status_printf_ln(s, c,
242
0
           _("  (use \"git restore --source=%s --staged <file>...\" to unstage)"),
243
0
           s->reference);
244
0
  } else
245
0
    status_printf_ln(s, c, _("  (use \"git rm --cached <file>...\" to unstage)"));
246
0
}
247
248
static void wt_longstatus_print_dirty_header(struct wt_status *s,
249
               int has_deleted,
250
               int has_dirty_submodules)
251
0
{
252
0
  const char *c = color(WT_STATUS_HEADER, s);
253
254
0
  status_printf_ln(s, c, _("Changes not staged for commit:"));
255
0
  if (!s->hints)
256
0
    return;
257
0
  if (!has_deleted)
258
0
    status_printf_ln(s, c, _("  (use \"git add <file>...\" to update what will be committed)"));
259
0
  else
260
0
    status_printf_ln(s, c, _("  (use \"git add/rm <file>...\" to update what will be committed)"));
261
0
  status_printf_ln(s, c, _("  (use \"git restore <file>...\" to discard changes in working directory)"));
262
0
  if (has_dirty_submodules)
263
0
    status_printf_ln(s, c, _("  (commit or discard the untracked or modified content in submodules)"));
264
0
}
265
266
static void wt_longstatus_print_other_header(struct wt_status *s,
267
               const char *what,
268
               const char *how)
269
0
{
270
0
  const char *c = color(WT_STATUS_HEADER, s);
271
0
  status_printf_ln(s, c, "%s:", what);
272
0
  if (!s->hints)
273
0
    return;
274
0
  status_printf_ln(s, c, _("  (use \"git %s <file>...\" to include in what will be committed)"), how);
275
0
}
276
277
static void wt_longstatus_print_trailer(struct wt_status *s)
278
0
{
279
0
  status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
280
0
}
281
282
static const char *wt_status_unmerged_status_string(int stagemask)
283
0
{
284
0
  switch (stagemask) {
285
0
  case 1:
286
0
    return _("both deleted:");
287
0
  case 2:
288
0
    return _("added by us:");
289
0
  case 3:
290
0
    return _("deleted by them:");
291
0
  case 4:
292
0
    return _("added by them:");
293
0
  case 5:
294
0
    return _("deleted by us:");
295
0
  case 6:
296
0
    return _("both added:");
297
0
  case 7:
298
0
    return _("both modified:");
299
0
  default:
300
0
    BUG("unhandled unmerged status %x", stagemask);
301
0
  }
302
0
}
303
304
static const char *wt_status_diff_status_string(int status)
305
0
{
306
0
  switch (status) {
307
0
  case DIFF_STATUS_ADDED:
308
0
    return _("new file:");
309
0
  case DIFF_STATUS_COPIED:
310
0
    return _("copied:");
311
0
  case DIFF_STATUS_DELETED:
312
0
    return _("deleted:");
313
0
  case DIFF_STATUS_MODIFIED:
314
0
    return _("modified:");
315
0
  case DIFF_STATUS_RENAMED:
316
0
    return _("renamed:");
317
0
  case DIFF_STATUS_TYPE_CHANGED:
318
0
    return _("typechange:");
319
0
  case DIFF_STATUS_UNKNOWN:
320
0
    return _("unknown:");
321
0
  case DIFF_STATUS_UNMERGED:
322
0
    return _("unmerged:");
323
0
  default:
324
0
    return NULL;
325
0
  }
326
0
}
327
328
static int maxwidth(const char *(*label)(int), int minval, int maxval)
329
0
{
330
0
  int result = 0, i;
331
332
0
  for (i = minval; i <= maxval; i++) {
333
0
    const char *s = label(i);
334
0
    int len = s ? utf8_strwidth(s) : 0;
335
0
    if (len > result)
336
0
      result = len;
337
0
  }
338
0
  return result;
339
0
}
340
341
static void wt_longstatus_print_unmerged_data(struct wt_status *s,
342
                struct string_list_item *it)
343
0
{
344
0
  const char *c = color(WT_STATUS_UNMERGED, s);
345
0
  struct wt_status_change_data *d = it->util;
346
0
  struct strbuf onebuf = STRBUF_INIT;
347
0
  static char *padding;
348
0
  static int label_width;
349
0
  const char *one, *how;
350
0
  int len;
351
352
0
  if (!padding) {
353
0
    label_width = maxwidth(wt_status_unmerged_status_string, 1, 7);
354
0
    label_width += strlen(" ");
355
0
    padding = xmallocz(label_width);
356
0
    memset(padding, ' ', label_width);
357
0
  }
358
359
0
  one = quote_path(it->string, s->prefix, &onebuf, 0);
360
0
  status_printf(s, color(WT_STATUS_HEADER, s), "\t");
361
362
0
  how = wt_status_unmerged_status_string(d->stagemask);
363
0
  len = label_width - utf8_strwidth(how);
364
0
  status_printf_more(s, c, "%s%.*s%s\n", how, len, padding, one);
365
0
  strbuf_release(&onebuf);
366
0
}
367
368
static void wt_longstatus_print_change_data(struct wt_status *s,
369
              int change_type,
370
              struct string_list_item *it)
371
0
{
372
0
  struct wt_status_change_data *d = it->util;
373
0
  const char *c = color(change_type, s);
374
0
  int status;
375
0
  char *one_name;
376
0
  char *two_name;
377
0
  const char *one, *two;
378
0
  struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT;
379
0
  struct strbuf extra = STRBUF_INIT;
380
0
  static char *padding;
381
0
  static int label_width;
382
0
  const char *what;
383
0
  int len;
384
385
0
  if (!padding) {
386
    /* If DIFF_STATUS_* uses outside the range [A..Z], we're in trouble */
387
0
    label_width = maxwidth(wt_status_diff_status_string, 'A', 'Z');
388
0
    label_width += strlen(" ");
389
0
    padding = xmallocz(label_width);
390
0
    memset(padding, ' ', label_width);
391
0
  }
392
393
0
  one_name = two_name = it->string;
394
0
  switch (change_type) {
395
0
  case WT_STATUS_UPDATED:
396
0
    status = d->index_status;
397
0
    break;
398
0
  case WT_STATUS_CHANGED:
399
0
    if (d->new_submodule_commits || d->dirty_submodule) {
400
0
      strbuf_addstr(&extra, " (");
401
0
      if (d->new_submodule_commits)
402
0
        strbuf_addstr(&extra, _("new commits, "));
403
0
      if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
404
0
        strbuf_addstr(&extra, _("modified content, "));
405
0
      if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
406
0
        strbuf_addstr(&extra, _("untracked content, "));
407
0
      strbuf_setlen(&extra, extra.len - 2);
408
0
      strbuf_addch(&extra, ')');
409
0
    }
410
0
    status = d->worktree_status;
411
0
    break;
412
0
  default:
413
0
    BUG("unhandled change_type %d in wt_longstatus_print_change_data",
414
0
        change_type);
415
0
  }
416
417
  /*
418
   * Only pick up the rename it's relevant. If the rename is for
419
   * the changed section and we're printing the updated section,
420
   * ignore it.
421
   */
422
0
  if (d->rename_status == status)
423
0
    one_name = d->rename_source;
424
425
0
  one = quote_path(one_name, s->prefix, &onebuf, 0);
426
0
  two = quote_path(two_name, s->prefix, &twobuf, 0);
427
428
0
  status_printf(s, color(WT_STATUS_HEADER, s), "\t");
429
0
  what = wt_status_diff_status_string(status);
430
0
  if (!what)
431
0
    BUG("unhandled diff status %c", status);
432
0
  len = label_width - utf8_strwidth(what);
433
0
  assert(len >= 0);
434
0
  if (one_name != two_name)
435
0
    status_printf_more(s, c, "%s%.*s%s -> %s",
436
0
           what, len, padding, one, two);
437
0
  else
438
0
    status_printf_more(s, c, "%s%.*s%s",
439
0
           what, len, padding, one);
440
0
  if (extra.len) {
441
0
    status_printf_more(s, color(WT_STATUS_HEADER, s), "%s", extra.buf);
442
0
    strbuf_release(&extra);
443
0
  }
444
0
  status_printf_more(s, GIT_COLOR_NORMAL, "\n");
445
0
  strbuf_release(&onebuf);
446
0
  strbuf_release(&twobuf);
447
0
}
448
449
static char short_submodule_status(struct wt_status_change_data *d)
450
0
{
451
0
  if (d->new_submodule_commits)
452
0
    return 'M';
453
0
  if (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED)
454
0
    return 'm';
455
0
  if (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
456
0
    return '?';
457
0
  return d->worktree_status;
458
0
}
459
460
static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
461
           struct diff_options *options UNUSED,
462
           void *data)
463
0
{
464
0
  struct wt_status *s = data;
465
0
  int i;
466
467
0
  if (!q->nr)
468
0
    return;
469
0
  s->workdir_dirty = 1;
470
0
  for (i = 0; i < q->nr; i++) {
471
0
    struct diff_filepair *p;
472
0
    struct string_list_item *it;
473
0
    struct wt_status_change_data *d;
474
475
0
    p = q->queue[i];
476
0
    it = string_list_insert(&s->change, p->two->path);
477
0
    d = it->util;
478
0
    if (!d) {
479
0
      CALLOC_ARRAY(d, 1);
480
0
      it->util = d;
481
0
    }
482
0
    if (!d->worktree_status)
483
0
      d->worktree_status = p->status;
484
0
    if (S_ISGITLINK(p->two->mode)) {
485
0
      d->dirty_submodule = p->two->dirty_submodule;
486
0
      d->new_submodule_commits = !oideq(&p->one->oid,
487
0
                &p->two->oid);
488
0
      if (s->status_format == STATUS_FORMAT_SHORT)
489
0
        d->worktree_status = short_submodule_status(d);
490
0
    }
491
492
0
    switch (p->status) {
493
0
    case DIFF_STATUS_ADDED:
494
0
      d->mode_worktree = p->two->mode;
495
0
      break;
496
497
0
    case DIFF_STATUS_DELETED:
498
0
      d->mode_index = p->one->mode;
499
0
      oidcpy(&d->oid_index, &p->one->oid);
500
      /* mode_worktree is zero for a delete. */
501
0
      break;
502
503
0
    case DIFF_STATUS_COPIED:
504
0
    case DIFF_STATUS_RENAMED:
505
0
      if (d->rename_status)
506
0
        BUG("multiple renames on the same target? how?");
507
0
      d->rename_source = xstrdup(p->one->path);
508
0
      d->rename_score = p->score * 100 / MAX_SCORE;
509
0
      d->rename_status = p->status;
510
      /* fallthru */
511
0
    case DIFF_STATUS_MODIFIED:
512
0
    case DIFF_STATUS_TYPE_CHANGED:
513
0
    case DIFF_STATUS_UNMERGED:
514
0
      d->mode_index = p->one->mode;
515
0
      d->mode_worktree = p->two->mode;
516
0
      oidcpy(&d->oid_index, &p->one->oid);
517
0
      break;
518
519
0
    default:
520
0
      BUG("unhandled diff-files status '%c'", p->status);
521
0
      break;
522
0
    }
523
524
0
  }
525
0
}
526
527
static int unmerged_mask(struct index_state *istate, const char *path)
528
0
{
529
0
  int pos, mask;
530
0
  const struct cache_entry *ce;
531
532
0
  pos = index_name_pos(istate, path, strlen(path));
533
0
  if (0 <= pos)
534
0
    return 0;
535
536
0
  mask = 0;
537
0
  pos = -pos-1;
538
0
  while (pos < istate->cache_nr) {
539
0
    ce = istate->cache[pos++];
540
0
    if (strcmp(ce->name, path) || !ce_stage(ce))
541
0
      break;
542
0
    mask |= (1 << (ce_stage(ce) - 1));
543
0
  }
544
0
  return mask;
545
0
}
546
547
static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
548
           struct diff_options *options UNUSED,
549
           void *data)
550
0
{
551
0
  struct wt_status *s = data;
552
0
  int i;
553
554
0
  for (i = 0; i < q->nr; i++) {
555
0
    struct diff_filepair *p;
556
0
    struct string_list_item *it;
557
0
    struct wt_status_change_data *d;
558
559
0
    p = q->queue[i];
560
0
    it = string_list_insert(&s->change, p->two->path);
561
0
    d = it->util;
562
0
    if (!d) {
563
0
      CALLOC_ARRAY(d, 1);
564
0
      it->util = d;
565
0
    }
566
0
    if (!d->index_status)
567
0
      d->index_status = p->status;
568
0
    switch (p->status) {
569
0
    case DIFF_STATUS_ADDED:
570
      /* Leave {mode,oid}_head zero for an add. */
571
0
      d->mode_index = p->two->mode;
572
0
      oidcpy(&d->oid_index, &p->two->oid);
573
0
      s->committable = 1;
574
0
      break;
575
0
    case DIFF_STATUS_DELETED:
576
0
      d->mode_head = p->one->mode;
577
0
      oidcpy(&d->oid_head, &p->one->oid);
578
0
      s->committable = 1;
579
      /* Leave {mode,oid}_index zero for a delete. */
580
0
      break;
581
582
0
    case DIFF_STATUS_COPIED:
583
0
    case DIFF_STATUS_RENAMED:
584
0
      if (d->rename_status)
585
0
        BUG("multiple renames on the same target? how?");
586
0
      d->rename_source = xstrdup(p->one->path);
587
0
      d->rename_score = p->score * 100 / MAX_SCORE;
588
0
      d->rename_status = p->status;
589
      /* fallthru */
590
0
    case DIFF_STATUS_MODIFIED:
591
0
    case DIFF_STATUS_TYPE_CHANGED:
592
0
      d->mode_head = p->one->mode;
593
0
      d->mode_index = p->two->mode;
594
0
      oidcpy(&d->oid_head, &p->one->oid);
595
0
      oidcpy(&d->oid_index, &p->two->oid);
596
0
      s->committable = 1;
597
0
      break;
598
0
    case DIFF_STATUS_UNMERGED:
599
0
      d->stagemask = unmerged_mask(s->repo->index,
600
0
                 p->two->path);
601
      /*
602
       * Don't bother setting {mode,oid}_{head,index} since the print
603
       * code will output the stage values directly and not use the
604
       * values in these fields.
605
       */
606
0
      break;
607
608
0
    default:
609
0
      BUG("unhandled diff-index status '%c'", p->status);
610
0
      break;
611
0
    }
612
0
  }
613
0
}
614
615
void wt_status_collect_changes_trees(struct wt_status *s,
616
             const struct object_id *old_treeish,
617
             const struct object_id *new_treeish)
618
0
{
619
0
  struct diff_options opts = { 0 };
620
621
0
  repo_diff_setup(s->repo, &opts);
622
0
  opts.output_format = DIFF_FORMAT_CALLBACK;
623
0
  opts.format_callback = wt_status_collect_updated_cb;
624
0
  opts.format_callback_data = s;
625
0
  opts.detect_rename = s->detect_rename >= 0 ? s->detect_rename : opts.detect_rename;
626
0
  opts.rename_limit = s->rename_limit >= 0 ? s->rename_limit : opts.rename_limit;
627
0
  opts.rename_score = s->rename_score >= 0 ? s->rename_score : opts.rename_score;
628
0
  opts.flags.recursive = 1;
629
0
  diff_setup_done(&opts);
630
631
0
  diff_tree_oid(old_treeish, new_treeish, "", &opts);
632
0
  diffcore_std(&opts);
633
0
  diff_flush(&opts);
634
0
  wt_status_get_state(s->repo, &s->state, 0);
635
636
0
  diff_free(&opts);
637
0
}
638
639
static void wt_status_collect_changes_worktree(struct wt_status *s)
640
0
{
641
0
  struct rev_info rev;
642
643
0
  repo_init_revisions(s->repo, &rev, NULL);
644
0
  setup_revisions(0, NULL, &rev, NULL);
645
0
  rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
646
0
  rev.diffopt.flags.dirty_submodules = 1;
647
0
  rev.diffopt.ita_invisible_in_index = 1;
648
0
  if (!s->show_untracked_files)
649
0
    rev.diffopt.flags.ignore_untracked_in_submodules = 1;
650
0
  if (s->ignore_submodule_arg) {
651
0
    rev.diffopt.flags.override_submodule_config = 1;
652
0
    handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
653
0
  } else if (!rev.diffopt.flags.ignore_submodule_set &&
654
0
      s->show_untracked_files != SHOW_NO_UNTRACKED_FILES)
655
0
    handle_ignore_submodules_arg(&rev.diffopt, "none");
656
0
  rev.diffopt.format_callback = wt_status_collect_changed_cb;
657
0
  rev.diffopt.format_callback_data = s;
658
0
  rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
659
0
  rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
660
0
  rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
661
0
  copy_pathspec(&rev.prune_data, &s->pathspec);
662
0
  run_diff_files(&rev, 0);
663
0
  release_revisions(&rev);
664
0
}
665
666
static void wt_status_collect_changes_index(struct wt_status *s)
667
0
{
668
0
  struct rev_info rev;
669
0
  struct setup_revision_opt opt;
670
671
0
  repo_init_revisions(s->repo, &rev, NULL);
672
0
  memset(&opt, 0, sizeof(opt));
673
0
  opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference;
674
0
  setup_revisions(0, NULL, &rev, &opt);
675
676
0
  rev.diffopt.flags.override_submodule_config = 1;
677
0
  rev.diffopt.ita_invisible_in_index = 1;
678
0
  if (s->ignore_submodule_arg) {
679
0
    handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
680
0
  } else {
681
    /*
682
     * Unless the user did explicitly request a submodule ignore
683
     * mode by passing a command line option we do not ignore any
684
     * changed submodule SHA-1s when comparing index and HEAD, no
685
     * matter what is configured. Otherwise the user won't be
686
     * shown any submodules manually added (and which are
687
     * staged to be committed), which would be really confusing.
688
     */
689
0
    handle_ignore_submodules_arg(&rev.diffopt, "dirty");
690
0
  }
691
692
0
  rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
693
0
  rev.diffopt.format_callback = wt_status_collect_updated_cb;
694
0
  rev.diffopt.format_callback_data = s;
695
0
  rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
696
0
  rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
697
0
  rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
698
699
  /*
700
   * The `recursive` option must be enabled to allow the diff to recurse
701
   * into subdirectories of sparse directory index entries. If it is not
702
   * enabled, a subdirectory containing file(s) with changes is reported
703
   * as "modified", rather than the modified files themselves.
704
   */
705
0
  rev.diffopt.flags.recursive = 1;
706
707
0
  copy_pathspec(&rev.prune_data, &s->pathspec);
708
0
  run_diff_index(&rev, DIFF_INDEX_CACHED);
709
0
  release_revisions(&rev);
710
0
}
711
712
static int add_file_to_list(const struct object_id *oid,
713
          struct strbuf *base, const char *path,
714
          unsigned int mode, void *context)
715
0
{
716
0
  struct string_list_item *it;
717
0
  struct wt_status_change_data *d;
718
0
  struct wt_status *s = context;
719
0
  struct strbuf full_name = STRBUF_INIT;
720
721
0
  if (S_ISDIR(mode))
722
0
    return READ_TREE_RECURSIVE;
723
724
0
  strbuf_add(&full_name, base->buf, base->len);
725
0
  strbuf_addstr(&full_name, path);
726
0
  it = string_list_insert(&s->change, full_name.buf);
727
0
  d = it->util;
728
0
  if (!d) {
729
0
    CALLOC_ARRAY(d, 1);
730
0
    it->util = d;
731
0
  }
732
733
0
  d->index_status = DIFF_STATUS_ADDED;
734
  /* Leave {mode,oid}_head zero for adds. */
735
0
  d->mode_index = mode;
736
0
  oidcpy(&d->oid_index, oid);
737
0
  s->committable = 1;
738
0
  strbuf_release(&full_name);
739
0
  return 0;
740
0
}
741
742
static void wt_status_collect_changes_initial(struct wt_status *s)
743
0
{
744
0
  struct index_state *istate = s->repo->index;
745
0
  struct strbuf base = STRBUF_INIT;
746
0
  int i;
747
748
0
  for (i = 0; i < istate->cache_nr; i++) {
749
0
    struct string_list_item *it;
750
0
    struct wt_status_change_data *d;
751
0
    const struct cache_entry *ce = istate->cache[i];
752
753
0
    if (!ce_path_match(istate, ce, &s->pathspec, NULL))
754
0
      continue;
755
0
    if (ce_intent_to_add(ce))
756
0
      continue;
757
0
    if (S_ISSPARSEDIR(ce->ce_mode)) {
758
      /*
759
       * This is a sparse directory entry, so we want to collect all
760
       * of the added files within the tree. This requires recursively
761
       * expanding the trees to find the elements that are new in this
762
       * tree and marking them with DIFF_STATUS_ADDED.
763
       */
764
0
      struct pathspec ps = { 0 };
765
0
      struct tree *tree = lookup_tree(istate->repo, &ce->oid);
766
767
0
      ps.recursive = 1;
768
0
      ps.has_wildcard = 1;
769
0
      ps.max_depth = -1;
770
771
0
      strbuf_reset(&base);
772
0
      strbuf_add(&base, ce->name, ce->ce_namelen);
773
0
      read_tree_at(istate->repo, tree, &base, 0, &ps,
774
0
             add_file_to_list, s);
775
776
0
      continue;
777
0
    }
778
779
0
    it = string_list_insert(&s->change, ce->name);
780
0
    d = it->util;
781
0
    if (!d) {
782
0
      CALLOC_ARRAY(d, 1);
783
0
      it->util = d;
784
0
    }
785
0
    if (ce_stage(ce)) {
786
0
      d->index_status = DIFF_STATUS_UNMERGED;
787
0
      d->stagemask |= (1 << (ce_stage(ce) - 1));
788
      /*
789
       * Don't bother setting {mode,oid}_{head,index} since the print
790
       * code will output the stage values directly and not use the
791
       * values in these fields.
792
       */
793
0
      s->committable = 1;
794
0
    } else {
795
0
      d->index_status = DIFF_STATUS_ADDED;
796
      /* Leave {mode,oid}_head zero for adds. */
797
0
      d->mode_index = ce->ce_mode;
798
0
      oidcpy(&d->oid_index, &ce->oid);
799
0
      s->committable = 1;
800
0
    }
801
0
  }
802
803
0
  strbuf_release(&base);
804
0
}
805
806
static void wt_status_collect_untracked(struct wt_status *s)
807
0
{
808
0
  int i;
809
0
  struct dir_struct dir = DIR_INIT;
810
0
  uint64_t t_begin = getnanotime();
811
0
  struct index_state *istate = s->repo->index;
812
813
0
  if (!s->show_untracked_files)
814
0
    return;
815
816
0
  if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
817
0
    dir.flags |=
818
0
      DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
819
0
  if (s->show_ignored_mode) {
820
0
    dir.flags |= DIR_SHOW_IGNORED_TOO;
821
822
0
    if (s->show_ignored_mode == SHOW_MATCHING_IGNORED)
823
0
      dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
824
0
  } else {
825
0
    dir.untracked = istate->untracked;
826
0
  }
827
828
0
  setup_standard_excludes(&dir);
829
830
0
  fill_directory(&dir, istate, &s->pathspec);
831
832
0
  for (i = 0; i < dir.nr; i++) {
833
0
    struct dir_entry *ent = dir.entries[i];
834
0
    if (index_name_is_other(istate, ent->name, ent->len))
835
0
      string_list_insert(&s->untracked, ent->name);
836
0
  }
837
838
0
  for (i = 0; i < dir.ignored_nr; i++) {
839
0
    struct dir_entry *ent = dir.ignored[i];
840
0
    if (index_name_is_other(istate, ent->name, ent->len))
841
0
      string_list_insert(&s->ignored, ent->name);
842
0
  }
843
844
0
  dir_clear(&dir);
845
846
0
  if (advice_enabled(ADVICE_STATUS_U_OPTION))
847
0
    s->untracked_in_ms = (getnanotime() - t_begin) / 1000000;
848
0
}
849
850
static int has_unmerged(struct wt_status *s)
851
0
{
852
0
  int i;
853
854
0
  for (i = 0; i < s->change.nr; i++) {
855
0
    struct wt_status_change_data *d;
856
0
    d = s->change.items[i].util;
857
0
    if (d->stagemask)
858
0
      return 1;
859
0
  }
860
0
  return 0;
861
0
}
862
863
void wt_status_collect(struct wt_status *s)
864
0
{
865
0
  trace2_region_enter("status", "worktrees", s->repo);
866
0
  wt_status_collect_changes_worktree(s);
867
0
  trace2_region_leave("status", "worktrees", s->repo);
868
869
0
  if (s->is_initial) {
870
0
    trace2_region_enter("status", "initial", s->repo);
871
0
    wt_status_collect_changes_initial(s);
872
0
    trace2_region_leave("status", "initial", s->repo);
873
0
  } else {
874
0
    trace2_region_enter("status", "index", s->repo);
875
0
    wt_status_collect_changes_index(s);
876
0
    trace2_region_leave("status", "index", s->repo);
877
0
  }
878
879
0
  trace2_region_enter("status", "untracked", s->repo);
880
0
  wt_status_collect_untracked(s);
881
0
  trace2_region_leave("status", "untracked", s->repo);
882
883
0
  wt_status_get_state(s->repo, &s->state, s->branch && !strcmp(s->branch, "HEAD"));
884
0
  if (s->state.merge_in_progress && !has_unmerged(s))
885
0
    s->committable = 1;
886
0
}
887
888
void wt_status_collect_free_buffers(struct wt_status *s)
889
0
{
890
0
  wt_status_state_free_buffers(&s->state);
891
0
}
892
893
void wt_status_state_free_buffers(struct wt_status_state *state)
894
0
{
895
0
  FREE_AND_NULL(state->branch);
896
0
  FREE_AND_NULL(state->onto);
897
0
  FREE_AND_NULL(state->detached_from);
898
0
  FREE_AND_NULL(state->bisecting_from);
899
0
}
900
901
static void wt_longstatus_print_unmerged(struct wt_status *s)
902
0
{
903
0
  int shown_header = 0;
904
0
  int i;
905
906
0
  for (i = 0; i < s->change.nr; i++) {
907
0
    struct wt_status_change_data *d;
908
0
    struct string_list_item *it;
909
0
    it = &(s->change.items[i]);
910
0
    d = it->util;
911
0
    if (!d->stagemask)
912
0
      continue;
913
0
    if (!shown_header) {
914
0
      wt_longstatus_print_unmerged_header(s);
915
0
      shown_header = 1;
916
0
    }
917
0
    wt_longstatus_print_unmerged_data(s, it);
918
0
  }
919
0
  if (shown_header)
920
0
    wt_longstatus_print_trailer(s);
921
922
0
}
923
924
static void wt_longstatus_print_updated(struct wt_status *s)
925
0
{
926
0
  int shown_header = 0;
927
0
  int i;
928
929
0
  for (i = 0; i < s->change.nr; i++) {
930
0
    struct wt_status_change_data *d;
931
0
    struct string_list_item *it;
932
0
    it = &(s->change.items[i]);
933
0
    d = it->util;
934
0
    if (!d->index_status ||
935
0
        d->index_status == DIFF_STATUS_UNMERGED)
936
0
      continue;
937
0
    if (!shown_header) {
938
0
      wt_longstatus_print_cached_header(s);
939
0
      shown_header = 1;
940
0
    }
941
0
    wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it);
942
0
  }
943
0
  if (shown_header)
944
0
    wt_longstatus_print_trailer(s);
945
0
}
946
947
/*
948
 * -1 : has delete
949
 *  0 : no change
950
 *  1 : some change but no delete
951
 */
952
static int wt_status_check_worktree_changes(struct wt_status *s,
953
               int *dirty_submodules)
954
0
{
955
0
  int i;
956
0
  int changes = 0;
957
958
0
  *dirty_submodules = 0;
959
960
0
  for (i = 0; i < s->change.nr; i++) {
961
0
    struct wt_status_change_data *d;
962
0
    d = s->change.items[i].util;
963
0
    if (!d->worktree_status ||
964
0
        d->worktree_status == DIFF_STATUS_UNMERGED)
965
0
      continue;
966
0
    if (!changes)
967
0
      changes = 1;
968
0
    if (d->dirty_submodule)
969
0
      *dirty_submodules = 1;
970
0
    if (d->worktree_status == DIFF_STATUS_DELETED)
971
0
      changes = -1;
972
0
  }
973
0
  return changes;
974
0
}
975
976
static void wt_longstatus_print_changed(struct wt_status *s)
977
0
{
978
0
  int i, dirty_submodules;
979
0
  int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
980
981
0
  if (!worktree_changes)
982
0
    return;
983
984
0
  wt_longstatus_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
985
986
0
  for (i = 0; i < s->change.nr; i++) {
987
0
    struct wt_status_change_data *d;
988
0
    struct string_list_item *it;
989
0
    it = &(s->change.items[i]);
990
0
    d = it->util;
991
0
    if (!d->worktree_status ||
992
0
        d->worktree_status == DIFF_STATUS_UNMERGED)
993
0
      continue;
994
0
    wt_longstatus_print_change_data(s, WT_STATUS_CHANGED, it);
995
0
  }
996
0
  wt_longstatus_print_trailer(s);
997
0
}
998
999
static int stash_count_refs(const char *refname UNUSED,
1000
          struct object_id *ooid UNUSED,
1001
          struct object_id *noid UNUSED,
1002
          const char *email UNUSED,
1003
          timestamp_t timestamp UNUSED, int tz UNUSED,
1004
          const char *message UNUSED, void *cb_data)
1005
0
{
1006
0
  int *c = cb_data;
1007
0
  (*c)++;
1008
0
  return 0;
1009
0
}
1010
1011
static int count_stash_entries(struct repository *r)
1012
0
{
1013
0
  int n = 0;
1014
0
  refs_for_each_reflog_ent(get_main_ref_store(r),
1015
0
         "refs/stash", stash_count_refs, &n);
1016
0
  return n;
1017
0
}
1018
1019
static void wt_longstatus_print_stash_summary(struct wt_status *s)
1020
0
{
1021
0
  int stash_count = count_stash_entries(s->repo);
1022
1023
0
  if (stash_count > 0)
1024
0
    status_printf_ln(s, GIT_COLOR_NORMAL,
1025
0
         Q_("Your stash currently has %d entry",
1026
0
            "Your stash currently has %d entries", stash_count),
1027
0
         stash_count);
1028
0
}
1029
1030
static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted)
1031
0
{
1032
0
  struct child_process sm_summary = CHILD_PROCESS_INIT;
1033
0
  struct strbuf cmd_stdout = STRBUF_INIT;
1034
0
  struct strbuf summary = STRBUF_INIT;
1035
0
  char *summary_content;
1036
1037
0
  strvec_pushf(&sm_summary.env, "GIT_INDEX_FILE=%s", s->index_file);
1038
1039
0
  strvec_push(&sm_summary.args, "submodule");
1040
0
  strvec_push(&sm_summary.args, "summary");
1041
0
  strvec_push(&sm_summary.args, uncommitted ? "--files" : "--cached");
1042
0
  strvec_push(&sm_summary.args, "--for-status");
1043
0
  strvec_push(&sm_summary.args, "--summary-limit");
1044
0
  strvec_pushf(&sm_summary.args, "%d", s->submodule_summary);
1045
0
  if (!uncommitted)
1046
0
    strvec_push(&sm_summary.args, s->amend ? "HEAD^" : "HEAD");
1047
1048
0
  sm_summary.git_cmd = 1;
1049
0
  sm_summary.no_stdin = 1;
1050
1051
0
  capture_command(&sm_summary, &cmd_stdout, 1024);
1052
1053
  /* prepend header, only if there's an actual output */
1054
0
  if (cmd_stdout.len) {
1055
0
    if (uncommitted)
1056
0
      strbuf_addstr(&summary, _("Submodules changed but not updated:"));
1057
0
    else
1058
0
      strbuf_addstr(&summary, _("Submodule changes to be committed:"));
1059
0
    strbuf_addstr(&summary, "\n\n");
1060
0
  }
1061
0
  strbuf_addbuf(&summary, &cmd_stdout);
1062
0
  strbuf_release(&cmd_stdout);
1063
1064
0
  if (s->display_comment_prefix) {
1065
0
    size_t len;
1066
0
    summary_content = strbuf_detach(&summary, &len);
1067
0
    strbuf_add_commented_lines(&summary, summary_content, len, comment_line_str);
1068
0
    free(summary_content);
1069
0
  }
1070
1071
0
  fputs(summary.buf, s->fp);
1072
0
  strbuf_release(&summary);
1073
0
}
1074
1075
static void wt_longstatus_print_other(struct wt_status *s,
1076
              struct string_list *l,
1077
              const char *what,
1078
              const char *how)
1079
0
{
1080
0
  int i;
1081
0
  struct strbuf buf = STRBUF_INIT;
1082
0
  static struct string_list output = STRING_LIST_INIT_DUP;
1083
0
  struct column_options copts;
1084
1085
0
  if (!l->nr)
1086
0
    return;
1087
1088
0
  wt_longstatus_print_other_header(s, what, how);
1089
1090
0
  for (i = 0; i < l->nr; i++) {
1091
0
    struct string_list_item *it;
1092
0
    const char *path;
1093
0
    it = &(l->items[i]);
1094
0
    path = quote_path(it->string, s->prefix, &buf, 0);
1095
0
    if (column_active(s->colopts)) {
1096
0
      string_list_append(&output, path);
1097
0
      continue;
1098
0
    }
1099
0
    status_printf(s, color(WT_STATUS_HEADER, s), "\t");
1100
0
    status_printf_more(s, color(WT_STATUS_UNTRACKED, s),
1101
0
           "%s\n", path);
1102
0
  }
1103
1104
0
  strbuf_release(&buf);
1105
0
  if (!column_active(s->colopts))
1106
0
    goto conclude;
1107
1108
0
  strbuf_addf(&buf, "%s%s\t%s",
1109
0
        color(WT_STATUS_HEADER, s),
1110
0
        s->display_comment_prefix ? "#" : "",
1111
0
        color(WT_STATUS_UNTRACKED, s));
1112
0
  memset(&copts, 0, sizeof(copts));
1113
0
  copts.padding = 1;
1114
0
  copts.indent = buf.buf;
1115
0
  if (want_color(s->use_color))
1116
0
    copts.nl = GIT_COLOR_RESET "\n";
1117
0
  print_columns(&output, s->colopts, &copts);
1118
0
  string_list_clear(&output, 0);
1119
0
  strbuf_release(&buf);
1120
0
conclude:
1121
0
  status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
1122
0
}
1123
1124
size_t wt_status_locate_end(const char *s, size_t len)
1125
0
{
1126
0
  const char *p;
1127
0
  struct strbuf pattern = STRBUF_INIT;
1128
1129
0
  strbuf_addf(&pattern, "\n%s %s", comment_line_str, cut_line);
1130
0
  if (starts_with(s, pattern.buf + 1))
1131
0
    len = 0;
1132
0
  else if ((p = strstr(s, pattern.buf))) {
1133
0
    size_t newlen = p - s + 1;
1134
0
    if (newlen < len)
1135
0
      len = newlen;
1136
0
  }
1137
0
  strbuf_release(&pattern);
1138
0
  return len;
1139
0
}
1140
1141
void wt_status_append_cut_line(struct strbuf *buf)
1142
0
{
1143
0
  const char *explanation = _("Do not modify or remove the line above.\nEverything below it will be ignored.");
1144
1145
0
  strbuf_commented_addf(buf, comment_line_str, "%s", cut_line);
1146
0
  strbuf_add_commented_lines(buf, explanation, strlen(explanation), comment_line_str);
1147
0
}
1148
1149
void wt_status_add_cut_line(struct wt_status *s)
1150
0
{
1151
0
  struct strbuf buf = STRBUF_INIT;
1152
1153
0
  if (s->added_cut_line)
1154
0
    return;
1155
0
  s->added_cut_line = 1;
1156
0
  wt_status_append_cut_line(&buf);
1157
0
  fputs(buf.buf, s->fp);
1158
0
  strbuf_release(&buf);
1159
0
}
1160
1161
static void wt_longstatus_print_verbose(struct wt_status *s)
1162
0
{
1163
0
  struct rev_info rev;
1164
0
  struct setup_revision_opt opt;
1165
0
  int dirty_submodules;
1166
0
  const char *c = color(WT_STATUS_HEADER, s);
1167
1168
0
  repo_init_revisions(s->repo, &rev, NULL);
1169
0
  rev.diffopt.flags.allow_textconv = 1;
1170
0
  rev.diffopt.ita_invisible_in_index = 1;
1171
1172
0
  memset(&opt, 0, sizeof(opt));
1173
0
  opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference;
1174
0
  setup_revisions(0, NULL, &rev, &opt);
1175
1176
0
  rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
1177
0
  rev.diffopt.detect_rename = s->detect_rename >= 0 ? s->detect_rename : rev.diffopt.detect_rename;
1178
0
  rev.diffopt.rename_limit = s->rename_limit >= 0 ? s->rename_limit : rev.diffopt.rename_limit;
1179
0
  rev.diffopt.rename_score = s->rename_score >= 0 ? s->rename_score : rev.diffopt.rename_score;
1180
0
  rev.diffopt.file = s->fp;
1181
0
  rev.diffopt.close_file = 0;
1182
  /*
1183
   * If we're not going to stdout, then we definitely don't
1184
   * want color, since we are going to the commit message
1185
   * file (and even the "auto" setting won't work, since it
1186
   * will have checked isatty on stdout). But we then do want
1187
   * to insert the scissor line here to reliably remove the
1188
   * diff before committing, if we didn't already include one
1189
   * before.
1190
   */
1191
0
  if (s->fp != stdout) {
1192
0
    rev.diffopt.use_color = GIT_COLOR_NEVER;
1193
0
    wt_status_add_cut_line(s);
1194
0
  }
1195
0
  if (s->verbose > 1 && s->committable) {
1196
    /* print_updated() printed a header, so do we */
1197
0
    if (s->fp != stdout)
1198
0
      wt_longstatus_print_trailer(s);
1199
0
    status_printf_ln(s, c, _("Changes to be committed:"));
1200
0
    rev.diffopt.a_prefix = "c/";
1201
0
    rev.diffopt.b_prefix = "i/";
1202
0
  } /* else use prefix as per user config */
1203
0
  run_diff_index(&rev, DIFF_INDEX_CACHED);
1204
0
  if (s->verbose > 1 &&
1205
0
      wt_status_check_worktree_changes(s, &dirty_submodules)) {
1206
0
    status_printf_ln(s, c,
1207
0
      "--------------------------------------------------");
1208
0
    status_printf_ln(s, c, _("Changes not staged for commit:"));
1209
0
    setup_work_tree();
1210
0
    rev.diffopt.a_prefix = "i/";
1211
0
    rev.diffopt.b_prefix = "w/";
1212
0
    run_diff_files(&rev, 0);
1213
0
  }
1214
0
  release_revisions(&rev);
1215
0
}
1216
1217
static void wt_longstatus_print_tracking(struct wt_status *s)
1218
0
{
1219
0
  struct strbuf sb = STRBUF_INIT;
1220
0
  const char *cp, *ep, *branch_name;
1221
0
  struct branch *branch;
1222
0
  uint64_t t_begin = 0;
1223
1224
0
  assert(s->branch && !s->is_initial);
1225
0
  if (!skip_prefix(s->branch, "refs/heads/", &branch_name))
1226
0
    return;
1227
0
  branch = branch_get(branch_name);
1228
1229
0
  t_begin = getnanotime();
1230
1231
0
  if (!format_tracking_info(branch, &sb, s->ahead_behind_flags,
1232
0
          !s->commit_template))
1233
0
    return;
1234
1235
0
  if (advice_enabled(ADVICE_STATUS_AHEAD_BEHIND_WARNING) &&
1236
0
      s->ahead_behind_flags == AHEAD_BEHIND_FULL) {
1237
0
    uint64_t t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
1238
0
    if (t_delta_in_ms > AB_DELAY_WARNING_IN_MS) {
1239
0
      strbuf_addf(&sb, _("\n"
1240
0
             "It took %.2f seconds to compute the branch ahead/behind values.\n"
1241
0
             "You can use '--no-ahead-behind' to avoid this.\n"),
1242
0
            t_delta_in_ms / 1000.0);
1243
0
    }
1244
0
  }
1245
1246
0
  for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1)
1247
0
    color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s),
1248
0
         "%s%s%.*s",
1249
0
         s->display_comment_prefix ? comment_line_str : "",
1250
0
         s->display_comment_prefix ? " " : "",
1251
0
         (int)(ep - cp), cp);
1252
0
  if (s->display_comment_prefix)
1253
0
    color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "%s",
1254
0
         comment_line_str);
1255
0
  else
1256
0
    fputs("\n", s->fp);
1257
0
  strbuf_release(&sb);
1258
0
}
1259
1260
static int uf_was_slow(struct wt_status *s)
1261
0
{
1262
0
  if (getenv("GIT_TEST_UF_DELAY_WARNING"))
1263
0
    s->untracked_in_ms = 3250;
1264
0
  return UF_DELAY_WARNING_IN_MS < s->untracked_in_ms;
1265
0
}
1266
1267
static void show_merge_in_progress(struct wt_status *s,
1268
           const char *color)
1269
0
{
1270
0
  if (has_unmerged(s)) {
1271
0
    status_printf_ln(s, color, _("You have unmerged paths."));
1272
0
    if (s->hints) {
1273
0
      status_printf_ln(s, color,
1274
0
           _("  (fix conflicts and run \"git commit\")"));
1275
0
      status_printf_ln(s, color,
1276
0
           _("  (use \"git merge --abort\" to abort the merge)"));
1277
0
    }
1278
0
  } else {
1279
0
    status_printf_ln(s, color,
1280
0
      _("All conflicts fixed but you are still merging."));
1281
0
    if (s->hints)
1282
0
      status_printf_ln(s, color,
1283
0
        _("  (use \"git commit\" to conclude merge)"));
1284
0
  }
1285
0
  wt_longstatus_print_trailer(s);
1286
0
}
1287
1288
static void show_am_in_progress(struct wt_status *s,
1289
        const char *color)
1290
0
{
1291
0
  int am_empty_patch;
1292
1293
0
  status_printf_ln(s, color,
1294
0
    _("You are in the middle of an am session."));
1295
0
  if (s->state.am_empty_patch)
1296
0
    status_printf_ln(s, color,
1297
0
      _("The current patch is empty."));
1298
0
  if (s->hints) {
1299
0
    am_empty_patch = s->state.am_empty_patch;
1300
0
    if (!am_empty_patch)
1301
0
      status_printf_ln(s, color,
1302
0
        _("  (fix conflicts and then run \"git am --continue\")"));
1303
0
    status_printf_ln(s, color,
1304
0
      _("  (use \"git am --skip\" to skip this patch)"));
1305
0
    if (am_empty_patch)
1306
0
      status_printf_ln(s, color,
1307
0
        _("  (use \"git am --allow-empty\" to record this patch as an empty commit)"));
1308
0
    status_printf_ln(s, color,
1309
0
      _("  (use \"git am --abort\" to restore the original branch)"));
1310
0
  }
1311
0
  wt_longstatus_print_trailer(s);
1312
0
}
1313
1314
static char *read_line_from_git_path(struct repository *r, const char *filename)
1315
0
{
1316
0
  struct strbuf buf = STRBUF_INIT;
1317
0
  FILE *fp = fopen_or_warn(repo_git_path_append(r, &buf,
1318
0
                  "%s", filename), "r");
1319
1320
0
  if (!fp) {
1321
0
    strbuf_release(&buf);
1322
0
    return NULL;
1323
0
  }
1324
0
  strbuf_getline_lf(&buf, fp);
1325
0
  if (!fclose(fp)) {
1326
0
    return strbuf_detach(&buf, NULL);
1327
0
  } else {
1328
0
    strbuf_release(&buf);
1329
0
    return NULL;
1330
0
  }
1331
0
}
1332
1333
static int split_commit_in_progress(struct wt_status *s)
1334
0
{
1335
0
  int split_in_progress = 0;
1336
0
  struct object_id head_oid, orig_head_oid;
1337
0
  char *rebase_amend, *rebase_orig_head;
1338
0
  int head_flags, orig_head_flags;
1339
1340
0
  if ((!s->amend && !s->nowarn && !s->workdir_dirty) ||
1341
0
      !s->branch || strcmp(s->branch, "HEAD"))
1342
0
    return 0;
1343
1344
0
  if (refs_read_ref_full(get_main_ref_store(s->repo), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
1345
0
             &head_oid, &head_flags) ||
1346
0
      refs_read_ref_full(get_main_ref_store(s->repo), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
1347
0
             &orig_head_oid, &orig_head_flags))
1348
0
    return 0;
1349
0
  if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF)
1350
0
    return 0;
1351
1352
0
  rebase_amend = read_line_from_git_path(s->repo, "rebase-merge/amend");
1353
0
  rebase_orig_head = read_line_from_git_path(s->repo, "rebase-merge/orig-head");
1354
1355
0
  if (!rebase_amend || !rebase_orig_head)
1356
0
    ; /* fall through, no split in progress */
1357
0
  else if (!strcmp(rebase_amend, rebase_orig_head))
1358
0
    split_in_progress = !!strcmp(oid_to_hex(&head_oid), rebase_amend);
1359
0
  else if (strcmp(oid_to_hex(&orig_head_oid), rebase_orig_head))
1360
0
    split_in_progress = 1;
1361
1362
0
  free(rebase_amend);
1363
0
  free(rebase_orig_head);
1364
1365
0
  return split_in_progress;
1366
0
}
1367
1368
/*
1369
 * Turn
1370
 * "pick d6a2f0303e897ec257dd0e0a39a5ccb709bc2047 some message"
1371
 * into
1372
 * "pick d6a2f03 some message"
1373
 *
1374
 * The function assumes that the line does not contain useless spaces
1375
 * before or after the command.
1376
 */
1377
static void abbrev_oid_in_line(struct repository *r, struct strbuf *line)
1378
0
{
1379
0
  struct string_list split = STRING_LIST_INIT_DUP;
1380
0
  struct object_id oid;
1381
1382
0
  if (starts_with(line->buf, "exec ") ||
1383
0
      starts_with(line->buf, "x ") ||
1384
0
      starts_with(line->buf, "label ") ||
1385
0
      starts_with(line->buf, "l "))
1386
0
    return;
1387
1388
0
  if ((2 <= string_list_split(&split, line->buf, " ", 2)) &&
1389
0
      !repo_get_oid(r, split.items[1].string, &oid)) {
1390
0
    strbuf_reset(line);
1391
0
    strbuf_addf(line, "%s ", split.items[0].string);
1392
0
    strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV);
1393
0
    for (size_t i = 2; i < split.nr; i++)
1394
0
      strbuf_addf(line, " %s", split.items[i].string);
1395
0
  }
1396
0
  string_list_clear(&split, 0);
1397
0
}
1398
1399
static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines)
1400
0
{
1401
0
  struct strbuf buf = STRBUF_INIT;
1402
0
  FILE *f = fopen(repo_git_path_append(r, &buf, "%s", fname), "r");
1403
0
  int ret;
1404
1405
0
  if (!f) {
1406
0
    if (errno == ENOENT) {
1407
0
      ret = -1;
1408
0
      goto out;
1409
0
    }
1410
0
    die_errno("Could not open file %s for reading",
1411
0
        repo_git_path_replace(r, &buf, "%s", fname));
1412
0
  }
1413
0
  while (!strbuf_getline_lf(&buf, f)) {
1414
0
    if (starts_with(buf.buf, comment_line_str))
1415
0
      continue;
1416
0
    strbuf_trim(&buf);
1417
0
    if (!buf.len)
1418
0
      continue;
1419
0
    abbrev_oid_in_line(r, &buf);
1420
0
    string_list_append(lines, buf.buf);
1421
0
  }
1422
0
  fclose(f);
1423
1424
0
  ret = 0;
1425
0
out:
1426
0
  strbuf_release(&buf);
1427
0
  return ret;
1428
0
}
1429
1430
static void show_rebase_information(struct wt_status *s,
1431
            const char *color)
1432
0
{
1433
0
  if (s->state.rebase_interactive_in_progress) {
1434
0
    int i;
1435
0
    int nr_lines_to_show = 2;
1436
1437
0
    struct string_list have_done = STRING_LIST_INIT_DUP;
1438
0
    struct string_list yet_to_do = STRING_LIST_INIT_DUP;
1439
1440
0
    read_rebase_todolist(s->repo, "rebase-merge/done", &have_done);
1441
0
    if (read_rebase_todolist(s->repo, "rebase-merge/git-rebase-todo",
1442
0
           &yet_to_do))
1443
0
      status_printf_ln(s, color,
1444
0
        _("git-rebase-todo is missing."));
1445
0
    if (have_done.nr == 0)
1446
0
      status_printf_ln(s, color, _("No commands done."));
1447
0
    else {
1448
0
      status_printf_ln(s, color,
1449
0
        Q_("Last command done (%"PRIuMAX" command done):",
1450
0
          "Last commands done (%"PRIuMAX" commands done):",
1451
0
          have_done.nr),
1452
0
        (uintmax_t)have_done.nr);
1453
0
      for (i = (have_done.nr > nr_lines_to_show)
1454
0
        ? have_done.nr - nr_lines_to_show : 0;
1455
0
        i < have_done.nr;
1456
0
        i++)
1457
0
        status_printf_ln(s, color, "   %s", have_done.items[i].string);
1458
0
      if (have_done.nr > nr_lines_to_show && s->hints) {
1459
0
        char *path = repo_git_path(s->repo, "rebase-merge/done");
1460
0
        status_printf_ln(s, color,
1461
0
          _("  (see more in file %s)"), path);
1462
0
        free(path);
1463
0
      }
1464
0
    }
1465
1466
0
    if (yet_to_do.nr == 0)
1467
0
      status_printf_ln(s, color,
1468
0
           _("No commands remaining."));
1469
0
    else {
1470
0
      status_printf_ln(s, color,
1471
0
        Q_("Next command to do (%"PRIuMAX" remaining command):",
1472
0
          "Next commands to do (%"PRIuMAX" remaining commands):",
1473
0
          yet_to_do.nr),
1474
0
        (uintmax_t)yet_to_do.nr);
1475
0
      for (i = 0; i < nr_lines_to_show && i < yet_to_do.nr; i++)
1476
0
        status_printf_ln(s, color, "   %s", yet_to_do.items[i].string);
1477
0
      if (s->hints)
1478
0
        status_printf_ln(s, color,
1479
0
          _("  (use \"git rebase --edit-todo\" to view and edit)"));
1480
0
    }
1481
0
    string_list_clear(&yet_to_do, 0);
1482
0
    string_list_clear(&have_done, 0);
1483
0
  }
1484
0
}
1485
1486
static void print_rebase_state(struct wt_status *s,
1487
             const char *color)
1488
0
{
1489
0
  if (s->state.branch)
1490
0
    status_printf_ln(s, color,
1491
0
         _("You are currently rebasing branch '%s' on '%s'."),
1492
0
         s->state.branch,
1493
0
         s->state.onto);
1494
0
  else
1495
0
    status_printf_ln(s, color,
1496
0
         _("You are currently rebasing."));
1497
0
}
1498
1499
static void show_rebase_in_progress(struct wt_status *s,
1500
            const char *color)
1501
0
{
1502
0
  struct stat st;
1503
1504
0
  show_rebase_information(s, color);
1505
0
  if (has_unmerged(s)) {
1506
0
    print_rebase_state(s, color);
1507
0
    if (s->hints) {
1508
0
      status_printf_ln(s, color,
1509
0
        _("  (fix conflicts and then run \"git rebase --continue\")"));
1510
0
      status_printf_ln(s, color,
1511
0
        _("  (use \"git rebase --skip\" to skip this patch)"));
1512
0
      status_printf_ln(s, color,
1513
0
        _("  (use \"git rebase --abort\" to check out the original branch)"));
1514
0
    }
1515
0
  } else if (s->state.rebase_in_progress ||
1516
0
       !stat(git_path_merge_msg(s->repo), &st)) {
1517
0
    print_rebase_state(s, color);
1518
0
    if (s->hints)
1519
0
      status_printf_ln(s, color,
1520
0
        _("  (all conflicts fixed: run \"git rebase --continue\")"));
1521
0
  } else if (split_commit_in_progress(s)) {
1522
0
    if (s->state.branch)
1523
0
      status_printf_ln(s, color,
1524
0
           _("You are currently splitting a commit while rebasing branch '%s' on '%s'."),
1525
0
           s->state.branch,
1526
0
           s->state.onto);
1527
0
    else
1528
0
      status_printf_ln(s, color,
1529
0
           _("You are currently splitting a commit during a rebase."));
1530
0
    if (s->hints)
1531
0
      status_printf_ln(s, color,
1532
0
        _("  (Once your working directory is clean, run \"git rebase --continue\")"));
1533
0
  } else {
1534
0
    if (s->state.branch)
1535
0
      status_printf_ln(s, color,
1536
0
           _("You are currently editing a commit while rebasing branch '%s' on '%s'."),
1537
0
           s->state.branch,
1538
0
           s->state.onto);
1539
0
    else
1540
0
      status_printf_ln(s, color,
1541
0
           _("You are currently editing a commit during a rebase."));
1542
0
    if (s->hints && !s->amend) {
1543
0
      status_printf_ln(s, color,
1544
0
        _("  (use \"git commit --amend\" to amend the current commit)"));
1545
0
      status_printf_ln(s, color,
1546
0
        _("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
1547
0
    }
1548
0
  }
1549
0
  wt_longstatus_print_trailer(s);
1550
0
}
1551
1552
static void show_cherry_pick_in_progress(struct wt_status *s,
1553
           const char *color)
1554
0
{
1555
0
  if (is_null_oid(&s->state.cherry_pick_head_oid))
1556
0
    status_printf_ln(s, color,
1557
0
      _("Cherry-pick currently in progress."));
1558
0
  else
1559
0
    status_printf_ln(s, color,
1560
0
      _("You are currently cherry-picking commit %s."),
1561
0
      repo_find_unique_abbrev(s->repo, &s->state.cherry_pick_head_oid,
1562
0
            DEFAULT_ABBREV));
1563
1564
0
  if (s->hints) {
1565
0
    if (has_unmerged(s))
1566
0
      status_printf_ln(s, color,
1567
0
        _("  (fix conflicts and run \"git cherry-pick --continue\")"));
1568
0
    else if (is_null_oid(&s->state.cherry_pick_head_oid))
1569
0
      status_printf_ln(s, color,
1570
0
        _("  (run \"git cherry-pick --continue\" to continue)"));
1571
0
    else
1572
0
      status_printf_ln(s, color,
1573
0
        _("  (all conflicts fixed: run \"git cherry-pick --continue\")"));
1574
0
    status_printf_ln(s, color,
1575
0
      _("  (use \"git cherry-pick --skip\" to skip this patch)"));
1576
0
    status_printf_ln(s, color,
1577
0
      _("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
1578
0
  }
1579
0
  wt_longstatus_print_trailer(s);
1580
0
}
1581
1582
static void show_revert_in_progress(struct wt_status *s,
1583
            const char *color)
1584
0
{
1585
0
  if (is_null_oid(&s->state.revert_head_oid))
1586
0
    status_printf_ln(s, color,
1587
0
      _("Revert currently in progress."));
1588
0
  else
1589
0
    status_printf_ln(s, color,
1590
0
      _("You are currently reverting commit %s."),
1591
0
      repo_find_unique_abbrev(s->repo, &s->state.revert_head_oid,
1592
0
            DEFAULT_ABBREV));
1593
0
  if (s->hints) {
1594
0
    if (has_unmerged(s))
1595
0
      status_printf_ln(s, color,
1596
0
        _("  (fix conflicts and run \"git revert --continue\")"));
1597
0
    else if (is_null_oid(&s->state.revert_head_oid))
1598
0
      status_printf_ln(s, color,
1599
0
        _("  (run \"git revert --continue\" to continue)"));
1600
0
    else
1601
0
      status_printf_ln(s, color,
1602
0
        _("  (all conflicts fixed: run \"git revert --continue\")"));
1603
0
    status_printf_ln(s, color,
1604
0
      _("  (use \"git revert --skip\" to skip this patch)"));
1605
0
    status_printf_ln(s, color,
1606
0
      _("  (use \"git revert --abort\" to cancel the revert operation)"));
1607
0
  }
1608
0
  wt_longstatus_print_trailer(s);
1609
0
}
1610
1611
static void show_bisect_in_progress(struct wt_status *s,
1612
            const char *color)
1613
0
{
1614
0
  if (s->state.bisecting_from)
1615
0
    status_printf_ln(s, color,
1616
0
         _("You are currently bisecting, started from branch '%s'."),
1617
0
         s->state.bisecting_from);
1618
0
  else
1619
0
    status_printf_ln(s, color,
1620
0
         _("You are currently bisecting."));
1621
0
  if (s->hints)
1622
0
    status_printf_ln(s, color,
1623
0
      _("  (use \"git bisect reset\" to get back to the original branch)"));
1624
0
  wt_longstatus_print_trailer(s);
1625
0
}
1626
1627
static void show_sparse_checkout_in_use(struct wt_status *s,
1628
          const char *color)
1629
0
{
1630
0
  if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_DISABLED)
1631
0
    return;
1632
1633
0
  if (s->state.sparse_checkout_percentage == SPARSE_CHECKOUT_SPARSE_INDEX)
1634
0
    status_printf_ln(s, color, _("You are in a sparse checkout."));
1635
0
  else
1636
0
    status_printf_ln(s, color,
1637
0
        _("You are in a sparse checkout with %d%% of tracked files present."),
1638
0
        s->state.sparse_checkout_percentage);
1639
0
  wt_longstatus_print_trailer(s);
1640
0
}
1641
1642
/*
1643
 * Extract branch information from rebase/bisect
1644
 */
1645
static char *get_branch(const struct worktree *wt, const char *path)
1646
0
{
1647
0
  struct strbuf sb = STRBUF_INIT;
1648
0
  struct object_id oid;
1649
0
  const char *branch_name;
1650
1651
0
  if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0)
1652
0
    goto got_nothing;
1653
1654
0
  while (sb.len && sb.buf[sb.len - 1] == '\n')
1655
0
    strbuf_setlen(&sb, sb.len - 1);
1656
0
  if (!sb.len)
1657
0
    goto got_nothing;
1658
0
  if (skip_prefix(sb.buf, "refs/heads/", &branch_name))
1659
0
    strbuf_remove(&sb, 0, branch_name - sb.buf);
1660
0
  else if (starts_with(sb.buf, "refs/"))
1661
0
    ;
1662
0
  else if (!get_oid_hex(sb.buf, &oid)) {
1663
0
    strbuf_reset(&sb);
1664
0
    strbuf_add_unique_abbrev(&sb, &oid, DEFAULT_ABBREV);
1665
0
  } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
1666
0
    goto got_nothing;
1667
0
  else      /* bisect */
1668
0
    ;
1669
0
  return strbuf_detach(&sb, NULL);
1670
1671
0
got_nothing:
1672
0
  strbuf_release(&sb);
1673
0
  return NULL;
1674
0
}
1675
1676
struct grab_1st_switch_cbdata {
1677
  struct strbuf buf;
1678
  struct object_id noid;
1679
};
1680
1681
static int grab_1st_switch(const char *refname UNUSED,
1682
         struct object_id *ooid UNUSED,
1683
         struct object_id *noid,
1684
         const char *email UNUSED,
1685
         timestamp_t timestamp UNUSED, int tz UNUSED,
1686
         const char *message, void *cb_data)
1687
0
{
1688
0
  struct grab_1st_switch_cbdata *cb = cb_data;
1689
0
  const char *target = NULL, *end;
1690
1691
0
  if (!skip_prefix(message, "checkout: moving from ", &message))
1692
0
    return 0;
1693
0
  target = strstr(message, " to ");
1694
0
  if (!target)
1695
0
    return 0;
1696
0
  target += strlen(" to ");
1697
0
  strbuf_reset(&cb->buf);
1698
0
  oidcpy(&cb->noid, noid);
1699
0
  end = strchrnul(target, '\n');
1700
0
  strbuf_add(&cb->buf, target, end - target);
1701
0
  if (!strcmp(cb->buf.buf, "HEAD")) {
1702
    /* HEAD is relative. Resolve it to the right reflog entry. */
1703
0
    strbuf_reset(&cb->buf);
1704
0
    strbuf_add_unique_abbrev(&cb->buf, noid, DEFAULT_ABBREV);
1705
0
  }
1706
0
  return 1;
1707
0
}
1708
1709
static void wt_status_get_detached_from(struct repository *r,
1710
          struct wt_status_state *state)
1711
0
{
1712
0
  struct grab_1st_switch_cbdata cb;
1713
0
  struct commit *commit;
1714
0
  struct object_id oid;
1715
0
  char *ref = NULL;
1716
1717
0
  strbuf_init(&cb.buf, 0);
1718
0
  if (refs_for_each_reflog_ent_reverse(get_main_ref_store(r), "HEAD", grab_1st_switch, &cb) <= 0) {
1719
0
    strbuf_release(&cb.buf);
1720
0
    return;
1721
0
  }
1722
1723
0
  if (repo_dwim_ref(r, cb.buf.buf, cb.buf.len, &oid, &ref,
1724
0
        1) == 1 &&
1725
      /* oid is a commit? match without further lookup */
1726
0
      (oideq(&cb.noid, &oid) ||
1727
       /* perhaps oid is a tag, try to dereference to a commit */
1728
0
       ((commit = lookup_commit_reference_gently(r, &oid, 1)) != NULL &&
1729
0
        oideq(&cb.noid, &commit->object.oid)))) {
1730
0
    const char *from = ref;
1731
0
    if (!skip_prefix(from, "refs/tags/", &from))
1732
0
      skip_prefix(from, "refs/remotes/", &from);
1733
0
    state->detached_from = xstrdup(from);
1734
0
  } else
1735
0
    state->detached_from =
1736
0
      xstrdup(repo_find_unique_abbrev(r, &cb.noid, DEFAULT_ABBREV));
1737
0
  oidcpy(&state->detached_oid, &cb.noid);
1738
0
  state->detached_at = !repo_get_oid(r, "HEAD", &oid) &&
1739
0
           oideq(&oid, &state->detached_oid);
1740
1741
0
  free(ref);
1742
0
  strbuf_release(&cb.buf);
1743
0
}
1744
1745
int wt_status_check_rebase(const struct worktree *wt,
1746
         struct wt_status_state *state)
1747
0
{
1748
0
  struct stat st;
1749
1750
0
  if (!wt)
1751
0
    BUG("wt_status_check_rebase() called with NULL worktree");
1752
1753
0
  if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) {
1754
0
    if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) {
1755
0
      state->am_in_progress = 1;
1756
0
      if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size)
1757
0
        state->am_empty_patch = 1;
1758
0
    } else {
1759
0
      state->rebase_in_progress = 1;
1760
0
      state->branch = get_branch(wt, "rebase-apply/head-name");
1761
0
      state->onto = get_branch(wt, "rebase-apply/onto");
1762
0
    }
1763
0
  } else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) {
1764
0
    if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st))
1765
0
      state->rebase_interactive_in_progress = 1;
1766
0
    else
1767
0
      state->rebase_in_progress = 1;
1768
0
    state->branch = get_branch(wt, "rebase-merge/head-name");
1769
0
    state->onto = get_branch(wt, "rebase-merge/onto");
1770
0
  } else
1771
0
    return 0;
1772
0
  return 1;
1773
0
}
1774
1775
int wt_status_check_bisect(const struct worktree *wt,
1776
         struct wt_status_state *state)
1777
0
{
1778
0
  struct stat st;
1779
1780
0
  if (!wt)
1781
0
    BUG("wt_status_check_bisect() called with NULL worktree");
1782
1783
0
  if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) {
1784
0
    state->bisect_in_progress = 1;
1785
0
    state->bisecting_from = get_branch(wt, "BISECT_START");
1786
0
    return 1;
1787
0
  }
1788
0
  return 0;
1789
0
}
1790
1791
static void wt_status_check_sparse_checkout(struct repository *r,
1792
              struct wt_status_state *state)
1793
0
{
1794
0
  int skip_worktree = 0;
1795
0
  int i;
1796
0
  struct repo_config_values *cfg = repo_config_values(the_repository);
1797
1798
0
  if (!cfg->apply_sparse_checkout ||
1799
0
      r->index->cache_nr == 0) {
1800
    /*
1801
     * Don't compute percentage of checked out files if we
1802
     * aren't in a sparse checkout or would get division by 0.
1803
     */
1804
0
    state->sparse_checkout_percentage = SPARSE_CHECKOUT_DISABLED;
1805
0
    return;
1806
0
  }
1807
1808
0
  if (r->index->sparse_index) {
1809
0
    state->sparse_checkout_percentage = SPARSE_CHECKOUT_SPARSE_INDEX;
1810
0
    return;
1811
0
  }
1812
1813
0
  for (i = 0; i < r->index->cache_nr; i++) {
1814
0
    struct cache_entry *ce = r->index->cache[i];
1815
0
    if (ce_skip_worktree(ce))
1816
0
      skip_worktree++;
1817
0
  }
1818
1819
0
  state->sparse_checkout_percentage =
1820
0
    100 - (100 * skip_worktree)/r->index->cache_nr;
1821
0
}
1822
1823
void wt_status_get_state(struct repository *r,
1824
       struct wt_status_state *state,
1825
       int get_detached_from)
1826
0
{
1827
0
  struct stat st;
1828
0
  struct object_id oid;
1829
0
  enum replay_action action;
1830
0
  struct worktree *wt = get_worktree_from_repository(r);
1831
1832
0
  if (!stat(git_path_merge_head(r), &st)) {
1833
0
    wt_status_check_rebase(wt, state);
1834
0
    state->merge_in_progress = 1;
1835
0
  } else if (wt_status_check_rebase(wt, state)) {
1836
0
    ;   /* all set */
1837
0
  } else if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
1838
0
       !repo_get_oid(r, "CHERRY_PICK_HEAD", &oid)) {
1839
0
    state->cherry_pick_in_progress = 1;
1840
0
    oidcpy(&state->cherry_pick_head_oid, &oid);
1841
0
  }
1842
0
  wt_status_check_bisect(wt, state);
1843
0
  if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") &&
1844
0
      !repo_get_oid(r, "REVERT_HEAD", &oid)) {
1845
0
    state->revert_in_progress = 1;
1846
0
    oidcpy(&state->revert_head_oid, &oid);
1847
0
  }
1848
0
  if (!sequencer_get_last_command(r, &action)) {
1849
0
    if (action == REPLAY_PICK && !state->cherry_pick_in_progress) {
1850
0
      state->cherry_pick_in_progress = 1;
1851
0
      oidcpy(&state->cherry_pick_head_oid, null_oid(r->hash_algo));
1852
0
    } else if (action == REPLAY_REVERT && !state->revert_in_progress) {
1853
0
      state->revert_in_progress = 1;
1854
0
      oidcpy(&state->revert_head_oid, null_oid(r->hash_algo));
1855
0
    }
1856
0
  }
1857
0
  if (get_detached_from)
1858
0
    wt_status_get_detached_from(r, state);
1859
0
  wt_status_check_sparse_checkout(r, state);
1860
1861
0
  free_worktree(wt);
1862
0
}
1863
1864
static void wt_longstatus_print_state(struct wt_status *s)
1865
0
{
1866
0
  const char *state_color = color(WT_STATUS_HEADER, s);
1867
0
  struct wt_status_state *state = &s->state;
1868
1869
0
  if (state->merge_in_progress) {
1870
0
    if (state->rebase_interactive_in_progress) {
1871
0
      show_rebase_information(s, state_color);
1872
0
      fputs("\n", s->fp);
1873
0
    }
1874
0
    show_merge_in_progress(s, state_color);
1875
0
  } else if (state->am_in_progress)
1876
0
    show_am_in_progress(s, state_color);
1877
0
  else if (state->rebase_in_progress || state->rebase_interactive_in_progress)
1878
0
    show_rebase_in_progress(s, state_color);
1879
0
  else if (state->cherry_pick_in_progress)
1880
0
    show_cherry_pick_in_progress(s, state_color);
1881
0
  else if (state->revert_in_progress)
1882
0
    show_revert_in_progress(s, state_color);
1883
0
  if (state->bisect_in_progress)
1884
0
    show_bisect_in_progress(s, state_color);
1885
1886
0
  if (state->sparse_checkout_percentage != SPARSE_CHECKOUT_DISABLED)
1887
0
    show_sparse_checkout_in_use(s, state_color);
1888
0
}
1889
1890
static void wt_longstatus_print(struct wt_status *s)
1891
0
{
1892
0
  const char *branch_color = color(WT_STATUS_ONBRANCH, s);
1893
0
  const char *branch_status_color = color(WT_STATUS_HEADER, s);
1894
0
  enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(s->repo);
1895
1896
0
  if (s->branch) {
1897
0
    const char *on_what = _("On branch ");
1898
0
    const char *branch_name = s->branch;
1899
0
    if (!strcmp(branch_name, "HEAD")) {
1900
0
      branch_status_color = color(WT_STATUS_NOBRANCH, s);
1901
0
      if (s->state.rebase_in_progress ||
1902
0
          s->state.rebase_interactive_in_progress) {
1903
0
        if (s->state.rebase_interactive_in_progress)
1904
0
          on_what = _("interactive rebase in progress; onto ");
1905
0
        else
1906
0
          on_what = _("rebase in progress; onto ");
1907
0
        branch_name = s->state.onto;
1908
0
      } else if (s->state.detached_from) {
1909
0
        branch_name = s->state.detached_from;
1910
0
        if (s->state.detached_at)
1911
0
          on_what = _("HEAD detached at ");
1912
0
        else
1913
0
          on_what = _("HEAD detached from ");
1914
0
      } else {
1915
0
        branch_name = "";
1916
0
        on_what = _("Not currently on any branch.");
1917
0
      }
1918
0
    } else
1919
0
      skip_prefix(branch_name, "refs/heads/", &branch_name);
1920
0
    status_printf(s, color(WT_STATUS_HEADER, s), "%s", "");
1921
0
    status_printf_more(s, branch_status_color, "%s", on_what);
1922
0
    status_printf_more(s, branch_color, "%s\n", branch_name);
1923
0
    if (!s->is_initial)
1924
0
      wt_longstatus_print_tracking(s);
1925
0
  }
1926
1927
0
  wt_longstatus_print_state(s);
1928
1929
0
  if (s->is_initial) {
1930
0
    status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
1931
0
    status_printf_ln(s, color(WT_STATUS_HEADER, s),
1932
0
         s->commit_template
1933
0
         ? _("Initial commit")
1934
0
         : _("No commits yet"));
1935
0
    status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
1936
0
  }
1937
1938
0
  wt_longstatus_print_updated(s);
1939
0
  wt_longstatus_print_unmerged(s);
1940
0
  wt_longstatus_print_changed(s);
1941
0
  if (s->submodule_summary &&
1942
0
      (!s->ignore_submodule_arg ||
1943
0
       strcmp(s->ignore_submodule_arg, "all"))) {
1944
0
    wt_longstatus_print_submodule_summary(s, 0);  /* staged */
1945
0
    wt_longstatus_print_submodule_summary(s, 1);  /* unstaged */
1946
0
  }
1947
0
  if (s->show_untracked_files) {
1948
0
    wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
1949
0
    if (s->show_ignored_mode)
1950
0
      wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
1951
0
    if (advice_enabled(ADVICE_STATUS_U_OPTION) && uf_was_slow(s)) {
1952
0
      status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
1953
0
      if (fsm_mode > FSMONITOR_MODE_DISABLED) {
1954
0
        status_printf_ln(s, GIT_COLOR_NORMAL,
1955
0
            _("It took %.2f seconds to enumerate untracked files,\n"
1956
0
            "but the results were cached, and subsequent runs may be faster."),
1957
0
            s->untracked_in_ms / 1000.0);
1958
0
      } else {
1959
0
        status_printf_ln(s, GIT_COLOR_NORMAL,
1960
0
            _("It took %.2f seconds to enumerate untracked files."),
1961
0
            s->untracked_in_ms / 1000.0);
1962
0
      }
1963
0
      status_printf_ln(s, GIT_COLOR_NORMAL,
1964
0
          _("See 'git help status' for information on how to improve this."));
1965
0
      status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
1966
0
    }
1967
0
  } else if (s->committable)
1968
0
    status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),
1969
0
      s->hints
1970
0
      ? _(" (use -u option to show untracked files)") : "");
1971
1972
0
  if (s->verbose)
1973
0
    wt_longstatus_print_verbose(s);
1974
0
  if (!s->committable) {
1975
0
    if (s->amend)
1976
0
      status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
1977
0
    else if (s->nowarn)
1978
0
      ; /* nothing */
1979
0
    else if (s->workdir_dirty) {
1980
0
      if (s->hints)
1981
0
        fprintf(s->fp, _("no changes added to commit "
1982
0
             "(use \"git add\" and/or "
1983
0
             "\"git commit -a\")\n"));
1984
0
      else
1985
0
        fprintf(s->fp, _("no changes added to "
1986
0
             "commit\n"));
1987
0
    } else if (s->untracked.nr) {
1988
0
      if (s->hints)
1989
0
        fprintf(s->fp, _("nothing added to commit but "
1990
0
             "untracked files present (use "
1991
0
             "\"git add\" to track)\n"));
1992
0
      else
1993
0
        fprintf(s->fp, _("nothing added to commit but "
1994
0
             "untracked files present\n"));
1995
0
    } else if (s->is_initial) {
1996
0
      if (s->hints)
1997
0
        fprintf(s->fp, _("nothing to commit (create/"
1998
0
             "copy files and use \"git "
1999
0
             "add\" to track)\n"));
2000
0
      else
2001
0
        fprintf(s->fp, _("nothing to commit\n"));
2002
0
    } else if (!s->show_untracked_files) {
2003
0
      if (s->hints)
2004
0
        fprintf(s->fp, _("nothing to commit (use -u to "
2005
0
             "show untracked files)\n"));
2006
0
      else
2007
0
        fprintf(s->fp, _("nothing to commit\n"));
2008
0
    } else
2009
0
      fprintf(s->fp, _("nothing to commit, working tree "
2010
0
           "clean\n"));
2011
0
  }
2012
0
  if(s->show_stash)
2013
0
    wt_longstatus_print_stash_summary(s);
2014
0
}
2015
2016
static void wt_shortstatus_unmerged(struct string_list_item *it,
2017
         struct wt_status *s)
2018
0
{
2019
0
  struct wt_status_change_data *d = it->util;
2020
0
  const char *how = "??";
2021
2022
0
  switch (d->stagemask) {
2023
0
  case 1: how = "DD"; break; /* both deleted */
2024
0
  case 2: how = "AU"; break; /* added by us */
2025
0
  case 3: how = "UD"; break; /* deleted by them */
2026
0
  case 4: how = "UA"; break; /* added by them */
2027
0
  case 5: how = "DU"; break; /* deleted by us */
2028
0
  case 6: how = "AA"; break; /* both added */
2029
0
  case 7: how = "UU"; break; /* both modified */
2030
0
  }
2031
0
  color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
2032
0
  if (s->null_termination) {
2033
0
    fprintf(s->fp, " %s%c", it->string, 0);
2034
0
  } else {
2035
0
    struct strbuf onebuf = STRBUF_INIT;
2036
0
    const char *one;
2037
0
    one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
2038
0
    fprintf(s->fp, " %s\n", one);
2039
0
    strbuf_release(&onebuf);
2040
0
  }
2041
0
}
2042
2043
static void wt_shortstatus_status(struct string_list_item *it,
2044
       struct wt_status *s)
2045
0
{
2046
0
  struct wt_status_change_data *d = it->util;
2047
2048
0
  if (d->index_status)
2049
0
    color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
2050
0
  else
2051
0
    fputc(' ', s->fp);
2052
0
  if (d->worktree_status)
2053
0
    color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
2054
0
  else
2055
0
    fputc(' ', s->fp);
2056
0
  fputc(' ', s->fp);
2057
0
  if (s->null_termination) {
2058
0
    fprintf(s->fp, "%s%c", it->string, 0);
2059
0
    if (d->rename_source)
2060
0
      fprintf(s->fp, "%s%c", d->rename_source, 0);
2061
0
  } else {
2062
0
    struct strbuf onebuf = STRBUF_INIT;
2063
0
    const char *one;
2064
2065
0
    if (d->rename_source) {
2066
0
      one = quote_path(d->rename_source, s->prefix, &onebuf,
2067
0
           QUOTE_PATH_QUOTE_SP);
2068
0
      fprintf(s->fp, "%s -> ", one);
2069
0
      strbuf_release(&onebuf);
2070
0
    }
2071
0
    one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
2072
0
    fprintf(s->fp, "%s\n", one);
2073
0
    strbuf_release(&onebuf);
2074
0
  }
2075
0
}
2076
2077
static void wt_shortstatus_other(struct string_list_item *it,
2078
         struct wt_status *s, const char *sign)
2079
0
{
2080
0
  color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
2081
0
  if (s->null_termination) {
2082
0
    fprintf(s->fp, " %s%c", it->string, 0);
2083
0
  } else {
2084
0
    struct strbuf onebuf = STRBUF_INIT;
2085
0
    const char *one;
2086
0
    one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP);
2087
0
    fprintf(s->fp, " %s\n", one);
2088
0
    strbuf_release(&onebuf);
2089
0
  }
2090
0
}
2091
2092
static void wt_shortstatus_print_tracking(struct wt_status *s)
2093
0
{
2094
0
  struct branch *branch;
2095
0
  const char *header_color = color(WT_STATUS_HEADER, s);
2096
0
  const char *branch_color_local = color(WT_STATUS_LOCAL_BRANCH, s);
2097
0
  const char *branch_color_remote = color(WT_STATUS_REMOTE_BRANCH, s);
2098
2099
0
  const char *base;
2100
0
  char *short_base;
2101
0
  const char *branch_name;
2102
0
  int num_ours, num_theirs, sti;
2103
0
  int upstream_is_gone = 0;
2104
2105
0
  color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "## ");
2106
2107
0
  if (!s->branch)
2108
0
    return;
2109
0
  branch_name = s->branch;
2110
2111
0
#define LABEL(string) (s->no_gettext ? (string) : _(string))
2112
2113
0
  if (s->is_initial)
2114
0
    color_fprintf(s->fp, header_color, LABEL(N_("No commits yet on ")));
2115
2116
0
  if (!strcmp(s->branch, "HEAD")) {
2117
0
    color_fprintf(s->fp, color(WT_STATUS_NOBRANCH, s), "%s",
2118
0
            LABEL(N_("HEAD (no branch)")));
2119
0
    goto conclude;
2120
0
  }
2121
2122
0
  skip_prefix(branch_name, "refs/heads/", &branch_name);
2123
2124
0
  branch = branch_get(branch_name);
2125
2126
0
  color_fprintf(s->fp, branch_color_local, "%s", branch_name);
2127
2128
0
  sti = stat_tracking_info(branch, &num_ours, &num_theirs, &base,
2129
0
         0, s->ahead_behind_flags);
2130
0
  if (sti < 0) {
2131
0
    if (!base)
2132
0
      goto conclude;
2133
2134
0
    upstream_is_gone = 1;
2135
0
  }
2136
2137
0
  short_base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo),
2138
0
              base, 0);
2139
0
  color_fprintf(s->fp, header_color, "...");
2140
0
  color_fprintf(s->fp, branch_color_remote, "%s", short_base);
2141
0
  free(short_base);
2142
2143
0
  if (!upstream_is_gone && !sti)
2144
0
    goto conclude;
2145
2146
0
  color_fprintf(s->fp, header_color, " [");
2147
0
  if (upstream_is_gone) {
2148
0
    color_fprintf(s->fp, header_color, LABEL(N_("gone")));
2149
0
  } else if (s->ahead_behind_flags == AHEAD_BEHIND_QUICK) {
2150
0
    color_fprintf(s->fp, header_color, LABEL(N_("different")));
2151
0
  } else if (!num_ours) {
2152
0
    color_fprintf(s->fp, header_color, LABEL(N_("behind ")));
2153
0
    color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
2154
0
  } else if (!num_theirs) {
2155
0
    color_fprintf(s->fp, header_color, LABEL(N_("ahead ")));
2156
0
    color_fprintf(s->fp, branch_color_local, "%d", num_ours);
2157
0
  } else {
2158
0
    color_fprintf(s->fp, header_color, LABEL(N_("ahead ")));
2159
0
    color_fprintf(s->fp, branch_color_local, "%d", num_ours);
2160
0
    color_fprintf(s->fp, header_color, ", %s", LABEL(N_("behind ")));
2161
0
    color_fprintf(s->fp, branch_color_remote, "%d", num_theirs);
2162
0
  }
2163
2164
0
  color_fprintf(s->fp, header_color, "]");
2165
0
 conclude:
2166
0
  fputc(s->null_termination ? '\0' : '\n', s->fp);
2167
0
}
2168
2169
static void wt_shortstatus_print(struct wt_status *s)
2170
0
{
2171
0
  struct string_list_item *it;
2172
2173
0
  if (s->show_branch)
2174
0
    wt_shortstatus_print_tracking(s);
2175
2176
0
  for_each_string_list_item(it, &s->change) {
2177
0
    struct wt_status_change_data *d = it->util;
2178
2179
0
    if (d->stagemask)
2180
0
      wt_shortstatus_unmerged(it, s);
2181
0
    else
2182
0
      wt_shortstatus_status(it, s);
2183
0
  }
2184
0
  for_each_string_list_item(it, &s->untracked)
2185
0
    wt_shortstatus_other(it, s, "??");
2186
2187
0
  for_each_string_list_item(it, &s->ignored)
2188
0
    wt_shortstatus_other(it, s, "!!");
2189
0
}
2190
2191
static void wt_porcelain_print(struct wt_status *s)
2192
0
{
2193
0
  s->use_color = GIT_COLOR_NEVER;
2194
0
  s->relative_paths = 0;
2195
0
  s->prefix = NULL;
2196
0
  s->no_gettext = 1;
2197
0
  wt_shortstatus_print(s);
2198
0
}
2199
2200
/*
2201
 * Print branch information for porcelain v2 output.  These lines
2202
 * are printed when the '--branch' parameter is given.
2203
 *
2204
 *    # branch.oid <commit><eol>
2205
 *    # branch.head <head><eol>
2206
 *   [# branch.upstream <upstream><eol>
2207
 *   [# branch.ab +<ahead> -<behind><eol>]]
2208
 *
2209
 *      <commit> ::= the current commit hash or the literal
2210
 *                   "(initial)" to indicate an initialized repo
2211
 *                   with no commits.
2212
 *
2213
 *        <head> ::= <branch_name> the current branch name or
2214
 *                   "(detached)" literal when detached head or
2215
 *                   "(unknown)" when something is wrong.
2216
 *
2217
 *    <upstream> ::= the upstream branch name, when set.
2218
 *
2219
 *       <ahead> ::= integer ahead value or '?'.
2220
 *
2221
 *      <behind> ::= integer behind value or '?'.
2222
 *
2223
 * The end-of-line is defined by the -z flag.
2224
 *
2225
 *                 <eol> ::= NUL when -z,
2226
 *                           LF when NOT -z.
2227
 *
2228
 * When an upstream is set and present, the 'branch.ab' line will
2229
 * be printed with the ahead/behind counts for the branch and the
2230
 * upstream.  When AHEAD_BEHIND_QUICK is requested and the branches
2231
 * are different, '?' will be substituted for the actual count.
2232
 */
2233
static void wt_porcelain_v2_print_tracking(struct wt_status *s)
2234
0
{
2235
0
  struct branch *branch;
2236
0
  const char *base;
2237
0
  const char *branch_name;
2238
0
  int ab_info, nr_ahead, nr_behind;
2239
0
  char eol = s->null_termination ? '\0' : '\n';
2240
2241
0
  fprintf(s->fp, "# branch.oid %s%c",
2242
0
      (s->is_initial ? "(initial)" : oid_to_hex(&s->oid_commit)),
2243
0
      eol);
2244
2245
0
  if (!s->branch)
2246
0
    fprintf(s->fp, "# branch.head %s%c", "(unknown)", eol);
2247
0
  else {
2248
0
    if (!strcmp(s->branch, "HEAD")) {
2249
0
      fprintf(s->fp, "# branch.head %s%c", "(detached)", eol);
2250
2251
0
      if (s->state.rebase_in_progress ||
2252
0
          s->state.rebase_interactive_in_progress)
2253
0
        branch_name = s->state.onto;
2254
0
      else if (s->state.detached_from)
2255
0
        branch_name = s->state.detached_from;
2256
0
      else
2257
0
        branch_name = "";
2258
0
    } else {
2259
0
      branch_name = NULL;
2260
0
      skip_prefix(s->branch, "refs/heads/", &branch_name);
2261
2262
0
      fprintf(s->fp, "# branch.head %s%c", branch_name, eol);
2263
0
    }
2264
2265
    /* Lookup stats on the upstream tracking branch, if set. */
2266
0
    branch = branch_get(branch_name);
2267
0
    base = NULL;
2268
0
    ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind,
2269
0
               &base, 0, s->ahead_behind_flags);
2270
0
    if (base) {
2271
0
      base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo),
2272
0
                  base, 0);
2273
0
      fprintf(s->fp, "# branch.upstream %s%c", base, eol);
2274
0
      free((char *)base);
2275
2276
0
      if (ab_info > 0) {
2277
        /* different */
2278
0
        if (nr_ahead || nr_behind)
2279
0
          fprintf(s->fp, "# branch.ab +%d -%d%c",
2280
0
            nr_ahead, nr_behind, eol);
2281
0
        else
2282
0
          fprintf(s->fp, "# branch.ab +? -?%c",
2283
0
            eol);
2284
0
      } else if (!ab_info) {
2285
        /* same */
2286
0
        fprintf(s->fp, "# branch.ab +0 -0%c", eol);
2287
0
      }
2288
0
    }
2289
0
  }
2290
0
}
2291
2292
/*
2293
 * Print the stash count in a porcelain-friendly format
2294
 */
2295
static void wt_porcelain_v2_print_stash(struct wt_status *s)
2296
0
{
2297
0
  int stash_count = count_stash_entries(s->repo);
2298
0
  char eol = s->null_termination ? '\0' : '\n';
2299
2300
0
  if (stash_count > 0)
2301
0
    fprintf(s->fp, "# stash %d%c", stash_count, eol);
2302
0
}
2303
2304
/*
2305
 * Convert various submodule status values into a
2306
 * fixed-length string of characters in the buffer provided.
2307
 */
2308
static void wt_porcelain_v2_submodule_state(
2309
  struct wt_status_change_data *d,
2310
  char sub[5])
2311
0
{
2312
0
  if (S_ISGITLINK(d->mode_head) ||
2313
0
    S_ISGITLINK(d->mode_index) ||
2314
0
    S_ISGITLINK(d->mode_worktree)) {
2315
0
    sub[0] = 'S';
2316
0
    sub[1] = d->new_submodule_commits ? 'C' : '.';
2317
0
    sub[2] = (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) ? 'M' : '.';
2318
0
    sub[3] = (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ? 'U' : '.';
2319
0
  } else {
2320
0
    sub[0] = 'N';
2321
0
    sub[1] = '.';
2322
0
    sub[2] = '.';
2323
0
    sub[3] = '.';
2324
0
  }
2325
0
  sub[4] = 0;
2326
0
}
2327
2328
/*
2329
 * Fix-up changed entries before we print them.
2330
 */
2331
static void wt_porcelain_v2_fix_up_changed(struct string_list_item *it)
2332
0
{
2333
0
  struct wt_status_change_data *d = it->util;
2334
2335
0
  if (!d->index_status) {
2336
    /*
2337
     * This entry is unchanged in the index (relative to the head).
2338
     * Therefore, the collect_updated_cb was never called for this
2339
     * entry (during the head-vs-index scan) and so the head column
2340
     * fields were never set.
2341
     *
2342
     * We must have data for the index column (from the
2343
     * index-vs-worktree scan (otherwise, this entry should not be
2344
     * in the list of changes)).
2345
     *
2346
     * Copy index column fields to the head column, so that our
2347
     * output looks complete.
2348
     */
2349
0
    assert(d->mode_head == 0);
2350
0
    d->mode_head = d->mode_index;
2351
0
    oidcpy(&d->oid_head, &d->oid_index);
2352
0
  }
2353
2354
0
  if (!d->worktree_status) {
2355
    /*
2356
     * This entry is unchanged in the worktree (relative to the index).
2357
     * Therefore, the collect_changed_cb was never called for this entry
2358
     * (during the index-vs-worktree scan) and so the worktree column
2359
     * fields were never set.
2360
     *
2361
     * We must have data for the index column (from the head-vs-index
2362
     * scan).
2363
     *
2364
     * Copy the index column fields to the worktree column so that
2365
     * our output looks complete.
2366
     *
2367
     * Note that we only have a mode field in the worktree column
2368
     * because the scan code tries really hard to not have to compute it.
2369
     */
2370
0
    assert(d->mode_worktree == 0);
2371
0
    d->mode_worktree = d->mode_index;
2372
0
  }
2373
0
}
2374
2375
/*
2376
 * Print porcelain v2 info for tracked entries with changes.
2377
 */
2378
static void wt_porcelain_v2_print_changed_entry(
2379
  struct string_list_item *it,
2380
  struct wt_status *s)
2381
0
{
2382
0
  struct wt_status_change_data *d = it->util;
2383
0
  struct strbuf buf = STRBUF_INIT;
2384
0
  struct strbuf buf_from = STRBUF_INIT;
2385
0
  const char *path = NULL;
2386
0
  const char *path_from = NULL;
2387
0
  char key[3];
2388
0
  char submodule_token[5];
2389
0
  char sep_char, eol_char;
2390
2391
0
  wt_porcelain_v2_fix_up_changed(it);
2392
0
  wt_porcelain_v2_submodule_state(d, submodule_token);
2393
2394
0
  key[0] = d->index_status ? d->index_status : '.';
2395
0
  key[1] = d->worktree_status ? d->worktree_status : '.';
2396
0
  key[2] = 0;
2397
2398
0
  if (s->null_termination) {
2399
    /*
2400
     * In -z mode, we DO NOT C-quote pathnames.  Current path is ALWAYS first.
2401
     * A single NUL character separates them.
2402
     */
2403
0
    sep_char = '\0';
2404
0
    eol_char = '\0';
2405
0
    path = it->string;
2406
0
    path_from = d->rename_source;
2407
0
  } else {
2408
    /*
2409
     * Path(s) are C-quoted if necessary. Current path is ALWAYS first.
2410
     * The source path is only present when necessary.
2411
     * A single TAB separates them (because paths can contain spaces
2412
     * which are not escaped and C-quoting does escape TAB characters).
2413
     */
2414
0
    sep_char = '\t';
2415
0
    eol_char = '\n';
2416
0
    path = quote_path(it->string, s->prefix, &buf, 0);
2417
0
    if (d->rename_source)
2418
0
      path_from = quote_path(d->rename_source, s->prefix, &buf_from, 0);
2419
0
  }
2420
2421
0
  if (path_from)
2422
0
    fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c",
2423
0
        key, submodule_token,
2424
0
        d->mode_head, d->mode_index, d->mode_worktree,
2425
0
        oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
2426
0
        d->rename_status, d->rename_score,
2427
0
        path, sep_char, path_from, eol_char);
2428
0
  else
2429
0
    fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c",
2430
0
        key, submodule_token,
2431
0
        d->mode_head, d->mode_index, d->mode_worktree,
2432
0
        oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
2433
0
        path, eol_char);
2434
2435
0
  strbuf_release(&buf);
2436
0
  strbuf_release(&buf_from);
2437
0
}
2438
2439
/*
2440
 * Print porcelain v2 status info for unmerged entries.
2441
 */
2442
static void wt_porcelain_v2_print_unmerged_entry(
2443
  struct string_list_item *it,
2444
  struct wt_status *s)
2445
0
{
2446
0
  struct wt_status_change_data *d = it->util;
2447
0
  struct index_state *istate = s->repo->index;
2448
0
  const struct cache_entry *ce;
2449
0
  struct strbuf buf_index = STRBUF_INIT;
2450
0
  const char *path_index = NULL;
2451
0
  int pos, stage, sum;
2452
0
  struct {
2453
0
    int mode;
2454
0
    struct object_id oid;
2455
0
  } stages[3];
2456
0
  const char *key;
2457
0
  char submodule_token[5];
2458
0
  char unmerged_prefix = 'u';
2459
0
  char eol_char = s->null_termination ? '\0' : '\n';
2460
2461
0
  wt_porcelain_v2_submodule_state(d, submodule_token);
2462
2463
0
  switch (d->stagemask) {
2464
0
  case 1: key = "DD"; break; /* both deleted */
2465
0
  case 2: key = "AU"; break; /* added by us */
2466
0
  case 3: key = "UD"; break; /* deleted by them */
2467
0
  case 4: key = "UA"; break; /* added by them */
2468
0
  case 5: key = "DU"; break; /* deleted by us */
2469
0
  case 6: key = "AA"; break; /* both added */
2470
0
  case 7: key = "UU"; break; /* both modified */
2471
0
  default:
2472
0
    BUG("unhandled unmerged status %x", d->stagemask);
2473
0
  }
2474
2475
  /*
2476
   * Disregard d.aux.porcelain_v2 data that we accumulated
2477
   * for the head and index columns during the scans and
2478
   * replace with the actual stage data.
2479
   *
2480
   * Note that this is a last-one-wins for each the individual
2481
   * stage [123] columns in the event of multiple cache entries
2482
   * for same stage.
2483
   */
2484
0
  memset(stages, 0, sizeof(stages));
2485
0
  sum = 0;
2486
0
  pos = index_name_pos(istate, it->string, strlen(it->string));
2487
0
  assert(pos < 0);
2488
0
  pos = -pos-1;
2489
0
  while (pos < istate->cache_nr) {
2490
0
    ce = istate->cache[pos++];
2491
0
    stage = ce_stage(ce);
2492
0
    if (strcmp(ce->name, it->string) || !stage)
2493
0
      break;
2494
0
    stages[stage - 1].mode = ce->ce_mode;
2495
0
    oidcpy(&stages[stage - 1].oid, &ce->oid);
2496
0
    sum |= (1 << (stage - 1));
2497
0
  }
2498
0
  if (sum != d->stagemask)
2499
0
    BUG("observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
2500
2501
0
  if (s->null_termination)
2502
0
    path_index = it->string;
2503
0
  else
2504
0
    path_index = quote_path(it->string, s->prefix, &buf_index, 0);
2505
2506
0
  fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c",
2507
0
      unmerged_prefix, key, submodule_token,
2508
0
      stages[0].mode, /* stage 1 */
2509
0
      stages[1].mode, /* stage 2 */
2510
0
      stages[2].mode, /* stage 3 */
2511
0
      d->mode_worktree,
2512
0
      oid_to_hex(&stages[0].oid), /* stage 1 */
2513
0
      oid_to_hex(&stages[1].oid), /* stage 2 */
2514
0
      oid_to_hex(&stages[2].oid), /* stage 3 */
2515
0
      path_index,
2516
0
      eol_char);
2517
2518
0
  strbuf_release(&buf_index);
2519
0
}
2520
2521
/*
2522
 * Print porcelain V2 status info for untracked and ignored entries.
2523
 */
2524
static void wt_porcelain_v2_print_other(
2525
  struct string_list_item *it,
2526
  struct wt_status *s,
2527
  char prefix)
2528
0
{
2529
0
  struct strbuf buf = STRBUF_INIT;
2530
0
  const char *path;
2531
0
  char eol_char;
2532
2533
0
  if (s->null_termination) {
2534
0
    path = it->string;
2535
0
    eol_char = '\0';
2536
0
  } else {
2537
0
    path = quote_path(it->string, s->prefix, &buf, 0);
2538
0
    eol_char = '\n';
2539
0
  }
2540
2541
0
  fprintf(s->fp, "%c %s%c", prefix, path, eol_char);
2542
2543
0
  strbuf_release(&buf);
2544
0
}
2545
2546
/*
2547
 * Print porcelain V2 status.
2548
 *
2549
 * [<v2_branch>]
2550
 * [<v2_changed_items>]*
2551
 * [<v2_unmerged_items>]*
2552
 * [<v2_untracked_items>]*
2553
 * [<v2_ignored_items>]*
2554
 *
2555
 */
2556
static void wt_porcelain_v2_print(struct wt_status *s)
2557
0
{
2558
0
  struct wt_status_change_data *d;
2559
0
  struct string_list_item *it;
2560
0
  int i;
2561
2562
0
  if (s->show_branch)
2563
0
    wt_porcelain_v2_print_tracking(s);
2564
2565
0
  if (s->show_stash)
2566
0
    wt_porcelain_v2_print_stash(s);
2567
2568
0
  for (i = 0; i < s->change.nr; i++) {
2569
0
    it = &(s->change.items[i]);
2570
0
    d = it->util;
2571
0
    if (!d->stagemask)
2572
0
      wt_porcelain_v2_print_changed_entry(it, s);
2573
0
  }
2574
2575
0
  for (i = 0; i < s->change.nr; i++) {
2576
0
    it = &(s->change.items[i]);
2577
0
    d = it->util;
2578
0
    if (d->stagemask)
2579
0
      wt_porcelain_v2_print_unmerged_entry(it, s);
2580
0
  }
2581
2582
0
  for (i = 0; i < s->untracked.nr; i++) {
2583
0
    it = &(s->untracked.items[i]);
2584
0
    wt_porcelain_v2_print_other(it, s, '?');
2585
0
  }
2586
2587
0
  for (i = 0; i < s->ignored.nr; i++) {
2588
0
    it = &(s->ignored.items[i]);
2589
0
    wt_porcelain_v2_print_other(it, s, '!');
2590
0
  }
2591
0
}
2592
2593
void wt_status_print(struct wt_status *s)
2594
0
{
2595
0
  trace2_data_intmax("status", s->repo, "count/changed", s->change.nr);
2596
0
  trace2_data_intmax("status", s->repo, "count/untracked",
2597
0
         s->untracked.nr);
2598
0
  trace2_data_intmax("status", s->repo, "count/ignored", s->ignored.nr);
2599
2600
0
  trace2_region_enter("status", "print", s->repo);
2601
2602
0
  switch (s->status_format) {
2603
0
  case STATUS_FORMAT_SHORT:
2604
0
    wt_shortstatus_print(s);
2605
0
    break;
2606
0
  case STATUS_FORMAT_PORCELAIN:
2607
0
    wt_porcelain_print(s);
2608
0
    break;
2609
0
  case STATUS_FORMAT_PORCELAIN_V2:
2610
0
    wt_porcelain_v2_print(s);
2611
0
    break;
2612
0
  case STATUS_FORMAT_UNSPECIFIED:
2613
0
    BUG("finalize_deferred_config() should have been called");
2614
0
    break;
2615
0
  case STATUS_FORMAT_NONE:
2616
0
  case STATUS_FORMAT_LONG:
2617
0
    wt_longstatus_print(s);
2618
0
    break;
2619
0
  }
2620
2621
0
  trace2_region_leave("status", "print", s->repo);
2622
0
}
2623
2624
/**
2625
 * Returns 1 if there are unstaged changes, 0 otherwise.
2626
 */
2627
int has_unstaged_changes(struct repository *r, int ignore_submodules)
2628
0
{
2629
0
  struct rev_info rev_info;
2630
0
  int result;
2631
2632
0
  repo_init_revisions(r, &rev_info, NULL);
2633
0
  if (ignore_submodules) {
2634
0
    rev_info.diffopt.flags.ignore_submodules = 1;
2635
0
    rev_info.diffopt.flags.override_submodule_config = 1;
2636
0
  }
2637
0
  rev_info.diffopt.flags.quick = 1;
2638
0
  diff_setup_done(&rev_info.diffopt);
2639
0
  run_diff_files(&rev_info, 0);
2640
0
  result = diff_result_code(&rev_info);
2641
0
  release_revisions(&rev_info);
2642
0
  return result;
2643
0
}
2644
2645
/**
2646
 * Returns 1 if there are uncommitted changes, 0 otherwise.
2647
 */
2648
int has_uncommitted_changes(struct repository *r,
2649
          int ignore_submodules)
2650
0
{
2651
0
  struct rev_info rev_info;
2652
0
  int result;
2653
2654
0
  if (is_index_unborn(r->index))
2655
0
    return 0;
2656
2657
0
  repo_init_revisions(r, &rev_info, NULL);
2658
0
  if (ignore_submodules)
2659
0
    rev_info.diffopt.flags.ignore_submodules = 1;
2660
0
  rev_info.diffopt.flags.quick = 1;
2661
2662
0
  add_head_to_pending(&rev_info);
2663
0
  if (!rev_info.pending.nr) {
2664
    /*
2665
     * We have no head (or it's corrupt); use the empty tree,
2666
     * which will complain if the index is non-empty.
2667
     */
2668
0
    struct tree *tree = lookup_tree(r, r->hash_algo->empty_tree);
2669
0
    add_pending_object(&rev_info, &tree->object, "");
2670
0
  }
2671
2672
0
  diff_setup_done(&rev_info.diffopt);
2673
0
  run_diff_index(&rev_info, DIFF_INDEX_CACHED);
2674
0
  result = diff_result_code(&rev_info);
2675
0
  release_revisions(&rev_info);
2676
0
  return result;
2677
0
}
2678
2679
/**
2680
 * If the work tree has unstaged or uncommitted changes, dies with the
2681
 * appropriate message.
2682
 */
2683
int require_clean_work_tree(struct repository *r,
2684
          const char *action,
2685
          const char *hint,
2686
          int ignore_submodules,
2687
          int gently)
2688
0
{
2689
0
  struct lock_file lock_file = LOCK_INIT;
2690
0
  int err = 0, fd;
2691
2692
0
  fd = repo_hold_locked_index(r, &lock_file, 0);
2693
0
  refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
2694
0
  if (0 <= fd)
2695
0
    repo_update_index_if_able(r, &lock_file);
2696
0
  rollback_lock_file(&lock_file);
2697
2698
0
  if (has_unstaged_changes(r, ignore_submodules)) {
2699
    /* TRANSLATORS: the action is e.g. "pull with rebase" */
2700
0
    error(_("cannot %s: You have unstaged changes."), _(action));
2701
0
    err = 1;
2702
0
  }
2703
2704
0
  if (has_uncommitted_changes(r, ignore_submodules)) {
2705
0
    if (err)
2706
0
      error(_("additionally, your index contains uncommitted changes."));
2707
0
    else
2708
0
      error(_("cannot %s: Your index contains uncommitted changes."),
2709
0
            _(action));
2710
0
    err = 1;
2711
0
  }
2712
2713
0
  if (err) {
2714
0
    if (hint) {
2715
0
      if (!*hint)
2716
0
        BUG("empty hint passed to require_clean_work_tree();"
2717
0
            " use NULL instead");
2718
0
      error("%s", hint);
2719
0
    }
2720
0
    if (!gently)
2721
0
      exit(128);
2722
0
  }
2723
2724
0
  return err;
2725
0
}