Using fledge


This guide will demonstrate how to use {fledge}, using a mock R package as an example. We are going to call our package "{tea}". We will develop it from scratch and also maintain a changelog as the development progresses. Finally, we will demonstrate how this changelog can eventually be converted to release notes when the package is submitted to CRAN.

:::{.alert .alert-info} We are typing this demo as an R Markdown vignette therefore we will be using R tools for creating files, editing them, and interacting with git: in real life you can be using e.g. an IDE or the command line. The fledge package won't care! :::

in_pkgdown <- identical(Sys.getenv("IN_PKGDOWN"), "true")
if (in_pkgdown) {
  options(crayon.enabled = TRUE)
options(cli.num_colors = 1)
  collapse = TRUE,
  error = !in_pkgdown

Set up the development environment

Before we start development for {tea}, we set up the basic development environment required for any typical R package.

Create a package

We will start by creating a new package. For this demo, the package is created in a temporary directory. A real project will live somewhere in your home directory.

The usethis::create_package() function sets up a package project ready for development.

pkg <- usethis::create_package("tea")
parent_dir <- file.path(tempdir(), "fledge")
dir.create(parent_dir, recursive = TRUE)
dir.create(file.path(parent_dir, "remote"))
gert::git_init(file.path(parent_dir, "remote"), bare = TRUE)
pkg <- fledge::create_demo_project("tea", open = FALSE, dir = parent_dir)

In an interactive RStudio session, a new window opens and you would work there. Users of other environments would change the working directory manually. For this demo, we manually set the active project.

knitr::opts_knit$set(root.dir = pkg)
  list("usethis.quiet" = TRUE), 

The infrastructure files and directories that comprise a minimal R package are created:


Create and configure a Git repository

Next, one would set up this package for development and create a Git repository for the package. We achieved this with gert code.

You could use usethis::use_git() function that creates an initial commit, and the repository is in a clean state.

# Number of commits until now
# Anything staged?

For working in branches, it is recommended to turn off fast-forward merging:

gert::git_config_set("merge.ff", "false")
# gert::git_config_global_set("merge.ff", "false") # to set globally

An alternative is to use squash commits.

We also set up a git remote. In real life you might be using a function like usethis::use_github(). We set up a local remote using a git repo we secretly created earlier.

# In real life this would be an actual URL not a filepath :-)
remote_url <- file.path(parent_dir, "remote")
gert::git_remote_add(remote_url, name = "origin")
gert::git_push(remote = "origin")

We create two functions to show the contents and tags of the remote. In real life, you'd probably simply browse the GitHub interface for instance!

show_files <- function(remote_url) {
  tempdir_remote <- withr::local_tempdir(pattern = "remote")
  withr::with_dir(tempdir_remote, {
    repo <- gert::git_clone(remote_url)  
    suppressMessages(gert::git_branch_checkout("main", force = TRUE, repo = "remote"))
    fs::dir_ls("remote", recurse = TRUE)

show_tags <- function(remote_url) {
  tempdir_remote <- withr::local_tempdir(pattern = "remote")
  withr::with_dir(tempdir_remote, {
    # Only show name and ref
    gert::git_tag_list(repo = "remote")[, c("name", "ref")]

Create initial file

An initial NEWS file can be created with usethis::use_news_md().


Let's take a look at the contents:

news <- readLines(usethis::proj_path(""))
cat(news, sep = "\n")

This file needs to be tracked by Git:

gert::git_commit("Initial .")
gert::git_push(remote = "origin")

:::{.alert .alert-info} Note that we have done nothing fledge specific yet. :::

The development phase

Create an R file

Now we start coding in the functionality for the package. We start by creating the new R file called cup.R and adding code (well only a comment).

writeLines("# cup", "R/cup.R")

We commit this file to Git with a relevant message.

:::{.alert .alert-info} That is our first fledge specific step! Note the use of the bullet (-) at the beginning of the commit message. This indicates that the message should be included in when it is updated. :::

It does not matter how and where you type the commit message (gert in R, RStudio Git window, command line, VSCode, etc.). What's important is the content of the commit message.

gert::git_commit("- New cup_of_tea() function makes it easy to drink a cup of tee.")

Create a test

The code in cup.R warrants a test (at least it would if it were actual code!):

cat(readLines("tests/testthat/test-cup.R"), sep = "\n")

In a real project we would substitute the testing code from the template by real tests. In this demo we commit straight away, again with a bulleted message.

gert::git_commit("- Add tests for cup of tea.")


Let us look at the commit history until now. You might use any Git tool you want to consult it, we use gert.

# Only show number of files, messages

We have two "bulletted" messages which for fledge means two NEWS-worthy messages.

Let us update!

We use fledge::bump_version() to assign a new dev version number to the package and also update

The current version number of our package is r desc::desc_get_version().


The new version number is r desc::desc_get_version().

:::{.alert .alert-info} If you run fledge::bump_version() too early by mistake (e.g. you wanted to do one more code edit), you can run fledge::unbump_version()! This should happen immediately after bumping. If you have pushed or edited code in the meantime, it's too late -- just continue and assign a new version when you are done with the edits. :::


Let us see what looks like after that bump.

news <- readLines("")
cat(news, sep = "\n")

While reviewing we notice that there was a typo in one of the comments (congrats if you noticed right away that we typed "tee" instead of "tea"!).

:::{.alert .alert-warning} The fledge package adds a comment about not editing by hand to but actually you can... if you do it right! Read on. :::

Let's fix the typo, which you'd do by hand.

news <- gsub("tee", "tea", news)
cat(news, sep = "\n")
writeLines(news, "")

This does not affect the original commit message, only (Editing commit messages is not something fledge supports).

Finalize version

:::{.alert .alert-warning} After tweaking, it is important to use fledge::finalize_version() and not to commit manually. :::

Using fledge::finalize_version() instead of committing manually ensures that the tag is set to the correct version in spite of the update. It should be called when is manually updated. Note that it should be called after fledge::bump_version(), an error is raised if another commit has been added after that.

fledge::finalize_version(push = TRUE)

Let's look at now:

news <- readLines("")
cat(news, sep = "\n")

The version of the package is r desc::desc_get_version().

A tag has been created for the version which is good practice, and crucial when using fledge: for updating the changelog, fledge looks through all commit messages since the latest tag.

Change code and commit

{tea} with cup is tested, now we want to enhance with bowl. This requires changes to the code, and perhaps a new test. We create a branch (whose name starts with a "f" for "feature") and switch to this branch to implement this.

gert::git_branch_create("f-bowl", checkout = TRUE)

On the branch, separate commits are used for the tests and the implementation. These commit messages do not need to be formatted specially, because {fledge} will ignore them anyway.

This time we write the tests first, test-driven development.

gert::git_commit("Add bowl tests.")
writeLines("# bowl of tea", "R/bowl.R")
gert::git_commit("Add bowl implementation.")

This branch can be pushed to the remote as usual. Only when merging we specify the message to be included in the changelog, again with a bullet.[^merge-ff] You might be used to doing the merges on a remote (e.g. GitHub pull requests) but here we demonstrate a local merge commit.

[^merge-ff]: Note that we really need a merge commit here; the default is to fast-forward which doesn't give us the opportunity to insert the message intended for the changelog. Earlier, we set the merge.ff config option to "false" to achieve this.

gert::git_merge("f-bowl", commit = FALSE)
gert::git_commit("- New bowl_of_tea() function makes it easy to drink a bowl of tea.")

The same strategy can be used when merging a pull/merge/... request on GitHub, GitLab, ...: use bullet points in the merge commit message to indicate the items to include in

Now that we have added bowl support to our package, it is time to bump the version again.

news <- readLines("")
fledge::finalize_version(push = TRUE)

It seems we do not even need to amend the by hand this time as we made no typo!

Prepare for release

After multiple cycles of development, review and testing, we decide that our package is ready for release to CRAN. This is where {fledge} simplifies the release process by making use of the all the commit messages that we provided earlier.

A difference between publishing on CRAN and publishing on GitHub is that there's an external system controlling your publication on CRAN so your package might need some tweaks between the first release candidate and what version you end up publishing on CRAN.

Bump version for release

We wish to release this package as a patch and so we use fledge::bump_version() with the "patch" argument. Other values for the arguments are "dev" (default), "minor" and "major".


This updates the version of our package to r desc::desc_get_version().

Generate release notes

We review the that were generated by {fledge}:

news <- readLines("")
cat(news, sep = "\n")

Some of the intermediate commit messages are not relevant in the release notes for this release. We need to edit to convert the changelog to meaningful release notes. E.g. in real life we might re-organize bullets.

:::{.alert .alert-warning} Unlike with development versions, we commit the changes to manually, not with a fledge function. :::

The package is now ready to be released to CRAN. I prefer devtools::use_release_issue() to create a checklist of things to do before release, and devtools::submit_cran() to submit. The devtools::release() function is a more verbose alternative.

After release

Some time passed and our {tea} package was accepted on CRAN. At this stage, {fledge} can help to tag the released version and create a new version for development.

Tag version

It is now the time to tag the released version using the fledge::tag_version() function.


It is advised to push to remote, with git push --tags from the command line, or your favorite Git client.

Create GitHub release

If your package is hosted on GitHub, usethis::use_github_release() creates a draft GitHub release from the contents already in You need to submit the draft release from the GitHub release page.

Restart development

We will now make the package ready for future development. The fledge::bump_version() takes care of it.

news <- readLines("")

Push to remote, add features with relevant commits (after mergining a branch or not), bump_version(), etc. Happy development, and happy smooth filling of the changelog!

knitr::opts_knit$set(root.dir = dirname(knitr::current_input(dir = TRUE)))
unlink(parent_dir, recursive = TRUE)

Try the fledge package in your browser

Any scripts or data that you put into this service are public.

fledge documentation built on July 9, 2023, 7:41 p.m.