diffcore-break.c 8.77 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) 2005 Junio C Hamano
 */
#include "cache.h"
#include "diff.h"
#include "diffcore.h"

8 9 10 11
static int should_break(struct diff_filespec *src,
			struct diff_filespec *dst,
			int break_score,
			int *merge_score_p)
12 13 14
{
	/* dst is recorded as a modification of src.  Are they so
	 * different that we are better off recording this as a pair
15
	 * of delete and create?
16
	 *
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
	 * There are two criteria used in this algorithm.  For the
	 * purposes of helping later rename/copy, we take both delete
	 * and insert into account and estimate the amount of "edit".
	 * If the edit is very large, we break this pair so that
	 * rename/copy can pick the pieces up to match with other
	 * files.
	 *
	 * On the other hand, we would want to ignore inserts for the
	 * pure "complete rewrite" detection.  As long as most of the
	 * existing contents were removed from the file, it is a
	 * complete rewrite, and if sizable chunk from the original
	 * still remains in the result, it is not a rewrite.  It does
	 * not matter how much or how little new material is added to
	 * the file.
	 *
	 * The score we leave for such a broken filepair uses the
	 * latter definition so that later clean-up stage can find the
	 * pieces that should not have been broken according to the
	 * latter definition after rename/copy runs, and merge the
	 * broken pair that have a score lower than given criteria
	 * back together.  The break operation itself happens
	 * according to the former definition.
	 *
	 * The minimum_edit parameter tells us when to break (the
	 * amount of "edit" required for us to consider breaking the
	 * pair).  We leave the amount of deletion in *merge_score_p
	 * when we return.
	 *
	 * The value we return is 1 if we want the pair to be broken,
	 * or 0 if we do not.
47
	 */
48
	unsigned long delta_size, max_size;
49
	unsigned long src_copied, literal_added, src_removed;
50 51 52 53

	*merge_score_p = 0; /* assume no deletion --- "do not break"
			     * is the default.
			     */
54

55 56 57 58
	if (S_ISREG(src->mode) != S_ISREG(dst->mode)) {
		*merge_score_p = (int)MAX_SCORE;
		return 1; /* even their types are different */
	}
59

60
	if (src->sha1_valid && dst->sha1_valid &&
61
	    !hashcmp(src->sha1, dst->sha1))
62 63
		return 0; /* they are the same */

64
	if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
65 66
		return 0; /* error but caught downstream */

67 68
	max_size = ((src->size > dst->size) ? src->size : dst->size);
	if (max_size < MINIMUM_BREAK_SIZE)
69
		return 0; /* we do not break too small filepair */
70

71 72 73
	if (!src->size)
		return 0; /* we do not let empty files get renamed */

74
	if (diffcore_count_changes(src, dst,
75
				   &src->cnt_data, &dst->cnt_data,
76 77 78
				   0,
				   &src_copied, &literal_added))
		return 0;
79

80 81 82 83 84 85 86 87 88 89 90
	/* sanity */
	if (src->size < src_copied)
		src_copied = src->size;
	if (dst->size < literal_added + src_copied) {
		if (src_copied < dst->size)
			literal_added = dst->size - src_copied;
		else
			literal_added = 0;
	}
	src_removed = src->size - src_copied;

91 92 93 94 95
	/* Compute merge-score, which is "how much is removed
	 * from the source material".  The clean-up stage will
	 * merge the surviving pair together if the score is
	 * less than the minimum, after rename/copy runs.
	 */
96
	*merge_score_p = (int)(src_removed * MAX_SCORE / src->size);
97 98
	if (*merge_score_p > break_score)
		return 1;
99

100 101 102
	/* Extent of damage, which counts both inserts and
	 * deletes.
	 */
103
	delta_size = src_removed + literal_added;
104
	if (delta_size * MAX_SCORE / max_size < break_score)
105 106 107 108
		return 0;

	/* If you removed a lot without adding new material, that is
	 * not really a rewrite.
109
	 */
110 111 112 113
	if ((src->size * break_score < src_removed * MAX_SCORE) &&
	    (literal_added * 20 < src_removed) &&
	    (literal_added * 20 < src_copied))
		return 0;
114

115
	return 1;
116 117
}

118
void diffcore_break(int break_score)
119 120 121
{
	struct diff_queue_struct *q = &diff_queued_diff;
	struct diff_queue_struct outq;
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

	/* When the filepair has this much edit (insert and delete),
	 * it is first considered to be a rewrite and broken into a
	 * create and delete filepair.  This is to help breaking a
	 * file that had too much new stuff added, possibly from
	 * moving contents from another file, so that rename/copy can
	 * match it with the other file.
	 *
	 * int break_score; we reuse incoming parameter for this.
	 */

	/* After a pair is broken according to break_score and
	 * subjected to rename/copy, both of them may survive intact,
	 * due to lack of suitable rename/copy peer.  Or, the caller
	 * may be calling us without using rename/copy.  When that
	 * happens, we merge the broken pieces back into one
	 * modification together if the pair did not have more than
	 * this much delete.  For this computation, we do not take
	 * insert into account at all.  If you start from a 100-line
	 * file and delete 97 lines of it, it does not matter if you
	 * add 27 lines to it to make a new 30-line file or if you add
	 * 997 lines to it to make a 1000-line file.  Either way what
	 * you did was a rewrite of 97%.  On the other hand, if you
	 * delete 3 lines, keeping 97 lines intact, it does not matter
	 * if you add 3 lines to it to make a new 100-line file or if
	 * you add 903 lines to it to make a new 1000-line file.
	 * Either way you did a lot of additions and not a rewrite.
	 * This merge happens to catch the latter case.  A merge_score
	 * of 80% would be a good default value (a broken pair that
	 * has score lower than merge_score will be merged back
	 * together).
	 */
	int merge_score;
155 156
	int i;

157 158 159 160 161 162 163 164 165 166
	/* See comment on DEFAULT_BREAK_SCORE and
	 * DEFAULT_MERGE_SCORE in diffcore.h
	 */
	merge_score = (break_score >> 16) & 0xFFFF;
	break_score = (break_score & 0xFFFF);

	if (!break_score)
		break_score = DEFAULT_BREAK_SCORE;
	if (!merge_score)
		merge_score = DEFAULT_MERGE_SCORE;
167

Bo Yang's avatar
Bo Yang committed
168
	DIFF_QUEUE_CLEAR(&outq);
169 170 171 172 173

	for (i = 0; i < q->nr; i++) {
		struct diff_filepair *p = q->queue[i];
		int score;

174 175
		/*
		 * We deal only with in-place edit of blobs.
176 177 178
		 * We do not break anything else.
		 */
		if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) &&
179 180
		    object_type(p->one->mode) == OBJ_BLOB &&
		    object_type(p->two->mode) == OBJ_BLOB &&
181
		    !strcmp(p->one->path, p->two->path)) {
182
			if (should_break(p->one, p->two,
183
					 break_score, &score)) {
184 185 186 187
				/* Split this into delete and create */
				struct diff_filespec *null_one, *null_two;
				struct diff_filepair *dp;

188 189 190 191 192 193
				/* Set score to 0 for the pair that
				 * needs to be merged back together
				 * should they survive rename/copy.
				 * Also we do not want to break very
				 * small files.
				 */
194
				if (score < merge_score)
195 196
					score = 0;

197 198 199 200 201 202 203 204 205 206 207 208
				/* deletion of one */
				null_one = alloc_filespec(p->one->path);
				dp = diff_queue(&outq, p->one, null_one);
				dp->score = score;
				dp->broken_pair = 1;

				/* creation of two */
				null_two = alloc_filespec(p->two->path);
				dp = diff_queue(&outq, null_two, p->two);
				dp->score = score;
				dp->broken_pair = 1;

209 210
				diff_free_filespec_blob(p->one);
				diff_free_filespec_blob(p->two);
211 212 213 214 215 216
				free(p); /* not diff_free_filepair(), we are
					  * reusing one and two here.
					  */
				continue;
			}
		}
217 218
		diff_free_filespec_data(p->one);
		diff_free_filespec_data(p->two);
219 220 221 222 223 224 225
		diff_q(&outq, p);
	}
	free(q->queue);
	*q = outq;

	return;
}
226 227 228 229 230 231

static void merge_broken(struct diff_filepair *p,
			 struct diff_filepair *pp,
			 struct diff_queue_struct *outq)
{
	/* p and pp are broken pairs we want to merge */
Junio C Hamano's avatar
Junio C Hamano committed
232
	struct diff_filepair *c = p, *d = pp, *dp;
233 234 235 236 237 238 239 240 241 242 243 244 245 246
	if (DIFF_FILE_VALID(p->one)) {
		/* this must be a delete half */
		d = p; c = pp;
	}
	/* Sanity check */
	if (!DIFF_FILE_VALID(d->one))
		die("internal error in merge #1");
	if (DIFF_FILE_VALID(d->two))
		die("internal error in merge #2");
	if (DIFF_FILE_VALID(c->one))
		die("internal error in merge #3");
	if (!DIFF_FILE_VALID(c->two))
		die("internal error in merge #4");

Junio C Hamano's avatar
Junio C Hamano committed
247 248
	dp = diff_queue(outq, d->one, c->two);
	dp->score = p->score;
249 250
	diff_free_filespec_data(d->two);
	diff_free_filespec_data(c->one);
251 252 253 254 255 256 257 258 259 260
	free(d);
	free(c);
}

void diffcore_merge_broken(void)
{
	struct diff_queue_struct *q = &diff_queued_diff;
	struct diff_queue_struct outq;
	int i, j;

Bo Yang's avatar
Bo Yang committed
261
	DIFF_QUEUE_CLEAR(&outq);
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297

	for (i = 0; i < q->nr; i++) {
		struct diff_filepair *p = q->queue[i];
		if (!p)
			/* we already merged this with its peer */
			continue;
		else if (p->broken_pair &&
			 !strcmp(p->one->path, p->two->path)) {
			/* If the peer also survived rename/copy, then
			 * we merge them back together.
			 */
			for (j = i + 1; j < q->nr; j++) {
				struct diff_filepair *pp = q->queue[j];
				if (pp->broken_pair &&
				    !strcmp(pp->one->path, pp->two->path) &&
				    !strcmp(p->one->path, pp->two->path)) {
					/* Peer survived.  Merge them */
					merge_broken(p, pp, &outq);
					q->queue[j] = NULL;
					break;
				}
			}
			if (q->nr <= j)
				/* The peer did not survive, so we keep
				 * it in the output.
				 */
				diff_q(&outq, p);
		}
		else
			diff_q(&outq, p);
	}
	free(q->queue);
	*q = outq;

	return;
}