blob: 6acacfa48ba686fa463f30a60e444459452c0a6a [file] [log] [blame]
/*
* Copyright (c) 2006, Al Viro. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
char *prefix1 = "a/", *prefix2 = "b/";
char *old_prefix = "O:";
char *line;
void die(char *s)
{
fprintf(stderr, "remap: %s\n", s);
exit(1);
}
void Enomem(void)
{
die("out of memory");
}
void Eio(void)
{
die("IO error");
}
enum {SIZE = 4096};
char *buffer;
int getline(int fd)
{
static char *end, *end_buffer, *next;
static size_t size;
ssize_t count;
if (!buffer) {
next = end = buffer = malloc(size = SIZE);
if (!buffer)
Enomem();
end_buffer = buffer + SIZE;
}
line = next;
while (1) {
if (next < end) {
next = memchr(next, '\n', end - next);
if (next) {
*next++ = '\0';
return 1;
}
}
if (end == end_buffer) {
size_t n = line - buffer;
if (n >= SIZE) {
n -= n % SIZE;
memmove(line - n, line, end - line);
line -= n;
end -= n;
} else {
char *p = malloc(size *= 2);
if (!p)
Enomem();
memcpy(p + n, line, end - line);
line = p + n;
end = p + (end - buffer);
free(buffer);
buffer = p;
end_buffer = p + size;
}
}
next = end;
count = read(fd, end, end_buffer - end);
if (!count)
break;
if (count < 0)
Eio();
end += count;
}
*end = '\0';
return line != end;
}
/* to == 0 -> deletion */
struct range_map {
int from, to;
};
struct file_map {
char *name;
size_t name_len;
struct file_map *next;
char *new_name;
int count;
int allocated;
int last;
struct range_map ranges[];
};
struct file_map *alloc_map(char *name)
{
struct file_map *map;
map = malloc(sizeof(struct file_map) + 16 * sizeof(struct range_map));
if (!map)
Enomem();
map->name_len = strlen(name);
map->name = map->new_name = malloc(map->name_len + 1);
if (!map->name)
Enomem();
memcpy(map->name, name, map->name_len + 1);
map->count = 0;
map->allocated = 16;
map->next = NULL;
map->last = 0;
return map;
}
/* this is 32bit FNV1 */
uint32_t FNV_hash(char *name, size_t len)
{
uint32_t n = 0x811c9dc5;
while (len--) {
unsigned char c = *name++;
n *= 0x01000193;
n ^= c;
}
return n;
}
struct file_map *hash[1024];
int hash_map(struct file_map *map)
{
size_t len = map->name_len;
char *name = map->name;
int n = FNV_hash(name, len) % 1024;
struct file_map **p = &hash[n];
while (*p) {
if ((*p)->name_len == len && !memcmp((*p)->name, name, len))
return 0;
p = &(*p)->next;
}
*p = map;
if (map->new_name && !map->count)
return 0;
if (map->new_name && map->ranges[0].from != 1)
return 0;
return 1;
}
struct file_map *find_map(char *name, size_t len)
{
static struct file_map *last = NULL;
int n = FNV_hash(name, len) % 1024;
struct file_map *p;
if (last && last->name_len == len && !memcmp(last->name, name, len))
return last;
for (p = hash[n]; p; p = p->next)
if (p->name_len == len && !memcmp(p->name, name, len))
break;
if (p)
last = p;
return p;
}
void parse_map(char *name)
{
struct file_map *map = NULL;
struct range_map *range;
char *s;
int fd;
fd = open(name, O_RDONLY);
if (fd < 0)
die("can't open map");
while (getline(fd)) {
if (line[0] == 'D') {
if (map && !hash_map(map))
goto Ebadmap;
if (line[1] != ' ')
goto Ebadmap;
if (strchr(line + 2, ' '))
goto Ebadmap;
map = alloc_map(line + 2);
map->new_name = NULL;
continue;
}
if (line[0] == 'M') {
if (map && !hash_map(map))
goto Ebadmap;
if (line[1] != ' ')
goto Ebadmap;
s = strchr(line + 2, ' ');
if (!s)
goto Ebadmap;
*s++ = '\0';
if (strchr(s, ' '))
goto Ebadmap;
map = alloc_map(line + 2);
if (strcmp(line + 2, s)) {
map->new_name = strdup(s);
if (!map->new_name)
Enomem();
}
continue;
}
if (!map || !map->new_name)
goto Ebadmap;
if (map->count == map->allocated) {
int n = 2 * map->allocated;
map = realloc(map, sizeof(struct file_map) +
n * sizeof(struct range_map));
if (!map)
Enomem();
map->allocated = n;
}
range = &map->ranges[map->count++];
if (sscanf(line, "%d %d%*c", &range->from, &range->to) != 2)
goto Ebadmap;
if (range > map->ranges && range->from <= range[-1].from)
goto Ebadmap;
}
if (map && !hash_map(map))
goto Ebadmap;
close(fd);
return;
Ebadmap:
die("bad map");
}
struct range_map *find_range(struct file_map *map, int l)
{
struct range_map *range = &map->ranges[map->last];
struct range_map *p;
if (range->from <= l) {
p = &map->ranges[map->count - 1];
if (p->from > l) {
for (p = range; p->from <= l; p++)
;
p--;
}
} else {
for (p = map->ranges; p->from <= l; p++)
;
p--;
}
map->last = p - map->ranges;
return p;
}
void mapline(void)
{
char *s = line, *end = line - 1, *sp = line - 1;
while (1) {
struct file_map *map;
struct range_map *range;
unsigned long l;
char *more;
end = strchr(end + 1, ':');
if (!end)
break;
if (sp < s)
sp = strchr(s, ' ');
while (sp && sp < end) {
fwrite(s, sp - s + 1, 1, stdout);
s = sp + 1;
sp = strchr(s , ' ');
}
l = strtoul(end + 1, &more, 10);
if (more == end + 1 || !l || l > INT_MAX)
continue;
map = find_map(s, end - s);
if (!map)
continue;
if (map->new_name && (range = find_range(map, l))->to) {
l += range->to - range->from;
printf("%s:%lu", map->new_name, l);
} else {
printf("%s%.*s", old_prefix, more - s, s);
}
s = more;
}
printf("%s\n", s);
}
int parse_hunk(int *l1, int *l2, int *n1, int *n2)
{
unsigned long n;
char *s, *p;
if (line[3] != '-')
return 0;
n = strtoul(line + 4, &s, 10);
if (s == line + 4 || n > INT_MAX)
return 0;
*l1 = n;
if (*s == ',') {
n = strtoul(s + 1, &p, 10);
if (p == s + 1 || n > INT_MAX)
return 0;
*n1 = n;
if (!n)
(*l1)++;
} else {
p = s;
*n1 = 1;
}
if (*p != ' ' || p[1] != '+')
return 0;
n = strtoul(p + 2, &s, 10);
if (s == p + 2 || n > INT_MAX)
return 0;
*l2 = n;
if (*s == ',') {
n = strtoul(s + 1, &p, 10);
if (p == s + 1 || n > INT_MAX)
return 0;
*n2 = n;
if (!n)
(*l2)++;
} else {
p = s;
*n2 = 1;
}
return 1;
}
void parse_diff(void)
{
int skipping = -1, suppress = 1;
char *name1 = NULL, *name2 = NULL;
int from = 1, to = 1;
int l1, l2, n1, n2;
enum cmd {
Diff, Hunk, New, Del, Copy, Rename, Junk
} cmd;
static struct { const char *s; size_t len; } pref[] = {
[Hunk] = {"@@ ", 3},
[Diff] = {"diff ", 5},
[New] = {"new file ", 9},
[Del] = {"deleted file ", 12},
[Copy] = {"copy from ", 10},
[Rename] = {"rename from ", 11},
[Junk] = {"", 0},
};
size_t len1 = strlen(prefix1), len2 = strlen(prefix2);
while (getline(0)) {
if (skipping > 0) {
switch (line[0]) {
case '+':
case '-':
case '\\':
continue;
}
}
for (cmd = 0; strncmp(line, pref[cmd].s, pref[cmd].len); cmd++)
;
switch (cmd) {
case Hunk:
if (skipping < 0)
goto Ediff;
if (!suppress) {
if (!skipping)
printf("M %s %s\n", name1, name2);
if (!parse_hunk(&l1, &l2, &n1, &n2))
goto Ediff;
if (l1 > from)
printf("%d %d\n", from, to);
if (n1)
printf("%d 0\n", l1);
from = l1 + n1;
to = l2 + n2;
}
skipping = 1;
break;
case Diff:
if (!suppress) {
if (!skipping)
printf("M %s %s\n", name1, name2);
printf("%d %d\n", from, to);
}
free(name1);
free(name2);
name2 = strrchr(line, ' ');
if (!name2)
goto Ediff;
*name2 = '\0';
name1 = strrchr(line, ' ');
if (!name1)
goto Ediff;
if (strncmp(name1 + 1, prefix1, len1))
goto Ediff;
if (strncmp(name2 + 1, prefix2, len2))
goto Ediff;
name1 = strdup(name1 + len1 + 1);
name2 = strdup(name2 + len2 + 1);
if (!name1 || !name2)
goto Ediff;
skipping = 0;
suppress = 0;
from = to = 1;
break;
case New:
if (skipping)
goto Ediff;
suppress = 1;
break;
case Del:
case Copy:
if (skipping)
goto Ediff;
printf("D %s\n", name2);
suppress = 1;
break;
case Rename:
if (skipping)
goto Ediff;
printf("D %s\n", name2);
break;
default:
break;
}
}
if (!suppress) {
if (!skipping)
printf("M %s %s\n", name1, name2);
printf("%d %d\n", from, to);
}
return;
Ediff:
die("odd diff");
}
int main(int argc, char **argv)
{
char *map_name = NULL;
char opt;
char *arg;
size_t len;
for (argc--, argv++; argc; argc--, argv++) {
if (argv[0][0] != '-') {
map_name = argv[0];
continue;
}
opt = argv[0][1];
if (!opt)
goto Eargs;
arg = argv[0] + 2;
if (!*arg) {
if (!--argc)
goto Eargs;
arg = *++argv;
}
len = strlen(arg);
switch (opt) {
case 'O':
prefix1 = malloc(len + 2);
if (!prefix1)
Enomem();
memcpy(prefix1, arg, len);
prefix1[len] = '/';
prefix1[len + 1] = '\0';
break;
case 'N':
prefix2 = malloc(len + 2);
if (!prefix2)
Enomem();
memcpy(prefix2, arg, len);
prefix2[len] = '/';
prefix2[len + 1] = '\0';
break;
case 'o':
old_prefix = arg;
break;
default:
Eargs:
die("bad arguments");
}
}
if (!map_name) {
parse_diff();
} else {
parse_map(map_name);
buffer = NULL;
while (getline(0))
mapline();
}
return 0;
}