30#pragma GCC diagnostic push
31#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
58 const git_error* err = git_error_last();
101static int _n_git_diff_print_cb(
const git_diff_delta* delta,
const git_diff_hunk* hunk,
const git_diff_line* line,
void* payload) {
107 if (line->content && line->content_len > 0) {
109 Malloc(tmp,
char, line->content_len + 2);
111 memcpy(tmp, line->content, line->content_len);
112 tmp[line->content_len] =
'\0';
132 ngit->
path = strdup(path);
139 int err = git_repository_open(&ngit->
repo, path);
164 ngit->
path = strdup(path);
171 int err = git_repository_init(&ngit->
repo, path, 0);
190 git_repository_free((*repo)->repo);
191 (*repo)->repo = NULL;
194 if ((*repo)->remote_auth) {
195 FreeNoLog((*repo)->remote_auth->username);
196 FreeNoLog((*repo)->remote_auth->password);
197 FreeNoLog((*repo)->remote_auth->ssh_pubkey_path);
198 FreeNoLog((*repo)->remote_auth->ssh_privkey_path);
199 FreeNoLog((*repo)->remote_auth->ssh_passphrase);
214 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
215 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
216 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
218 git_status_list* status_list = NULL;
219 int err = git_status_list_new(&status_list, repo->
repo, &opts);
227 git_status_list_free(status_list);
231 size_t count = git_status_list_entrycount(status_list);
232 for (
size_t i = 0; i < count; i++) {
233 const git_status_entry* entry = git_status_byindex(status_list, i);
234 if (!entry)
continue;
240 const char* fpath = NULL;
241 if (entry->head_to_index && entry->head_to_index->new_file.path) {
242 fpath = entry->head_to_index->new_file.path;
243 }
else if (entry->index_to_workdir && entry->index_to_workdir->new_file.path) {
244 fpath = entry->index_to_workdir->new_file.path;
245 }
else if (entry->index_to_workdir && entry->index_to_workdir->old_file.path) {
246 fpath = entry->index_to_workdir->old_file.path;
250 snprintf(se->
path,
sizeof(se->
path),
"%s", fpath);
256 if (entry->status & GIT_STATUS_INDEX_NEW) {
259 if (entry->status & GIT_STATUS_INDEX_MODIFIED) {
262 if (entry->status & GIT_STATUS_INDEX_DELETED) {
267 if (entry->status & GIT_STATUS_WT_NEW) {
270 if (entry->status & GIT_STATUS_WT_MODIFIED) {
273 if (entry->status & GIT_STATUS_WT_DELETED) {
280 git_status_list_free(status_list);
295 git_index* index = NULL;
298 int err = git_repository_index(&index, repo->
repo);
304 err = git_index_add_bypath(index, filepath);
310 err = git_index_write(index);
319 git_index_free(index);
332 git_index* index = NULL;
335 int err = git_repository_index(&index, repo->
repo);
341 err = git_index_add_all(index, NULL, GIT_INDEX_ADD_DEFAULT, NULL, NULL);
347 err = git_index_write(index);
356 git_index_free(index);
371 git_reference* head_ref = NULL;
372 git_object* head_obj = NULL;
376 int err = git_repository_head(&head_ref, repo->
repo);
378 err = git_reference_peel(&head_obj, head_ref, GIT_OBJECT_COMMIT);
386 char* path_str = (
char*)filepath;
387 paths.strings = &path_str;
390 err = git_reset_default(repo->
repo, head_obj, &paths);
399 if (head_obj) git_object_free(head_obj);
400 if (head_ref) git_reference_free(head_ref);
419 git_index* index = NULL;
422 git_tree* tree = NULL;
423 git_signature* sig = NULL;
424 git_commit* parent_commit = NULL;
425 git_reference* head_ref = NULL;
428 int err = git_repository_index(&index, repo->
repo);
434 err = git_index_write_tree(&tree_oid, index);
440 err = git_tree_lookup(&tree, repo->
repo, &tree_oid);
446 err = git_signature_now(&sig, author_name, author_email);
454 err = git_repository_head(&head_ref, repo->
repo);
456 err = git_reference_peel((git_object**)&parent_commit, head_ref, GIT_OBJECT_COMMIT);
463 const git_commit* parents[] = {parent_commit};
464 err = git_commit_create(&commit_oid, repo->
repo,
"HEAD", sig, sig,
"UTF-8", message, tree, 1, parents);
466 err = git_commit_create(&commit_oid, repo->
repo,
"HEAD", sig, sig,
"UTF-8", message, tree, 0, NULL);
477 if (parent_commit) git_commit_free(parent_commit);
478 if (head_ref) git_reference_free(head_ref);
479 if (sig) git_signature_free(sig);
480 if (tree) git_tree_free(tree);
481 git_index_free(index);
495 git_revwalk* walker = NULL;
496 int err = git_revwalk_new(&walker, repo->
repo);
502 git_revwalk_sorting(walker, GIT_SORT_TIME);
504 err = git_revwalk_push_head(walker);
507 git_revwalk_free(walker);
513 git_revwalk_free(walker);
519 while (count < max_entries && git_revwalk_next(&oid, walker) == 0) {
520 git_commit* commit = NULL;
521 err = git_commit_lookup(&commit, repo->
repo, &oid);
530 git_commit_free(commit);
535 char hex[GIT_OID_SHA1_HEXSIZE + 1];
536 git_oid_tostr(hex,
sizeof(hex), &oid);
537 snprintf(info->
hash,
sizeof(info->
hash),
"%s", hex);
541 const char* msg = git_commit_message(commit);
547 const git_signature* author = git_commit_author(commit);
549 snprintf(info->
author,
sizeof(info->
author),
"%s <%s>", author->name ? author->name :
"", author->email ? author->email :
"");
550 info->
timestamp = (int64_t)author->when.time;
554 git_commit_free(commit);
558 git_revwalk_free(walker);
571 git_diff* diff = NULL;
572 int err = git_diff_index_to_workdir(&diff, repo->
repo, NULL, NULL);
603 git_object* obj_a = NULL;
604 git_object* obj_b = NULL;
605 git_commit* commit_a = NULL;
606 git_commit* commit_b = NULL;
607 git_tree* tree_a = NULL;
608 git_tree* tree_b = NULL;
609 git_diff* diff = NULL;
610 N_STR* result = NULL;
613 err = git_revparse_single(&obj_a, repo->
repo, hash_a);
619 err = git_revparse_single(&obj_b, repo->
repo, hash_b);
625 err = git_commit_lookup(&commit_a, repo->
repo, git_object_id(obj_a));
631 err = git_commit_lookup(&commit_b, repo->
repo, git_object_id(obj_b));
637 err = git_commit_tree(&tree_a, commit_a);
643 err = git_commit_tree(&tree_b, commit_b);
649 err = git_diff_tree_to_tree(&diff, repo->
repo, tree_a, tree_b, NULL);
663 if (diff) git_diff_free(diff);
664 if (tree_b) git_tree_free(tree_b);
665 if (tree_a) git_tree_free(tree_a);
666 if (commit_b) git_commit_free(commit_b);
667 if (commit_a) git_commit_free(commit_a);
668 if (obj_b) git_object_free(obj_b);
669 if (obj_a) git_object_free(obj_a);
684 char* path_str = (
char*)filepath;
686 paths.strings = &path_str;
689 git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
690 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
693 int err = git_checkout_head(repo->
repo, &opts);
713 git_object* obj = NULL;
716 int err = git_revparse_single(&obj, repo->
repo, hash);
722 git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
723 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
725 err = git_checkout_tree(repo->
repo, obj, &opts);
731 err = git_repository_set_head_detached(repo->
repo, git_object_id(obj));
740 git_object_free(obj);
753 git_branch_iterator* iter = NULL;
754 int err = git_branch_iterator_new(&iter, repo->
repo, GIT_BRANCH_LOCAL);
762 git_branch_iterator_free(iter);
766 git_reference* ref = NULL;
768 while (git_branch_next(&ref, &type, iter) == 0) {
769 const char* name = NULL;
770 err = git_branch_name(&name, ref);
771 if (err == 0 && name) {
772 char* dup = strdup(name);
777 git_reference_free(ref);
780 git_branch_iterator_free(iter);
795 git_reference* head_ref = NULL;
796 git_commit* head_commit = NULL;
797 git_reference* new_branch = NULL;
800 int err = git_repository_head(&head_ref, repo->
repo);
806 err = git_reference_peel((git_object**)&head_commit, head_ref, GIT_OBJECT_COMMIT);
812 err = git_branch_create(&new_branch, repo->
repo, branch_name, head_commit, 0);
821 if (new_branch) git_reference_free(new_branch);
822 if (head_commit) git_commit_free(head_commit);
823 git_reference_free(head_ref);
838 git_reference* branch_ref = NULL;
841 int err = git_branch_lookup(&branch_ref, repo->
repo, branch_name, GIT_BRANCH_LOCAL);
848 snprintf(refname,
sizeof(refname),
"refs/heads/%s", branch_name);
850 err = git_repository_set_head(repo->
repo, refname);
856 git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
857 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
859 err = git_checkout_head(repo->
repo, &opts);
868 git_reference_free(branch_ref);
884 git_reference* head_ref = NULL;
886 int err = git_repository_head(&head_ref, repo->
repo);
892 const char* shorthand = git_reference_shorthand(head_ref);
894 snprintf(out, out_size,
"%s", shorthand);
899 git_reference_free(head_ref);
914 git_reference* branch_ref = NULL;
916 int err = git_branch_lookup(&branch_ref, repo->
repo, branch_name, GIT_BRANCH_LOCAL);
922 err = git_branch_delete(branch_ref);
923 git_reference_free(branch_ref);
940static int _n_git_cred_cb(git_credential** out,
const char* url,
const char* username_from_url,
unsigned int allowed_types,
void* payload) {
943 if (!auth)
return GIT_PASSTHROUGH;
945 switch (auth->
type) {
947 if ((allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) &&
950 return git_credential_userpass_plaintext_new(out, user, auth->
password);
955 if ((allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT) &&
957 return git_credential_userpass_plaintext_new(out, auth->
username, auth->
password);
962 if ((allowed_types & GIT_CREDENTIAL_SSH_KEY) &&
964 const char* user = auth->
username ? auth->
username : (username_from_url ? username_from_url :
"git");
965 return git_credential_ssh_key_new(out, user,
970 if (allowed_types & GIT_CREDENTIAL_SSH_KEY) {
971 const char* user = auth->
username ? auth->
username : (username_from_url ? username_from_url :
"git");
972 return git_credential_ssh_key_from_agent(out, user);
980 return GIT_PASSTHROUGH;
1003 memset(a, 0,
sizeof(*a));
1026 git_remote* remote = NULL;
1027 int err = git_remote_lookup(&remote, repo->
repo,
"origin");
1030 const char* current_url = git_remote_url(remote);
1031 if (!current_url || strcmp(current_url, url) != 0) {
1032 git_remote_free(remote);
1033 err = git_remote_set_url(repo->
repo,
"origin", url);
1039 git_remote_free(remote);
1043 err = git_remote_create(&remote, repo->
repo,
"origin", url);
1048 git_remote_free(remote);
1066 n_log(
LOG_ERR,
"n_git_push: cannot determine current branch");
1072 snprintf(refspec,
sizeof(refspec),
"refs/heads/%s:refs/heads/%s",
1075 git_remote* remote = NULL;
1076 int err = git_remote_lookup(&remote, repo->
repo,
"origin");
1083 git_push_options opts = GIT_PUSH_OPTIONS_INIT;
1089 const char* refspecs[] = {refspec};
1090 git_strarray refspec_arr = {(
char**)refspecs, 1};
1092 err = git_remote_push(remote, &refspec_arr, &opts);
1093 git_remote_free(remote);
1114 git_remote* remote = NULL;
1115 int err = git_remote_lookup(&remote, repo->
repo,
"origin");
1121 git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
1127 err = git_remote_fetch(remote, NULL, &fetch_opts,
"pull");
1128 git_remote_free(remote);
1138 n_log(
LOG_ERR,
"n_git_pull: cannot determine current branch");
1143 char remote_ref[300];
1144 snprintf(remote_ref,
sizeof(remote_ref),
"refs/remotes/origin/%s", branch);
1147 err = git_reference_name_to_id(&remote_oid, repo->
repo, remote_ref);
1155 err = git_reference_name_to_id(&local_oid, repo->
repo,
"HEAD");
1162 if (git_oid_equal(&local_oid, &remote_oid)) {
1168 git_annotated_commit* remote_commit = NULL;
1169 err = git_annotated_commit_lookup(&remote_commit, repo->
repo, &remote_oid);
1175 git_merge_analysis_t analysis;
1176 git_merge_preference_t preference;
1177 const git_annotated_commit* their_heads[] = {remote_commit};
1178 err = git_merge_analysis(&analysis, &preference, repo->
repo, their_heads, 1);
1180 git_annotated_commit_free(remote_commit);
1185 if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) {
1186 git_annotated_commit_free(remote_commit);
1191 if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) {
1193 git_reference* head_ref = NULL;
1194 err = git_repository_head(&head_ref, repo->
repo);
1196 git_annotated_commit_free(remote_commit);
1201 git_reference* new_ref = NULL;
1202 err = git_reference_set_target(&new_ref, head_ref, &remote_oid,
"pull: fast-forward");
1203 git_reference_free(head_ref);
1205 git_annotated_commit_free(remote_commit);
1209 git_reference_free(new_ref);
1212 git_object* target = NULL;
1213 git_object_lookup(&target, repo->
repo, &remote_oid, GIT_OBJECT_COMMIT);
1215 git_checkout_options co_opts = GIT_CHECKOUT_OPTIONS_INIT;
1216 co_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
1217 git_checkout_tree(repo->
repo, target, &co_opts);
1218 git_object_free(target);
1221 git_annotated_commit_free(remote_commit);
1227 git_annotated_commit_free(remote_commit);
1228 n_log(
LOG_ERR,
"n_git_pull: cannot fast-forward, manual merge required");
1232#pragma GCC diagnostic pop
#define FreeNoLog(__ptr)
Free Handler without log.
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
#define __n_assert(__ptr, __ret)
macro to assert things
char short_hash[10]
short (abbreviated) hex hash
char author[256]
author name and email
int64_t timestamp
unix timestamp of the commit
char message[512]
first line of commit message
char * password
password or personal access token, or NULL
char * ssh_privkey_path
path to SSH private key file, or NULL
char hash[42]
full hex hash
char * ssh_pubkey_path
path to SSH public key file, or NULL (derived from private key + ".pub")
int flags
combination of N_GIT_STATUS_* flags
char * path
filesystem path to the repository
git_repository * repo
libgit2 repository handle
char * ssh_passphrase
passphrase for SSH key, or NULL
int type
auth type: N_GIT_AUTH_NONE/TOKEN/BASIC/SSH
char * username
username for basic auth or SSH, or NULL
char path[512]
relative path of the file
N_GIT_REMOTE_AUTH * remote_auth
credentials for remote operations, or NULL
void n_git_close(N_GIT_REPO **repo)
Close a Git repository and free resources.
#define N_GIT_AUTH_TOKEN
Remote auth: personal access token (used as password with empty or "x-access-token" user)
N_STR * n_git_diff_commits(N_GIT_REPO *repo, const char *hash_a, const char *hash_b)
Get a diff between two commits identified by hash.
#define N_GIT_STATUS_NEW
File is new / untracked.
int n_git_push(N_GIT_REPO *repo)
Push the current branch to the "origin" remote.
int n_git_remote_set_url(N_GIT_REPO *repo, const char *url)
Ensure a remote named "origin" exists with the given URL.
int n_git_switch_branch(N_GIT_REPO *repo, const char *branch_name)
Switch to an existing local branch.
int n_git_pull(N_GIT_REPO *repo)
Fetch from "origin" and fast-forward merge the current branch.
int n_git_checkout_path(N_GIT_REPO *repo, const char *filepath)
Restore a single file from HEAD (discard working directory changes).
#define N_GIT_STATUS_MODIFIED
File has been modified.
LIST * n_git_list_branches(N_GIT_REPO *repo)
List all local branch names.
LIST * n_git_status(N_GIT_REPO *repo)
Get the status of all files in the working directory.
int n_git_unstage(N_GIT_REPO *repo, const char *filepath)
Unstage a single file (reset from HEAD).
int n_git_set_remote_auth(N_GIT_REPO *repo, const N_GIT_REMOTE_AUTH *auth)
Set remote authentication credentials on a repo handle.
LIST * n_git_log(N_GIT_REPO *repo, size_t max_entries)
Retrieve commit log entries starting from HEAD.
#define N_GIT_STATUS_STAGED
File is staged in the index.
#define N_GIT_AUTH_SSH
Remote auth: SSH key file.
#define N_GIT_AUTH_BASIC
Remote auth: username + password.
int n_git_stage_all(N_GIT_REPO *repo)
Stage all modified and untracked files.
int n_git_stage(N_GIT_REPO *repo, const char *filepath)
Stage a single file by path.
int n_git_create_branch(N_GIT_REPO *repo, const char *branch_name)
Create a new branch from HEAD.
int n_git_current_branch(N_GIT_REPO *repo, char *out, size_t out_size)
Get the name of the current branch.
int n_git_checkout_commit(N_GIT_REPO *repo, const char *hash)
Checkout a specific commit (detached HEAD).
#define N_GIT_STATUS_DELETED
File has been deleted.
int n_git_commit(N_GIT_REPO *repo, const char *message, const char *author_name, const char *author_email)
Create a commit from the current index.
N_GIT_REPO * n_git_open(const char *path)
Open an existing Git repository.
N_STR * n_git_diff_workdir(N_GIT_REPO *repo)
Get a diff of the working directory against the index.
N_GIT_REPO * n_git_init(const char *path)
Initialize a new Git repository.
int n_git_delete_branch(N_GIT_REPO *repo, const char *branch_name)
Delete a local branch.
Credentials for remote operations.
Wrapper around a git_repository handle.
Single file status entry.
#define UNLIMITED_LIST_ITEMS
flag to pass to new_generic_list for an unlimited number of item in the list.
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
Structure of a generic LIST container.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
#define LOG_INFO
informational
#define nstrprintf_cat(__nstr_var, __format,...)
Macro to quickly allocate and sprintf and cat to a N_STR.
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
A box including a string and his lenght.
Common headers and low-level functions & define.
static int _n_git_cred_cb(git_credential **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload)
libgit2 credential callback for remote operations.
static void _n_git_status_entry_destroy(void *ptr)
Destructor for N_GIT_STATUS_ENTRY used by LIST.
static void _n_git_string_destroy(void *ptr)
Destructor for char* branch name strings used by LIST.
static void _n_git_log_error(const char *func_name)
Log the last libgit2 error.
static void _n_git_ensure_init(void)
Ensure libgit2 is initialized (idempotent).
static void _n_git_commit_info_destroy(void *ptr)
Destructor for N_GIT_COMMIT_INFO used by LIST.
static int _n_git_diff_print_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload)
Callback for git_diff_print, appends each line to an N_STR.
static int _n_git_initialized
static flag ensuring git_libgit2_init is called only once
libgit2 wrapper for Git repository operations