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.
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!
Before we start development for {tea}, we set up the basic development environment required for any typical R 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.
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.
The infrastructure files and directories that comprise a minimal R package are created:
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
nrow(gert::git_log())
## [1] 1
# Anything staged?
gert::git_status()
## # A tibble: 0 × 3
## # ℹ 3 variables: file <chr>, status <chr>, staged <lgl>
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.
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_files(remote_url)
## remote/DESCRIPTION remote/NAMESPACE remote/tea.Rproj
show_tags <- function(remote_url) {
tempdir_remote <- withr::local_tempdir(pattern = "remote")
withr::with_dir(tempdir_remote, {
gert::git_clone(remote_url)
# Only show name and ref
gert::git_tag_list(repo = "remote")[, c("name", "ref")]
})
}
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("NEWS.md"))
cat(news, sep = "\n")
## # tea (development version)
##
## * Added a `NEWS.md` file to track changes to the package.
This file needs to be tracked by Git:
Note that we have done nothing fledge specific yet.
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).
We commit this file to Git with a relevant message.
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 NEWS.md
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.
The code in cup.R
warrants a test (at least it would if
it were actual code!):
usethis::use_test("cup")
## ✔ Adding testthat to 'Suggests' field in DESCRIPTION.
## ✔ Adding "3" to 'Config/testthat/edition'.
## ✔ Creating 'tests/testthat/'.
## ✔ Writing 'tests/testthat.R'.
## ✔ Writing 'tests/testthat/test-cup.R'.
## ☐ Edit 'tests/testthat/test-cup.R'.
cat(readLines("tests/testthat/test-cup.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.
Let us look at the commit history until now. You might use any Git tool you want to consult it, we use gert.
files | merge | message |
---|---|---|
3 | FALSE | - Add tests for cup of tea. |
1 | FALSE | - New cup_of_tea() function makes it easy to drink a cup of tee. |
1 | FALSE | Initial NEWS.md . |
5 | FALSE | First commit |
We have two “bulletted” messages which for fledge means two NEWS-worthy messages.
Let us update NEWS.md
!
We use fledge::bump_version()
to assign a new dev
version number to the package and also update NEWS.md
.
The current version number of our package is 0.0.0.9000.
fledge::bump_version()
## → Digesting messages from 4 commits.
## ✔ Found 2 NEWS-worthy entries.
##
## ── Updating NEWS ──
##
## → Adding new entries to 'NEWS.md'.
##
## ── Updating Version ──
##
## ✔ Package version bumped to 0.0.0.9001.
## → Added header to 'NEWS.md'.
## → Committing changes.
##
## ── Tagging Version ──
##
## → Creating tag v0.0.0.9001 with tag message derived from 'NEWS.md'.
## ! Run `fledge::finalize_version(push = TRUE)`.
The new version number is 0.0.0.9001.
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 NEWS.md
looks like after that bump.
news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://fledge.cynkra.com, contributors should not edit this file -->
##
## # tea 0.0.0.9001
##
## - Add tests for cup of tea.
## - New cup_of_tea() function makes it easy to drink a cup of tee.
## - 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 (congrats if you noticed right away that we typed “tee” instead of “tea”!).
The fledge package adds a comment about not editing
NEWS.md
by hand to NEWS.md
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")
## <!-- NEWS.md is maintained by https://fledge.cynkra.com, contributors should not edit this file -->
##
## # tea 0.0.0.9001
##
## - Add tests for cup of tea.
## - New cup_of_tea() function makes it easy to drink a cup of tea.
## - 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
. (Editing
commit messages is not something fledge supports).
After tweaking NEWS.md
, 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 NEWS.md
update. It should be called when
NEWS.md
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.
show_tags(remote_url)
## # A tibble: 0 × 2
## # ℹ 2 variables: name <chr>, ref <chr>
fledge::finalize_version(push = TRUE)
## → Resetting to previous commit.
## → Committing changes.
##
## ── Tagging Version ──
##
## → Deleting existing tag v0.0.0.9001.
## → Creating tag v0.0.0.9001 with tag message derived from 'NEWS.md'.
## → Pushing main.
## → Force-pushing tag v0.0.0.9001.
show_tags(remote_url)
## # A tibble: 1 × 2
## name ref
## <chr> <chr>
## 1 v0.0.0.9001 refs/tags/v0.0.0.9001
Let’s look at NEWS.md now:
news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://fledge.cynkra.com, contributors should not edit this file -->
##
## # tea 0.0.0.9001
##
## - Add tests for cup of tea.
## - New cup_of_tea() function makes it easy to drink a cup of tea.
## - Added a `NEWS.md` file to track changes to the package.
The version of the package is 0.0.0.9001.
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.
{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.
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.
usethis::use_test("bowl")
## ✔ Writing 'tests/testthat/test-bowl.R'.
## ☐ Edit 'tests/testthat/test-bowl.R'.
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 You might be used to doing the merges on a remote (e.g. GitHub pull requests) but here we demonstrate a local merge commit.
gert::git_branch_checkout("main")
gert::git_merge("f-bowl", commit = FALSE)
## Merge was not committed due to merge conflict(s). Please fix and run git_commit() or git_merge_abort()
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 NEWS.md
.
Now that we have added bowl support to our package, it is time to bump the version again.
fledge::bump_version()
## → Digesting messages from 2 commits.
## ✔ Found 1 NEWS-worthy entry.
##
## ── Updating NEWS ──
##
## → Adding new entries to 'NEWS.md'.
##
## ── Updating Version ──
##
## ✔ Package version bumped to 0.0.0.9002.
## → Added header to 'NEWS.md'.
## → Committing changes.
##
## ── Tagging Version ──
##
## → Creating tag v0.0.0.9002 with tag message derived from 'NEWS.md'.
## ! Run `fledge::finalize_version(push = TRUE)`.
news <- readLines("NEWS.md")
writeLines(news)
## <!-- NEWS.md is maintained by https://fledge.cynkra.com, contributors should not edit this file -->
##
## # tea 0.0.0.9002
##
## - New bowl_of_tea() function makes it easy to drink a bowl of tea.
##
##
## # tea 0.0.0.9001
##
## - Add tests for cup of tea.
## - New cup_of_tea() function makes it easy to drink a cup of tea.
## - Added a `NEWS.md` file to track changes to the package.
fledge::finalize_version(push = TRUE)
## → Resetting to previous commit.
## → Committing changes.
##
## ── Tagging Version ──
##
## ℹ Tag v0.0.0.9002 exists and points to the current commit.
## → Pushing main.
## → Force-pushing tag v0.0.0.9002.
It seems we do not even need to amend the NEWS.md
by
hand this time as we made no typo!
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.
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”.
fledge::bump_version("patch")
## → Digesting messages from 1 commits.
## ℹ Same as previous version.
##
## ── Updating NEWS ──
##
## → Adding new entries to 'NEWS.md'.
##
## ── Updating Version ──
##
## ✔ Package version bumped to 0.0.1.
## → Added header to 'NEWS.md'.
## → Committing changes.
##
## ── Preparing package for CRAN release ──
##
## • Convert the change log in 'NEWS.md' to release notes.
This updates the version of our package to 0.0.1.
We review the NEWS.md
that were generated by
{fledge}:
news <- readLines("NEWS.md")
cat(news, sep = "\n")
## <!-- NEWS.md is maintained by https://fledge.cynkra.com, contributors should not edit this file -->
##
## # tea 0.0.1
##
## - Same as previous version.
##
##
## # tea 0.0.0.9002
##
## - New bowl_of_tea() function makes it easy to drink a bowl of tea.
##
##
## # tea 0.0.0.9001
##
## - Add tests for cup of tea.
## - New cup_of_tea() function makes it easy to drink a cup of tea.
## - 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. E.g. in real life we
might re-organize
bullets.
Unlike with development versions, we commit the changes to
NEWS.md
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.
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.
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'.
show_tags(remote_url)
## # A tibble: 2 × 2
## name ref
## <chr> <chr>
## 1 v0.0.0.9001 refs/tags/v0.0.0.9001
## 2 v0.0.0.9002 refs/tags/v0.0.0.9002
It is advised to push to remote, with git push --tags
from the command line, or your favorite Git client.
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.
We will now make the package ready for future development. The
fledge::bump_version()
takes care of it.
fledge::bump_version()
## → Digesting messages from 1 commits.
## ℹ Same as previous version.
##
## ── Updating NEWS ──
##
## → Adding new entries to 'NEWS.md'.
##
## ── Updating Version ──
##
## ✔ Package version bumped to 0.0.1.9000.
## → Added header to 'NEWS.md'.
## → Committing changes.
##
## ── Tagging Version ──
##
## → Creating tag v0.0.1.9000 with tag message derived from 'NEWS.md'.
## ! Run `fledge::finalize_version(push = TRUE)`.
news <- readLines("NEWS.md")
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!
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.↩︎