//
// aegis - project change supervisor
// Copyright (C) 2001, 2003-2006, 2008 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// .
//
#include
#include
#include
#include
#include
#include
static string_ty *star15;
static int
starts_with(string_ty *line, const char *prefix, size_t pfxlen)
{
return
(
line->str_length > pfxlen + 1
&&
0 == memcmp(line->str_text, prefix, pfxlen)
&&
(line->str_text[pfxlen] == ' ' || line->str_text[pfxlen] == '\t')
);
}
static string_ty *
second_word(string_ty *line)
{
const char *cp;
const char *ep;
cp = line->str_text;
while (*cp && *cp != ' ' && *cp != '\t')
++cp;
while (*cp && (*cp == ' ' || *cp == '\t'))
++cp;
ep = cp;
while (*ep && *ep != ' ' && *ep != '\t')
++ep;
return str_n_from_c(cp, ep - cp);
}
static patch_ty *
context_diff_header(patch_context_ty *context)
{
string_ty *line;
int idx;
patch_ty *result;
string_ty *s;
trace(("context_diff_header()\n{\n"));
if (!star15)
star15 = str_from_c("***************");
result = patch_new();
//
// Look for the optional index line.
//
line = patch_context_getline(context, 0);
if (!line)
{
oops:
patch_delete(result);
trace(("return 0\n"));
trace(("}\n"));
return 0;
}
idx = 0;
if (starts_with(line, "Index:", 6))
{
s = second_word(line);
result->name.push_back(s);
str_free(s);
idx++;
}
//
// Look for the optional before line.
//
line = patch_context_getline(context, idx);
if (!line)
goto oops;
static string_ty *dev_null;
if (!dev_null)
dev_null = str_from_c("/dev/null");
if (starts_with(line, "***", 3))
{
s = second_word(line);
if (!str_equal(s, dev_null))
result->name.push_back(s);
str_free(s);
idx++;
}
//
// Look for the optional after line.
//
line = patch_context_getline(context, idx);
if (!line)
goto oops;
if (starts_with(line, "---", 3))
{
s = second_word(line);
if (!str_equal(s, dev_null))
result->name.push_back(s);
str_free(s);
idx++;
}
//
// If there are no names at all,
// this isn't one of our files.
//
if (result->name.nstrings == 0)
goto oops;
//
// Look for a line which contains exactly 15 stars.
//
line = patch_context_getline(context, idx);
if (!str_equal(line, star15))
goto oops;
//
// Discard all of the header lines, except the row of stars
// (it's actually part of the first hunk).
//
patch_context_discard(context, idx);
trace(("return %08lX\n", (long)result));
trace(("}\n"));
return result;
}
static int
range(string_ty *line, const char *prefix, int *n1, int *n2)
{
const char *cp;
int n;
trace(("range(line = \"%s\", pfx = \"%s\")\n{\n", line->str_text, prefix));
if (line->str_length < 10)
{
oops:
trace(("return 0;\n}\n"));
return 0;
}
cp = line->str_text;
if (0 != memcmp(cp, prefix, 3))
goto oops;
cp += 3;
if (*cp++ != ' ')
goto oops;
switch (*cp)
{
default:
goto oops;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
n = 0;
for (;;)
{
n = n * 10 + *cp++ - '0';
switch (*cp)
{
default:
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
}
break;
}
*n1 = n;
*n2 = n;
trace(("n1 = %d\n", n));
if (*cp == ',')
{
++cp;
switch (*cp)
{
default:
goto oops;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
n = 0;
for (;;)
{
n = n * 10 + *cp++ - '0';
switch (*cp)
{
default:
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
}
break;
}
*n2 = n;
trace(("n2 = %d\n", n));
if (*n1 > *n2)
goto oops;
}
if (*cp++ != ' ')
goto oops;
if (0 != memcmp(cp, prefix, 4))
goto oops;
cp += 4;
if (*cp)
goto oops;
trace(("return 1;\n}\n"));
return 1;
}
static int
line_append(patch_line_list_ty *pllp, string_ty *line)
{
patch_line_type type;
string_ty *value;
if (line->str_length < 2)
return 0;
if (line->str_text[1] != ' ')
return 0;
type = patch_line_type_unchanged;
switch (line->str_text[0])
{
default:
return 0;
case ' ':
type = patch_line_type_unchanged;
break;
case '!':
type = patch_line_type_changed;
break;
case '+':
type = patch_line_type_inserted;
break;
case '-':
type = patch_line_type_deleted;
break;
}
value = str_n_from_c(line->str_text + 2, line->str_length - 2);
patch_line_list_append(pllp, type, value);
str_free(value);
return 1;
}
static patch_hunk_ty *
context_diff_hunk(patch_context_ty *context)
{
int before1;
int before2;
int before_list;
int after1;
int after2;
int after_list;
string_ty *line;
int lino;
patch_hunk_ty *php;
int idx;
size_t j;
patch_line_ty *plp;
trace(("context_diff_hunk()\n{\n"));
assert(star15);
line = patch_context_getline(context, 0);
if (!line || !str_equal(line, star15))
{
trace(("no start 15 line\n"));
trace(("return 0;\n}\n"));
return 0;
}
idx = 1;
php = patch_hunk_new();
//
// Look for a line of the form
// *** N ****
// or
// *** N,N ****
//
trace(("mark\n"));
line = patch_context_getline(context, idx++);
if (!line)
{
oops:
patch_hunk_delete(php);
trace(("return 0;\n}\n"));
return 0;
}
trace(("line = \"%s\"\n", line->str_text));
if (!range(line, "****", &before1, &before2))
goto oops;
php->before.start_line_number = before1;
line = patch_context_getline(context, idx);
if (!line)
goto oops;
trace(("line = \"%s\"\n", line->str_text));
before_list = (line->str_length >= 2 && line->str_text[1] == ' ');
if (before_list)
{
trace(("mark\n"));
// collect a bunch of lines
for (lino = before1; lino <= before2; ++lino)
{
trace(("mark\n"));
line = patch_context_getline(context, idx++);
if (!line)
goto oops;
trace(("line = \"%s\"\n", line->str_text));
if (!line_append(&php->before, line))
goto oops;
}
trace(("mark\n"));
}
//
// Look for a line of the form
// --- N ----
// or
// --- N,N ----
//
trace(("mark\n"));
line = patch_context_getline(context, idx++);
if (!line)
goto oops;
trace(("line = \"%s\"\n", line->str_text));
if (!range(line, "----", &after1, &after2))
goto oops;
trace(("mark\n"));
php->after.start_line_number = after1;
trace(("mark\n"));
line = patch_context_getline(context, idx);
if (!line)
{
after_list = 0;
}
else
{
trace(("line = \"%s\"\n", line->str_text));
after_list = (line->str_length >= 2 && line->str_text[1] == ' ');
}
trace(("mark\n"));
if (after_list)
{
trace(("mark\n"));
// collect a bunch of lines
for (lino = after1; lino <= after2; ++lino)
{
trace(("mark\n"));
line = patch_context_getline(context, idx++);
if (!line)
goto oops;
trace(("line = \"%s\"\n", line->str_text));
if (!line_append(&php->after, line))
goto oops;
}
trace(("mark\n"));
}
//
// We have a viable hunk, take them out of the context because
// we won't need to backtrack them any more.
//
trace(("mark\n"));
patch_context_discard(context, idx);
//
// Now we have all the lines, fill in the blanks. This happens
// when there is only deletes or only inserts in a hunk.
//
if (!before_list)
{
trace(("mark\n"));
for (j = 0; j < php->after.length; ++j)
{
plp = &php->after.item[j];
if (plp->type == patch_line_type_unchanged)
{
trace(("mark\n"));
patch_line_list_append(&php->before, plp->type, plp->value);
}
}
trace(("mark\n"));
}
if (!after_list)
{
trace(("mark\n"));
for (j = 0; j < php->before.length; ++j)
{
plp = &php->before.item[j];
if (plp->type == patch_line_type_unchanged)
{
patch_line_list_append(&php->after, plp->type, plp->value);
}
}
}
//
// In the limiting case, using the diff -C0 flag, inserts
// and deletes are off by one. They mean "insert after" and
// "delete after", but we need them to mean "insert before" and
// "delete before".
//
if (php->before.start_line_number && php->before.length == 0)
php->before.start_line_number++;
if (php->after.start_line_number && php->after.length == 0)
php->after.start_line_number++;
trace(("mark\n"));
trace(("return %08lX\n", (long)php));
trace(("}\n"));
return php;
}
patch_format_ty patch_format_context =
{
"context diff",
context_diff_header,
context_diff_hunk,
};