diff --git a/examples/network/Makefile b/examples/network/Makefile new file mode 100644 index 000000000..59a607632 --- /dev/null +++ b/examples/network/Makefile @@ -0,0 +1,23 @@ +default: all + +# If you've installed libgit2 to a non-standard location, you can use +# these lines to make pkg-config find it. + +#LIBGIT2_PATH ?= $(HOME)/staging/libgit2/lib DEPS = +#$(shell PKG_CONFIG_PATH=$(LIBGIT2_PATH)/pkgconfig pkg-config --cflags +#--libs libgit2) + +DEPS = $(shell pkg-config --cflags --libs libgit2) + +CC = gcc +CFLAGS += -g +CFLAGS += $(DEPS) + +OBJECTS = \ + git2.o \ + ls-remote.o \ + fetch.o \ + index-pack.o + +all: $(OBJECTS) + $(CC) $(CFLAGS) -o git2 $(OBJECTS) diff --git a/examples/network/common.h b/examples/network/common.h new file mode 100644 index 000000000..29460bb36 --- /dev/null +++ b/examples/network/common.h @@ -0,0 +1,14 @@ +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#include + +typedef int (*git_cb)(git_repository *, int , char **); + +int ls_remote(git_repository *repo, int argc, char **argv); +int parse_pkt_line(git_repository *repo, int argc, char **argv); +int show_remote(git_repository *repo, int argc, char **argv); +int fetch(git_repository *repo, int argc, char **argv); +int index_pack(git_repository *repo, int argc, char **argv); + +#endif /* __COMMON_H__ */ diff --git a/examples/network/fetch.c b/examples/network/fetch.c new file mode 100644 index 000000000..dd732f22e --- /dev/null +++ b/examples/network/fetch.c @@ -0,0 +1,127 @@ +#include "common.h" +#include +#include +#include +#include + +static void show_refs(git_headarray *refs) +{ + int i; + git_remote_head *head; + + if(refs->len == 0) + puts("Everything up-to-date"); + + for(i = 0; i < refs->len; ++i){ + char oid[GIT_OID_HEXSZ + 1] = {0}; + char *havewant; + head = refs->heads[i]; + git_oid_fmt(oid, &head->oid); + printf("%s\t%s\n", oid, head->name); + } +} + +static int rename_packfile(char *packname, git_indexer *idx) +{ + char path[GIT_PATH_MAX], oid[GIT_OID_HEXSZ + 1], *slash; + int ret; + + strcpy(path, packname); + slash = strrchr(path, '/'); + + if (!slash) + return GIT_EINVALIDARGS; + + memset(oid, 0x0, sizeof(oid)); + // The name of the packfile is given by it's hash which you can get + // with git_indexer_hash after the index has been written out to + // disk. Rename the packfile to its "real" name in the same + // directory as it was originally (libgit2 stores it in the folder + // where the packs go, so a rename in place is the right thing to do here + git_oid_fmt(oid, git_indexer_hash(idx)); + ret = sprintf(slash + 1, "pack-%s.pack", oid); + if(ret < 0) + return GIT_EOSERR; + + printf("Renaming pack to %s\n", path); + return rename(packname, path); +} + +int fetch(git_repository *repo, int argc, char **argv) +{ + git_remote *remote = NULL; + git_config *cfg = NULL; + git_indexer *idx = NULL; + git_indexer_stats stats; + int error; + char *packname = NULL; + + // Load the repository's configuration + error = git_repository_config(&cfg, repo, NULL, NULL); + if (error < GIT_SUCCESS) + return error; + + // Get the remote and connect to it + printf("Fetching %s\n", argv[1]); + error = git_remote_get(&remote, cfg, argv[1]); + if (error < GIT_SUCCESS) + return error; + + error = git_remote_connect(remote, GIT_DIR_FETCH); + if (error < GIT_SUCCESS) + return error; + + // Perform the packfile negotiation. This is where the two ends + // figure out the minimal amount of data that should be transmitted + // to bring the repository up-to-date + error = git_remote_negotiate(remote); + if (error < GIT_SUCCESS) + return error; + + // Download the packfile from the server. As we don't know its hash + // yet, it will get a temporary filename + error = git_remote_download(&packname, remote); + if (error < GIT_SUCCESS) + return error; + + // No error and a NULL packname means no packfile was needed + if (packname != NULL) { + printf("The packname is %s\n", packname); + + // Create a new instance indexer + error = git_indexer_new(&idx, packname); + if (error < GIT_SUCCESS) + return error; + + // This should be run in paralel, but it'd be too complicated for the example + error = git_indexer_run(idx, &stats); + if (error < GIT_SUCCESS) + return error; + + printf("Received %d objects\n", stats.total); + + // Write the index file. The index will be stored with the + // correct filename + error = git_indexer_write(idx); + if (error < GIT_SUCCESS) + return error; + + error = rename_packfile(packname, idx); + if (error < GIT_SUCCESS) + return error; + } + + // Update the references in the remote's namespace to point to the + // right commits. This may be needed even if there was no packfile + // to download, which can happen e.g. when the branches have been + // changed but all the neede objects are available locally. + error = git_remote_update_tips(remote); + if (error < GIT_SUCCESS) + return error; + + free(packname); + git_indexer_free(idx); + git_remote_free(remote); + + return GIT_SUCCESS; +} diff --git a/examples/network/git2.c b/examples/network/git2.c new file mode 100644 index 000000000..0468c8ace --- /dev/null +++ b/examples/network/git2.c @@ -0,0 +1,57 @@ +#include +#include + +#include "common.h" + +// This part is not strictly libgit2-dependent, but you can use this +// as a starting point for a git-like tool + +struct { + char *name; + git_cb fn; +} commands[] = { + {"ls-remote", ls_remote}, + {"fetch", fetch}, + {"index-pack", index_pack}, + { NULL, NULL} +}; + +int run_command(git_cb fn, int argc, char **argv) +{ + int error; + git_repository *repo; + +// Before running the actual command, create an instance of the local +// repository and pass it to the function. + + error = git_repository_open(&repo, ".git"); + if (error < GIT_SUCCESS) + repo = NULL; + + // Run the command. If something goes wrong, print the error message to stderr + error = fn(repo, argc, argv); + if (error < GIT_SUCCESS) + fprintf(stderr, "Bad news:\n %s\n", git_lasterror()); + + if(repo) + git_repository_free(repo); + + return !!error; +} + +int main(int argc, char **argv) +{ + int i, error; + + if (argc < 2) { + fprintf(stderr, "usage: %s [repo]", argv[0]); + } + + for (i = 0; commands[i].name != NULL; ++i) { + if (!strcmp(argv[1], commands[i].name)) + return run_command(commands[i].fn, --argc, ++argv); + } + + fprintf(stderr, "Command not found: %s\n", argv[1]); + +} diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c new file mode 100644 index 000000000..671035fb7 --- /dev/null +++ b/examples/network/index-pack.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include "common.h" + +// This could be run in the main loop whilst the application waits for +// the indexing to finish in a worker thread +int index_cb(const git_indexer_stats *stats, void *data) +{ + printf("\rProcessing %d of %d", stats->processed, stats->total); +} + +int index_pack(git_repository *repo, int argc, char **argv) +{ + git_indexer *indexer; + git_indexer_stats stats; + int error; + char hash[GIT_OID_HEXSZ + 1] = {0}; + + if (argc < 2) { + fprintf(stderr, "I need a packfile\n"); + return EXIT_FAILURE; + } + + // Create a new indexer + error = git_indexer_new(&indexer, argv[1]); + if (error < GIT_SUCCESS) + return error; + + // Index the packfile. This function can take a very long time and + // should be run in a worker thread. + error = git_indexer_run(indexer, &stats); + if (error < GIT_SUCCESS) + return error; + + // Write the information out to an index file + error = git_indexer_write(indexer); + + // Get the packfile's hash (which should become it's filename) + git_oid_fmt(hash, git_indexer_hash(indexer)); + puts(hash); + + git_indexer_free(indexer); + + return GIT_SUCCESS; +} diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c new file mode 100644 index 000000000..77a9f215d --- /dev/null +++ b/examples/network/ls-remote.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include "common.h" + +static void show_refs(git_headarray *refs) +{ + int i; + git_remote_head *head; + +// Take each head that the remote has advertised, store the string +// representation of the OID in a buffer and print it + + for(i = 0; i < refs->len; ++i){ + char oid[GIT_OID_HEXSZ + 1] = {0}; + head = refs->heads[i]; + git_oid_fmt(oid, &head->oid); + printf("%s\t%s\n", oid, head->name); + } +} + +int use_unnamed(git_repository *repo, const char *url) +{ + git_remote *remote = NULL; + git_headarray refs; + int error; + + // Create an instance of a remote from the URL. The transport to use + // is detected from the URL + error = git_remote_new(&remote, repo, url); + if (error < GIT_SUCCESS) + goto cleanup; + + // When connecting, the underlying code needs to know wether we + // want to push or fetch + error = git_remote_connect(remote, GIT_DIR_FETCH); + if (error < GIT_SUCCESS) + goto cleanup; + + // With git_remote_ls we can retrieve the advertised heads + error = git_remote_ls(remote, &refs); + if (error < GIT_SUCCESS) + goto cleanup; + + show_refs(&refs); + +cleanup: + git_remote_free(remote); + + return error; +} + +int use_remote(git_repository *repo, char *name) +{ + git_remote *remote = NULL; + git_config *cfg = NULL; + git_headarray refs; + int error; + + // Load the local configuration for the repository + error = git_repository_config(&cfg, repo, NULL, NULL); + if (error < GIT_SUCCESS) + return error; + + // Find the remote by name + error = git_remote_get(&remote, cfg, name); + if (error < GIT_SUCCESS) + goto cleanup; + + error = git_remote_connect(remote, GIT_DIR_FETCH); + if (error < GIT_SUCCESS) + goto cleanup; + + error = git_remote_ls(remote, &refs); + if (error < GIT_SUCCESS) + goto cleanup; + + show_refs(&refs); + +cleanup: + git_remote_free(remote); + + return error; +} + +// This gets called to do the work. The remote can be given either as +// the name of a configured remote or an URL. + +int ls_remote(git_repository *repo, int argc, char **argv) +{ + git_headarray heads; + git_remote_head *head; + int error, i; + + /* If there's a ':' in the name, assume it's an URL */ + if (strchr(argv[1], ':') != NULL) { + error = use_unnamed(repo, argv[1]); + } else { + error = use_remote(repo, argv[1]); + } + + return error; +}