Use stow for managing dotfiles

This commit is contained in:
Oliver Davies 2021-07-10 22:20:50 +01:00
parent 7b29ee2104
commit 7fed1cf922
83 changed files with 61 additions and 40 deletions

BIN
bin/bin/drupalorg Executable file

Binary file not shown.

20
bin/bin/git-abort Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
# Abort a rebase, merge, `am`, a cherry-pick or a revert, depending on the situation.
if [[ -e .git/CHERRY_PICK_HEAD ]] ; then
exec git cherry-pick --abort "$@"
elif [[ -e .git/REVERT_HEAD ]] ; then
exec git revert --abort "$@"
elif [[ -e .git/rebase-apply/applying ]] ; then
exec git am --abort "$@"
elif [[ -e .git/rebase-apply ]] ; then
exec git rebase --abort "$@"
elif [[ -e .git/rebase-merge ]] ; then
exec git rebase --abort "$@"
elif [[ -e .git/MERGE_MODE ]] ; then
exec git merge --abort "$@"
else
echo git-abort: unknown state
exit -1
fi

271
bin/bin/git-close-pull-request Executable file
View file

@ -0,0 +1,271 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* Usage: git close-pull-request -t <target>
*
* Run this from a branch which has an upstream remote branch, and an associated
* pull request.
*
* The script will merge the branch into master, push master (which will
* automatically close the pull request), and delete both the local and remote
* branches.
*
* Based on a script by @christoomey. Translated into PHP.
*/
class ClosesPullRequests
{
private $targetBranch;
private $localBranch;
private $remoteBranch;
private const RUN_TYPE_COMMAND = 'command';
private const RUN_TYPE_QUERY = 'query';
public function __construct()
{
$this->localBranch = $this->run(
'git rev-parse --abbrev-ref HEAD',
self::RUN_TYPE_QUERY
);
$this->targetBranch = $this->getTargetBranchFromArgs();
$this->remoteBranch = $this->run(
'git rev-parse --abbrev-ref --symbolic-full-name @{u}',
self::RUN_TYPE_QUERY
);
$this->remoteBranch = str_replace('origin/', '', $this->remoteBranch);
}
public function __invoke(): void
{
$this->confirmCiStatusIsPassing();
// TODO: Check that the current branch has a tracking branch.
$this->ensureWorkingDirectoryAndIndexAreClean();
$this->fetchOrigin();
$this->ensureFeatureBranchInSync();
$this->ensureTargetBranchInSync();
$this->checkoutTargetBranch();
$this->mergeLocalBranch();
$this->pushTargetBranch();
$this->deleteRemoteBranch();
$this->deleteLocalBranch();
}
private function ensureWorkingDirectoryAndIndexAreClean(): void
{
echo 'Ensuring that index and working directory are clean...' . PHP_EOL;
$isIndexClean = $this->run('git diff --cached --exit-code', self::RUN_TYPE_COMMAND);
$isWorkingDirClean = $this->run('git diff --exit-code', self::RUN_TYPE_COMMAND);
if (!$isIndexClean || !$isWorkingDirClean) {
$this->dieWithMessage('Index or working dir not clean. Aborting.');
}
}
private function getTargetBranchFromArgs(): string
{
if (!$targetBranchName = $this->getArg('t:', ['target:'])) {
$this->dieWithMessage('Invalid target branch specified. Aborting.');
}
return $targetBranchName;
}
private function confirmCiStatusIsPassing(): void
{
if ($this->isForce()) {
echo 'Forced. Skipping ci-status check...' . PHP_EOL;
return;
}
echo 'Confirming ci-status on PR is green...' . PHP_EOL;
$passedCi = $this->run('gh pr checks', self::RUN_TYPE_COMMAND);
// TODO: Check if there are no CI checks. Does this return `true` as well?
if (!$passedCi) {
$this->dieWithMessage('CI pending or failed.');
}
}
private function fetchOrigin(): void
{
print 'Fetching origin to confirm local and remote in sync...'
. PHP_EOL;
$this->run('git fetch origin', self::RUN_TYPE_COMMAND);
}
private function ensureTargetBranchInSync(): void
{
$this->ensureBranchInSyncWithUpstream(
$this->targetBranch,
$this->targetBranch
);
}
private function ensureFeatureBranchInSync(): void
{
$this->ensureBranchInSyncWithUpstream(
$this->localBranch,
$this->remoteBranch
);
}
private function ensureBranchInSyncWithUpstream(
string $localBranch,
string $remoteBranch
): void {
echo sprintf(
'Ensuring that %s is in sync with its upstream...',
$localBranch
) . PHP_EOL;
$localCommitTip = $this->tipCommitOfBranch($localBranch);
$remoteCommitTip = $this->tipCommitOfBranch(sprintf(
'origin/%s',
$remoteBranch
));
if ($localCommitTip != $remoteCommitTip) {
$this->dieWithMessage(sprintf(
'Branch %s was out of date, needs rebasing. Aborting.',
$localBranch
));
}
}
private function tipCommitOfBranch(string $branchName): string
{
return $this->run(
sprintf('git rev-parse %s', $branchName),
self::RUN_TYPE_QUERY
);
}
private function checkoutTargetBranch(): void
{
echo sprintf('Checking out %s...' . PHP_EOL, $this->targetBranch);
$this->run(
sprintf('git checkout %s', $this->targetBranch),
self::RUN_TYPE_COMMAND
);
}
private function mergeLocalBranch(): void
{
echo sprintf(
'Merging %s into %s...' . PHP_EOL,
$this->localBranch,
$this->targetBranch
);
$mergeCommand = sprintf('git merge --ff-only %s', $this->localBranch);
if (!$this->run($mergeCommand, self::RUN_TYPE_COMMAND)) {
// Switch back to the previous branch.
$this->run('git checkout -', self::RUN_TYPE_COMMAND);
$this->dieWithMessage(sprintf(
'Branch %s is not fast-forwardable.',
$this->localBranch
));
}
}
public function pushTargetBranch(): void
{
print(sprintf('Pushing updated %s branch...', $this->targetBranch));
$this->run(
sprintf('git push origin %s', $this->targetBranch),
self::RUN_TYPE_COMMAND
);
}
public function deleteRemoteBranch(): void
{
echo 'Deleting remote branch...' . PHP_EOL;
$this->run(
sprintf('git push origin :%s', $this->remoteBranch),
self::RUN_TYPE_COMMAND
);
}
public function deleteLocalBranch(): void
{
echo 'Deleting local branch...' . PHP_EOL;
$this->run(
sprintf('git branch -d %s', $this->localBranch),
self::RUN_TYPE_COMMAND
);
}
private function getArg(string $shortOpts, array $longOpts = []): ?string
{
if (!$values = getopt($shortOpts, $longOpts)) {
return NULL;
}
return current($values);
}
private function hasArg(string $shortOpts, array $longOpts = []): bool
{
return !empty(getopt($shortOpts, $longOpts));
}
private function isForce(): bool
{
return $this->hasArg('f::', ['force::']);
}
/**
* Run the command.
*
* @return bool|string
* If the type is 'command', the method will return if there were any
* errors when running the command based on its return code.
*
* If the type is 'query', then the output of the command will be returned
* as a string.
*/
private function run(string $command, string $type)
{
switch ($type) {
case self::RUN_TYPE_COMMAND:
// Perform the command, hiding the original output and return
// whether or not there were errors.
@exec("$command", $output, $return);
return $return == 0;
case self::RUN_TYPE_QUERY:
// Perform the command and return the output.
return exec($command, $output);
}
}
private function dieWithMessage(string $message): void
{
echo sprintf("\e[31m%s\e[0m", $message);
exit(1);
}
private function exitWithWarning(string $message): void
{
echo sprintf("\e[33m%s\e[0m", $message);
exit(2);
}
}
(new ClosesPullRequests())->__invoke();

7
bin/bin/git-cm Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
if [[ $# > 0 ]]; then
git commit -m "$@"
else
git commit -v
fi

16
bin/bin/git-continue Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# Continue a rebase or cherry-pick in the event of conflicts.
if [[ -e .git/CHERRY_PICK_HEAD ]] ; then
exec git cherry-pick --continue "$@"
elif [[ -e .git/rebase-apply/applying ]] ; then
exec git rebase --continue "$@"
elif [[ -e .git/rebase-apply ]] ; then
exec git rebase --continue "$@"
elif [[ -e .git/rebase-merge ]] ; then
exec git rebase --continue "$@"
else
echo git-abort: unknown state
exit -1
fi

View file

@ -0,0 +1,15 @@
#!/bin/sh
set -e
if [ "$#" -lt 1 ]; then
echo "Error: Not enough arguments."
exit l
fi
# Create a new branch including any additional arguments.
git checkout -b "$@"
# Push the branch to origin, bypassing any Git hooks.
new_branch_name=$1
git push --no-verify -u origin "${new_branch_name}:opd-${new_branch_name}"

View file

@ -0,0 +1,42 @@
#!/usr/bin/env php
<?php
function extractBranchNamesFromInfo(string $branchInfo): array
{
$branchNames = array_map(function (string $branchInfo): string {
preg_match('/\s*((\w|-|\/)+)\s*/', $branchInfo, $matches);
return $matches[1] ?? '';
}, explode(PHP_EOL, $branchInfo));
return array_filter($branchNames);
}
function filterIgnoredBranches(array $branchNames): array
{
return array_filter($branchNames, function (string $branchName): bool {
return !in_array($branchName, ['develop', 'master', 'staging', 'production']);
});
}
$branchInfo = shell_exec('git branch -vv | grep ": gone]"');
# Return early if there are no branches to delete.
if ($branchInfo === NULL) {
return;
}
$branchNames = extractBranchNamesFromInfo($branchInfo);
$filteredBranchNames = filterIgnoredBranches($branchNames);
$currentBranch = exec('git rev-parse --abbrev-ref HEAD');
foreach ($filteredBranchNames as $branchName) {
if ($branchName == $currentBranch) {
echo "Cannot delete {$branchName} as it is the current branch.";
continue;
}
echo "Deleting {$branchName}...";
exec("git branch -D ${branchName}");
}

View file

@ -0,0 +1,19 @@
#!/usr/bin/env zsh
# Usage: instead of
#
# git rebase -i master
#
# run this:
#
# git master-to-main-wrapper rebase -i %BRANCH%
#
# It will replace the literal string `%BRANCH%` with "main" (preferred) or
# "master" depending on what the current repository uses.
command=$*
branchname=$(main-or-master-branch)
replaced_commands=$(echo $command | sed "s/%BRANCH%/$branchname/g")
# sh_glob ignores special meaning of parentheses so that fancy logs like this
# work: `git master-to-main-wrapper log --format='%w(78)%s%n%+b'`
zsh -c "setopt sh_glob; git ${replaced_commands}"

26
bin/bin/git-opr Executable file
View file

@ -0,0 +1,26 @@
#!/bin/bash
set -e
ensure_is_published() {
[[ ! $is_published ]] && git publish
}
is_published() {
echo $(git upstream)
}
open_or_build_pull_request() {
type gh &>/dev/null
if [ $? -ne 0 ]; then
echo "Error: gh command not found."
exit 1
fi
# Load an existing PR, or create a new one.
gh pr view --web || gh pr create --assignee opdavies --web
}
ensure_is_published
open_or_build_pull_request

15
bin/bin/git-publish Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env php
<?php
/**
* Usage: git publish
*/
$currentBranch = exec('git rev-parse --abbrev-ref HEAD');
if (in_array($currentBranch, ['develop', 'main', 'master', 'staging', 'production'])) {
print "Currently on ${currentBranch}. Aborting.";
exit(1);
}
exec("git push -u origin $currentBranch:opd-{$currentBranch}");

21
bin/bin/git-up Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Usage: git up {branch} {remote}
set -e
if [[ $# < 1 ]]; then
echo "You must specify a branch name to update"
exit 1
fi
BRANCH=$1
REMOTE=${2:-origin}
git checkout ${BRANCH} && \
git fetch ${REMOTE} && \
echo && \
git sl ${BRANCH}..${REMOTE}/${BRANCH} && \
echo && \
git pull --quiet && \
git checkout -

28
bin/bin/git.sh Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
# The MIT License (MIT)
# Copyright (c) 2013 Alvin Abad
if [ $# -eq 0 ]; then
echo "Git wrapper script that can specify an ssh-key file
Usage:
git.sh -i ssh-key-file git-command
"
exit 1
fi
# remove temporary file on exit
trap 'rm -f /tmp/.git_ssh.$$' 0
if [ "$1" = "-i" ]; then
SSH_KEY=$2; shift; shift
echo "ssh -i $SSH_KEY \$@" > /tmp/.git_ssh.$$
chmod +x /tmp/.git_ssh.$$
export GIT_SSH=/tmp/.git_ssh.$$
fi
# in case the git command is repeated
[ "$1" = "git" ] && shift
# Run the git command
git "$@"

10
bin/bin/main-or-master-branch Executable file
View file

@ -0,0 +1,10 @@
#!/bin/zsh
# Check if we should use the `main` or `master` branch for this repo.
# Prefer `main` to `master`.
if git show-ref --quiet origin/main || git rev-parse main &>/dev/null; then
echo main
else
echo master
fi

8
bin/bin/phpunit-or-pest Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [[ -f "vendor/bin/pest" ]]; then
echo "pest"
exit 0
fi
echo "phpunit"

31
bin/bin/tat Executable file
View file

@ -0,0 +1,31 @@
#!/bin/sh
#
# Attach or create tmux session named the same as current directory.
path_name="$(basename "$PWD" | tr . -)"
session_name=${1-$path_name}
not_in_tmux() {
[ -z "$TMUX" ]
}
session_exists() {
tmux has-session -t "=$session_name"
}
create_detached_session() {
(TMUX='' tmux new-session -Ad -s "$session_name")
}
create_if_needed_and_attach() {
if not_in_tmux; then
tmux new-session -As "$session_name"
else
if ! session_exists; then
create_detached_session
fi
tmux switch-client -t "$session_name"
fi
}
create_if_needed_and_attach