Skip to content

Commit e0052f4

Browse files
newrengitster
authored andcommitted
merge-recursive: fix overwriting dirty files involved in renames
This fixes an issue that existed before my directory rename detection patches that affects both normal renames and renames implied by directory rename detection. Additional codepaths that only affect overwriting of dirty files that are involved in directory rename detection will be added in a subsequent commit. Reviewed-by: Stefan Beller <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7b3d3b0 commit e0052f4

7 files changed

+77
-24
lines changed

merge-recursive.c

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -337,32 +337,37 @@ static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
337337
init_tree_desc(desc, tree->buffer, tree->size);
338338
}
339339

340-
static int git_merge_trees(int index_only,
340+
static int git_merge_trees(struct merge_options *o,
341341
struct tree *common,
342342
struct tree *head,
343343
struct tree *merge)
344344
{
345345
int rc;
346346
struct tree_desc t[3];
347-
struct unpack_trees_options opts;
348347

349-
memset(&opts, 0, sizeof(opts));
350-
if (index_only)
351-
opts.index_only = 1;
348+
memset(&o->unpack_opts, 0, sizeof(o->unpack_opts));
349+
if (o->call_depth)
350+
o->unpack_opts.index_only = 1;
352351
else
353-
opts.update = 1;
354-
opts.merge = 1;
355-
opts.head_idx = 2;
356-
opts.fn = threeway_merge;
357-
opts.src_index = &the_index;
358-
opts.dst_index = &the_index;
359-
setup_unpack_trees_porcelain(&opts, "merge");
352+
o->unpack_opts.update = 1;
353+
o->unpack_opts.merge = 1;
354+
o->unpack_opts.head_idx = 2;
355+
o->unpack_opts.fn = threeway_merge;
356+
o->unpack_opts.src_index = &the_index;
357+
o->unpack_opts.dst_index = &the_index;
358+
setup_unpack_trees_porcelain(&o->unpack_opts, "merge");
360359

361360
init_tree_desc_from_tree(t+0, common);
362361
init_tree_desc_from_tree(t+1, head);
363362
init_tree_desc_from_tree(t+2, merge);
364363

365-
rc = unpack_trees(3, t, &opts);
364+
rc = unpack_trees(3, t, &o->unpack_opts);
365+
/*
366+
* unpack_trees NULLifies src_index, but it's used in verify_uptodate,
367+
* so set to the new index which will usually have modification
368+
* timestamp info copied over.
369+
*/
370+
o->unpack_opts.src_index = &the_index;
366371
cache_tree_free(&active_cache_tree);
367372
return rc;
368373
}
@@ -795,6 +800,20 @@ static int would_lose_untracked(const char *path)
795800
return !was_tracked(path) && file_exists(path);
796801
}
797802

803+
static int was_dirty(struct merge_options *o, const char *path)
804+
{
805+
struct cache_entry *ce;
806+
int dirty = 1;
807+
808+
if (o->call_depth || !was_tracked(path))
809+
return !dirty;
810+
811+
ce = cache_file_exists(path, strlen(path), ignore_case);
812+
dirty = (ce->ce_stat_data.sd_mtime.sec > 0 &&
813+
verify_uptodate(ce, &o->unpack_opts) != 0);
814+
return dirty;
815+
}
816+
798817
static int make_room_for_path(struct merge_options *o, const char *path)
799818
{
800819
int status, i;
@@ -2677,6 +2696,7 @@ static int handle_modify_delete(struct merge_options *o,
26772696

26782697
static int merge_content(struct merge_options *o,
26792698
const char *path,
2699+
int file_in_way,
26802700
struct object_id *o_oid, int o_mode,
26812701
struct object_id *a_oid, int a_mode,
26822702
struct object_id *b_oid, int b_mode,
@@ -2751,7 +2771,7 @@ static int merge_content(struct merge_options *o,
27512771
return -1;
27522772
}
27532773

2754-
if (df_conflict_remains) {
2774+
if (df_conflict_remains || file_in_way) {
27552775
char *new_path;
27562776
if (o->call_depth) {
27572777
remove_file_from_cache(path);
@@ -2785,6 +2805,30 @@ static int merge_content(struct merge_options *o,
27852805
return mfi.clean;
27862806
}
27872807

2808+
static int conflict_rename_normal(struct merge_options *o,
2809+
const char *path,
2810+
struct object_id *o_oid, unsigned int o_mode,
2811+
struct object_id *a_oid, unsigned int a_mode,
2812+
struct object_id *b_oid, unsigned int b_mode,
2813+
struct rename_conflict_info *ci)
2814+
{
2815+
int clean_merge;
2816+
int file_in_the_way = 0;
2817+
2818+
if (was_dirty(o, path)) {
2819+
file_in_the_way = 1;
2820+
output(o, 1, _("Refusing to lose dirty file at %s"), path);
2821+
}
2822+
2823+
/* Merge the content and write it out */
2824+
clean_merge = merge_content(o, path, file_in_the_way,
2825+
o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
2826+
ci);
2827+
if (clean_merge > 0 && file_in_the_way)
2828+
clean_merge = 0;
2829+
return clean_merge;
2830+
}
2831+
27882832
/* Per entry merge function */
27892833
static int process_entry(struct merge_options *o,
27902834
const char *path, struct stage_data *entry)
@@ -2804,9 +2848,12 @@ static int process_entry(struct merge_options *o,
28042848
switch (conflict_info->rename_type) {
28052849
case RENAME_NORMAL:
28062850
case RENAME_ONE_FILE_TO_ONE:
2807-
clean_merge = merge_content(o, path,
2808-
o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
2809-
conflict_info);
2851+
clean_merge = conflict_rename_normal(o,
2852+
path,
2853+
o_oid, o_mode,
2854+
a_oid, a_mode,
2855+
b_oid, b_mode,
2856+
conflict_info);
28102857
break;
28112858
case RENAME_DIR:
28122859
clean_merge = 1;
@@ -2902,7 +2949,7 @@ static int process_entry(struct merge_options *o,
29022949
} else if (a_oid && b_oid) {
29032950
/* Case C: Added in both (check for same permissions) and */
29042951
/* case D: Modified in both, but differently. */
2905-
clean_merge = merge_content(o, path,
2952+
clean_merge = merge_content(o, path, 0 /* file_in_way */,
29062953
o_oid, o_mode, a_oid, a_mode, b_oid, b_mode,
29072954
NULL);
29082955
} else if (!o_oid && !a_oid && !b_oid) {
@@ -2943,7 +2990,7 @@ int merge_trees(struct merge_options *o,
29432990
return 1;
29442991
}
29452992

2946-
code = git_merge_trees(o->call_depth, common, head, merge);
2993+
code = git_merge_trees(o, common, head, merge);
29472994

29482995
if (code != 0) {
29492996
if (show(o, 4) || o->call_depth)

merge-recursive.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef MERGE_RECURSIVE_H
22
#define MERGE_RECURSIVE_H
33

4+
#include "unpack-trees.h"
45
#include "string-list.h"
56

67
struct merge_options {
@@ -27,6 +28,7 @@ struct merge_options {
2728
struct strbuf obuf;
2829
struct hashmap current_file_dir_set;
2930
struct string_list df_conflict_file_set;
31+
struct unpack_trees_options unpack_opts;
3032
};
3133

3234
/*

t/t3501-revert-cherry-pick.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' '
141141
test_cmp expect actual
142142
'
143143

144-
test_expect_failure 'cherry-pick works with dirty renamed file' '
144+
test_expect_success 'cherry-pick works with dirty renamed file' '
145145
test_commit to-rename &&
146146
git checkout -b unrelated &&
147147
test_commit unrelated &&

t/t6043-merge-rename-directories.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3298,7 +3298,7 @@ test_expect_success '11a-setup: Avoid losing dirty contents with simple rename'
32983298
)
32993299
'
33003300

3301-
test_expect_failure '11a-check: Avoid losing dirty contents with simple rename' '
3301+
test_expect_success '11a-check: Avoid losing dirty contents with simple rename' '
33023302
(
33033303
cd 11a &&
33043304

t/t7607-merge-overwrite.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ test_expect_success 'will not overwrite removed file with staged changes' '
9292
test_cmp important c1.c
9393
'
9494

95-
test_expect_failure 'will not overwrite unstaged changes in renamed file' '
95+
test_expect_success 'will not overwrite unstaged changes in renamed file' '
9696
git reset --hard c1 &&
9797
git mv c1.c other.c &&
9898
git commit -m rename &&

unpack-trees.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1486,8 +1486,8 @@ static int verify_uptodate_1(const struct cache_entry *ce,
14861486
add_rejected_path(o, error_type, ce->name);
14871487
}
14881488

1489-
static int verify_uptodate(const struct cache_entry *ce,
1490-
struct unpack_trees_options *o)
1489+
int verify_uptodate(const struct cache_entry *ce,
1490+
struct unpack_trees_options *o)
14911491
{
14921492
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
14931493
return 0;

unpack-trees.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef UNPACK_TREES_H
22
#define UNPACK_TREES_H
33

4+
#include "tree-walk.h"
45
#include "string-list.h"
56

67
#define MAX_UNPACK_TREES 8
@@ -78,6 +79,9 @@ struct unpack_trees_options {
7879
extern int unpack_trees(unsigned n, struct tree_desc *t,
7980
struct unpack_trees_options *options);
8081

82+
int verify_uptodate(const struct cache_entry *ce,
83+
struct unpack_trees_options *o);
84+
8185
int threeway_merge(const struct cache_entry * const *stages,
8286
struct unpack_trees_options *o);
8387
int twoway_merge(const struct cache_entry * const *src,

0 commit comments

Comments
 (0)