@@ -212,7 +212,13 @@ git_ls <- function(repo = '.', ref = NULL) {
212212# ' @param after date or timestamp: only include commits starting this date
213213# ' @param path character vector with paths to filter on; only commits that
214214# ' touch these paths are included
215- git_log <- function (ref = " HEAD" , max = 100 , after = NULL , path = NULL , repo = " ." ) {
215+ git_log <- function (
216+ ref = " HEAD" ,
217+ max = 100 ,
218+ after = NULL ,
219+ path = NULL ,
220+ repo = " ."
221+ ) {
216222 repo <- git_open(repo )
217223 ref <- as.character(ref )
218224 max <- as.integer(max )
@@ -233,6 +239,120 @@ git_stat_files <- function(files, ref = "HEAD", max = NULL, repo = '.') {
233239 .Call(R_git_stat_files , repo , files , ref , max )
234240}
235241
242+ # ' Revert a commit
243+ # '
244+ # ' Applies the inverse of the changes introduced by a given commit, equivalent
245+ # ' to `git revert <commit>`. The commit must be reachable from the current HEAD.
246+ # '
247+ # ' By default, a new revert commit is created immediately. Set `no_commit = TRUE`
248+ # ' to only stage the reverted changes without committing, leaving you free to
249+ # ' amend or combine them before calling [git_commit()].
250+ # '
251+ # ' @export
252+ # ' @name git_revert
253+ # ' @rdname git_revert
254+ # ' @family git
255+ # ' @inheritParams git_open
256+ # ' @inheritParams git_commit
257+ # ' @param commit a commit reference (SHA, branch name, or revision expression
258+ # ' such as `HEAD~1`) identifying the commit to revert. Must be an ancestor of
259+ # ' the current HEAD.
260+ # ' @param no_commit if `TRUE`, stage the reverted changes without creating a
261+ # ' commit. Default is `FALSE`, that is to say, by default a commit is made.
262+ # ' @param message a commit message. Similar default to `git revert`.
263+ # ' @return The SHA of the new revert commit (invisibly), or `NULL` when
264+ # ' `no_commit = TRUE`.
265+ # ' @examples
266+ # ' repo <- file.path(tempdir(), "myrepo")
267+ # ' git_init(repo)
268+ # '
269+ # ' # Set a user if no default
270+ # ' if (!user_is_configured()) {
271+ # ' git_config_set("user.name", "Jerry")
272+ # ' git_config_set("user.email", "jerry@gmail.com")
273+ # ' }
274+ # '
275+ # ' writeLines("hello", file.path(repo, "hello.txt"))
276+ # ' git_add("hello.txt", repo = repo)
277+ # ' git_commit("First commit", repo = repo)
278+ # '
279+ # ' writeLines("world", file.path(repo, "hello.txt"))
280+ # ' git_add("hello.txt", repo = repo)
281+ # ' bad_commit <- git_commit("Second commit", repo = repo)
282+ # '
283+ # ' # Default: revert and commit with an auto-generated message
284+ # ' git_revert(bad_commit, repo = repo)
285+ # ' git_log(repo = repo)
286+ # '
287+ # ' # Revert with a custom message
288+ # ' writeLines("oops", file.path(repo, "hello.txt"))
289+ # ' git_add("hello.txt", repo = repo)
290+ # ' bad_commit2 <- git_commit("Third commit", repo = repo)
291+ # ' git_revert(bad_commit2, message = "Undo third commit\n", repo = repo)
292+ # ' git_log(repo = repo)
293+ # '
294+ # ' # Stage the revert without committing
295+ # ' writeLines("again", file.path(repo, "hello.txt"))
296+ # ' git_add("hello.txt", repo = repo)
297+ # ' bad_commit3 <- git_commit("Fourth commit", repo = repo)
298+ # ' git_revert(bad_commit3, no_commit = TRUE, repo = repo)
299+ # ' git_status(repo = repo)
300+ # '
301+ # ' unlink(repo, recursive = TRUE)
302+ # ' @useDynLib gert R_git_revert
303+ git_revert <- function (
304+ commit ,
305+ message = NULL ,
306+ author = NULL ,
307+ committer = NULL ,
308+ no_commit = FALSE ,
309+ repo = ' .'
310+ ) {
311+ repo <- git_open(repo )
312+ assert_string(commit )
313+ stopifnot(is.logical(no_commit ), length(no_commit ) == 1 )
314+
315+ sha <- try(git_commit_id(commit , repo = repo ), silent = TRUE )
316+ if (inherits(sha , " try-error" )) {
317+ stop(sprintf(
318+ " Can't find commit '%s' in the current branch history" ,
319+ commit
320+ ))
321+ }
322+
323+ head_sha <- git_commit_id(" HEAD" , repo = repo )
324+ sha_descends_from_head <- git_commit_descendant_of(
325+ ancestor = sha ,
326+ ref = " HEAD" ,
327+ repo = repo
328+ )
329+ if (sha != head_sha && ! sha_descends_from_head ) {
330+ stop(sprintf(" commit '%s' is not in the current branch history" , commit ))
331+ }
332+
333+ .Call(R_git_revert , repo , sha )
334+
335+ if (no_commit ) {
336+ return (NULL )
337+ }
338+ if (! length(message )) {
339+ info <- git_commit_info(sha , repo = repo )
340+ subject <- strsplit(info $ message , " \n " )[[1 ]][1 ]
341+ subject <- sub(" \\ s+$" , " " , subject )
342+ message <- sprintf(
343+ ' Revert "%s"\n\n This reverts commit %s.\n ' ,
344+ subject ,
345+ info $ id
346+ )
347+ }
348+ invisible (git_commit(
349+ message ,
350+ author = author ,
351+ committer = committer ,
352+ repo = repo
353+ ))
354+ }
355+
236356assert_string <- function (x ) {
237357 if (! is.character(x ) || ! length(x )) {
238358 stop(" Argument must be a string of length 1" )
0 commit comments