#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include "common.h"
#include "types.h"
#include "scaffold.h"
#include "zstdseek.h"
#include "blake3.h"
#include "repository.h"
#include "hash.h"
#include "vertex.h"
#include "atom.h"
#include "hunk.h"
#include "mbuf.h"
#include "change.h"
#include "base32.h"
#include "bincode.h"
enum error
changefileoffsets(struct mbuf *ch, struct offsets *off)
{
struct bincode bc;
if (ch->len < OFFSETS_SIZE)
return CHANGEFILE_TOOSHORT;
bc.buf = ch->buf;
bc.avail = OFFSETS_SIZE;
off->version = bincode_getu64(&bc);
off->hashed_len = bincode_getu64(&bc);
off->unhashed_off = bincode_getu64(&bc);
off->unhashed_len = bincode_getu64(&bc);
off->contents_off = bincode_getu64(&bc);
off->contents_len = bincode_getu64(&bc);
off->total = bincode_getu64(&bc);
if (off->total != (u64)ch->len)
return CHANGEFILE_LENGTHMISMATCH;
switch (off->version) {
case VERSION:
case VERSION_NOENC:
return CHANGEFILE_OK;
default:
return CHANGEFILE_UNSUPPORTEDVERSION;
}
}
static char *
change_strerror(enum error err)
{
switch (err) {
case CHANGEFILE_OK:
return "ok";
case CHANGEFILE_TOOSHORT:
return ".change file is too short";
case CHANGEFILE_LENGTHMISMATCH:
return "length in .change file mismatch with actual length";
case CHANGEFILE_UNSUPPORTEDVERSION:
return ".change file version not supported";
case CHANGEFILE_HASHMISMATCH:
return "hash mismatch";
}
die("unhandled error variant: %d\n", err);
}
static void
change_readhash(struct bincode *bc, u8 *dest, u8 variant)
{
switch (variant) {
case HASH_BLAKE3:
bincode_getbytes(bc, dest, BLAKE3_BYTES);
break;
case HASH_NONE:
break;
default:
die("unknown hash variant: %u", variant);
}
}
static void
change_decode_hashlist(struct bincode *bc, struct hashlist *target)
{
usize len;
usize i;
len = bincode_getu64(bc);
target->len = len;
target->entries = xmalloc(sizeof(struct hash) * len);
for (i = 0; i < target->len; i++) {
target->entries[i].variant = (u8)bincode_getu32(bc);
change_readhash(
bc, target->entries[i].bytes, target->entries[i].variant
);
}
}
static void
change_decode_position(struct bincode *bc, struct position *pos)
{
if (bincode_getu8(bc)) {
pos->change.variant = (u8)bincode_getu32(bc);
change_readhash(
bc, (u8 *)&pos->change.bytes, pos->change.variant
);
}
pos->pos = bincode_getu64(bc);
}
static void
change_decode_vertex(struct bincode *bc, struct vertex *v)
{
struct hash *hash;
hash = &v->change;
u8 ishash = bincode_getu8(bc);
if (ishash) {
hash->variant = (u8)bincode_getu32(bc);
change_readhash(bc, (u8 *)hash->bytes, hash->variant);
}
v->start = bincode_getu64(bc);
v->end = bincode_getu64(bc);
}
static void
change_decode_introducedby(struct bincode *bc, struct hash *hash)
{
u8 ishash;
ishash = bincode_getu8(bc);
if (ishash) {
hash->variant = (u8)bincode_getu32(bc);
change_readhash(bc, (u8 *)hash->bytes, hash->variant);
}
}
static void
change_decode_newedge(struct bincode *bc, struct edge *edge)
{
edge->previous = bincode_getu8(bc);
edge->flag = bincode_getu8(bc);
change_decode_position(bc, &edge->from);
change_decode_vertex(bc, &edge->to);
change_decode_introducedby(bc, &edge->introducedby);
}
static void
change_decode_positionlist(struct bincode *bc, struct positionlist *poslist)
{
usize len;
usize i;
poslist->len = len = bincode_getu64(bc);
if (len > 1)
poslist->entries = xmalloc(sizeof(struct position) * (len - 1));
if (len > 0) {
change_decode_position(bc, &poslist->first);
for (i = 0; i < len - 1; i++)
change_decode_position(bc, &poslist->entries[i]);
}
}
static void
change_decode_newvertex(struct bincode *bc, struct newvertex *newvertex)
{
change_decode_positionlist(bc, &newvertex->upcontext);
change_decode_positionlist(bc, &newvertex->downcontext);
newvertex->flag = bincode_getu8(bc);
newvertex->start = bincode_getu64(bc);
newvertex->end = bincode_getu64(bc);
change_decode_position(bc, &newvertex->inode);
}
static void
change_decode_edgemap(struct bincode *bc, struct edgemap *edgemap)
{
usize i;
usize len;
edgemap->edges.len = len = bincode_getu64(bc);
edgemap->edges.entries = xmalloc(sizeof(struct edge) * len);
for (i = 0; i < len; i++) {
change_decode_newedge(bc, &edgemap->edges.entries[i]);
}
change_decode_position(bc, &edgemap->inode);
}
static void
change_decode_atom(struct bincode *bc, struct atom *atom)
{
u32 x;
x = bincode_getu32(bc);
switch (x) {
case 0:
atom->atomtype = NEW_VERTEX;
change_decode_newvertex(bc, &atom->newvertex);
break;
case 1:
atom->atomtype = EDGE_MAP;
change_decode_edgemap(bc, &atom->edgemap);
break;
default:
die("unknown atom type: %u", x);
}
}
static u64
change_readstr(char **dest, struct bincode *bc)
{
u64 len;
len = bincode_getu64(bc);
*dest = xmalloc(len + 1);
bincode_getstr(bc, *dest, len);
return len;
}
static void
change_decode_local(struct bincode *bc, struct local *local)
{
change_readstr(&local->path, bc);
local->line = bincode_getu64(bc);
}
static void
change_decode_encoding(struct bincode *bc, char **encoding)
{
if (bincode_getu8(bc)) {
change_readstr(encoding, bc);
}
}
static void
change_decode_encoding_noenc(struct bincode *bc, char **encoding)
{
(void)bc;
*encoding = xstrdup("UTF-8");
}
static void
change_decode_hunks(struct bincode *bc, struct hashed *hashed)
{
u64 len, slen;
usize i;
void (*fn_change_decode_encoding)(struct bincode *, char **);
fn_change_decode_encoding = &change_decode_encoding;
if (hashed->version == VERSION_NOENC)
fn_change_decode_encoding = change_decode_encoding_noenc;
len = bincode_getu64(bc);
hunklistinit(&hashed->hunks, len);
for (i = 0; i < len; i++) {
struct basehunk *bhunk = &hashed->hunks.entries[i];
bhunk->hunktype = bincode_getu32(bc);
switch (bhunk->hunktype) {
case EDIT:
change_decode_atom(
bc, &bhunk->edit.change
);
change_decode_local(bc, &bhunk->edit.local);
fn_change_decode_encoding(bc, &bhunk->edit.encoding);
break;
case REPLACEMENT:
change_decode_atom(bc, &bhunk->replacement.change);
change_decode_atom(bc, &bhunk->replacement.replacement);
change_decode_local(bc, &bhunk->replacement.local);
fn_change_decode_encoding(
bc, &bhunk->replacement.encoding
);
break;
case FILE_ADD:
change_decode_atom(bc, &bhunk->fileadd.addname);
change_decode_atom(bc, &bhunk->fileadd.addinode);
if (bincode_getu8(bc))
change_decode_atom(bc, &bhunk->fileadd.contents);
change_readstr(&bhunk->fileadd.path, bc);
fn_change_decode_encoding(bc, &bhunk->fileadd.encoding);
break;
case FILE_MOVE:
change_decode_atom(bc, &bhunk->filemove.del);
change_decode_atom(bc, &bhunk->filemove.add);
slen = bincode_getu64(bc);
bhunk->filemove.path = xmalloc(slen + 1);
bincode_getstr(bc, bhunk->filemove.path, slen);
break;
case FILE_DEL:
change_decode_atom(bc, &bhunk->filedel.del);
if (bincode_getu8(bc)) {
change_decode_atom(bc, &bhunk->filedel.contents);
}
slen = bincode_getu64(bc);
bhunk->filedel.path = xmalloc(slen + 1);
bincode_getstr(bc, bhunk->filedel.path, slen);
fn_change_decode_encoding(bc, &bhunk->filedel.encoding);
break;
case FILE_UNDEL:
change_decode_atom(bc, &bhunk->fileundel.undel);
if (bincode_getu8(bc))
change_decode_atom(
bc, &bhunk->fileundel.contents
);
slen = bincode_getu64(bc);
bhunk->fileundel.path = xmalloc(slen + 1);
bincode_getstr(bc, bhunk->fileundel.path, slen);
fn_change_decode_encoding(
bc, &bhunk->fileundel.encoding
);
break;
case SOLVE_ORDER_CONFLICT:
change_decode_atom(
bc, &bhunk->solveorderconflict.change
);
change_decode_local(
bc, &bhunk->solveorderconflict.local
);
break;
case UNSOLVE_ORDER_CONFLICT:
change_decode_atom(
bc, &bhunk->unsolveorderconflict.change
);
change_decode_local(
bc, &bhunk->unsolveorderconflict.local
);
break;
case SOLVE_NAME_CONFLICT:
change_decode_atom(bc, &bhunk->solvenameconflict.name);
slen = bincode_getu64(bc);
bhunk->solvenameconflict.path = xmalloc(slen + 1);
bincode_getstr(bc, bhunk->solvenameconflict.path, slen);
break;
case UNSOLVE_NAME_CONFLICT:
change_decode_atom(bc, &bhunk->unsolvenameconflict.name);
slen = bincode_getu64(bc);
bhunk->unsolvenameconflict.path = xmalloc(slen + 1);
bincode_getstr(
bc, bhunk->unsolvenameconflict.path, slen
);
break;
case RESURRECT_ZOMBIES:
change_decode_atom(bc, &bhunk->resurrectzombies.change);
change_decode_local(bc, &bhunk->resurrectzombies.local);
fn_change_decode_encoding(
bc, &bhunk->resurrectzombies.encoding
);
break;
case ADD_ROOT:
change_decode_atom(bc, &bhunk->addroot.name);
change_decode_atom(bc, &bhunk->addroot.inode);
break;
case DEL_ROOT:
change_decode_atom(bc, &bhunk->delroot.name);
change_decode_atom(bc, &bhunk->delroot.inode);
break;
default:
die("not yet implemented: %s",
hunk_basehunk_type_str(bhunk->hunktype));
}
}
}
void
change_decode_author(struct bincode *bc, struct author *author)
{
usize len, i;
len = bincode_getu64(bc);
author->len = len;
author->entries = xmalloc(sizeof(struct authorentry) * len);
for (i = 0; i < len; i++) {
change_readstr(&author->entries[i].key, bc);
change_readstr(&author->entries[i].value, bc);
}
}
void
change_decode_author_noenc(struct bincode *bc, struct author *author)
{
usize i;
char *name, *full_name, *email;
i = 1;
full_name = email = NULL;
change_readstr(&name, bc);
if (bincode_getu8(bc)) {
i++;
change_readstr(&full_name, bc);
}
if (bincode_getu8(bc)) {
i++;
change_readstr(&email, bc);
}
author->len = i;
author->entries = xmalloc(sizeof(struct authorentry) * i);
i = 0;
author->entries[i].key = xstrdup("name");
author->entries[i].value = name;
if (full_name) {
i++;
author->entries[i].key = xstrdup("full_name");
author->entries[i].value = full_name;
}
if (email) {
i++;
author->entries[i].key = xstrdup("email");
author->entries[i].value = email;
}
}
enum error
change_decodehashed(
u8 *data, usize datalen, u8 *expectedhash, struct hashed *hashed
)
{
u8 computedhash[BLAKE3_LEN];
struct bincode bc;
u64 len;
usize i;
void (*fn_change_decode_author)(struct bincode *, struct author *);
if (expectedhash != NULL) {
blake3_hash(computedhash, data, datalen);
if (blake3_cmp(computedhash, expectedhash))
return CHANGEFILE_HASHMISMATCH;
}
bc.avail = datalen;
bc.buf = data;
hashed->version = bincode_getu64(&bc);
change_readstr(&hashed->header.message, &bc);
hashed->header.description = NULL;
if (bincode_getu8(&bc)) {
change_readstr(&hashed->header.description, &bc);
}
len = change_readstr(&hashed->header.timestamp, &bc);
if (len != 30)
fprintf(stderr,
"warning: timestamp field has unexpected length %lu\n",
len);
len = bincode_getu64(&bc);
hashed->header.authors.len = len;
hashed->header.authors.map = xmalloc(sizeof(struct author) * len);
fn_change_decode_author = change_decode_author;
if (hashed->version == VERSION_NOENC)
fn_change_decode_author = change_decode_author_noenc;
for (i = 0; i < hashed->header.authors.len; i++) {
fn_change_decode_author(&bc, &hashed->header.authors.map[i]);
}
change_decode_hashlist(&bc, &hashed->dependencies);
change_decode_hashlist(&bc, &hashed->extraknown);
len = bincode_getu64(&bc);
hashed->metadata = NULL;
if (len > 0) {
hashed->metadata = xmalloc(len);
bincode_getbytes(&bc, hashed->metadata, len);
}
change_decode_hunks(&bc, hashed);
return CHANGEFILE_OK;
}
static u8 *
changecontents(
struct changestore *changes, struct atom *change, u8 *contents,
usize *n
)
{
struct newvertex *v;
struct edgemap *e;
struct change *ch;
struct vertex *vto = NULL;
u8 *buf = NULL;
usize sz = 0;
switch (change->atomtype) {
case NEW_VERTEX:
v = &change->newvertex;
sz = v->end - v->start;
buf = xmalloc(sizeof(u8) * sz);
memcpy(buf, &contents[v->start], sz);
break;
case EDGE_MAP:
e = &change->edgemap;
if (e->edges.len == 0) {
break;
}
if (!(e->edges.entries[0].flag & EDGE_FLAG_DELETED))
break;
for (usize i = 0; i < e->edges.len; i++) {
struct edge *edge = &e->edges.entries[i];
if (vto && vertexeq(&edge->to, vto))
continue;
vto = &edge->to;
usize z = vto->end - vto->start;
ch = changestoreget(changes, &vto->change);
if (!ch) {
changestoreload(changes, &vto->change);
ch = changestoreget(changes, &vto->change);
}
if (ch) {
if (buf)
buf = xrealloc(
buf, sz + sizeof(u8) * z
);
else
buf = xmalloc(sizeof(u8) * z);
memcpy(&buf[sz], &ch->contents[vto->start], z);
sz += z;
}
}
break;
}
*n = sz;
return buf;
}
void
print_positionlist(struct positionlist *plist)
{
struct position *p;
usize i;
p = &plist->first;
printf("P0.%lu", p->pos);
if (plist->len > 1)
for (i = 0; i < plist->len - 1; i++) {
printf("P%lu.%lu", i + 1, plist->entries[i].pos);
}
}
void
print_edgeflags(u8 flags)
{
if (flags & EDGE_FLAG_BLOCK)
printf("B");
if (flags & EDGE_FLAG_FOLDER)
printf("F");
if (flags & EDGE_FLAG_DELETED)
printf("D");
}
void
printcontents(u8 *buf, usize len, char prefix)
{
int waseol = 1;
usize i;
u8 x;
for (i = 0; i < len; i++) {
if (waseol) {
printf("%c ", prefix);
waseol = 0;
}
x = buf[i];
putchar(x);
if (x == '\n')
waseol = 1;
}
if (!waseol)
printf("\n");
}
void
printedgemap(struct edgemap *m)
{
printf("edgemap {\n edges: [\n");
for (usize i = 0; i < m->edges.len; i++) {
struct edge *e = &m->edges.entries[i];
printf(" edge { previous = %u, flag = ", e->previous);
print_edgeflags(e->flag);
printf(", from = position { change = ");
hashprint(&e->from.change);
printf(", pos = %lu }", e->from.pos);
printf(", to = vertex { change = ");
hashprint(&e->to.change);
printf(", start = %lu, end = %lu }, introducedby = ",
e->to.start, e->to.end);
hashprint(&e->introducedby);
printf(" }\n");
}
printf(" ]\n}\n");
}
void
print_atom(
struct changestore *changes, struct atom *a, u8 *contents,
int verbose
)
{
struct newvertex *v;
struct edgemap *m;
usize n;
u8 *res;
switch (a->atomtype) {
case NEW_VERTEX:
v = &a->newvertex;
printcontents(&contents[v->start], v->end - v->start, '+');
break;
case EDGE_MAP:
m = &a->edgemap;
if (verbose)
printedgemap(m);
res = changecontents(changes, a, contents, &n);
if (res) {
printcontents(res, n, '-');
free(res);
}
break;
default:
die("unknown atom type: %u", a->atomtype);
}
}
static void
read_filemetadata(struct filemetadata *m, u8 *contents, usize contents_len)
{
usize len;
struct bincode bc = { .avail = contents_len, .buf = contents };
m->inodemetadata = bincode_getu16(&bc);
len = bincode_getu64(&bc);
m->basename = xmalloc(len + 1);
bincode_getstr(&bc, m->basename, len);
change_decode_encoding(&bc, &m->encoding);
}
static void
read_filemetadata_noenc(
struct filemetadata *m, u8 *contents, usize contents_len
)
{
usize len;
struct bincode bc = { .avail = contents_len, .buf = contents };
m->inodemetadata = bincode_getu16(&bc);
len = contents_len - 2;
m->basename = xmalloc(len + 1);
memcpy(m->basename, &contents[2], len);
m->basename[len] = '\0';
}
void
print_raw_change(struct changestore *changes, struct change *ch)
{
usize i;
printf("offsets\n");
printf(" version: %lu\n", ch->offsets.version);
printf(" hashed_len: %lu\n", ch->offsets.hashed_len);
printf(" unhashed_off: %lu\n", ch->offsets.unhashed_off);
printf(" unhashed_len: %lu\n", ch->offsets.unhashed_len);
printf(" contents_off: %lu\n", ch->offsets.contents_off);
printf(" contents_len: %lu\n", ch->offsets.contents_len);
printf(" total: %lu\n", ch->offsets.total);
printf("\n");
if (ch->offsets.contents_len > 0) {
printf("contents: [");
printf("0x%02x", ch->contents[0]);
for (i = 1; i < ch->offsets.contents_len; i++) {
printf(", 0x%02x", ch->contents[i]);
}
printf("]\n");
}
}
void
print_change(
struct changestore *changes, struct hashed *hashed, u8 *contents,
int verbose
)
{
usize i;
void (*fn_read_filemetadata)(struct filemetadata *, u8 *, usize);
fn_read_filemetadata = read_filemetadata;
if (hashed->version == VERSION_NOENC)
fn_read_filemetadata = read_filemetadata_noenc;
printf("message = %s\n", hashed->header.message);
if (hashed->header.description)
printf("description = '%s'\n", hashed->header.description);
printf("timestamp = '%s'\n\n", hashed->header.timestamp);
for (i = 0; i < hashed->header.authors.len; i++) {
printf("[[authors]]\n");
usize j;
for (j = 0; j < hashed->header.authors.map[i].len; j++) {
printf("%s = '%s'\n",
hashed->header.authors.map[i].entries[j].key,
hashed->header.authors.map[i].entries[j].value);
}
printf("\n");
}
printf("# Dependencies\n");
for (i = 0; i < hashed->dependencies.len; i++) {
printf(" ");
hashprintln(&hashed->dependencies.entries[i]);
}
for (i = 0; i < hashed->extraknown.len; i++) {
printf("+");
hashprintln(&hashed->extraknown.entries[i]);
}
printf("\n# Hunks\n");
for (i = 0; i < hashed->hunks.len; i++) {
struct basehunk *hunk = &hashed->hunks.entries[i];
printf("\n%lu. %s", i + 1,
hunk_basehunk_type_str(hunk->hunktype));
switch (hunk->hunktype) {
case EDIT: {
struct edit *e = &hunk->edit;
struct atom *c = &e->change;
printf(" in %s:%lu (%s)", e->local.path, e->local.line,
e->encoding);
printf("\n");
print_atom(changes, c, contents, verbose);
break;
}
case REPLACEMENT: {
struct replacement *r = &hunk->replacement;
struct atom *c =
&r->change;
struct atom *replacement =
&r->replacement;
printf(" in %s:%lu (%s)", r->local.path, r->local.line,
r->encoding);
printf("\n");
print_atom(changes, c, contents, verbose);
print_atom(changes, replacement, contents, verbose);
break;
}
case FILE_ADD: {
struct fileadd *f = &hunk->fileadd;
struct filemetadata metadata = { 0 };
u64 start, end;
start = f->addname.newvertex.start;
end = f->addname.newvertex.end;
if (start == end) {
} else {
fn_read_filemetadata(
&metadata, &contents[start], end - start
);
}
printf(" %s (%s)\n", metadata.basename,
metadata.encoding);
print_atom(changes, &f->contents, contents, verbose);
free(metadata.basename);
if (metadata.encoding)
free(metadata.encoding);
break;
}
case FILE_MOVE: {
struct filemove *f = &hunk->filemove;
struct filemetadata metadata = { 0 };
u64 start, end;
start = f->add.newvertex.start;
end = f->add.newvertex.end;
fn_read_filemetadata(
&metadata, &contents[start], end - start
);
printf(" %s -> %s\n", f->path, metadata.basename);
free(metadata.basename);
if (metadata.encoding)
free(metadata.encoding);
break;
}
case FILE_DEL: {
struct filedel *f = &hunk->filedel;
printf(" %s\n", f->path);
break;
}
case FILE_UNDEL: {
struct fileundel *f = &hunk->fileundel;
printf(" %s\n", f->path);
break;
}
case SOLVE_ORDER_CONFLICT: {
struct solveorderconflict *s =
&hunk->solveorderconflict;
printf(" in %s:%lu\n", s->local.path, s->local.line);
break;
}
case UNSOLVE_ORDER_CONFLICT: {
struct unsolveorderconflict *s =
&hunk->unsolveorderconflict;
printf(" in %s:%lu\n", s->local.path, s->local.line);
break;
}
case SOLVE_NAME_CONFLICT: {
struct solvenameconflict *s = &hunk->solvenameconflict;
printf(" in %s\n", s->path);
break;
}
case UNSOLVE_NAME_CONFLICT: {
struct unsolvenameconflict *s =
&hunk->unsolvenameconflict;
printf(" in %s\n", s->path);
break;
}
case RESURRECT_ZOMBIES: {
struct resurrectzombies *z = &hunk->resurrectzombies;
printf(" in %s:%lu\n", z->local.path, z->local.line);
break;
}
case ADD_ROOT: {
struct addroot *r = &hunk->addroot;
printf(" start = %lu\n", r->name.newvertex.start);
break;
}
case DEL_ROOT: {
struct delroot *r = &hunk->delroot;
printf("name: ");
print_atom(changes, &r->name, contents, verbose);
printf("inode: ");
print_atom(changes, &r->inode, contents, verbose);
break;
}
default:
printf(" [not yet implemented]\n");
break;
}
}
}
static void
formatchangepath(char *dst, const char *repodir, const char *hashstr)
{
char *p;
p = stpncpy(dst, repodir, PATH_MAX);
*p++ = '/';
p = stpncpy(p, DOTPIJUL, 6);
*p++ = '/';
p = stpncpy(p, "changes/", 8);
*p++ = hashstr[0];
*p++ = hashstr[1];
*p++ = '/';
p = stpncpy(p, &hashstr[2], 51);
stpncpy(p, ".change", 7);
}
static int
loadchange(
struct change *c, struct hash *hash, const char *repodir,
const char *hashstr
)
{
int fd, err;
enum error cherr;
char chfile[PATH_MAX] = { 0 };
struct mbuf buf = { 0 };
struct offsets *off;
struct hashed *hashed;
u8 contents_hash[32];
u8 *scratch;
usize r;
off = &c->offsets;
hashed = &c->hashed;
err = 0;
formatchangepath(chfile, repodir, hashstr);
fd = open(chfile, O_RDONLY);
if (fd == -1) {
printf("error: %s\n", strerror(errno));
return -1;
}
err = mkmbuf(fd, &buf);
if (err) {
printf("error: %s\n", strerror(errno));
goto out2;
}
b32dec(contents_hash, hashstr);
memcpy(hash->bytes, contents_hash, BLAKE3_BYTES);
hash->variant = HASH_BLAKE3;
cherr = changefileoffsets(&buf, off);
if (cherr != CHANGEFILE_OK) {
printf("error: %s\n", change_strerror(cherr));
err = (int)cherr;
goto out;
}
scratch = xmalloc(sizeof(u8) * off->hashed_len);
r = zstdseek_decompress(
scratch, off->hashed_len, buf.buf + OFFSETS_SIZE,
off->unhashed_off - OFFSETS_SIZE
);
if (!r) {
err = 1;
free(scratch);
goto out;
}
err = change_decodehashed(
scratch, off->hashed_len, contents_hash, hashed
);
free(scratch);
if (err != 0) {
printf("error: failed to decode hashed\n");
goto out;
}
c->contents = xmalloc(sizeof(u8) * off->contents_len);
r = zstdseek_decompress(
c->contents, off->contents_len, buf.buf + off->contents_off,
off->total - off->contents_off
);
if (!r) {
err = 1;
free(c->contents);
}
err = 0;
out:
freembuf(&buf);
out2:
close(fd);
return err;
}
static int
loadchangeh(
struct change *c, struct hash *h, const char *repodir, struct hash *hash
)
{
char hashstr[54];
b32enc(hashstr, hash->bytes);
return loadchange(c, h, repodir, hashstr);
}
struct change *
changestoreget(struct changestore *store, struct hash *hash)
{
usize i;
for (i = 0; i < store->len; i++) {
if (hasheq(hash, &store->entries[i].hash))
return &store->entries[i].change;
}
return NULL;
}
int
changestoreload(struct changestore *store, struct hash *hash)
{
struct changeentry *ch;
usize i;
int err;
if (store->len == store->cap) {
usize newcap = store->cap << 1;
store->entries = xrealloc(
store->entries, sizeof(struct changeentry) * newcap
);
store->cap = newcap;
}
i = store->len;
ch = &store->entries[i];
err = loadchangeh(&ch->change, &ch->hash, store->repodir, hash);
if (err)
return err;
store->len++;
return err;
}
void
changestoreinit(struct changestore *s, usize cap, const char *repodir)
{
s->repodir = xstrdup(repodir);
s->entries = xmalloc(sizeof(struct changeentry) * cap);
s->cap = cap;
s->len = 0;
}
void
changestorefree(struct changestore *s)
{
usize i;
struct change *c;
for (i = 0; i < s->len; i++) {
c = &s->entries[i].change;
hashedfree(&c->hashed);
if (c->contents)
free(c->contents);
}
free(s->repodir);
free(s->entries);
}
void
authorsfree(struct authors *authors)
{
usize i, j;
struct author *author;
for (i = 0; i < authors->len; i++) {
author = &authors->map[i];
for (j = 0; j < author->len; j++) {
free(author->entries[j].key);
free(author->entries[j].value);
}
free(author->entries);
}
free(authors->map);
}
void
hashedfree(struct hashed *h)
{
free(h->header.message);
if (h->header.description)
free(h->header.description);
free(h->header.timestamp);
authorsfree(&h->header.authors);
hashlist_free(&h->dependencies);
hashlist_free(&h->extraknown);
if (h->metadata)
free(h->metadata);
hunklistfree(&h->hunks);
}
int
change(const char *hash, int verbose, int raw, struct repository *repo)
{
int err;
struct changestore changestore = { 0 };
struct changeentry *ch;
usize x;
changestoreinit(&changestore, 4, repo->path);
ch = &changestore.entries[0];
err = loadchange(&ch->change, &ch->hash, repo->path, hash);
if (err)
goto changeout;
changestore.len = x = 1;
changestore.entries[0].num = 1;
if (raw)
print_raw_change(&changestore, &ch->change);
else
print_change(
&changestore, &ch->change.hashed, ch->change.contents,
verbose
);
changeout:
changestorefree(&changestore);
return err;
}
static void
cmd_change_usage()
{
printf("ani change [-h] [-r] [-v] <hash>\n");
}
int
cmd_change(int argc, char **argv, struct repository *repo)
{
const char *hash;
usize hash_len;
int verbose;
int raw;
int c;
verbose = raw = 0;
while ((c = getopt(argc, argv, "hrv")) != -1) {
switch (c) {
case 'v':
verbose = 1;
break;
case 'r':
raw = 1;
break;
case 'h':
cmd_change_usage();
return 0;
case '?':
fprintf(stderr, "unrecognized option: '-%c'\n", optopt);
return -1;
}
}
if (optind >= argc) {
fprintf(stderr, "error: invalid number of arguments. See -h\n");
return -1;
}
hash = argv[optind];
if ((hash_len = strnlen(hash, 54)) != 53) {
fprintf(stderr,
"error: hash must be exactly 53 characters (got: %lu)\n",
hash_len);
return -1;
}
return change(hash, verbose, raw, repo);
}