Introduction

This guide will demonstrate how to use {fledge}, using a mock R package as an example. We are going to call our package “{SuperFrob}”. 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.

Set up the development environment

Before we start development for {SuperFrob}, 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.

tempdir <- tempfile("fledge")
dir.create(tempdir)

The usethis::create_package() function sets up a package project ready for development. The output shows the details of the package created.

pkg <- usethis::create_package(file.path(tempdir, "SuperFrob"))
##  Creating '/tmp/Rtmpv1KxcO/fledge55204bb24e1f/SuperFrob/'
##  Setting active project to '/tmp/Rtmpv1KxcO/fledge55204bb24e1f/SuperFrob'
##  Creating 'R/'
##  Writing 'DESCRIPTION'
## Package: SuperFrob
## Title: What the Package Does (One Line, Title Case)
## Version: 0.0.0.9000
## Authors@R (parsed):
##     * First Last <first.last@example.com> [aut, cre] (YOUR-ORCID-ID)
## Description: What the package does (one paragraph).
## License: `use_mit_license()`, `use_gpl3_license()` or friends to
##     pick a license
## Encoding: UTF-8
## LazyData: true
## Roxygen: list(markdown = TRUE)
## RoxygenNote: 7.0.0
##  Writing 'NAMESPACE'
##  Setting active project to '<no active project>'

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

usethis::proj_set()
##  Setting active project to '/tmp/Rtmpv1KxcO/fledge55204bb24e1f/SuperFrob'

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

fs::dir_tree()
## .
## ├── DESCRIPTION
## ├── NAMESPACE
## └── R

Create and configure a Git repository

Next we set up this package for development and create a Git repository for the package.

usethis::use_git()
##  Initialising Git repo
##  Adding '.Rproj.user', '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store' to '.gitignore'

In interactive mode, the usethis::use_git() function creates an initial commit, and the repository is in a clean state. In the demo this needs to be carried out manually.

git2r::add(path = ".")
git2r::commit(message = "Initial commit.")
## [89b5632] 2021-03-08: Initial commit.
git2r::commits()
## [[1]]
## [89b5632] 2021-03-08: Initial commit.
git2r::status()
## working directory clean

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

git2r::config(merge.ff = "false")

An alternative is to use squash commits.

Create initial NEWS.md file

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

usethis::use_news_md()
##  Writing 'NEWS.md'

Let’s take a look at the contents:

news <- readLines(usethis::proj_path("NEWS.md"))
cat(news, sep = "\n")
## # SuperFrob 0.0.0.9000
## 
## * Added a `NEWS.md` file to track changes to the package.

This file needs to be tracked by Git:

git2r::add(path = ".")
git2r::status()
## Staged changes:
##  New:        NEWS.md
git2r::commit(message = "Initial NEWS.md .")
## [9745a6d] 2021-03-08: Initial NEWS.md .

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 super.R and adding frobnication code.

usethis::use_r("super")
##  Edit 'R/super.R'
##  Call `use_test()` to create a matching test file
writeLines("# frobnicate", "R/super.R")

We commit this file to Git with a relevant message. Note the use of the bullet (-) before the commit message. This indicates that the message should be included in NEWS.md when it is updated.

git2r::add(path = ".")
git2r::commit(message = "- Add support for frobmication.")
## [9a3360e] 2021-03-08: - Add support for frobmication.

Create a test

The code in super.R warrants a test:

usethis::use_test("super")
##  Adding 'testthat' to Suggests field in DESCRIPTION
##  Setting Config/testthat/edition field in DESCRIPTION to '3'
##  Creating 'tests/testthat/'
##  Writing 'tests/testthat.R'
##  Writing 'tests/testthat/test-super.R'
##  Edit 'tests/testthat/test-super.R'
cat(readLines("tests/testthat/test-super.R"), sep = "\n")
## test_that("multiplication works", {
##   expect_equal(2 * 2, 4)
## })

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.

git2r::add(path = ".")
git2r::status()
## Staged changes:
##  Modified:   DESCRIPTION
##  New:        tests/testthat.R
##  New:        tests/testthat/test-super.R
git2r::commit(message = "- Add tests for frobnication.")
## [7d402a5] 2021-03-08: - Add tests for frobnication.

Update NEWS.md

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

fledge::bump_version()
## → Scraping 4 commit messages.
##  Found 2 NEWS-worthy entries.
## 
## ── Updating NEWS ──
## 
## → Adding new entries to NEWS.md.
## 
## ── Update Version ──
## 
##  Package version bumped to 0.0.0.9001.
## → Adding header to NEWS.md.
## → Committing changes.
## 
## ── Tagging Version ──
## 
## → Creating tag v0.0.0.9001 with tag message derived from NEWS.md.
##  Edit 'NEWS.md'
## ! Call `fledge::finalize_version()`.
## NULL

Review NEWS.md

Let us see what NEWS.md looks like after that bump.

news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://cynkra.github.io/fledge, do not edit -->
## 
## # SuperFrob 0.0.0.9001
## 
## - Add tests for frobnication.
## - Add support for frobmication.
## 
## 
## # SuperFrob 0.0.0.9000
## 
## * Added a `NEWS.md` file to track changes to the package.

While reviewing we notice that there was a typo in one of the comments. Let’s fix the typo:

news <- gsub("frobmication", "frobnication", news)
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://cynkra.github.io/fledge, do not edit -->
## 
## # SuperFrob 0.0.0.9001
## 
## - Add tests for frobnication.
## - Add support for frobnication.
## 
## 
## # SuperFrob 0.0.0.9000
## 
## * Added a `NEWS.md` file to track changes to the package.
writeLines(news, "NEWS.md")

This does not affect the original commit message, only NEWS.md.

Finalize version

After tweaking NEWS.md, it is important to use fledge::finalize_version() and not to commit manually. This ensures that the tag is set to the correct version in spite of the NEWS.md update. It should be called every time NEWS.md is manually updated.

## → Resetting to previous commit.
## → Committing changes.
## 
## ── Tagging Version ──
## 
## → Deleting tag v0.0.0.9001.
## → Creating tag v0.0.0.9001 with tag message derived from NEWS.md.

When done, we push to the remote (GitHub or another Git service) with tags using fledge::finalize_version(push = TRUE), as advised. In this demo, no remote repository is set up, and the push process is not shown.

Change code and commit

{SuperFrob} with frobnication is tested, now we want to enhance with super-frob. This requires changes to the code, and perhaps a new test. We create a branch and switch to this branch to implement this.

git2r::branch_create(git2r::last_commit(), name = "f-super-frob")
git2r::checkout(branch = "f-super-frob")

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.

usethis::use_test("frob")
##  Writing 'tests/testthat/test-frob.R'
##  Edit 'tests/testthat/test-frob.R'
git2r::add(path = ".")
git2r::status()
## Staged changes:
##  New:        tests/testthat/test-frob.R
git2r::commit(message = "Add super-frob tests.")
## [4526096] 2021-03-08: Add super-frob tests.
usethis::use_r("frob")
##  Edit 'R/frob.R'
##  Call `use_test()` to create a matching test file
writeLines("# super-frob", "R/frob.R")
git2r::add(path = ".")
git2r::status()
## Staged changes:
##  New:        R/frob.R
git2r::commit(message = "Add super-frob implementation.")
## [9258b13] 2021-03-08: Add super-frob 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.1

git2r::checkout(branch = "master")
git2r::merge(".", "f-super-frob", commit_on_success = FALSE)
## Merge
git2r::status()
## Staged changes:
##  New:        R/frob.R
##  New:        tests/testthat/test-frob.R
git2r::commit(message = "- Super-frobnication enabled.")
## [9ff4840] 2021-03-08: - Super-frobnication enabled.

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 NEWS.md.

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

fledge::bump_version()
## → Scraping 2 commit messages.
##  Found 1 NEWS-worthy entries.
## 
## ── Updating NEWS ──
## 
## → Adding new entries to NEWS.md.
## 
## ── Update Version ──
## 
##  Package version bumped to 0.0.0.9002.
## → Adding header to NEWS.md.
## → Committing changes.
## 
## ── Tagging Version ──
## 
## → Creating tag v0.0.0.9002 with tag message derived from NEWS.md.
##  Edit 'NEWS.md'
## ! Call `fledge::finalize_version()`.
## NULL
news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://cynkra.github.io/fledge, do not edit -->
## 
## # SuperFrob 0.0.0.9002
## 
## - Super-frobnication enabled.
## 
## 
## # SuperFrob 0.0.0.9001
## 
## - Add tests for frobnication.
## - Add support for frobnication.
## 
## 
## # SuperFrob 0.0.0.9000
## 
## * Added a `NEWS.md` file to track changes to the package.

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.

Bump version for release

We wish to release this package as a patch and so we use fledge::bump_version() with the “patch” argument.

fledge::bump_version("patch")
## → Scraping 1 commit messages.
##  Found 1 NEWS-worthy entries.
## 
## ── Updating NEWS ──
## 
## → Adding new entries to NEWS.md.
## 
## ── Update Version ──
## 
##  Package version bumped to 0.0.1.
## → Adding header to NEWS.md.
## → Committing changes.
##  Preparing package for release (CRAN or otherwise).
##  Edit 'NEWS.md'
## ! Convert the change log in NEWS.md to release notes.
## ! After CRAN release, call `fledge::tag_version()` and
## `fledge::bump_version()` to re-enter development mode

This updates the version of our package to 0.0.4.

Generate release notes

We review the NEWS.md that were generated by {fledge}:

news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://cynkra.github.io/fledge, do not edit -->
## 
## # SuperFrob 0.0.1
## 
## - Same as previous version.
## 
## 
## # SuperFrob 0.0.0.9002
## 
## - Super-frobnication enabled.
## 
## 
## # SuperFrob 0.0.0.9001
## 
## - Add tests for frobnication.
## - Add support for frobnication.
## 
## 
## # SuperFrob 0.0.0.9000
## 
## * Added a `NEWS.md` file to track changes to the package.

Some of the intermediate commit messages are not relevant in the release notes for this release. We need to edit NEWS.md to convert the changelog to meaningful release notes.

length(news) <- 5
news[3:5] <- c(
  "Initial CRAN release.",
  "",
  "Basic functionality: Super-frobnication."
)
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://cynkra.github.io/fledge, do not edit -->
## 
## Initial CRAN release.
## 
## Basic functionality: Super-frobnication.
writeLines(news, "NEWS.md")

Unlike with development versions, we commit the changes to NEWS.md manually:

git2r::add(path = ".")
git2r::status()
## Staged changes:
##  Modified:   NEWS.md
git2r::commit(message = "Update NEWS.")
## [efb4962] 2021-03-08: Update NEWS.

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 {SuperFrob} 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.

fledge::tag_version()
## 
## ── Tagging Version ──
## 
## → Creating tag v0.0.1 with tag message derived from NEWS.md.

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 NEWS.md. 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.

fledge::bump_version()
## → Scraping 1 commit messages.
##  Found 1 NEWS-worthy entries.
## 
## ── Updating NEWS ──
## 
## → Adding new entries to NEWS.md.
## 
## ── Update Version ──
## 
##  Package version bumped to 0.0.1.9000.
## → Adding header to NEWS.md.
## → Committing changes.
## 
## ── Tagging Version ──
## 
## → Creating tag v0.0.1.9000 with tag message derived from NEWS.md.
##  Edit 'NEWS.md'
## ! Call `fledge::finalize_version()`.
## NULL
news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://cynkra.github.io/fledge, do not edit -->
## 
## # SuperFrob 0.0.1.9000 (2021-03-08)
## 
## - Same as previous version.
## 
## 
## Initial CRAN release.
## 
## Basic functionality: Super-frobnication.

Push to remote.


  1. 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.↩︎