An Alternative to release-please: A Custom SemVer Workflow

Recently, I decided to create a custom semantic versioning GitHub workflow because my team was previously using Google’s release-please. Although release-please is good at automating the release workflow, it didn’t quite fit our needs. Specifically, we needed it to create a draft release instead of publishing a release immediately after the release PR was merged into our main branch. While release-please had a configuration option for draft releases, it proved to be buggy and behaved unexpectedly. Consequently, we couldn’t trust the automation to work as we needed. Additionally, we faced issues with our engineers remembering to use conventional commits, which the tool requires. Given these challenges, it made sense to develop our own custom tooling.

To address this, I worked on creating a custom semantic versioning workflow. The current iteration of the project involves two JS scripts:

  • bump_version.cjs – This script bumps the version of a repository’s package.json and package-lock.json based on the type of bump the user manually selects from the GitHub action (patch, minor, or major).
  • update_changelog.cjs – This script updates the CHANGELOG.md with commits since the last chore(release): PR was merged, as well as the date when the bump_version.yml action was run.

The workflow includes two main components:

  • bump_version.yml – This manual workflow is triggered when a user selects patch, minor, or major from a dropdown in the GitHub UI (this generates a release PR that bumps the package files and updates the CHANGELOG.md):
  • draft_release.yml – When the release PR is merged, this action is automatically run to create a draft release with the title and tag matching the new bumped version number.

How to keep GitHub Codeowners from getting removed from notifications when one team member reviews the PR

If you’re on a team that has branch protections in place that require more than one pull request (PR) approval, GitHub’s default behavior can cause notifications to be dismissed prematurely. For instance, let’s say a team is assigned to review a pull request, and one team member provides their approval. In this scenario, GitHub dismisses notifications for other team members, assuming that the pull request no longer requires additional review. However, this can be problematic if branch protections mandate multiple approvals. An effective workaround is to have the latest PR reviewer reassign the team for additional review. Yet, this manual process is prone to oversight. So, how can we automate this process?

A solution to this dilemma lies in setting up GitHub’s auto assignment feature. By enabling auto assignment, whenever your team is requested to review a PR, the system automatically removes the team as a reviewer and designates a specified subset of team members in its place. You can include your entire team in this subset to ensure everyone receives notifications without the need for manual reassignment. For detailed guidance on configuring your team’s settings for this automation, refer to the documentation provided here.

What are Symlinks?

Symlinks, short for symbolic links, are a powerful and versatile feature in the world of computing. Despite being an important piece of file systems, many users are unfamiliar with what symlinks are and how they can be utilized. Let’s take a look at what they are, how they work, and how they’re beneficial.

Overview of Symlinks

At its core, a symlink is a pointer to another file or directory. Unlike a hard link, which points directly to the data blocks of a file, a symlink acts as a reference to the target file or directory. This reference is essentially a path that allows users to access the target file or directory indirectly.

How Symlinks Work

Symlinks work by storing the path to the target file or directory. When a user accesses the symlink, the operating system transparently redirects the request to the actual file or directory specified by the symlink. This provides a level of abstraction and flexibility, allowing users to create symbolic links across different file systems and even on remote servers.

Benefits of Symlinks

  1. Space Efficiency: Symlinks help save disk space by creating references to files instead of duplicating them. This is particularly useful when dealing with large datasets or when multiple instances of the same file are required.
  2. Organizing Files: Symlinks allow users to organize their files in a more intuitive manner. For example, a user might create symlinks in their home directory pointing to frequently accessed files in deeper directories, making navigation more efficient.
  3. Cross-Platform Compatibility: Symlinks can be used to create cross-platform compatibility. If a file or directory needs to be accessed on both Windows and Linux systems, symlinks can be created to provide a seamless experience.
  4. Upgrading Software: Software updates often require replacing or modifying existing files. Symlinks can be used to switch between different versions of files, making the process of upgrading or downgrading software smoother and more manageable.
  5. Simplified File Maintenance: When dealing with complex directory structures, symlinks can simplify file maintenance. They allow users to create shortcuts to important files or directories, reducing the complexity of navigating through deep directory trees.

Creating Symlinks

Creating a symlink involves using the ln command in the terminal. For example, to create a symlink named link_to_file pointing to a file named target_file, the following command can be used:

ln -s /path/to/target_file link_to_file

Understanding Grep

What is grep?

grep stands for Global Regular Expression Print. In simpler terms, it’s a command-line utility that searches through text using regular expressions.

The Basics

At its core, grep is a master of finding patterns. Whether you’re searching for a specific word, a line of code, or a complex pattern, grep has got your back. Let’s take a look at the basics:

grep "search_term" file.txt

This simple command will scour file.txt for instances of “search_term” and print out the matching lines.

Regular Expressions

grep truly shines with regular expressions (regex) – you can create intricate patterns to match exactly what you need. Wildcards, quantifiers, and character classes – the regex world is vast, and grep has got your covered.

grep "^\d{3}-\d{2}-\d{4}$" data.txt

In this example, we’re searching for lines in data.txt that match the pattern of a social security number. The ^ and $ anchor the regex to the beginning and end of the line, ensuring an exact match.

Recursion

Imagine you have a project with nested folders, and you want to find where a particular function is being used:

grep -r "function_name" project_folder/

The -r flag tells grep to search recursively. It will traverse through folders, unveiling every file that contains the sacred “function_name.”

Filtering

grep isn’t just about finding; it’s about filtering too. Let’s say you want to find all JavaScript files containing the word “error,” but you’re not interested in the case:

grep -i "error" *.js

The -i flag makes the search case-insensitive, ensuring you catch all variations of “error.”

Counting and Beyond

Sometimes you just want the numbers. How many lines contain your search term? grep is on it:

grep -c "search_term" file.txt

And to go even further, combine grep with other commands by piping:

cat log.txt | grep "error" | wc -l

Here, we’re counting the lines with “error” in a log file. cat displays the file, grep finds the errors, and wc -l counts the lines.

Essential Command Line Commands for Developers

The command line interface (CLI) is a powerful tool that developers can leverage to streamline their workflow and boost productivity. While graphical user interfaces (GUIs) are user-friendly, the command line offers a more efficient and flexible way to interact with your computer.

Here are some useful commands to keep in your developer toolbox:

  1. Navigating the File System:
    • cd <directory>: Change directory. Use this command to navigate between folders (add the directory path in the place of <directory>).
    • ls: List the contents of a directory. Add options like -l for a detailed list or -a to show hidden files.
    • pwd: Print working directory. Prints the file path of the directory you’re currently in.
  2. File Operations:
    • cp: Copy files or directories. Helpful for duplicating files (use cp <file> <directory> to copy a file to a directory – which can possibly overwrite files).
    • mv: Move or rename files and directories (to move files, use mv <file> <directory>, to rename a file, use mv <file-old> <file-new>).
    • rm: Remove files or directories (rm <file> for files or rm -r <directory> for directories). Exercise caution, as this command is irreversible.
    • mkdir <directory>: Create a directory (add the name of the directory in place of <directory>).
    • touch <file>: Create a file (add the file name in place of <file>).
  3. Text Manipulation:
    • cat <file>: Concatenate and display the content of files.
    • grep: Search for specific patterns in files.
    • sed: Stream editor for filtering and transforming text.
  4. File Inspection:
    • file: Determine the file type.
    • wc: Count words, lines, and characters in a file.
    • head and tail: Display the beginning or end of a file.
  5. System Information:
    • df: Display disk space usage.
    • free: Display amount of free and used memory.
    • top and htop: Show real-time system statistics.
  6. Version Control:
    • git: Essential for version control. Commands like git clone, git pull, git push, and more are crucial for collaborative development.
  7. Package Management:
    • npm or yarn (for Node.js): Manage packages and dependencies for JavaScript projects.
    • pip (for Python): Install Python packages effortlessly.
  8. Network-related Commands:
    • ping: Test the reachability of a host.
    • curl and wget: Download files from the web directly in the terminal.
  9. Process Management:
    • ps: Display information about active processes.
    • kill: Terminate a process. Use with caution.
  10. User and Permissions:
    • sudo: Execute a command with superuser privileges.
    • chown and chmod: Change ownership and permissions of files.

Mastering the command line is a key skill for developers. These essential commands empower developers to perform a wide range of tasks efficiently and are particularly valuable in server environments and automation scripts. As you become more comfortable with the command line, you’ll discover its potential for enhancing your development workflow and troubleshooting capabilities.

GitHub Tips and Tricks

GitHub is a developer platform that allows developers to create, store, and manage their code. If you’re new to GitHub, or if you’ve been using it for years and have yet to look into shortcuts, I figured I’d share a few handy GitHub tips and tricks.

GitHub Keyboard Shortcuts

If you navigate to a repo page, and press ? (or Shift + /) GitHub will show you a modal with a list of quick keyboard shortcuts, as well as a link that displays all the available shortcuts.

GitHub shortcuts

One of my favorite ones to use is typing / or s when you’ve navigated to a repo page, since it allows you to pull up the file search.

Autodelete GitHub Branches

You can automatically delete GitHub branches after pull requests have been merged into your repo. This is great for managing your repo’s branches so that they don’t get out of control. To set up the automatic deletion of branches, navigate to the settings for a specific repo. On the “General” settings page, under the “Pull Requests” section, select “Automatically delete head branches.”

Link to Specific Lines of Code in a GitHub Repo

If you click the number beside a line of code, it will highlight the line and display a few menu options. If you click “Copy permalink” you’ll have access to a link that will link to the code snippet even if the file is later updated or deleted on the main branch. If you hold down shift while selecting lines of code, you can select several lines to highlight and link to.

Close GitHub Issues Automatically with Keywords in your Commit Messages

You can automatically close issues fixed by commits by using a keyword followed by the issue number in the commit message, i.e. git commit -m "fixes #30"

Here’s a list of all the keywords you can use to automatically close issues:

close #30
closed #30
closes #30
fix #30
fixed #30
fixes #30
resolve #30
resolved #30
resolves #30

Handy GitHub URLs

GitHub uses a few URL patterns that are handy for getting access to diffs of code or even user avatars:

  • Avatars: https://github.com/<username>.png
  • Diff of a commit: https://github.com/<repo-owner>/<repo>/commit/<sha>.diff
  • Patch of a commit: https://github.com/<repo-owner>/<repo>/commit/<sha>.patch
  • Diff of a pull request: https://github.com/<repo-owner>/<repo>/pull/<id>.diff
  • Patch of a pull request: https://github.com/<repo-owner>/<repo>/pull/<id>.patch
  • Latest release: https://github.com/<repo-owner>/<repo>/releases/latest

Format your GitHub README.md with Markdown

When visiting a GitHub repo, GitHub will serve the repo’s README.md. You can make these documents pretty elaborate by including a project overview, info on how to run the project locally, deployment info, etc. I like to use a project’s repo as the single source of truth for that project and keep it updated as processes change and as adjustments are made to various environments. You can find some basic markdown formatting info via GitHub’s documentation. One helpful tip for markdown formatting that I enjoy using, is that the <details></details> tags allow you to hide/show content via a simple accordion.

Host Web Pages and Simple Applications with GitHub Pages

You can host a GitHub Pages site for free via your GitHub account. This is great for simple websites and applications. You can even customize the domain URL, but the out-of-the-box URL is <your-github-username>.github.io/<repo-name>. You can find out more about using GitHub pages here.

Handy Git Commands

Git is a version control system that tracks changes in a set of files, which is used by developers to coordinate work done for various projects. Here are some useful git commands that can help with your workflow:

General Commands

  • git status to see what changes have been made locally
  • git add . to commit all locally edited files in the repo
  • git add <file> to add a file to the next commit
  • git rm <file> to delete a file in the next commit
  • git mv <file> to rename a file in the next commit
  • git add -p is useful if you’ve done too much work for one commit, ? is a useful command to print all
  • git cat-file -p <sha> prints the contents of blobs (sha refers to the SHA or hash that git assigns to each commit)
  • cat .git/HEAD to see what HEAD is pointing to

Stashing

Stashing your work is useful if you’re moving between branches when you’re in the middle of work. It’s a safe, non-destructive way to save your work.

  • git stash to save uncommitted work
  • git stash list to view the current stashes that are available
  • git stash apply applies the last stash
  • git stash apply stash@{0} to apply a specific stash (in place of the 0 you can add the number associated with the stash from your git stash list that you’d like to apply)
  • git stash --include-untracked to keep untracked files in your stash
  • git stash save "WIP: working on recent bug fix" in place of the text in the quotes here, you can add a name for easy references when viewing the list of your stashes and figuring out which stash to apply on your current branch
  • git checkout <stash name> -- <filename> grabs a single file form a stash
  • git stash show stash@{2} to show the files changed

Keeping Your Stash Clean

  • git stash drop to remove the last stash
  • git stash clear to remove all stashes

Logging

  • git log to view an overview of the most recent git commits
  • git log --since="yesterday" to view commits from a specific time period. You can also pass arguments like git log --since="2 weeks ago"
  • git log --name-status --follow -- <file> to log files that have been moved or renamed
  • git log --diff-filter=R --find-renames to find files that have been renamed
  • git show <commit> to show commit and its contents
  • git show <commit> --stat show files changed in commit
  • git show <commit>:<file> look at a file from another commit

Fixing Mistakes

  • git checkout -- <file-path> overwrites the working area file with the staging area version from the last commit (this operation overwrites without warning so use with caution)
  • git clean will clear your working area by deleting untracked files (this operation cannot be undone). Use the --dry-run flag to see what will get removed
  • git reset moves the HEAD pointer, modifies files for commits (can change history)
  • git revert <commit> is the safe reset (creates a new commit that introduces the opposite changes from the specified commit, the original commit stays in the repo)
  • git reset --hard HEAD use this command if your staging area has gotten really messed up and you want to blow away all of your local work
  • git commit --amend if you need to amend your latest commit
  • RERERE (Reuse Recorded Resolution) is a tool that remembers a previously used solution for a merge conflict. Set git config rerere.enabled true within a project to have access to this tool. You can also set it globally with git config --global rerere.enabled true

Rebasing

Rebasing your feature branch with main will help keep your development work up-to-date with the latest changes that have been committed to the repo. This will help make merge conflicts more manageable whenever it’s time to merge in your feature work.

  • git rebase main makes history of current branch cleaner and makes managing merge conflicts easier
  • git rebase -i <commit to fix> addressing specific commits

Working with GitHub

  • git clone <project reference to clone a GitHub repo locally
  • git pull performs a git fetch && git merge to ensure the latest version of a branch has been updated on your machine
  • git push origin <name-of-branch> to push any changes to the remote repo
  • git pull --rebase will fetch, update your local branch to a copy of the upstream branch, then replay any commits you made via rebase (this doesn’t work well on branches with local merge commits, it works best when branching off master and working on a feature)

Local Destructive Operations

When performing a destructive operation, make sure you properly stash your work so you don’t accidentally delete any of your work in progress. Use git stash --include-untracked to include working area changes in your stash.

  • git checkout --<file> if file is presnt in staging, it will be overwritten
  • git reset --hard overwrite changes that are staged and in working area
  • rebase, amend, and reset can rewrite history (if your code is hosted or shared, never run git push -f which forces a push of your changes)

Configuring Your Editor

To customize which editor git opens when you run a commit with no -m flag, a merge, or rebase, run the following command:

git config --global core.editor <your_editor>

In the place of <your_editor>, add the command associated with your editor of choice:

  • atom: atom --wait
  • emacs: emacs
  • sublime: subl -n -w
  • vi: vi or vim
  • vscode: code --wait