diff_read(
    int		idx_orig,	// idx of original file
    int		idx_new,	// idx of new file
    diffio_T   *dio)		// diff output
{
    FILE	*fd = NULL;
    int		line_idx = 0;
    diff_T	*dprev = NULL;
    diff_T	*dp = curtab->tp_first_diff;
    diff_T	*dn, *dpl;
    diffout_T   *dout = &dio->dio_diff;
    char_u	linebuf[LBUFLEN];   // only need to hold the diff line
    char_u	*line;
    long	off;
    int		i;
    int		notset = TRUE;	    // block "*dp" not set yet
    diffhunk_T	*hunk = NULL;	    // init to avoid gcc warning

    enum {
	DIFF_ED,
	DIFF_UNIFIED,
	DIFF_NONE
    } diffstyle = DIFF_NONE;

    if (dout->dout_fname == NULL)
    {
	diffstyle = DIFF_UNIFIED;
    }
    else
    {
	fd = mch_fopen((char *)dout->dout_fname, "r");
	if (fd == NULL)
	{
	    emsg(_(e_cannot_read_diff_output));
	    return;
	}
    }

    if (!dio->dio_internal)
    {
	hunk = ALLOC_ONE(diffhunk_T);
	if (hunk == NULL)
	{
	    if (fd != NULL)
		fclose(fd);
	    return;
	}
    }

    for (;;)
    {
	if (dio->dio_internal)
	{
	    if (line_idx >= dout->dout_ga.ga_len) {
		break;      // did last line
	    }
	    hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++];
	}
	else
	{
	    if (fd == NULL)
	    {
		if (line_idx >= dout->dout_ga.ga_len)
		    break;	    // did last line
		line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
	    }
	    else
	    {
		if (vim_fgets(linebuf, LBUFLEN, fd))
		    break;		// end of file
		line = linebuf;
	    }

	    if (diffstyle == DIFF_NONE)
	    {
		// Determine diff style.
		// ed like diff looks like this:
		// {first}[,{last}]c{first}[,{last}]
		// {first}a{first}[,{last}]
		// {first}[,{last}]d{first}
		//
		// unified diff looks like this:
		// --- file1       2018-03-20 13:23:35.783153140 +0100
		// +++ file2       2018-03-20 13:23:41.183156066 +0100
		// @@ -1,3 +1,5 @@
		if (isdigit(*line))
		    diffstyle = DIFF_ED;
		else if ((STRNCMP(line, "@@ ", 3) == 0))
		    diffstyle = DIFF_UNIFIED;
		else if ((STRNCMP(line, "--- ", 4) == 0)
			&& (vim_fgets(linebuf, LBUFLEN, fd) == 0)
			&& (STRNCMP(line, "+++ ", 4) == 0)
			&& (vim_fgets(linebuf, LBUFLEN, fd) == 0)
			&& (STRNCMP(line, "@@ ", 3) == 0))
		    diffstyle = DIFF_UNIFIED;
		else
		    // Format not recognized yet, skip over this line.  Cygwin
		    // diff may put a warning at the start of the file.
		    continue;
	    }

	    if (diffstyle == DIFF_ED)
	    {
		if (!isdigit(*line))
		    continue;	// not the start of a diff block
		if (parse_diff_ed(line, hunk) == FAIL)
		    continue;
	    }
	    else if (diffstyle == DIFF_UNIFIED)
	    {
		if (STRNCMP(line, "@@ ", 3)  != 0)
		    continue;	// not the start of a diff block
		if (parse_diff_unified(line, hunk) == FAIL)
		    continue;
	    }
	    else
	    {
		emsg(_(e_invalid_diff_format));
		break;
	    }
	}

	// Go over blocks before the change, for which orig and new are equal.
	// Copy blocks from orig to new.
	while (dp != NULL
		&& hunk->lnum_orig > dp->df_lnum[idx_orig]
						      + dp->df_count[idx_orig])
	{
	    if (notset)
		diff_copy_entry(dprev, dp, idx_orig, idx_new);
	    dprev = dp;
	    dp = dp->df_next;
	    notset = TRUE;
	}

	if (dp != NULL
		&& hunk->lnum_orig <= dp->df_lnum[idx_orig]
						       + dp->df_count[idx_orig]
		&& hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])
	{
	    // New block overlaps with existing block(s).
	    // First find last block that overlaps.
	    for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next)
		if (hunk->lnum_orig + hunk->count_orig
					     < dpl->df_next->df_lnum[idx_orig])
		    break;

	    // If the newly found block starts before the old one, set the
	    // start back a number of lines.
	    off = dp->df_lnum[idx_orig] - hunk->lnum_orig;
	    if (off > 0)
	    {
		for (i = idx_orig; i < idx_new; ++i)
		    if (curtab->tp_diffbuf[i] != NULL)
			dp->df_lnum[i] -= off;
		dp->df_lnum[idx_new] = hunk->lnum_new;
		dp->df_count[idx_new] = hunk->count_new;
	    }
	    else if (notset)
	    {
		// new block inside existing one, adjust new block
		dp->df_lnum[idx_new] = hunk->lnum_new + off;
		dp->df_count[idx_new] = hunk->count_new - off;
	    }
	    else
		// second overlap of new block with existing block
		dp->df_count[idx_new] += hunk->count_new - hunk->count_orig
		    + dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]
		    - (dp->df_lnum[idx_orig] + dp->df_count[idx_orig]);

	    // Adjust the size of the block to include all the lines to the
	    // end of the existing block or the new diff, whatever ends last.
	    off = (hunk->lnum_orig + hunk->count_orig)
			 - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
	    if (off < 0)
	    {
		// new change ends in existing block, adjust the end if not
		// done already
		if (notset)
		    dp->df_count[idx_new] += -off;
		off = 0;
	    }
	    for (i = idx_orig; i < idx_new; ++i)
		if (curtab->tp_diffbuf[i] != NULL)
		    dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i]
						       - dp->df_lnum[i] + off;

	    // Delete the diff blocks that have been merged into one.
	    dn = dp->df_next;
	    dp->df_next = dpl->df_next;
	    while (dn != dp->df_next)
	    {
		dpl = dn->df_next;
		vim_free(dn);
		dn = dpl;
	    }
	}
	else
	{
	    // Allocate a new diffblock.
	    dp = diff_alloc_new(curtab, dprev, dp);
	    if (dp == NULL)
		goto done;

	    dp->df_lnum[idx_orig] = hunk->lnum_orig;
	    dp->df_count[idx_orig] = hunk->count_orig;
	    dp->df_lnum[idx_new] = hunk->lnum_new;
	    dp->df_count[idx_new] = hunk->count_new;

	    // Set values for other buffers, these must be equal to the
	    // original buffer, otherwise there would have been a change
	    // already.
	    for (i = idx_orig + 1; i < idx_new; ++i)
		if (curtab->tp_diffbuf[i] != NULL)
		    diff_copy_entry(dprev, dp, idx_orig, i);
	}
	notset = FALSE;		// "*dp" has been set
    }

    // for remaining diff blocks orig and new are equal
    while (dp != NULL)
    {
	if (notset)
	    diff_copy_entry(dprev, dp, idx_orig, idx_new);
	dprev = dp;
	dp = dp->df_next;
	notset = TRUE;
    }

done:
    if (!dio->dio_internal)
	vim_free(hunk);

    if (fd != NULL)
	fclose(fd);
}