App-FuguBench

 view release on metacpan or  search on metacpan

lib/App/FuguBench/Worktree.pm  view on Meta::CPAN


# _copy_env_tree($app, $src, $dst, $nested):
#	Copy each regular .env file of one source tree, at any depth,
#	into the clone (WT-CLONE-3). The files are gitignored, so the
#	clone above holds none of them. The walk skips .git and a
#	nested worktree directory, and it copies no symbolic link: a
#	.env link is content of the repository, and it stays there.
sub _copy_env_tree ( $app, $src, $dst, $nested )
{
	my $ok = 1;
	File::Find::find( {
			no_chdir => 1,
			wanted   => sub {
				my $name = $File::Find::name;
				my $base = basename($name);
				my $skip = $base eq '.git'
				    || ( $name ne $src && $name =~ $nested );
				if ($skip) {
					$File::Find::prune = 1;
					return;
				}
				return unless $base eq '.env';
				return if -l $name || !-f $name;

				# A gitignored parent directory is absent
				# in the clone, so the copy makes it.
				my $rel    = substr $name, length($src) + 1;
				my $parent = dirname("$dst/$rel");
				File::Path::make_path($parent)
				    unless -d $parent;
				$ok = 0
				    unless _copy_file( $app, $name,
					"$dst/$rel" );

				return;
			},
		},
		$src
	);

	return $ok;
}

# _copy_file($app, $src, $dst):
#	Copy one file with the mode of the source. The method returns
#	0 after a failure.
sub _copy_file ( $app, $src, $dst )
{
	my $log = $app->cli->log;

	# A project can leave a symbolic link at the destination, and
	# the copy must not write through it. A regular file that
	# exists stays, so a second run keeps a local change
	# (WT-CLONE-4).
	unlink $dst if -l $dst;
	if ( -e $dst ) {
		$log->notice( 'already exists, skipped: %s', $dst );
		return 1;
	}

	# A .env file holds credentials, so the copy takes the mode of
	# the source and not the default of the umask. The stat runs
	# before the copy: a stat after it can follow a parallel
	# removal of the source, and the chmod then gives the copy the
	# mode 0.
	my @stat = stat $src;
	unless ( File::Copy::copy( $src, $dst ) ) {
		$log->error( 'cannot copy %s -> %s: %s', $src, $dst, $! );
		return 0;
	}
	if ( @stat && chmod( $stat[2] & 07777, $dst ) != 1 ) {
		$log->error( 'cannot set the mode of %s: %s', $dst, $! );

		# A copy of a credential file at the mode of the umask
		# is wider than the source, so it must not stay.
		unlink $dst
		    or $log->error( 'cannot remove %s: %s', $dst, $! );

		return 0;
	}
	$log->notice( 'copied %s -> %s', $src, $dst );

	return 1;
}

# _delete_branch($app, $root, $branch):
#	Delete one branch, and report nothing about a branch that is
#	gone. The method never deletes main, and never the branch that
#	the main checkout has checked out (WT-REMOVE-5). A parallel
#	remove that deletes the branch first is no error. The method
#	returns 0 when the branch stays.
sub _delete_branch ( $app, $root, $branch )
{
	return 1
	    unless defined $branch && length $branch && $branch ne 'main';

	my $head = _capture( $app, 'git', '-C', $root, 'symbolic-ref',
		'--quiet', '--short', 'HEAD' );
	return 1 if defined $head && $branch eq $head;
	return 1 unless _branch_exists( $app, $root, $branch );

	return 1
	    if defined $app->command(
		[ 'git', '-C', $root, 'branch', '-D', $branch ] );
	return 1 unless _branch_exists( $app, $root, $branch );

	$app->cli->log->error( 'cannot delete the branch %s', $branch );

	return 0;
}

# _branch_exists($app, $repo, $branch):
#	True when the repository holds the branch.
sub _branch_exists ( $app, $repo, $branch )
{
	return defined $app->command( [
		'git',      '-C',
		$repo,      'show-ref',
		'--verify', '--quiet',
		"refs/heads/$branch"
	] );



( run in 1.347 second using v1.01-cache-2.11-cpan-007c89162af )