MODULE = Git::Raw			PACKAGE = Git::Raw::Repository

SV *
init(class, path, is_bare)
	SV *class
	SV *path
	unsigned is_bare

	PREINIT:
		int rc;
		Repository repo;

	CODE:
		rc = git_repository_init(
			&repo, SvPVbyte_nolen(path), is_bare
		);
		git_check_error(rc);

		RETVAL = sv_setref_pv(newSV(0), SvPVbyte_nolen(class), repo);

	OUTPUT: RETVAL

SV *
clone(class, url, path, opts)
	SV *class
	SV *url
	SV *path
	HV *opts

	PREINIT:
		int rc;

		SV **opt;
		Repository repo;

		git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;

	CODE:
		if ((opt = hv_fetchs(opts, "bare", 0)) && SvIV(*opt) != 0)
			clone_opts.bare = 1;

		if ((opt = hv_fetchs(opts, "ignore_cert_errors", 0)) && SvIV(*opt) != 0)
			clone_opts.ignore_cert_errors = 1;

		if ((opt = hv_fetchs(opts, "remote_name", 0)))
			clone_opts.remote_name = SvPVbyte_nolen(*opt);

		if ((opt = hv_fetchs(opts, "checkout_branch", 0)))
			clone_opts.checkout_branch = SvPVbyte_nolen(*opt);

		if ((opt = hv_fetchs(opts, "disable_checkout", 0)) && SvIV(*opt) != 0)
			clone_opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;

		xs_git_remote_callbacks cbs;
		clone_opts.remote_callbacks.payload = &cbs;

		/* Callbacks */
		if ((opt = hv_fetchs(opts, "callbacks", 0))) {
			SV **cb;
			HV *callbacks;

			if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVHV)
				Perl_croak(aTHX_ "Invalid type");

			callbacks = (HV *) SvRV(*opt);

			if ((cb = hv_fetchs(opts, "credentials", 0))) {
				if (SvTYPE(SvRV(*cb)) != SVt_PVCV)
					Perl_croak(aTHX_ "Expected a subroutine for credential acquisition callback'");

				cbs.credentials = *cb;
				clone_opts.remote_callbacks.credentials =
					git_credentials_cbb;
			}

			if ((cb = hv_fetchs(callbacks, "progress", 0))) {
				if (SvTYPE(SvRV(*cb)) != SVt_PVCV)
					Perl_croak(aTHX_ "Expected a subroutine for progress callback");

				cbs.progress = *cb;
				clone_opts.remote_callbacks.progress = 
					git_progress_cbb;
			}

			if ((cb = hv_fetchs(callbacks, "completion", 0))) {
				if (SvTYPE(SvRV(*cb)) != SVt_PVCV)
					Perl_croak(aTHX_ "Expected a subroutine for completion callback");

				cbs.completion = *cb;
				clone_opts.remote_callbacks.completion =
					git_completion_cbb;
			}

			if ((cb = hv_fetchs(callbacks, "transfer_progress", 0))) {
				if (SvTYPE(SvRV(*cb)) != SVt_PVCV)
					Perl_croak(aTHX_ "Expected a subroutine for transfer progress callback");

				cbs.transfer_progress = *cb;
				clone_opts.remote_callbacks.transfer_progress =
					git_transfer_progress_cbb;
			}

			if ((cb = hv_fetchs(callbacks, "update_tips", 0))) {
				if (SvTYPE(SvRV(*cb)) != SVt_PVCV)
					Perl_croak(aTHX_ "Expected a subroutine for update tips callback");

				cbs.update_tips = *cb;
				clone_opts.remote_callbacks.update_tips =
					git_update_tips_cbb;
			}
		}

		rc = git_clone(
			&repo, SvPVbyte_nolen(url), SvPVbyte_nolen(path),
			&clone_opts
		);

		git_check_error(rc);

		RETVAL = sv_setref_pv(newSV(0), SvPVbyte_nolen(class), repo);

	OUTPUT: RETVAL

SV *
open(class, path)
	SV *class
	SV *path

	PREINIT:
		int rc;
		Repository repo;

	CODE:
		rc = git_repository_open(&repo, SvPVbyte_nolen(path));
		git_check_error(rc);

		RETVAL = sv_setref_pv(newSV(0), SvPVbyte_nolen(class), repo);

	OUTPUT: RETVAL

SV *
discover(class, path)
	SV *class
	SV *path

	PREINIT:
		int rc;

		Repository repo;
		char found[GIT_PATH_MAX];

	CODE:
		rc = git_repository_discover(
			found, GIT_PATH_MAX, SvPVbyte_nolen(path), 1, NULL
		);
		git_check_error(rc);

		rc = git_repository_open(&repo, found);
		git_check_error(rc);

		RETVAL = sv_setref_pv(newSV(0), SvPVbyte_nolen(class), repo);

	OUTPUT: RETVAL

SV *
new(class)
	SV *class

	PREINIT:
		int rc;
		Repository repo;

	CODE:
		rc = git_repository_new(&repo);
		git_check_error(rc);

		RETVAL = sv_setref_pv(newSV(0), SvPVbyte_nolen(class), repo);

	OUTPUT: RETVAL

Config
config(self)
	Repository self

	PREINIT:
		int rc;
		Config cfg;

	CODE:
		rc = git_repository_config(&cfg, self);
		git_check_error(rc);

		RETVAL = cfg;

	OUTPUT: RETVAL

SV *
index(self)
	SV *self

	PREINIT:
		int rc;
		Index index;

	CODE:
		rc = git_repository_index(&index, GIT_SV_TO_PTR(Repository, self));
		git_check_error(rc);

		GIT_NEW_OBJ(RETVAL, "Git::Raw::Index", index, SvRV(self));

	OUTPUT: RETVAL

SV *
head(self, ...)
	SV *self

	PROTOTYPE: $;$

	PREINIT:
		int rc;

		Reference head;
		Repository repo;

	CODE:
		repo = GIT_SV_TO_PTR(Repository, self);

		if (items == 2) {
			Reference new_head = GIT_SV_TO_PTR(Reference, ST(1));

			rc = git_repository_set_head(
				repo, git_reference_name(new_head)
			);
			git_check_error(rc);
		}

		rc = git_repository_head(&head, repo);
		git_check_error(rc);

		GIT_NEW_OBJ(RETVAL, "Git::Raw::Reference", head, SvRV(self));

	OUTPUT: RETVAL

SV *
lookup(self, id)
	SV *self
	SV *id

	PREINIT:
		int rc;

		git_oid oid;
		git_object *obj;

		STRLEN len;
		const char *id_str;

	CODE:
		id_str = SvPVbyte(id, len);

		rc = git_oid_fromstrn(&oid, id_str, len);
		git_check_error(rc);

		rc = git_object_lookup_prefix(
			&obj, GIT_SV_TO_PTR(Repository, self), &oid, len, GIT_OBJ_ANY
		);
		git_check_error(rc);

		RETVAL = git_obj_to_sv(obj, self);

	OUTPUT: RETVAL

void
checkout(self, target, opts)
	Repository self
	SV *target
	HV *opts

	PREINIT:
		int rc;

		git_checkout_opts checkout_opts = GIT_CHECKOUT_OPTS_INIT;

	CODE:
		git_hv_to_checkout_opts(opts, &checkout_opts);

		rc = git_checkout_tree(
			self, git_sv_to_obj(target), &checkout_opts
		);

		Safefree(checkout_opts.paths.strings);
		git_check_error(rc);

void
reset(self, target, opts)
	Repository self
	SV *target
	HV *opts

	PREINIT:
		int rc;

		SV **opt;

	CODE:
		if ((opt = hv_fetchs(opts, "paths", 0))) {
			SV **path;

			size_t count = 0;
			git_strarray paths = {0, 0};

			if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVAV)
				Perl_croak(aTHX_ "Expected a list of 'paths'");

			while ((path = av_fetch((AV *) SvRV(*opt), count, 0))) {
				if (SvOK(*path)) {
					Renew(paths.strings, count + 1, char *);
					paths.strings[count++] = SvPVbyte_nolen(*path);
				}
			}

			paths.count = count;

			rc = git_reset_default(self, git_sv_to_obj(target), &paths);
			Safefree(paths.strings);
			git_check_error(rc);
		} else {
			if ((opt = hv_fetchs(opts, "type", 0))) {
				git_reset_t reset;
				const char *type_str = SvPVbyte_nolen(*opt);

				if (strcmp(type_str, "soft") == 0)
					reset = GIT_RESET_SOFT;
				else if (strcmp(type_str, "mixed") == 0)
					reset = GIT_RESET_MIXED;
				else
					Perl_croak(aTHX_ "Invalid type");

				rc = git_reset(self, git_sv_to_obj(target), reset);
				git_check_error(rc);
			}
		}

HV *
status(self, ...)
	Repository self

	PROTOTYPE: $;@

	PREINIT:
		int rc;

		size_t i, count;

		HV *status_hv;

		git_status_list *list;
		git_status_options opt = GIT_STATUS_OPTIONS_INIT;

	CODE:
		opt.flags |= GIT_STATUS_OPT_DEFAULTS |
			GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
			GIT_STATUS_OPT_RENAMES_FROM_REWRITES;

		/*
		 * Core git does not recurse untracked dirs, it merely informs
		 * the user that the directory is untracked.
		 */
		opt.flags &= ~GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;

		/*
		 * GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR seems to be broken
		 * if files are renamed in both the index and in the working
		 * tree. Core git does not tell you if the file was renamed in
		 * the worktree anyway.
		 */

		if (items > 1) {
			opt.flags |= GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;

			Newx(opt.pathspec.strings, items - 1, char *);

			for (i = 1; i < items; i++)
				opt.pathspec.strings[i - 1] =
					SvPVbyte_nolen(ST(i));

			opt.pathspec.count = i - 1;
		}

		rc = git_status_list_new(&list, self, &opt);
		Safefree(opt.pathspec.strings);
		git_check_error(rc);

		count = git_status_list_entrycount(list);

		status_hv = newHV();
		for (i = 0; i < count; i++) {
			AV *flags;
			HV *file_status_hv;

			const char *path = NULL;
			const git_status_entry *entry = NULL;

			if ((entry = git_status_byindex(list, i)) == NULL)
				continue;

			flags = newAV();

			if (entry -> status & GIT_STATUS_INDEX_NEW)
				av_push(flags, newSVpv("index_new", 0));

			if (entry -> status & GIT_STATUS_INDEX_MODIFIED)
				av_push(flags, newSVpv("index_modified", 0));

			if (entry -> status & GIT_STATUS_INDEX_DELETED)
				av_push(flags, newSVpv("index_deleted", 0));

			if (entry -> status & GIT_STATUS_INDEX_RENAMED)
				av_push(flags, newSVpv("index_renamed", 0));

			if (entry -> status & GIT_STATUS_WT_NEW)
				av_push(flags, newSVpv("worktree_new", 0));

			if (entry -> status & GIT_STATUS_WT_MODIFIED)
				av_push(flags, newSVpv("worktree_modified", 0));

			if (entry -> status & GIT_STATUS_WT_DELETED)
				av_push(flags, newSVpv("worktree_deleted", 0));

			if (entry -> status & GIT_STATUS_WT_RENAMED)
				av_push(flags, newSVpv("worktree_renamed", 0));

			if (entry -> status & GIT_STATUS_IGNORED)
				av_push(flags, newSVpv("ignored", 0));

			file_status_hv = newHV();

			if (entry -> index_to_workdir) {
				if (entry -> status & GIT_STATUS_WT_RENAMED) {
					HV *worktree_status_hv = newHV();

					hv_stores(worktree_status_hv, "old_file",
						newSVpv(entry -> index_to_workdir -> old_file.path, 0));
					hv_stores(file_status_hv, "worktree", newRV_noinc((SV *) worktree_status_hv));
				}

				path = entry -> index_to_workdir -> new_file.path;
			}

			if (entry -> head_to_index) {
				if (entry -> status & GIT_STATUS_INDEX_RENAMED) {
					HV *index_status_hv = newHV();

					hv_stores(index_status_hv, "old_file",
						newSVpv(entry -> head_to_index -> old_file.path, 0));
					hv_stores(file_status_hv, "index", newRV_noinc((SV *) index_status_hv));
				}

				if (!path)
					path = entry -> head_to_index -> new_file.path;
			}

			hv_stores(file_status_hv, "flags", newRV_noinc((SV *) flags));
			hv_store(status_hv, path, strlen(path), newRV_noinc((SV *) file_status_hv), 0);
		}

		git_status_list_free(list);

		RETVAL = status_hv;

	OUTPUT: RETVAL

SV *
path_is_ignored(self, path)
	Repository self
	const char *path

	PREINIT:
		int rc, ignore;

	CODE:
		rc = git_ignore_path_is_ignored(&ignore, self, path);
		git_check_error(rc);

		RETVAL = newSViv(ignore);

	OUTPUT: RETVAL

void
ignore(self, rules)
	Repository self
	SV *rules

	PREINIT:
		int rc;

	CODE:
		rc = git_ignore_add_rule(self, SvPVbyte_nolen(rules));
		git_check_error(rc);

Diff
diff(self, ...)
	Repository self

	PROTOTYPE: $;$

	PREINIT:
		int rc;

		Diff diff;
		Index index;

		char **paths = NULL;
		Tree tree = NULL;

		git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;

	CODE:
		if (items > 2)
			Perl_croak(aTHX_ "Wrong number of arguments");

		rc = git_repository_index(&index, self);
		git_check_error(rc);

		if (items == 2) {
			SV **opt;
			HV *opts;

			if (!SvROK(ST(1)) || SvTYPE(SvRV(ST(1))) != SVt_PVHV)
				Perl_croak(aTHX_ "Invalid type");

			opts = (HV *) SvRV(ST(1));
			if ((opt = hv_fetchs(opts, "tree", 0))) {
				tree = GIT_SV_TO_PTR(Tree, *opt);
			}

			if ((opt = hv_fetchs(opts, "flags", 0))) {
				if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVHV)
					Perl_croak(aTHX_ "Expected a list of 'flags'");

				diff_opts.flags |=
					git_hv_to_diff_flag((HV *) SvRV(*opt));
			}

			if ((opt = hv_fetchs(opts, "prefix", 0))) {
				SV **ab;

				if ((ab = hv_fetchs((HV *) SvRV(*opt), "a", 0))) {
					if (!SvPOK(*ab))
						Perl_croak(aTHX_ "Expected a string for prefix");

					diff_opts.old_prefix = SvPVbyte_nolen(*ab);
				}

				if ((ab = hv_fetchs((HV *) SvRV(*opt), "b", 0))) {
					if (!SvPOK(*ab))
						Perl_croak(aTHX_ "Expected a string for prefix");

					diff_opts.new_prefix = SvPVbyte_nolen(*ab);
				}
			}

			if ((opt = hv_fetchs(opts, "context_lines", 0))) {
				if (!SvIOK(*opt))
					Perl_croak(aTHX_ "Expected an integer for 'context_lines'");

				diff_opts.context_lines = SvIV(*opt);
			}

			if ((opt = hv_fetchs(opts, "interhunk_lines", 0))) {
				if (!SvIOK(*opt))
					Perl_croak(aTHX_ "Expected an integer for 'interhunk_lines'");

				diff_opts.interhunk_lines = SvIV(*opt);
			}

			if ((opt = hv_fetchs(opts, "paths", 0))) {
				SV **path;
				size_t count = 0;

				if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVAV)
					Perl_croak(aTHX_ "Expected a list of 'paths'");

				while ((path = av_fetch((AV *) SvRV(*opt), count, 0))) {
					if (SvOK(*path)) {
						Renew(paths, count + 1, char *);
						paths[count++] = SvPVbyte_nolen(*path);
					}
				}

				if (count > 0) {
					diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
					diff_opts.pathspec.strings = paths;
					diff_opts.pathspec.count   = count;
				}
			}
		}

		if (tree) {
			rc = git_diff_tree_to_index(
				&diff, self, tree, index, &diff_opts
			);
		} else {
			rc = git_diff_index_to_workdir(
				&diff, self, index, &diff_opts
			);
		}

		git_index_free(index);
		Safefree(paths);
		git_check_error(rc);

		RETVAL = diff;

	OUTPUT: RETVAL

SV *
merge(self, ref, opts)
	Repository self
	Reference ref
	HV *opts

	PREINIT:
		int rc;

		git_merge_head *merge_head;
		git_merge_result *merge_result;

		SV **opt;
		git_merge_opts merge_opts = GIT_MERGE_OPTS_INIT;

		HV *result;

	CODE:
		rc = git_merge_head_from_ref(&merge_head, self, ref);
		git_check_error(rc);

		if ((opt = hv_fetchs(opts, "flags", 0))) {
			HV *flags;

			if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVHV)
				Perl_croak(aTHX_ "Invalid type");

			flags = (HV *) SvRV(*opt);

			if ((opt = hv_fetchs(flags, "fastforward_only", 0)) && SvIV(*opt))
				merge_opts.merge_flags |= GIT_MERGE_FASTFORWARD_ONLY;
			else if ((opt = hv_fetchs(flags, "no_fastfoward", 0)) && SvIV(*opt))
				merge_opts.merge_flags |= GIT_MERGE_NO_FASTFORWARD;
		}

		if ((opt = hv_fetchs(opts, "tree_opts", 0))) {
			if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVHV)
				Perl_croak(aTHX_ "Invalid type");

			git_hv_to_merge_tree_opts((HV *) SvRV(*opt),
				&merge_opts.merge_tree_opts);
		}

		if ((opt = hv_fetchs(opts, "checkout_opts", 0))) {
			if (!SvROK(*opt) || SvTYPE(SvRV(*opt)) != SVt_PVHV)
				Perl_croak(aTHX_ "Invalid type");

			git_hv_to_checkout_opts((HV *) SvRV(*opt),
			&merge_opts.checkout_opts);
		}

		rc = git_merge(&merge_result, self,
			(const git_merge_head **) &merge_head, 1, &merge_opts);
		Safefree(merge_opts.checkout_opts.paths.strings);
		git_check_error(rc);

		result = newHV();

		hv_stores(result, "up_to_date",
			newSViv(git_merge_result_is_uptodate(merge_result)));

		if (git_merge_result_is_fastforward(merge_result)) {
			git_oid id;

			git_merge_result_fastforward_oid(&id, merge_result);
			hv_stores(result, "id", git_oid_to_sv(&id));
			hv_stores(result, "fast_forward", newSViv(1));
		} else {
			hv_stores(result, "fast_forward", newSViv(0));
		}

		RETVAL = newRV_noinc((SV *) result);

	OUTPUT: RETVAL

void
branches(self)
	SV *self

	PREINIT:
		int rc;

		Branch branch;
		int num_branches = 0;

		git_branch_t type;
		git_branch_iterator *itr;

		Repository repo;

	PPCODE:
		repo = GIT_SV_TO_PTR(Repository, self);

		rc = git_branch_iterator_new(&itr, repo,
			GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE);
		git_check_error(rc);

		while ((rc = git_branch_next(&branch, &type, itr)) == 0) {
			SV *perl_ref;

			GIT_NEW_OBJ(perl_ref, "Git::Raw::Branch", branch, SvRV(self));

			EXTEND(SP, 1);
			PUSHs(sv_2mortal(perl_ref));

			num_branches++;
		}
		git_branch_iterator_free(itr);

		if (rc != GIT_ITEROVER)
			git_check_error(rc);

		XSRETURN(num_branches);

void
remotes(self)
	SV *self

	PREINIT:
		int rc;
		size_t i;

		Remote remote;
		int num_remotes = 0;
		git_strarray remotes;

		Repository repo;

	PPCODE:
		repo = GIT_SV_TO_PTR(Repository, self);

		rc = git_remote_list(&remotes, repo);
		git_check_error(rc);

		for (i = 0; i < remotes.count; i++) {
			SV *perl_ref;

			rc = git_remote_load(&remote, repo, remotes.strings[i]);
			git_check_error(rc);

			GIT_NEW_OBJ(perl_ref, "Git::Raw::Remote", remote, SvRV(self));

			EXTEND(SP, 1);
			PUSHs(sv_2mortal(perl_ref));

			num_remotes++;
		}

		git_strarray_free(&remotes);

		XSRETURN(num_remotes);

void
refs(self)
	SV *self

	PREINIT:
		int rc;

		Reference ref;
		int num_refs = 0;

		git_reference_iterator *itr;

	PPCODE:
		rc = git_reference_iterator_new(&itr, GIT_SV_TO_PTR(Repository, self));
		git_check_error(rc);

		while ((rc = git_reference_next(&ref, itr)) == 0) {
			SV *perl_ref;

			GIT_NEW_OBJ(perl_ref, "Git::Raw::Reference", ref, SvRV(self));

			EXTEND(SP, 1);
			PUSHs(sv_2mortal(perl_ref));

			num_refs++;
		}
		git_reference_iterator_free(itr);

		if (rc != GIT_ITEROVER)
			git_check_error(rc);

		XSRETURN(num_refs);

SV *
path(self)
	Repository self

	PREINIT:
		const char *path;

	CODE:
		path = git_repository_path(self);
		RETVAL = newSVpv(path, 0);

	OUTPUT: RETVAL

SV *
workdir(self, ...)
	Repository self

	PROTOTYPE: $;$

	PREINIT:
		int rc;
		const char *path;

	CODE:
		if (items == 2) {
			const char *new_path = SvPVbyte_nolen(ST(1));

			rc = git_repository_set_workdir(self, new_path, 1);
			git_check_error(rc);
		}

		path = git_repository_workdir(self);
		RETVAL = newSVpv(path, 0);

	OUTPUT: RETVAL

SV *
state(self)
	Repository self

	PREINIT:
		int rc;
		const char *s;

	CODE:
		rc = git_repository_state(self);

		switch (rc) {
			case GIT_REPOSITORY_STATE_NONE:
				s = "none";
				break;

			case GIT_REPOSITORY_STATE_MERGE:
				s = "merge";
				break;

			case GIT_REPOSITORY_STATE_REVERT:
				s = "revert";
				break;

			case GIT_REPOSITORY_STATE_CHERRY_PICK:
				s = "cherry_pick";
				break;

			case GIT_REPOSITORY_STATE_BISECT:
				s = "bisect";
				break;

			case GIT_REPOSITORY_STATE_REBASE:
				s = "rebase";
				break;

			case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE:
				s = "rebase_interactive";
				break;

			case GIT_REPOSITORY_STATE_REBASE_MERGE:
				s = "rebase_merge";
				break;

			case GIT_REPOSITORY_STATE_APPLY_MAILBOX:
				s = "apply_mailbox";
				break;

			case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE:
				s = "mailbox_or_rebase";
				break;

			default:
				Perl_croak(aTHX_ "Unhandle state: %i", rc);
		}

		RETVAL = newSVpv(s, 0);

	OUTPUT: RETVAL

void
state_cleanup(self)
	Repository self

	PREINIT:
		int rc;

	CODE:
		rc = git_repository_state_cleanup(self);
		git_check_error(rc);

SV *
is_bare(self)
	Repository self

	CODE:
		RETVAL = newSViv(git_repository_is_bare(self));

	OUTPUT: RETVAL

SV *
is_empty(self)
	Repository self

	CODE:
		RETVAL = newSViv(git_repository_is_empty(self));

	OUTPUT: RETVAL

SV *
is_shallow(self)
	Repository self

	CODE:
		RETVAL = newSViv(git_repository_is_shallow(self));

	OUTPUT: RETVAL

void
DESTROY(self)
	Repository self

	CODE:
		git_repository_free(self);