mirror of
git://projects.qi-hardware.com/eda-tools.git
synced 2025-04-21 12:27:27 +03:00
eeshow/: move file and history access to file/
This commit is contained in:
248
eeshow/file/file.c
Normal file
248
eeshow/file/file.c
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* file/file.c - Open and read a file
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "diag.h"
|
||||
#include "file/git-file.h"
|
||||
#include "file/file.h"
|
||||
|
||||
|
||||
void *file_oid(const struct file *file)
|
||||
{
|
||||
if (!file->vcs)
|
||||
return NULL;
|
||||
return vcs_git_get_oid(file->vcs);
|
||||
}
|
||||
|
||||
|
||||
bool file_oid_eq(const void *a, const void *b)
|
||||
{
|
||||
/*
|
||||
* If both a and b are NULL, we don't have revision data and thus
|
||||
* can't tell if they're identical.
|
||||
*/
|
||||
return a && b && vcs_git_oid_eq(a, b);
|
||||
}
|
||||
|
||||
|
||||
bool file_cat(const struct file *file, void *user, const char *line)
|
||||
{
|
||||
printf("%s\n", line);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
char *file_graft_relative(const char *base, const char *name)
|
||||
{
|
||||
const char *slash;
|
||||
char *res;
|
||||
unsigned len;
|
||||
|
||||
if (*name == '/')
|
||||
return NULL;
|
||||
|
||||
slash = strrchr(base, '/');
|
||||
if (!slash)
|
||||
return NULL;
|
||||
|
||||
len = slash + 1 - base;
|
||||
res = alloc_size(len + strlen(name) + 1);
|
||||
memcpy(res, base, len);
|
||||
strcpy(res + len, name);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static bool try_related(struct file *file)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
if (!file->related)
|
||||
return 0;
|
||||
|
||||
tmp = file_graft_relative(file->related->name, file->name);
|
||||
if (!tmp)
|
||||
return NULL;
|
||||
|
||||
if (*file->name == '/')
|
||||
return 0;
|
||||
|
||||
file->file = fopen(tmp, "r");
|
||||
if (!file->file) {
|
||||
free(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
progress(1, "reading %s\n", tmp);
|
||||
|
||||
free((char *) file->name);
|
||||
file->name = tmp;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @@@ logic isn't quite complete yet. It should go something like this:
|
||||
*
|
||||
* - if there is no related item,
|
||||
* - try file,
|
||||
* - if there is a colon, try VCS,
|
||||
* - give up
|
||||
* - if there is a related item,
|
||||
* - if it is a VCS,
|
||||
* - try the revision matching or predating (if different repo) that of the
|
||||
* related repo,
|
||||
( - fall through and try as if it was a file
|
||||
* - try opening as file. If this fails,
|
||||
* - if the name of the file to open is absolute, give up
|
||||
* - try `dirname related`/file_ope_open
|
||||
* - give up
|
||||
*
|
||||
* @@@ should we see if non-VCS file is in a VCS ? E.g.,
|
||||
* /home/my-libs/foo.lib 1234:/home/my-project/foo.sch
|
||||
* or maybe use : as indictor for VCS, i.e.,
|
||||
* :/home/my-libs/foo.lib ...
|
||||
*
|
||||
* @@@ explicit revision should always win over related.
|
||||
*/
|
||||
|
||||
static void *open_vcs(struct file *file)
|
||||
{
|
||||
char *colon;
|
||||
|
||||
colon = strchr(file->name, ':');
|
||||
if (colon) {
|
||||
char *tmp;
|
||||
|
||||
tmp = stralloc(file->name);
|
||||
tmp[colon - file->name] = 0;
|
||||
file->vcs = vcs_git_open(tmp, colon + 1,
|
||||
file->related ? file->related->vcs : NULL);
|
||||
if (file->vcs) {
|
||||
free(tmp);
|
||||
return file->vcs;
|
||||
}
|
||||
progress(2, "could not open %s:%s\n", tmp, colon + 1);
|
||||
return NULL;
|
||||
} else {
|
||||
file->vcs = vcs_git_open(NULL, file->name,
|
||||
file->related ? file->related->vcs : NULL);
|
||||
if (file->vcs)
|
||||
return file->vcs;
|
||||
progress(2, "could not open %s\n", file->name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void file_init(struct file *file, const char *name,
|
||||
const struct file *related)
|
||||
{
|
||||
file->name = stralloc(name);
|
||||
file->lineno = 0;
|
||||
file->related = related;
|
||||
file->file = NULL;
|
||||
file->vcs = NULL;
|
||||
}
|
||||
|
||||
|
||||
bool file_open(struct file *file, const char *name, const struct file *related)
|
||||
{
|
||||
file_init(file, name, related);
|
||||
|
||||
if (related && related->vcs) {
|
||||
file->vcs = open_vcs(file);
|
||||
if (file->vcs)
|
||||
return 1;
|
||||
}
|
||||
|
||||
file->file = fopen(name, "r");
|
||||
if (file->file) {
|
||||
progress(1, "reading %s\n", name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (try_related(file))
|
||||
return 1;
|
||||
|
||||
if (verbose)
|
||||
diag_perror(name);
|
||||
|
||||
if (!strchr(name, ':')) {
|
||||
if (!verbose)
|
||||
diag_perror(name);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
file->vcs = open_vcs(file);
|
||||
if (file->vcs)
|
||||
return 1;
|
||||
|
||||
error("could not open %s\n", name);
|
||||
fail:
|
||||
free((char *) file->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool file_open_revision(struct file *file, const char *rev, const char *name,
|
||||
const struct file *related)
|
||||
{
|
||||
if (!rev)
|
||||
return file_open(file, name, related);
|
||||
|
||||
file_init(file, name, related);
|
||||
file->vcs = vcs_git_open(rev, name, related ? related->vcs : NULL);
|
||||
if (file->vcs)
|
||||
return 1;
|
||||
progress(2, "could not open %s at %s\n", name, rev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool file_read(struct file *file,
|
||||
bool (*parse)(const struct file *file, void *user, const char *line),
|
||||
void *user)
|
||||
{
|
||||
static char *buf = NULL;
|
||||
static size_t n = 0;
|
||||
char *nl;
|
||||
|
||||
if (file->vcs)
|
||||
return vcs_read(file->vcs, file, parse, user);
|
||||
while (getline(&buf, &n, file->file) > 0) {
|
||||
nl = strchr(buf, '\n');
|
||||
if (nl)
|
||||
*nl = 0;
|
||||
file->lineno++;
|
||||
if (!parse(file, user, buf))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void file_close(struct file *file)
|
||||
{
|
||||
if (file->file)
|
||||
fclose(file->file);
|
||||
if (file->vcs)
|
||||
vcs_close(file->vcs);
|
||||
free((char *) file->name);
|
||||
}
|
||||
45
eeshow/file/file.h
Normal file
45
eeshow/file/file.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* file/file.h - Open and read a file
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef FILE_FILE_H
|
||||
#define FILE_FILE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
struct file {
|
||||
FILE *file; /* NULL if using a version control system */
|
||||
void *vcs; /* VCS descriptor or NULL */
|
||||
const char *name; /* name/designator given to file_open */
|
||||
unsigned lineno;
|
||||
const struct file *related; /* NULL if not related to anything */
|
||||
};
|
||||
|
||||
|
||||
void *file_oid(const struct file *file);
|
||||
bool file_oid_eq(const void *a, const void *b);
|
||||
|
||||
bool file_cat(const struct file *file, void *user, const char *line);
|
||||
|
||||
char *file_graft_relative(const char *base, const char *name);
|
||||
|
||||
bool file_open(struct file *file, const char *name,
|
||||
const struct file *related);
|
||||
bool file_open_revision(struct file *file, const char *rev, const char *name,
|
||||
const struct file *related);
|
||||
bool file_read(struct file *file,
|
||||
bool (*parse)(const struct file *file, void *user, const char *line),
|
||||
void *user);
|
||||
void file_close(struct file *file);
|
||||
|
||||
#endif /* !FILE_FILE_H */
|
||||
502
eeshow/file/git-file.c
Normal file
502
eeshow/file/git-file.c
Normal file
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* file/git-file.c - Open and read a file from git version control system
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE /* for get_current_dir_name */
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <git2.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "diag.h"
|
||||
#include "file/file.h"
|
||||
#include "file/git-util.h"
|
||||
#include "file/git-file.h"
|
||||
|
||||
|
||||
struct vcs_git {
|
||||
const char *name;
|
||||
const char *revision;
|
||||
const struct vcs_git *related;
|
||||
|
||||
git_repository *repo;
|
||||
git_tree *tree;
|
||||
git_object *obj;
|
||||
|
||||
const void *data;
|
||||
unsigned size;
|
||||
};
|
||||
|
||||
|
||||
/* ----- OID matching ------------------------------------------------------ */
|
||||
|
||||
|
||||
void *vcs_git_get_oid(const void *ctx)
|
||||
{
|
||||
const struct vcs_git *vcs_git = ctx;
|
||||
struct git_oid *new;
|
||||
|
||||
new = alloc_type(git_oid);
|
||||
git_oid_cpy(new, git_object_id(vcs_git->obj));
|
||||
return new;
|
||||
}
|
||||
|
||||
|
||||
bool vcs_git_oid_eq(const void *a, const void *b)
|
||||
{
|
||||
return !git_oid_cmp(a, b);
|
||||
}
|
||||
|
||||
|
||||
/* ----- Open -------------------------------------------------------------- */
|
||||
|
||||
|
||||
static git_repository *select_repo(const char *path)
|
||||
{
|
||||
git_repository *repo = NULL;
|
||||
char *tmp = stralloc(path);
|
||||
char *slash;
|
||||
|
||||
/*
|
||||
* If we can't find a repo, this may be due to the file or directory
|
||||
* the path points to not existing in the currently checked-out tree.
|
||||
* So we trim off elements until we find a repository.
|
||||
*/
|
||||
while (1) {
|
||||
progress(3, "trying \"%s\"\n", tmp);
|
||||
if (!git_repository_open_ext(&repo, *tmp ? tmp : "/",
|
||||
GIT_REPOSITORY_OPEN_CROSS_FS, NULL))
|
||||
break;
|
||||
slash = strrchr(tmp, '/');
|
||||
if (!slash)
|
||||
break;
|
||||
*slash = 0;
|
||||
}
|
||||
free(tmp);
|
||||
return repo;
|
||||
}
|
||||
|
||||
|
||||
static git_tree *pick_revision(git_repository *repo, const char *revision)
|
||||
{
|
||||
git_commit *commit;
|
||||
git_object *obj;
|
||||
git_tree *tree;
|
||||
|
||||
if (git_revparse_single(&obj, repo, revision)) {
|
||||
const git_error *e = giterr_last();
|
||||
|
||||
fatal("%s: %s\n", git_repository_path(repo), e->message);
|
||||
}
|
||||
|
||||
if (git_object_type(obj) != GIT_OBJ_COMMIT)
|
||||
fatal("%s: not a commit\n", revision);
|
||||
commit = (git_commit *) obj;
|
||||
|
||||
if (git_commit_tree(&tree, commit)) {
|
||||
const git_error *e = giterr_last();
|
||||
|
||||
fatal("%s: %s\n", revision, e->message);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
|
||||
static char *canonical_path_into_repo(const char *repo_dir, const char *path)
|
||||
{
|
||||
struct stat repo_st, path_st;
|
||||
char *tmp, *tmp2, *slash, *tail, *real;
|
||||
char *to;
|
||||
const char *end, *from;
|
||||
|
||||
/* identify inode of repo root */
|
||||
|
||||
if (stat(repo_dir, &repo_st) < 0)
|
||||
diag_pfatal(repo_dir);
|
||||
if (!S_ISDIR(repo_st.st_mode))
|
||||
fatal("%s: not a directory\n", repo_dir);
|
||||
|
||||
/* convert relative paths to absolute */
|
||||
|
||||
if (*path == '/') {
|
||||
tmp = stralloc(path);
|
||||
} else {
|
||||
char *cwd = get_current_dir_name();
|
||||
|
||||
tmp = alloc_size(strlen(cwd) + 1 + strlen(path) + 1);
|
||||
sprintf(tmp, "%s/%s", cwd, path);
|
||||
free(cwd);
|
||||
}
|
||||
|
||||
/* remove trailing / */
|
||||
|
||||
slash = strrchr(tmp, '/');
|
||||
if (slash && slash != tmp && !slash[1])
|
||||
*slash = 0;
|
||||
|
||||
/*
|
||||
* If path does point to inexistent object, separate into the part that
|
||||
* is valid on the current system and the tail containing dead things.
|
||||
*/
|
||||
end = tail = strchr(tmp, 0);
|
||||
|
||||
while (1) {
|
||||
progress(3, "probing \"%s\" tail \"%s\"\n", tmp, tail);
|
||||
if (stat(tmp, &path_st) == 0)
|
||||
break;
|
||||
if (!tmp[1])
|
||||
fatal("%s: cannot resolve\n", path);
|
||||
slash = strrchr(tmp, '/');
|
||||
if (tail != end)
|
||||
tail[-1] = '/';
|
||||
tail = slash + 1;
|
||||
*slash = 0;
|
||||
}
|
||||
|
||||
/* remove . and .. from tail */
|
||||
|
||||
progress(3, "input tail \"%s\"\n", tail);
|
||||
from = to = tail;
|
||||
while (1) {
|
||||
if (!strncmp(from, "./", 2)) {
|
||||
from += 2;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(from, "."))
|
||||
break;
|
||||
if (strncmp(from, "../", 3) && strcmp(from, "..")) {
|
||||
while (*from) {
|
||||
*to++ = *from++;
|
||||
if (from[-1] == '/')
|
||||
break;
|
||||
}
|
||||
if (!*from)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have something like this:
|
||||
* /home/repo/dead/../../foo
|
||||
*/
|
||||
if (to == tail)
|
||||
fatal("%s: can't climb out of dead path\n", path);
|
||||
|
||||
/*
|
||||
* We have something like
|
||||
* "foo/" -> ""
|
||||
* or
|
||||
* "foo/bar/" -> "foo/"
|
||||
* where "to" points to the end.
|
||||
*/
|
||||
to--;
|
||||
while (to != tail && to[-1] != '/')
|
||||
to--;
|
||||
}
|
||||
*to = 0;
|
||||
progress(3, "output tail \"%s\"\n", tail);
|
||||
|
||||
/* resolve all symlinks */
|
||||
|
||||
real = realpath(tmp, NULL);
|
||||
progress(3, "realpath(\"%s\") = \"%s\"\n", tmp, real);
|
||||
|
||||
/* append tail */
|
||||
|
||||
if (*tail) {
|
||||
tmp2 = alloc_size(strlen(real) + 1 + strlen(tail) + 1);
|
||||
sprintf(tmp2, "%s/%s", real, tail);
|
||||
free(real);
|
||||
} else {
|
||||
tmp2 = real;
|
||||
}
|
||||
free(tmp);
|
||||
tmp = tmp2;
|
||||
|
||||
progress(2, "full object path \"%s\"\n", tmp);
|
||||
|
||||
/* find which part of our path is inside the repo */
|
||||
|
||||
end = tail = strchr(tmp, 0);
|
||||
while (1) {
|
||||
progress(3, "trying \"%s\" tail \"%s\"\n", tmp, tail);
|
||||
|
||||
if (stat(tmp, &path_st) == 0 &&
|
||||
path_st.st_dev == repo_st.st_dev &&
|
||||
path_st.st_ino == repo_st.st_ino)
|
||||
break;
|
||||
|
||||
slash = strrchr(tmp, '/');
|
||||
|
||||
/* "this cannot happen" */
|
||||
if (tail == tmp || !slash)
|
||||
fatal("divergent paths:\nrepo \"%s\"\nobject \"%s\"\n",
|
||||
repo_dir, tail);
|
||||
|
||||
if (tail != end)
|
||||
tail[-1] = '/';
|
||||
tail = slash + 1;
|
||||
*slash = 0;
|
||||
}
|
||||
|
||||
progress(2, "path in repo \"%s\"\n", tail);
|
||||
|
||||
tmp2 = stralloc(tail);
|
||||
free(tmp);
|
||||
return tmp2;
|
||||
}
|
||||
|
||||
|
||||
static git_tree_entry *find_file(git_repository *repo, git_tree *tree,
|
||||
const char *path)
|
||||
{
|
||||
git_tree_entry *entry;
|
||||
char *repo_path = stralloc(git_repository_workdir(repo));
|
||||
/* use workdir, not path, for submodules */
|
||||
char *slash, *canon_path;
|
||||
int len;
|
||||
|
||||
/* remove trailing / from repo_path */
|
||||
slash = strrchr(repo_path, '/');
|
||||
if (slash && slash != repo_path && !slash[1])
|
||||
*slash = 0;
|
||||
|
||||
len = strlen(repo_path);
|
||||
if (len >= 5 && !strcmp(repo_path + len - 5, "/.git"))
|
||||
repo_path[len == 5 ? 1 : len - 5] = 0;
|
||||
|
||||
progress(2, "repo dir \"%s\"\n", repo_path);
|
||||
|
||||
canon_path = canonical_path_into_repo(repo_path, path);
|
||||
free(repo_path);
|
||||
|
||||
if (git_tree_entry_bypath(&entry, tree, canon_path)) {
|
||||
const git_error *e = giterr_last();
|
||||
|
||||
error("%s: %s\n", path, e->message);
|
||||
free(canon_path);
|
||||
return NULL;
|
||||
}
|
||||
free(canon_path);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
static const void *get_data(struct vcs_git *vcs_git, git_tree_entry *entry,
|
||||
unsigned *size)
|
||||
{
|
||||
git_repository *repo =vcs_git->repo;
|
||||
git_object *obj;
|
||||
git_blob *blob;
|
||||
|
||||
if (git_tree_entry_type(entry) != GIT_OBJ_BLOB)
|
||||
fatal("entry is not a blob\n");
|
||||
if (git_tree_entry_to_object(&obj, repo, entry)) {
|
||||
const git_error *e = giterr_last();
|
||||
|
||||
fatal("%s\n", e->message);
|
||||
}
|
||||
vcs_git->obj = obj;
|
||||
|
||||
if (verbose > 2) {
|
||||
git_buf buf = { 0 };
|
||||
|
||||
if (git_object_short_id(&buf, obj)) {
|
||||
const git_error *e = giterr_last();
|
||||
|
||||
fatal("%s\n", e->message);
|
||||
}
|
||||
progress(3, "object %s\n", buf.ptr);
|
||||
git_buf_free(&buf);
|
||||
}
|
||||
blob = (git_blob *) obj;
|
||||
*size = git_blob_rawsize(blob);
|
||||
return git_blob_rawcontent(blob);
|
||||
}
|
||||
|
||||
|
||||
static bool send_line(const char *s, unsigned len,
|
||||
bool (*parse)(const struct file *file, void *user, const char *line),
|
||||
void *user, const struct file *file)
|
||||
{
|
||||
char *tmp = alloc_size(len + 1);
|
||||
bool res;
|
||||
|
||||
memcpy(tmp, s, len);
|
||||
tmp[len] = 0;
|
||||
res = parse(file, user, tmp);
|
||||
free(tmp);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static bool access_file_data(struct vcs_git *vcs_git, const char *name)
|
||||
{
|
||||
git_tree_entry *entry;
|
||||
|
||||
entry = find_file(vcs_git->repo, vcs_git->tree, name);
|
||||
if (!entry)
|
||||
return 0;
|
||||
progress(1, "reading %s\n", name);
|
||||
|
||||
vcs_git->data = get_data(vcs_git, entry, &vcs_git->size);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static bool related_same_repo(struct vcs_git *vcs_git)
|
||||
{
|
||||
const struct vcs_git *related = vcs_git->related;
|
||||
|
||||
vcs_git->repo = related->repo;
|
||||
vcs_git->tree = related->tree;
|
||||
|
||||
return access_file_data(vcs_git, vcs_git->name);
|
||||
}
|
||||
|
||||
|
||||
static bool related_other_repo(struct vcs_git *vcs_git)
|
||||
{
|
||||
static bool shown = 0;
|
||||
|
||||
/* @@@ find revision <= date of revision in related */
|
||||
if (!shown)
|
||||
warning("related_other_repo is not yet implemented\n");
|
||||
shown = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static bool related_only_repo(struct vcs_git *vcs_git)
|
||||
{
|
||||
const struct vcs_git *related = vcs_git->related;
|
||||
char *tmp;
|
||||
|
||||
progress(2, "trying graft \"%s\" \"%s\"\n",
|
||||
related->name, vcs_git->name);
|
||||
tmp = file_graft_relative(related->name, vcs_git->name);
|
||||
if (!tmp)
|
||||
return 0;
|
||||
|
||||
vcs_git->repo = related->repo;
|
||||
vcs_git->tree = related->tree;
|
||||
|
||||
if (!access_file_data(vcs_git, tmp)) {
|
||||
free(tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free((char *) vcs_git->name);
|
||||
vcs_git->name = tmp;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static bool try_related(struct vcs_git *vcs_git)
|
||||
{
|
||||
if (!vcs_git->related)
|
||||
return 0;
|
||||
if (vcs_git->revision)
|
||||
return 0;
|
||||
|
||||
vcs_git->repo = select_repo(vcs_git->name);
|
||||
if (vcs_git->repo) {
|
||||
if (!strcmp(git_repository_path(vcs_git->related->repo),
|
||||
git_repository_path(vcs_git->repo)))
|
||||
return related_same_repo(vcs_git);
|
||||
else
|
||||
return related_other_repo(vcs_git);
|
||||
}
|
||||
|
||||
return related_only_repo(vcs_git);
|
||||
}
|
||||
|
||||
|
||||
struct vcs_git *vcs_git_open(const char *revision, const char *name,
|
||||
const struct vcs_git *related)
|
||||
{
|
||||
struct vcs_git *vcs_git = alloc_type(struct vcs_git);
|
||||
|
||||
git_init_once();
|
||||
|
||||
vcs_git->name = stralloc(name);
|
||||
vcs_git->revision = revision ? stralloc(revision) : NULL;
|
||||
vcs_git->related = related;
|
||||
|
||||
if (try_related(vcs_git))
|
||||
return vcs_git;
|
||||
|
||||
vcs_git->repo = select_repo(name);
|
||||
if (!vcs_git->repo) {
|
||||
error("%s: not found\n", name);
|
||||
goto fail;
|
||||
}
|
||||
progress(2, "using repository %s\n",
|
||||
git_repository_path(vcs_git->repo));
|
||||
|
||||
if (!revision)
|
||||
revision = "HEAD";
|
||||
vcs_git->tree = pick_revision(vcs_git->repo, revision);
|
||||
|
||||
if (!access_file_data(vcs_git, name))
|
||||
goto fail;
|
||||
|
||||
return vcs_git;
|
||||
|
||||
fail:
|
||||
vcs_git_close(vcs_git);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ----- Read -------------------------------------------------------------- */
|
||||
|
||||
|
||||
bool vcs_git_read(void *ctx, struct file *file,
|
||||
bool (*parse)(const struct file *file, void *user, const char *line),
|
||||
void *user)
|
||||
{
|
||||
const struct vcs_git *vcs_git = ctx;
|
||||
const char *end = vcs_git->data + vcs_git->size;
|
||||
const char *p = vcs_git->data;
|
||||
const char *nl;
|
||||
|
||||
while (p != end) {
|
||||
nl = memchr(p, '\n', end - p);
|
||||
file->lineno++;
|
||||
if (!nl)
|
||||
return send_line(p, end - p, parse, user, file);
|
||||
if (!send_line(p, nl - p, parse, user, file))
|
||||
return 0;
|
||||
p = nl + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* ----- Close ------------------------------------------------------------- */
|
||||
|
||||
|
||||
void vcs_git_close(void *ctx)
|
||||
{
|
||||
struct vcs_git *vcs_git = ctx;
|
||||
|
||||
free((char *) vcs_git->name);
|
||||
free((char *) vcs_git->revision);
|
||||
free(vcs_git);
|
||||
}
|
||||
45
eeshow/file/git-file.h
Normal file
45
eeshow/file/git-file.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* file/git-file.h - Open and read a file from git version control system
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef FILE_GIT_FILE_H
|
||||
#define FILE_GIT_FILE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
/*
|
||||
* future-proofing: if someone wants to add back-ends for other version control
|
||||
* systems, the identifiers will already be there.
|
||||
*/
|
||||
|
||||
#define vcs_open vcs_git_open
|
||||
#define vcs_read vcs_git_read
|
||||
#define vcs_close vcs_git_close
|
||||
|
||||
|
||||
struct vcs_git;
|
||||
struct file;
|
||||
|
||||
|
||||
void vcs_git_init(void);
|
||||
|
||||
void *vcs_git_get_oid(const void *ctx); /* mallocs */
|
||||
bool vcs_git_oid_eq(const void *a, const void *b);
|
||||
|
||||
struct vcs_git *vcs_git_open(const char *revision, const char *name,
|
||||
const struct vcs_git *related);
|
||||
bool vcs_git_read(void *ctx, struct file *file,
|
||||
bool (*parse)(const struct file *file, void *user, const char *line),
|
||||
void *user);
|
||||
void vcs_git_close(void *ctx);
|
||||
|
||||
#endif /* !FILE_GIT_FILE_H */
|
||||
273
eeshow/file/git-hist.c
Normal file
273
eeshow/file/git-hist.c
Normal file
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* file/git-hist.c - Retrieve revision history from GIT repo
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h> /* for vcs_long_for_pango */
|
||||
#include <alloca.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "diag.h"
|
||||
#include "file/git-util.h"
|
||||
#include "file/git-file.h"
|
||||
#include "file/git-hist.h"
|
||||
|
||||
|
||||
/*
|
||||
* @@@ we assume to have a single head. That isn't necessarily true, since
|
||||
* each open branch has its own head. Getting this right is for further study.
|
||||
*/
|
||||
|
||||
|
||||
static struct hist *new_commit(unsigned branch)
|
||||
{
|
||||
struct hist *h;
|
||||
|
||||
h = alloc_type(struct hist);
|
||||
h->commit = NULL;
|
||||
h->branch = branch;
|
||||
h->newer = NULL;
|
||||
h->n_newer = 0;
|
||||
h->older = NULL;
|
||||
h->n_older = 0;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
static void uplink(struct hist *down, struct hist *up)
|
||||
{
|
||||
down->newer = realloc(down->newer,
|
||||
sizeof(struct hist *) * (down->n_newer + 1));
|
||||
if (!down->newer)
|
||||
diag_pfatal("realloc");
|
||||
down->newer[down->n_newer++] = up;
|
||||
}
|
||||
|
||||
|
||||
static struct hist *find_commit(struct hist *h, const git_commit *commit)
|
||||
{
|
||||
unsigned i;
|
||||
struct hist *found;
|
||||
|
||||
/*
|
||||
* @@@ should probably use
|
||||
* git_oid_equal(git_object_id(a), git_object_id(b))
|
||||
*/
|
||||
if (h->commit == commit)
|
||||
return h;
|
||||
for (i = 0; i != h->n_older; i++) {
|
||||
if (h->older[i]->newer[0] != h)
|
||||
continue;
|
||||
found = find_commit(h->older[i], commit);
|
||||
if (found)
|
||||
return found;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void recurse(struct hist *h,
|
||||
unsigned n_branches, struct hist **branches)
|
||||
{
|
||||
unsigned n, i, j;
|
||||
struct hist **b;
|
||||
const git_error *e;
|
||||
|
||||
n = git_commit_parentcount(h->commit);
|
||||
if (verbose > 2)
|
||||
progress(3, "commit %p: %u + %u\n", h->commit, n_branches, n);
|
||||
|
||||
b = alloca(sizeof(struct hist) * (n_branches - 1 + n));
|
||||
n_branches--;
|
||||
memcpy(b, branches, sizeof(struct hist *) * n_branches);
|
||||
|
||||
h->older = alloc_type_n(struct hist *, n);
|
||||
h->n_older = n;
|
||||
|
||||
for (i = 0; i != n; i++) {
|
||||
git_commit *commit;
|
||||
struct hist *found = NULL;
|
||||
|
||||
if (git_commit_parent(&commit, h->commit, i)) {
|
||||
e = giterr_last();
|
||||
fatal("git_commit_parent: %s\n", e->message);
|
||||
}
|
||||
for (j = 0; j != n_branches; j++) {
|
||||
found = find_commit(b[j], commit);
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
if (found) {
|
||||
uplink(found, h);
|
||||
h->older[i] = found;
|
||||
} else {
|
||||
struct hist *new;
|
||||
|
||||
new = new_commit(n_branches);
|
||||
new->commit = commit;
|
||||
h->older[i] = new;
|
||||
b[n_branches++] = new;
|
||||
uplink(new, h);
|
||||
recurse(new, n_branches, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool vcs_git_try(const char *path)
|
||||
{
|
||||
git_repository *repo;
|
||||
|
||||
git_init_once();
|
||||
|
||||
if (git_repository_open_ext(&repo, path,
|
||||
GIT_REPOSITORY_OPEN_CROSS_FS, NULL))
|
||||
return 0;
|
||||
return !git_repository_is_empty(repo);
|
||||
}
|
||||
|
||||
|
||||
struct hist *vcs_git_hist(const char *path)
|
||||
{
|
||||
struct hist *head, *dirty;
|
||||
git_repository *repo;
|
||||
git_oid oid;
|
||||
const git_error *e;
|
||||
|
||||
head = new_commit(0);
|
||||
|
||||
git_init_once();
|
||||
|
||||
if (git_repository_open_ext(&repo, path,
|
||||
GIT_REPOSITORY_OPEN_CROSS_FS, NULL)) {
|
||||
e = giterr_last();
|
||||
fatal("%s: %s\n", path, e->message);
|
||||
}
|
||||
|
||||
if (git_reference_name_to_id(&oid, repo, "HEAD")) {
|
||||
e = giterr_last();
|
||||
fatal("%s: %s\n", git_repository_path(repo), e->message);
|
||||
}
|
||||
|
||||
if (git_commit_lookup(&head->commit, repo, &oid)) {
|
||||
e = giterr_last();
|
||||
fatal("%s: %s\n", git_repository_path(repo), e->message);
|
||||
}
|
||||
|
||||
recurse(head, 1, &head);
|
||||
|
||||
if (!git_repo_is_dirty(repo))
|
||||
return head;
|
||||
|
||||
dirty = new_commit(0);
|
||||
dirty->older = alloc_type(struct hist *);
|
||||
dirty->older[0] = head;
|
||||
dirty->n_older = 1;
|
||||
uplink(head, dirty);
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
|
||||
char *vcs_git_get_rev(struct hist *h)
|
||||
{
|
||||
const git_oid *oid = git_commit_id(h->commit);
|
||||
char *s = alloc_size(GIT_OID_HEXSZ + 1);
|
||||
|
||||
return git_oid_tostr(s, GIT_OID_HEXSZ + 1, oid);
|
||||
}
|
||||
|
||||
|
||||
const char *vcs_git_summary(struct hist *h)
|
||||
{
|
||||
const char *summary;
|
||||
const git_error *e;
|
||||
|
||||
if (!h->commit)
|
||||
return "Uncommitted changes";
|
||||
summary = git_commit_summary(h->commit);
|
||||
if (summary)
|
||||
return summary;
|
||||
|
||||
e = giterr_last();
|
||||
fatal("git_commit_summary: %s\n", e->message);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* @@@ This one is a bit inconvenient. It depends both on the information the
|
||||
* VCS provides, some of which is fairly generic, but some may not be, and
|
||||
* the very specific constraints imposed by the markup format of Pango.
|
||||
*/
|
||||
|
||||
char *vcs_git_long_for_pango(struct hist *h,
|
||||
char *(*formatter)(const char *fmt, ...))
|
||||
{
|
||||
const git_error *e;
|
||||
git_buf buf = { 0 };
|
||||
time_t commit_time;
|
||||
const git_signature *sig;
|
||||
char *s;
|
||||
|
||||
if (!h->commit)
|
||||
return stralloc("Uncommitted changes");
|
||||
if (git_object_short_id(&buf, (git_object *) h->commit))
|
||||
goto fail;
|
||||
commit_time = git_commit_time(h->commit);
|
||||
sig = git_commit_committer(h->commit);
|
||||
s = formatter("<b>%s</b> %s%s <%s><small>\n%s</small>",
|
||||
buf.ptr, ctime(&commit_time), sig->name, sig->email,
|
||||
git_commit_summary(h->commit));
|
||||
git_buf_free(&buf);
|
||||
return s;
|
||||
|
||||
fail:
|
||||
e = giterr_last();
|
||||
fatal("vcs_git_long_for_pango: %s\n", e->message);
|
||||
}
|
||||
|
||||
|
||||
void hist_iterate(struct hist *h,
|
||||
void (*fn)(void *user, struct hist *h), void *user)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
fn(user, h);
|
||||
for (i = 0; i != h->n_older; i++)
|
||||
if (h->older[i]->newer[h->older[i]->n_newer - 1] == h)
|
||||
hist_iterate(h->older[i], fn, user);
|
||||
}
|
||||
|
||||
|
||||
void dump_hist(struct hist *h)
|
||||
{
|
||||
git_buf buf = { 0 };
|
||||
const git_error *e;
|
||||
unsigned i;
|
||||
|
||||
if (h->commit) {
|
||||
if (git_object_short_id(&buf, (git_object *) h->commit)) {
|
||||
e = giterr_last();
|
||||
fatal("git_object_short_id: %s\n", e->message);
|
||||
}
|
||||
printf("%*s%s %s\n",
|
||||
2 * h->branch, "", buf.ptr, vcs_git_summary(h));
|
||||
git_buf_free(&buf);
|
||||
} else {
|
||||
printf("dirty\n");
|
||||
}
|
||||
|
||||
for (i = 0; i != h->n_older; i++)
|
||||
if (h->older[i]->newer[h->older[i]->n_newer - 1] == h)
|
||||
dump_hist(h->older[i]);
|
||||
}
|
||||
44
eeshow/file/git-hist.h
Normal file
44
eeshow/file/git-hist.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* file/git-hist.h - Retrieve revision history from GIT repo
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef FILE_GIT_HIST_H
|
||||
#define FILE_GIT_HIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <git2.h>
|
||||
|
||||
|
||||
struct hist {
|
||||
struct git_commit *commit; /* NULL if uncommitted changes */
|
||||
|
||||
unsigned branch; /* branch index */
|
||||
|
||||
struct hist **newer;
|
||||
unsigned n_newer;
|
||||
|
||||
struct hist **older;
|
||||
unsigned n_older;
|
||||
};
|
||||
|
||||
|
||||
bool vcs_git_try(const char *path);
|
||||
struct hist *vcs_git_hist(const char *path);
|
||||
char *vcs_git_get_rev(struct hist *h);
|
||||
const char *vcs_git_summary(struct hist *hist);
|
||||
char *vcs_git_long_for_pango(struct hist *hist,
|
||||
char *(*formatter)(const char *fmt, ...));
|
||||
void hist_iterate(struct hist *h,
|
||||
void (*fn)(void *user, struct hist *h), void *user);
|
||||
void dump_hist(struct hist *h);
|
||||
|
||||
#endif /* !FILE_GIT_HIST_H */
|
||||
83
eeshow/file/git-util.c
Normal file
83
eeshow/file/git-util.c
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* file/git-util.c - Git utility functions
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <git2.h>
|
||||
|
||||
#include "file/git-util.h"
|
||||
|
||||
|
||||
/*
|
||||
* This seems to be an efficient way for finding out if a repo is dirty.
|
||||
*
|
||||
* http://ben.straub.cc/2013/04/02/libgit2-checkout/
|
||||
*
|
||||
* References:
|
||||
* https://libgit2.github.com/libgit2/#HEAD/group/checkout/git_checkout_index
|
||||
* https://libgit2.github.com/libgit2/#HEAD/type/git_checkout_options
|
||||
* https://github.com/libgit2/libgit2/blob/HEAD/include/git2/checkout.h#L251-295
|
||||
*/
|
||||
|
||||
|
||||
static int checkout_notify_cb(git_checkout_notify_t why,
|
||||
const char *path, const git_diff_file *baseline,
|
||||
const git_diff_file *target, const git_diff_file *workdir,
|
||||
void *payload)
|
||||
{
|
||||
bool *res = payload;
|
||||
|
||||
assert(why == GIT_CHECKOUT_NOTIFY_DIRTY);
|
||||
|
||||
*res = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool git_repo_is_dirty(git_repository *repo)
|
||||
{
|
||||
git_checkout_options opts;
|
||||
bool res = 0;
|
||||
|
||||
/*
|
||||
* Initialization with GIT_CHECKOUT_OPTIONS_INIT complains about not
|
||||
* setting checkout_strategy. git_checkout_init_options is fine.
|
||||
*/
|
||||
git_checkout_init_options(&opts, GIT_CHECKOUT_OPTIONS_VERSION);
|
||||
opts.checkout_strategy = GIT_CHECKOUT_NONE;
|
||||
/* let's be explicit about this */
|
||||
opts.notify_flags = GIT_CHECKOUT_NOTIFY_DIRTY;
|
||||
opts.notify_cb = checkout_notify_cb;
|
||||
opts.notify_payload = &res;
|
||||
git_checkout_index(repo, NULL, &opts);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Git documentation says that git_libgit2_init can be called more then once
|
||||
* but doesn't quite what happens then, e.g., whether references obtained
|
||||
* before an init (except for the first, of course) can still be used after
|
||||
* it. So we play it safe and initialize only once.
|
||||
*/
|
||||
|
||||
void git_init_once(void)
|
||||
{
|
||||
static bool initialized = 0;
|
||||
|
||||
if (!initialized) {
|
||||
git_libgit2_init();
|
||||
initialized = 1;
|
||||
}
|
||||
}
|
||||
24
eeshow/file/git-util.h
Normal file
24
eeshow/file/git-util.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* file/git-util.h - Git utility functions
|
||||
*
|
||||
* Written 2016 by Werner Almesberger
|
||||
* Copyright 2016 by Werner Almesberger
|
||||
*
|
||||
* 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 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef FILE_GIT_UTIL_H
|
||||
#define FILE_GIT_UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <git2.h>
|
||||
|
||||
|
||||
bool git_repo_is_dirty(git_repository *repo);
|
||||
void git_init_once(void);
|
||||
|
||||
#endif /* !FILE_GIT_UTIL_H */
|
||||
Reference in New Issue
Block a user