#!/usr/bin/perl
# Allows a site to be branched.
package IkiWiki::Plugin::branchable;

use warnings;
use strict;
use IkiWiki 3.00;

sub import {
	hook(type => "getsetup", id => "branchable", call => \&getsetup);
	hook(type => "checkconfig", id => "branchable", call => \&checkconfig,
		first => 1);
	hook(type => "sessioncgi", id => "branchable", call => \&sessioncgi);
	hook(type => "pageactions", id => "branchable", call => \&pageactions,
		# Want it to be the last thing on the action bar.
		last => 1);
	hook(type => "genwrapper", id => "branchable", call => \&genwrapper,
		last => 1);
	hook(type => "disable", id => "branchable", call => \&disable);
}

sub getsetup () {
	return
		plugin => {
			safe => 0,
			rebuild => undef,
			section => "core",
		},
		branchable => {
			type => "boolean",
			example => 1,
			description => "Allow anyone to branch, check out, and copy this site?",
			safe => 1,
			rebuild => 1, # disables diffurl and historyurl
		},
		anonpush => {
			type => "boolean",
			example => 0,
			description => "Allow anyone to git push verified changes to this site?",
			safe => 1,
			rebuild => 0,
		},
		branchable_action => {
			type => "boolean",
			example => 1,
			description => "Display \"Branchable\" link on action bar?",
			safe => 1,
			rebuild => 1, # update action bar
		},
}

sub checkconfig {
	$config{branchable_action}=1 unless defined $config{branchable_action};
	
	# Automatically configure repositories for repolist plugin.
	# (This hook needs to run first to do so.)
	$config{repositories}=[grep defined, (gitsshurl(), gitanonurl())];

	# Sites that are not branchable do not have gitweb configured, so
	# automatically disable the historyurl and diffurl.
	# TODO: store old values and re-enable if branchable is enabled
	# Also, non-branchability disabled anonpush.
	if (! $config{branchable}) {
		delete $config{historyurl};
		delete $config{diffurl};
		$config{anonpush}=0;
	}
	
	# Sites with anonpush enabled have a git_test_receive_wrapper.
	if ($config{anonpush}) {
		my $repo=find_git_repository();
		if (defined $repo) {
			$config{git_test_receive_wrapper}="$repo/hooks/pre-receive";
		}
		else {
			$config{anonpush}=0;
			delete $config{git_test_receive_wrapper};
		}
	}
	else {
		delete $config{git_test_receive_wrapper};
	}
}

my $inbranchable;
my $link;
sub pageactions (@) {
	return $link if defined $link;
	return if $inbranchable;
	return unless $config{branchable_action};
	return $link='<a href="'.IkiWiki::cgiurl("do" => "branchable").'">'.
		"Branchable".'</a>';
}

sub sessioncgi ($$) {
	my $cgi=shift;
	my $session=shift;

	return unless defined $cgi->param("do");

	if ($cgi->param("do") eq "branchable") {
		showbranchable($cgi, $session);
	}
}

sub showbranchable ($$) {
	my $cgi=shift;
	my $session=shift;
	
	$inbranchable=1;

	eval q{use IkiWiki::Hosting};
	error $@ if $@;
	IkiWiki::Hosting::readconfig();

	my $template=IkiWiki::Hosting::ikisite_template("branchable.tmpl");
	$template->param(wikiname => $config{wikiname});
	$template->param(url => $config{url});
	$template->param(cgiurl => $config{cgiurl});
	$template->param(setupurl => IkiWiki::cgiurl(do => "setup").
		# jump right to the branchable plugin setup
		"#setup_core_plugin_branchable");
	$template->param(gitsshurl => gitsshurl());
	$template->param(gitanonurl => gitanonurl());
	$template->param(hostname => $config{hostname});
	$template->param(branchable => $config{branchable});
	$template->param(anonpush => $config{anonpush});
	$template->param(controlsitecgiurl => $config{controlsitecgiurl});
	IkiWiki::printheader($session);
	print IkiWiki::cgitemplate($cgi, 'Branchable', $template->output);
	exit 0;
}

sub genwrapper {
	# This hook is called whenever wrappers are being built,
	# and that happens when configuration has changed.
	# (This hook is set to run last, in order to run after
	# the creation of the git_test_receive_wrapper.)
	setbranchable();

	# C code to inject into wrapper.
	return "";
}

sub disable {
	# When this plugin is disabled, clean up any previously
	# configured branchability.
	$config{branchable}=0;
	$config{anonpush}=0;
	setbranchable();
}

sub setbranchable {
	# When the branchable setting is changed, we need to
	# touch or remove the git-daemon-export-ok flag file,
	# and the symlink to gitweb.conf, and tweak the permissions
	# of the git repo.
	my $repo=find_git_repository();
	if (defined $repo) {
		my $flagfile="$repo/git-daemon-export-ok";
		my $gitweb_conf_file=$config{srcdir}."/.ikiwiki/gitweb.conf";
		if ($config{branchable}) {
			open(FLAG, ">", $flagfile);
			close FLAG;
			unlink($gitweb_conf_file); # force refresh symlink
			symlink("$gitweb_conf_file.real", $gitweb_conf_file);
			chmod(02775, $repo) || error "chmod $repo: $!";
		}
		else {
			unlink($flagfile);
			unlink($gitweb_conf_file);
			chmod(0700, $repo) || error "chmod $repo: $!";
		}
	}

	if (defined $repo && $config{anonpush}) {
		# Allow git daemon to push.
		system("cd $repo && git config daemon.receivepack true");
	}
	else {
		system("cd $repo && git config daemon.receivepack false");
		# Delete unnecesary hook for speed.
		if (defined $repo) {
			unlink("$repo/hooks/pre-receive");
		}
	}
}

# Parses git_wrapper to find out where the git repository is.
sub find_git_repository {
	if ($config{rcs} eq 'git' &&
	    $config{git_wrapper}=~m!^(.*)/hooks/post-update$!) {
		return $1;
	}
	else {
		return undef;
	}
}

sub gitsshurl {
	# Ikiwiki will be running as the site's username,
	my $username=(getpwuid($<))[0];
	if (defined $username && length $username &&
	   defined $config{hostname} && length $config{hostname}) {
		return "ssh://$username\@$config{hostname}/";
	}
	else {
		return undef;
	}
}

sub gitanonurl {
	if ($config{branchable} &&
		   defined $config{hostname} && length $config{hostname}) {
		return "git://$config{hostname}/";
	}
	else {
		return undef;
	}
}

1
