@matheusbsbs / matheus-bagliano / commits / 4d4d5726ae
Fork 0

status: show worktree status of conflicted paths separately

When a path is unmerged in the index, we used to always say "unmerged" in the "Changed but not updated" section, even when the path was deleted in the work tree. Remove unmerged entries from the "Updated" section, and create a new section "Unmerged paths". Describe how the different stages conflict in more detail in this new section. Note that with the current 3-way merge policy (with or without recursive), certain combinations of index stages should never happen. For example, having only stage #2 means that a path that did not exist in the common ancestor was added by us while the other branch did not do anything to it, which would have autoresolved to take our addition. The code nevertheless prepares for the possibility that future merge policies may leave a path in such a state. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Aug 5, 2009 at 00:04 UTC 4d4d5726aee31522e90df21ef62ee3377c5d8f8d
3 files changed +120 -3
t/t7060-wtstatus.sh
+27
@@ -28,4 +28,31 @@ test_expect_success 'Report new path with conflict' '
28 test_cmp expect actual
29 '
30
31 +cat >expect <<EOF
32 +# On branch side
33 +# Unmerged paths:
34 +# (use "git reset HEAD <file>..." to unstage)
35 +# (use "git add <file>..." to mark resolution)
36 +#
37 +# deleted by us: foo
38 +#
39 +no changes added to commit (use "git add" and/or "git commit -a")
40 +EOF
41 +
42 +test_expect_success 'M/D conflict does not segfault' '
43 + mkdir mdconflict &&
44 + (
45 + cd mdconflict &&
46 + git init &&
47 + test_commit initial foo "" &&
48 + test_commit modify foo foo &&
49 + git checkout -b side HEAD^ &&
50 + git rm foo &&
51 + git commit -m delete &&
52 + test_must_fail git merge master &&
53 + test_must_fail git status > ../actual
54 + ) &&
55 + test_cmp expect actual
56 +'
57 +
58 test_done
wt-status.c
+92 -3
@@ -20,6 +20,7 @@ static char wt_status_colors[][COLOR_MAXLEN] = {
20 GIT_COLOR_RED, /* WT_STATUS_CHANGED */
21 GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */
22 GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */
23 + GIT_COLOR_RED, /* WT_STATUS_UNMERGED */
24 };
25
26 enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
@@ -37,6 +38,8 @@ static int parse_status_slot(const char *var, int offset)
38 return WT_STATUS_UNTRACKED;
39 if (!strcasecmp(var+offset, "nobranch"))
40 return WT_STATUS_NOBRANCH;
41 + if (!strcasecmp(var+offset, "unmerged"))
42 + return WT_STATUS_UNMERGED;
43 die("bad config variable '%s'", var);
44 }
45
@@ -59,6 +62,18 @@ void wt_status_prepare(struct wt_status *s)
62 s->change.strdup_strings = 1;
63 }
64
65 +static void wt_status_print_unmerged_header(struct wt_status *s)
66 +{
67 + const char *c = color(WT_STATUS_HEADER);
68 + color_fprintf_ln(s->fp, c, "# Unmerged paths:");
69 + if (!s->is_initial)
70 + color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference);
71 + else
72 + color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)");
73 + color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to mark resolution)");
74 + color_fprintf_ln(s->fp, c, "#");
75 +}
76 +
77 static void wt_status_print_cached_header(struct wt_status *s)
78 {
79 const char *c = color(WT_STATUS_HEADER);
@@ -99,6 +114,29 @@ static void wt_status_print_trailer(struct wt_status *s)
114
115 #define quote_path quote_path_relative
116
117 +static void wt_status_print_unmerged_data(struct wt_status *s,
118 + struct string_list_item *it)
119 +{
120 + const char *c = color(WT_STATUS_UNMERGED);
121 + struct wt_status_change_data *d = it->util;
122 + struct strbuf onebuf = STRBUF_INIT;
123 + const char *one, *how = "bug";
124 +
125 + one = quote_path(it->string, -1, &onebuf, s->prefix);
126 + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
127 + switch (d->stagemask) {
128 + case 1: how = "both deleted:"; break;
129 + case 2: how = "added by us:"; break;
130 + case 3: how = "deleted by them:"; break;
131 + case 4: how = "added by them:"; break;
132 + case 5: how = "deleted by us:"; break;
133 + case 6: how = "both added:"; break;
134 + case 7: how = "both modified:"; break;
135 + }
136 + color_fprintf(s->fp, c, "%-20s%s\n", how, one);
137 + strbuf_release(&onebuf);
138 +}
139 +
140 static void wt_status_print_change_data(struct wt_status *s,
141 int change_type,
142 struct string_list_item *it)
@@ -187,6 +225,26 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
225 }
226 }
227
228 +static int unmerged_mask(const char *path)
229 +{
230 + int pos, mask;
231 + struct cache_entry *ce;
232 +
233 + pos = cache_name_pos(path, strlen(path));
234 + if (0 <= pos)
235 + return 0;
236 +
237 + mask = 0;
238 + pos = -pos-1;
239 + while (pos < active_nr) {
240 + ce = active_cache[pos++];
241 + if (strcmp(ce->name, path) || !ce_stage(ce))
242 + break;
243 + mask |= (1 << (ce_stage(ce) - 1));
244 + }
245 + return mask;
246 +}
247 +
248 static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
249 struct diff_options *options,
250 void *data)
@@ -213,6 +271,9 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
271 case DIFF_STATUS_RENAMED:
272 d->head_path = xstrdup(p->one->path);
273 break;
274 + case DIFF_STATUS_UNMERGED:
275 + d->stagemask = unmerged_mask(p->two->path);
276 + break;
277 }
278 }
279 }
@@ -260,8 +321,10 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
321 d = xcalloc(1, sizeof(*d));
322 it->util = d;
323 }
263 - if (ce_stage(ce))
324 + if (ce_stage(ce)) {
325 d->index_status = DIFF_STATUS_UNMERGED;
326 + d->stagemask |= (1 << (ce_stage(ce) - 1));
327 + }
328 else
329 d->index_status = DIFF_STATUS_ADDED;
330 }
@@ -277,6 +340,29 @@ void wt_status_collect_changes(struct wt_status *s)
340 wt_status_collect_changes_index(s);
341 }
342
343 +static void wt_status_print_unmerged(struct wt_status *s)
344 +{
345 + int shown_header = 0;
346 + int i;
347 +
348 + for (i = 0; i < s->change.nr; i++) {
349 + struct wt_status_change_data *d;
350 + struct string_list_item *it;
351 + it = &(s->change.items[i]);
352 + d = it->util;
353 + if (!d->stagemask)
354 + continue;
355 + if (!shown_header) {
356 + wt_status_print_unmerged_header(s);
357 + shown_header = 1;
358 + }
359 + wt_status_print_unmerged_data(s, it);
360 + }
361 + if (shown_header)
362 + wt_status_print_trailer(s);
363 +
364 +}
365 +
366 static void wt_status_print_updated(struct wt_status *s)
367 {
368 int shown_header = 0;
@@ -314,7 +400,8 @@ static int wt_status_check_worktree_changes(struct wt_status *s)
400 for (i = 0; i < s->change.nr; i++) {
401 struct wt_status_change_data *d;
402 d = s->change.items[i].util;
317 - if (!d->worktree_status)
403 + if (!d->worktree_status ||
404 + d->worktree_status == DIFF_STATUS_UNMERGED)
405 continue;
406 changes = 1;
407 if (d->worktree_status == DIFF_STATUS_DELETED)
@@ -338,7 +425,8 @@ static void wt_status_print_changed(struct wt_status *s)
425 struct string_list_item *it;
426 it = &(s->change.items[i]);
427 d = it->util;
341 - if (!d->worktree_status)
428 + if (!d->worktree_status ||
429 + d->worktree_status == DIFF_STATUS_UNMERGED)
430 continue;
431 wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
432 }
@@ -479,6 +567,7 @@ void wt_status_print(struct wt_status *s)
567 color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
568 }
569
570 + wt_status_print_unmerged(s);
571 wt_status_print_updated(s);
572 wt_status_print_changed(s);
573 if (wt_status_submodule_summary)
wt-status.h
+1
@@ -10,6 +10,7 @@ enum color_wt_status {
10 WT_STATUS_CHANGED,
11 WT_STATUS_UNTRACKED,
12 WT_STATUS_NOBRANCH,
13 + WT_STATUS_UNMERGED,
14 };
15
16 enum untracked_status_type {