├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── git_amend.R ├── git_as_git_commit.R ├── git_branch.R ├── git_branch_checkout.R ├── git_branch_create.R ├── git_branch_delete.R ├── git_branch_rename.R ├── git_commit.R ├── git_config.R ├── git_file_rename.R ├── git_history.R ├── git_init.R ├── git_repository.R ├── git_revision.R ├── git_stage-add.R ├── git_status.R ├── git_uncommit.R ├── git_unstage.R ├── githug-package.r ├── githug_list-class.R ├── utils-git2r.R └── utils.R ├── README.Rmd ├── README.md ├── githug.Rproj ├── img ├── morguefile-P1070087.JPG └── voldyhug-1440161473.gif ├── inst └── templates │ └── template.Rproj ├── internal ├── ddd-processing.R ├── ddd-processing.md ├── git-reset-checkout-visuals.key ├── git-reset-checkout-visuals.pdf ├── git-survival.R ├── git-survival.md ├── git-switch-noninteractive.R ├── git-switch-noninteractive.md ├── git2r-reset-path-study.R ├── git2r-reset-path-study.md ├── git2r-reset-soft-study.R ├── git2r-reset-soft-study.md ├── git2r-status-study.R ├── git2r-status-study.md ├── git_config.R ├── git_config.md ├── git_init_status_stage_commit.R ├── git_init_status_stage_commit.md ├── make-me-a-new-github-repo.R ├── make-me-a-new-github-repo.md ├── pro-git-reset-cheat-table.png ├── repo-path.R ├── repo-path.md ├── thelma-and-lousie-quotes.Rmd ├── thelma-and-lousie-quotes.md ├── uncommit-unstage-equals-mixed-reset.R └── uncommit-unstage-equals-mixed-reset.md ├── man-roxygen ├── repo.R ├── return-SHA-with-hint.R ├── return-repo-path.R └── rev.R ├── man ├── add-and-commit.Rd ├── as.git_commit.Rd ├── as.git_repository.Rd ├── as_git_repository.Rd ├── gh_pat.Rd ├── gh_username.Rd ├── git-revision.Rd ├── git_HEAD.Rd ├── git_amend.Rd ├── git_branch_create.Rd ├── git_branch_delete.Rd ├── git_branch_rename.Rd ├── git_commit.Rd ├── git_config.Rd ├── git_config_get.Rd ├── git_config_set.Rd ├── git_file_rename.Rd ├── git_history.Rd ├── git_init.Rd ├── git_log.Rd ├── git_stage.Rd ├── git_status.Rd ├── git_uncommit.Rd ├── git_unstage.Rd ├── githug-branch.Rd ├── githug-branches.Rd ├── githug-switch.Rd ├── githug.Rd ├── githug_init.Rd └── print.githug_list.Rd ├── tests ├── testthat.R └── testthat │ ├── git_log_print_output.rds │ ├── helper00_git_set-up.R │ ├── helper01_funs.R │ ├── helper02_backup-gitconfig.R │ ├── test-as-git-commit.R │ ├── test-classes.R │ ├── test-gh-pat-username.R │ ├── test-git-HEAD.R │ ├── test-git-add-commit.R │ ├── test-git-amend.R │ ├── test-git-branch-checkout.R │ ├── test-git-branch-create.R │ ├── test-git-branch-delete.R │ ├── test-git-branch-rename.R │ ├── test-git-branch.R │ ├── test-git-commit.R │ ├── test-git-config.R │ ├── test-git-history.R │ ├── test-git-init.R │ ├── test-git-log.R │ ├── test-git-mv.R │ ├── test-git-repository.R │ ├── test-git-revision.R │ ├── test-git-stage-add.R │ ├── test-git-status.R │ ├── test-git-uncommit.R │ ├── test-git-unstage.R │ ├── test-githug-init.R │ ├── test-githug_list.R │ ├── test-utils.R │ └── test-zzz-restore-gitconfig.R └── vignettes └── rstudio-to-github.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^internal$ 4 | ^man-roxygen$ 5 | ^README\.Rmd$ 6 | ^README-.*\.png$ 7 | ^scratch\.R$ 8 | ^scratch\.txt$ 9 | ^img$ 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | scratch.* 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | addons: 4 | apt: 5 | sources: 6 | - r-packages-precise 7 | packages: 8 | - r-base-dev 9 | - r-recommended 10 | - pandoc 11 | env: 12 | matrix: 13 | - R_LIBS_USER=~/R/library 14 | global: 15 | secure: 2ofPJB0k11cxrmUL9pTgqIj2fDTdG9YlEUFnOwys1Phx3+IT2i23/VDiTcksYPmqEMJAELEzJfowmm4ktBpkas8RHqPMFa+HyaFzP7NRRjTm1bABdFTzNrLveFWpMG0Xwd6FeooEUDuBac450uPeCrX3UYzCTZIUdQ0Etwcea333yWR2Nc1N8vtNW+oo+lI+VCWhJKxri3GdjvHYP4vQY712XjLNNI7zY28sFx2N4++sYzwnt/wNkImbpy+CL9EvoNf6iiozJkFZ3aRwzNzfx8xpZWko0FGadcxRJxHwAZVvCQ4OTzwdNBvcVS7YPbyk+rGI026Ek2vNFG6ZsE7b40XBJlbZ/yBpISv0UQzHzuRm1aT75Hmjb9BPMGtQajHpn9Co3QBbDRmo3iVqO8He/9sj/LRVlrnO0aKHqpeDibgDuEXMs73Re8/e23py1CZv8Fv6QOnNlLGv8BkHwA3+1C0bXeTIr3ZbCHjcYYNNtTCUn6QeN1YGBIE5YHid4///R6wV20LeQV9YRyz36tigZcERnI+48K8mXUpzQeyFf5tNgFldbFf/88LpzfMvX/pqbH+w18F7KrrG/Yuf00UUcUAPg074lWkqX5/ZG97k1D+YQ3RDIN69ZpAQNW3BZCUmiDyS/ZYAs9/WLZyLYdBvZVmWSCw8rz2FDJMvX9g9hhM= 16 | cache: 17 | directories: $R_LIBS_USER 18 | before_script: 19 | - git config --global user.name "githugci" 20 | - git config --global user.email "githugci@bryanbc.com" 21 | - mkdir -p "$R_LIBS_USER" 22 | - Rscript -e 'if (length(find.package("devtools", quiet = TRUE)) == 0L) { install.packages("devtools", 23 | repos = "http://cran.rstudio.com") }' 24 | - Rscript -e 'library(devtools);update_packages("devtools", repos = "http://cran.rstudio.com")' 25 | - Rscript -e 'library(devtools);install_deps(repos = "http://cran.rstudio.com", dependencies 26 | = TRUE)' 27 | script: 28 | - Rscript -e 'devtools::check(vignettes = FALSE)' 29 | notifications: 30 | email: 31 | on_success: change 32 | on_failure: change 33 | after_success: 34 | - Rscript -e 'if (length(find.package("covr", quiet = TRUE)) == 0L) { install.packages("covr", 35 | repos = "http://cran.rstudio.com") }' 36 | - Rscript -e 'covr::codecov()' 37 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: githug 2 | Title: Interface to local and remote Git operations 3 | Version: 0.0.0.9000 4 | Authors@R: c( 5 | person("Jennifer", "Bryan", , "jenny@stat.ubc.ca", c("cre", "aut")), 6 | person("Hadley", "Wickham", , "hadley@rstudio.com", "ctb") 7 | ) 8 | Description: Interface to local and remote Git operations. Interface to local 9 | and remote Git operations. Interface to local and remote Git operations. 10 | Interface to local and remote Git operations. 11 | Depends: 12 | R (>= 3.2.2) 13 | License: MIT + file LICENSE 14 | Encoding: UTF-8 15 | LazyData: true 16 | URL: https://github.com/jennybc/githug 17 | BugReports: https://github.com/jennybc/githug/issues 18 | Imports: 19 | gh, 20 | git2r (>= 0.13.1), 21 | methods, 22 | purrr, 23 | stats, 24 | tibble, 25 | utils 26 | Remotes: gaborcsardi/gh 27 | RoxygenNote: 5.0.1.9000 28 | Suggests: rprojroot, 29 | testthat 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: Jennifer Bryan 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method("[",githug_list) 4 | S3method(as.git_commit,character) 5 | S3method(as.git_repository,"NULL") 6 | S3method(as.git_repository,character) 7 | S3method(print,git_history) 8 | S3method(print,githug_list) 9 | export(as.git_commit) 10 | export(as.git_repository) 11 | export(git_add) 12 | export(git_amend) 13 | export(git_branch) 14 | export(git_branch_checkout) 15 | export(git_branch_create) 16 | export(git_branch_current) 17 | export(git_branch_delete) 18 | export(git_branch_list) 19 | export(git_branch_rename) 20 | export(git_commit) 21 | export(git_config) 22 | export(git_config_get) 23 | export(git_config_global) 24 | export(git_config_local) 25 | export(git_config_set) 26 | export(git_file_rename) 27 | export(git_history) 28 | export(git_init) 29 | export(git_mv) 30 | export(git_revision) 31 | export(git_revision_exists) 32 | export(git_stage) 33 | export(git_status) 34 | export(git_switch) 35 | export(git_uncommit) 36 | export(git_unstage) 37 | importFrom(purrr,"%||%") 38 | -------------------------------------------------------------------------------- /R/git_amend.R: -------------------------------------------------------------------------------- 1 | #' Re-do the most recent commit 2 | #' 3 | #' Undo the most recent commit WHILE LEAVING ALL YOUR FILES ALONE, combine those 4 | #' changes with the currently staged changes, and make a new commit. If nothing 5 | #' is currently staged, this is just a way to edit the most recent commit 6 | #' message. This function is "working directory safe" but "history unsafe". 7 | #' Think twice before "amending" a commit that you have pushed (see Details). 8 | #' 9 | #' \code{git_amend()} will not change your files. It gives you a do over on your 10 | #' most recent Git commit. When might you use this? If you realize the most 11 | #' recent commit included the wrong file changes or had a bad commit message. If 12 | #' you're not ready to re-commit yet, use \code{\link{git_uncommit}()} to just 13 | #' undo the commit. 14 | #' 15 | #' When might you NOT want to use this? If you have already pushed the most 16 | #' recent commit to a remote. It could still be OK if you're sure no one else 17 | #' has pulled. But be prepared to force push in this situation. 18 | #' 19 | #' \code{git_amend()} addresses the fourth most up-voted question on 20 | #' StackOverflow: 21 | #' \href{http://stackoverflow.com/questions/179123/edit-an-incorrect-commit-message-in-git}{Edit 22 | #' an incorrect commit message in Git}, with over 1.7 million views. It is 23 | #' equivalent to \code{git commit --amend -m "New commit message"}. 24 | #' 25 | #' @param message The commit message. If not provided and \code{ask = FALSE}, 26 | #' the original commit message is reused. If message is not given and 27 | #' \code{ask = TRUE} and session is interactive, you get a chance to supply 28 | #' the message, including an option to reuse the original message. 29 | #' @param ask Whether to confirm that user wants to change history 30 | #' @template repo 31 | #' 32 | #' @template return-SHA-with-hint 33 | #' @export 34 | #' 35 | #' @examples 36 | #' repo <- git_init(tempfile("githug-")) 37 | #' owd <- setwd(repo) 38 | #' write("Are these girls real smart or real real lucky?", "max.txt") 39 | #' git_commit("max.txt", message = "lines from max") 40 | #' write("Did I hear somebody say \"Peaches\"?", "jimmy.txt") 41 | #' git_commit("jimmy.txt", message = "lines from some guy") 42 | #' git_history() ## note the SHA of the most recent commit 43 | #' 44 | #' ## fix the previous commit message 45 | #' git_amend(message = "lines from jimmy", ask = FALSE) 46 | #' git_history() ## note the SHA of most recent commit has changed 47 | #' 48 | #' setwd(owd) 49 | git_amend <- function(message = character(), ask = TRUE, repo = ".") { 50 | stopifnot(is_lol(ask)) 51 | just_do_it <- isFALSE(ask) 52 | stop_if_no_rev(rev = "HEAD", repo = repo, 53 | desc = "the most recent commit (a.k.a. HEAD)") 54 | ## temporary measure: abort now if HEAD^ doesn't exist 55 | ## https://github.com/jennybc/githug0/issues/32 56 | stop_if_no_rev(rev = "HEAD^", repo = repo, 57 | desc = "parent of the most recent commit (a.k.a. HEAD^)") 58 | 59 | ## TO DO: describe if/how staging area differs from HEAD 60 | 61 | message_before <- git_revision_gco("HEAD", repo = repo)@message 62 | 63 | if (is_not_FALSE(ask)) { 64 | message("Warning: changing history!\n\n", 65 | "git_amend() actually removes a commit from the history\n", 66 | " and then adds a new one.\n", 67 | "If you've already pushed to a remote,\n", 68 | " especially if others have already pulled,\n", 69 | " this will cause problems.") 70 | if (interactive()) { 71 | just_do_it <- yesno("\nDo you still want to git_amend()?") 72 | } else { 73 | message("\nYou must explicitly authorize this with 'ask = FALSE'.") 74 | } 75 | } 76 | if (!just_do_it) { 77 | message("Aborting.") 78 | return(invisible()) 79 | } 80 | 81 | if (no_string(message)) { 82 | if (isFALSE(ask)) { 83 | message <- message_before 84 | } else if (interactive()) { 85 | message("Here is the previous commit message:\n", message_before) 86 | keep_message <- yesno("Do you want to use it again?\n", 87 | yes = "yes, use again", 88 | no = "no, let me enter a new message") 89 | if (keep_message) { 90 | message <- message_before 91 | } else { 92 | message <- get_user_input("Enter new commit message (ESC to abort)") 93 | } 94 | } 95 | } 96 | ## git_commit_do() will error if no message, but I don't want to uncommit if 97 | ## I already know the new commit won't succeed 98 | if (no_string(message)) { 99 | stop("Commit message is required. Aborting.") 100 | } 101 | 102 | git_uncommit_do(repo = repo) 103 | sha <- git_commit_do(repo = repo, message = message) 104 | message("Commit:\n", bulletize_sha(sha)) 105 | invisible(sha) 106 | } 107 | -------------------------------------------------------------------------------- /R/git_as_git_commit.R: -------------------------------------------------------------------------------- 1 | #' Address a commit, the git2r way 2 | #' 3 | #' Use this to convert a 4 | #' \href{https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 5 | #' string} into an object of class \code{\linkS4class{git_commit}}, which is how 6 | #' the \code{\link{git2r}} package handles Git commits. 7 | #' 8 | #' \code{githug} uses \code{\link{git2r}}, under the hood, to perform local Git 9 | #' operations. While \code{githug} always identifies a commit via its SHA or a 10 | #' revision string, \code{\link{git2r}} handles repositories as objects of class 11 | #' \code{\linkS4class{git_commit}}. If you want to do a Git operation that isn't 12 | #' exposed via \code{githug}, this function helps you specify the commit 13 | #' \code{\link{git2r}}-style. 14 | #' 15 | #' @param x Target commit, as a 16 | #' \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 17 | #' string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 18 | #' substring thereof. 19 | #' @template repo 20 | #' @param ... additional arguments (none currently in use) 21 | #' 22 | #' @return An S4 \code{\linkS4class{git_commit}} object 23 | #' 24 | #' @examples 25 | #' repo <- git_init(tempfile("githug-")) 26 | #' owd <- setwd(repo) 27 | #' write("Are these girls real smart or real real lucky?", "max.txt") 28 | #' write("You get what you settle for.", "louise.txt") 29 | #' git_commit(all = TRUE, 30 | #' message = "Brains'll only get you so far and luck always runs out.") 31 | #' write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 32 | #' "jd.txt") 33 | #' git_commit("jd.txt", message = "J.D. is charming") 34 | #' 35 | #' ## refer to these commits 36 | #' as.git_commit("HEAD") 37 | #' as.git_commit("HEAD^") 38 | #' as.git_commit("master~1") 39 | #' 40 | #' setwd(owd) 41 | #' @export 42 | as.git_commit <- function(x, repo = ".", ...) UseMethod("as.git_commit") 43 | 44 | #' @export 45 | as.git_commit.character <- function(x = "HEAD", repo = ".", ...) { 46 | git_revision_gco(x, repo = repo) 47 | } 48 | -------------------------------------------------------------------------------- /R/git_branch_create.R: -------------------------------------------------------------------------------- 1 | #' Create a new branch 2 | #' 3 | #' Create a new local branch. You must specify the \code{name} of the new 4 | #' branch, at the very least. By default, the new branch will point at current 5 | #' HEAD. Optionally, you can specify another commit to base the branch on, via a 6 | #' \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 7 | #' string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 8 | #' substring thereof. 9 | #' 10 | #' Convenience wrapper around 11 | #' \code{\link[git2r:branch_create]{git2r::branch_create}()}. 12 | #' 13 | #' @param name Name for the new branch 14 | #' @template repo 15 | #' @template rev 16 | #' 17 | #' @examples 18 | #' repo <- git_init(tempfile("githug-branches-")) 19 | #' owd <- setwd(repo) 20 | #' 21 | #' ## first commit 22 | #' writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 23 | #' git_commit("nowhere.txt", message = "1: not in the middle of nowhere") 24 | #' git_branch_list() 25 | #' 26 | #' ## second commit 27 | #' write("but we can see it from here.", "nowhere.txt", append = TRUE) 28 | #' git_commit("nowhere.txt", message = "2: but we can see it from here") 29 | #' 30 | #' ## create new branch that points at HEAD = second commit 31 | #' git_branch_create("carpe_diem") 32 | #' git_branch_list() 33 | #' 34 | #' ## create new branch that points at *first commit*, via its SHA 35 | #' (gl <- git_history()) 36 | #' git_branch_create("hindsight", rev = gl$sha[[2]]) 37 | #' git_branch_list() 38 | #' 39 | #' \dontrun{ 40 | #' ## try to re-create an existing branch and fail 41 | #' git_branch_create("hindsight") 42 | #' } 43 | #' 44 | #' setwd(owd) 45 | #' @export 46 | git_branch_create <- function(name, repo = ".", rev = "HEAD") { 47 | 48 | stopifnot(is.character(name), length(name) == 1L) 49 | stopifnot(is.character(rev), length(rev) == 1L) 50 | 51 | gco <- git_revision_gco(rev = rev, repo = repo) 52 | 53 | ## I'm intentionally not exposing 'force' 54 | gb <- git2r::branch_create(commit = gco, name = name, force = FALSE) 55 | if (!git2r::is_branch(gb)) { 56 | stop("Could not create new branch '", name,"' pointed at:\n", 57 | bulletize_gco(gco), call. = FALSE) 58 | } 59 | 60 | sha <- git_revision_sha(rev = gb@name, repo = repo) 61 | message("New branch '", name, "' pointed at:\n", bulletize_sha(sha)) 62 | invisible(sha) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /R/git_branch_delete.R: -------------------------------------------------------------------------------- 1 | #' Delete a branch 2 | #' 3 | #' Delete an existing local branch. You won't be allowed to delete the branch 4 | #' you're on. Switch to another branch, then delete. 5 | #' 6 | #' Convenience wrapper around 7 | #' \code{\link[git2r:branch_delete]{git2r::branch_delete}()}. 8 | #' 9 | #' @param name Name of the branch 10 | #' @template repo 11 | #' 12 | #' @examples 13 | #' repo <- git_init(tempfile("githug-branches-")) 14 | #' owd <- setwd(repo) 15 | #' 16 | #' ## commit so that master branch exists 17 | #' writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 18 | #' git_commit("nowhere.txt", message = "... but we can see it from here.") 19 | #' 20 | #' ## create a branch off of master 21 | #' git_branch_create("doomed") 22 | #' git_branch_list() 23 | #' 24 | #' ## switch to doomed branch 25 | #' git_switch("doomed") 26 | #' git_branch() 27 | #' 28 | #' \dontrun{ 29 | #' ## try -- and fail -- to delete doomed branch 30 | #' git_branch_delete("doomed") 31 | #' } 32 | #' 33 | #' ## switch back to master 34 | #' git_switch() 35 | #' 36 | #' ## delete the new branch 37 | #' git_branch_delete("doomed") 38 | #' git_branch_list() 39 | #' 40 | #' setwd(owd) 41 | #' @export 42 | git_branch_delete <- function(name, repo = ".") { 43 | stopifnot(is.character(name), length(name) == 1L) 44 | 45 | gb <- git_branch_from_name(name = name, repo = repo) 46 | if (!git2r::is_branch(gb)) { 47 | stop("No existing local branch named '", name, "' found in this repo:\n", 48 | repo_path(repo), call. = FALSE) 49 | } 50 | 51 | git2r::branch_delete(gb) 52 | message("Branch deleted:\n * ", name) 53 | invisible() 54 | } 55 | -------------------------------------------------------------------------------- /R/git_branch_rename.R: -------------------------------------------------------------------------------- 1 | #' Rename a branch 2 | #' 3 | #' Rename an existing local branch. It's fine to rename the branch you're on. 4 | #' 5 | #' Convenience wrapper around 6 | #' \code{\link[git2r:branch_create]{git2r::branch_rename}()}. 7 | #' 8 | #' @param from Name of the existing branch 9 | #' @param to New name for the branch 10 | #' @template repo 11 | #' 12 | #' @examples 13 | #' repo <- git_init(tempfile("githug-branches-")) 14 | #' owd <- setwd(repo) 15 | #' 16 | #' ## commit so that master branch exists 17 | #' writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 18 | #' git_commit("nowhere.txt", message = "... but we can see it from here.") 19 | #' git_branch_list() 20 | #' 21 | #' ## rename master 22 | #' git_branch_rename("master", "louise") 23 | #' git_branch_list() 24 | #' 25 | #' setwd(owd) 26 | #' @export 27 | git_branch_rename <- function(from, to, repo = ".") { 28 | stopifnot(is.character(from), length(from) == 1L) 29 | stopifnot(is.character(to), length(to) == 1L) 30 | 31 | from_branch <- git_branch_from_name(name = from, repo = repo) 32 | if (!git2r::is_branch(from_branch)) { 33 | stop("No existing local branch named '", from, "' found in this repo:\n", 34 | repo_path(repo), call. = FALSE) 35 | } 36 | 37 | ## I'm intentionally not exposing 'force' 38 | to_branch <- git2r::branch_rename(from_branch, name = to, force = FALSE) 39 | if (!git2r::is_branch(to_branch)) { 40 | stop("Could not rename branch '", from, "' to '", to, "'.", call. = FALSE) 41 | } 42 | 43 | message("Branch renamed:\n * ", from_branch@name, " --> ", to_branch@name) 44 | invisible(git_revision_sha(to_branch@name, repo = repo)) 45 | } 46 | -------------------------------------------------------------------------------- /R/git_commit.R: -------------------------------------------------------------------------------- 1 | #' Make a commit 2 | #' 3 | #' Write file changes to the repository. If there are staged changes, the simple 4 | #' call \code{git_commit(message = "my message")} makes a commit. If nothing's 5 | #' staged and it's an interactive session, you'll get an offer to "just stage & 6 | #' commit everything: Y/N?". To explicitly authorize "stage and commit" for all 7 | #' current file additions, deletions, and modifications, use 8 | #' \code{git_commit(all = TRUE)}, which emulates \code{git add -A && git 9 | #' commit}. In an interactive session, you will also get the chance to enter a 10 | #' missing commit message. 11 | #' 12 | #' Convenience wrapper around \code{\link[git2r:commit]{git2r::commit}()} and, 13 | #' possibly, \code{\link{git_stage}()}. 14 | #' 15 | #' @inheritParams git_stage 16 | #' @param message The commit message. Required. If not supplied, user will get a 17 | #' chance to provide the message in an interactive session. 18 | #' @template repo 19 | #' @template return-SHA-with-hint 20 | #' @examples 21 | #' repo <- git_init(tempfile("githug-")) 22 | #' owd <- setwd(repo) 23 | #' write("Are these girls real smart or real real lucky?", "max.txt") 24 | #' write("You get what you settle for.", "louise.txt") 25 | #' git_status() 26 | #' git_commit(all = TRUE, 27 | #' message = "Brains'll only get you so far and luck always runs out.") 28 | #' write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 29 | #' "jd.txt") 30 | #' git_status() 31 | #' git_commit("jd.txt", message = "J.D. is charming") 32 | #' setwd(owd) 33 | #' @export 34 | git_commit <- function(..., all = FALSE, force = FALSE, 35 | message = character(), repo = ".") { 36 | path <- as.character(c(...)) 37 | stopifnot(is_lol(all), is_lol(force)) 38 | if (no_string(message) && !interactive()) { 39 | stop("You must provide a commit message. Aborting.", call. = FALSE) 40 | } 41 | 42 | n_path <- length(path) 43 | if (n_path == 0L) { 44 | st <- git_status_check(repo = repo) 45 | n_staged <- sum(st$status == "staged") 46 | } 47 | 48 | if (n_path > 0L || n_staged == 0L) { 49 | ## faithfully transmit missingness of 'all' 50 | sargs <- list(path = path, force = force, repo = repo) 51 | if (!missing(all)) { 52 | sargs$all <- all 53 | } 54 | do.call(git_stage, sargs) 55 | } 56 | 57 | st <- git_status_check(repo = repo) 58 | n_staged <- sum(st$status == "staged") 59 | 60 | if (n_staged == 0L) { 61 | warning("Nothing staged for commit.") 62 | return(invisible()) 63 | } 64 | 65 | if (no_string(message) && interactive()) { 66 | message <- get_user_input("You must provide a commit message.", 67 | "Enter it now (ESC to abort)") 68 | } 69 | if (no_string(message)) { 70 | stop("Commit message is required. Aborting.") 71 | } 72 | 73 | ## command line git would say something like this: 74 | ## 1 file changed, 5 insertions(+), 5 deletions(-) 75 | ## should I also indicate something about the nature of the changes? 76 | ## if I'm going to say that, maybe do it before prompting for message? 77 | sha <- git_commit_do(repo = repo, message = message) 78 | message("Commit:\n", bulletize_sha(sha)) 79 | invisible(sha) 80 | } 81 | 82 | git_commit_do <- function(repo = ".", message) { 83 | stopifnot(is.character(message), length(message) == 1L) 84 | gco <- git2r::commit(repo = as.git_repository(repo), message = message) 85 | sha <- sha_with_hint(gco) 86 | sha 87 | } 88 | -------------------------------------------------------------------------------- /R/git_file_rename.R: -------------------------------------------------------------------------------- 1 | #' Rename or move a file and stage both ends 2 | #' 3 | #' Rename a file and stage both ends of the transaction, i.e. the deletion of 4 | #' the file under its original name and the addition of the file under its new 5 | #' name. You still need to make the commit. If Git recognizes a rename event, 6 | #' then the history of the file will be preserved. This is worth striving for. 7 | #' Maximize the chance of this happy event by making the rename/move a distinct 8 | #' operation that is not muddled up with other changes to the file. 9 | #' 10 | #' This is an extremely simple implementation of basic \code{git mv}. Why? True 11 | #' \code{git mv} is offered neither by libgit2 nor, therefore, by 12 | #' \code{\link{git2r}}. 13 | #' 14 | #' @param from Path to an existing file, relative to the repo working directory 15 | #' @param to The desired new name, relative to the repo working directory 16 | #' @template repo 17 | #' 18 | #' @return Nothing 19 | #' 20 | #' @export 21 | #' 22 | #' @examples 23 | #' repo <- git_init(tempfile("githug-")) 24 | #' owd <- setwd(repo) 25 | #' write("Are these girls real smart or real real lucky?", "louise.txt") 26 | #' git_commit(all = TRUE, message = "filename is all wrong") 27 | #' git_mv(from = "louise.txt", to = "max.txt") 28 | #' git_commit(all = TRUE, message = "corrected filename") 29 | #' git_history() 30 | #' setwd(owd) 31 | git_file_rename <- function(from, to, repo = ".") { 32 | stopifnot(is.character(from), length(from) == 1L) 33 | stopifnot(is.character(to), length(to) == 1L) 34 | from_path <- file.path(repo_path(repo), from) 35 | to_path <- file.path(repo_path(repo), to) 36 | stopifnot(file_exists(from_path), !file_exists(to_path)) 37 | ok <- file.rename(from = from_path, to = to_path) 38 | if (!ok) { 39 | stop("Unable to rename '", from, "' to '", to, "'", call. = FALSE) 40 | } 41 | message("File renamed:\n * '", from, "' --> '", to, "'") 42 | git_stage(from, to, repo = repo) 43 | invisible() 44 | } 45 | 46 | #' @rdname git_file_rename 47 | #' @export 48 | git_mv <- git_file_rename 49 | -------------------------------------------------------------------------------- /R/git_history.R: -------------------------------------------------------------------------------- 1 | #' Get the commit history 2 | #' 3 | #' Get an overview of the last \code{n} commits. Convenience wrapper around 4 | #' \code{\link[git2r:commits]{git2r::commits}()}. The print method shows 5 | #' truncated versions of selected variables, e.g., the commit message, time, and 6 | #' SHA, but rest assured the full information is present in the returned object. 7 | #' 8 | #' @template repo 9 | #' @param ... Optional parameters passed through to 10 | #' \code{\link[git2r:commits]{git2r::commits}()}. Can include: 11 | #' \itemize{ 12 | #' \item \code{n} Max number of commits. 13 | #' \item \code{topological} Logical, requests topological sort, i.e. 14 | #' parent before child, defaults to \code{TRUE}. Can be combined with 15 | #' \code{time}. 16 | #' \item \code{time} Logical, requests chronological sort, defaults to 17 | #' \code{TRUE}. Can be combined with \code{topological}. 18 | #' \item \code{reverse} Logical, reverses the order, defaults to 19 | #' \code{FALSE}. 20 | #' } 21 | #' @return A data frame with S3 class \code{git_history}, solely for printing 22 | #' purposes. Variables: the \code{SHA}, commit \code{message}, \code{when} the 23 | #' commit happened, \code{author}, and \code{email}. 24 | #' @export 25 | #' @examples 26 | #' repo <- git_init(tempfile("git-history-")) 27 | #' owd <- setwd(repo) 28 | #' line1 <- "Thelma: You're a real live outlaw, aren't ya?" 29 | #' line2 <- paste("J.D.: Well I may be an outlaw, darlin', but you're the one", 30 | #' "stealing my heart.") 31 | #' write(line1, "tl.txt") 32 | #' git_commit("tl.txt", message = "first commit") 33 | #' write(line2, "tl.txt", append = TRUE) 34 | #' git_commit("tl.txt", message = "second commit") 35 | #' git_history() 36 | #' setwd(owd) 37 | git_history <- function(repo = ".", ...) { 38 | commits <- git2r::commits(as.git_repository(repo), ...) 39 | if (length(commits) == 0L) { 40 | message("No commits yet.") 41 | return(invisible()) 42 | } 43 | raw_author <- purrr::map(commits, methods::slot, "author") 44 | ctbl <- tibble::tibble( 45 | sha = purrr::map_chr(commits, methods::slot, "sha"), 46 | message = purrr::map_chr(commits, methods::slot, "message"), 47 | when = purrr::map(raw_author, methods::slot, "when"), 48 | author = purrr::map_chr(raw_author, methods::slot, "name"), 49 | email = purrr::map_chr(raw_author, methods::slot, "email") 50 | ) 51 | ctbl$when <- purrr::map(ctbl$when, ~ methods::as(.x, "POSIXct")) 52 | ctbl$when <- do.call(c, ctbl$when) 53 | structure(ctbl, class = c("git_history", class(ctbl))) 54 | } 55 | 56 | #' @export 57 | print.git_history <- function(x, ...) { 58 | x_pretty <- tibble::tibble( 59 | sha = substr(x$sha, 1, 7), 60 | message = sprintf("%-24s", ellipsize(x$message, 24)), 61 | when = format(x$when, format = "%Y-%m-%d %H:%M"), 62 | author = x$author, 63 | email = x$email 64 | ) 65 | print(x_pretty) 66 | invisible(x) 67 | } 68 | -------------------------------------------------------------------------------- /R/git_init.R: -------------------------------------------------------------------------------- 1 | #' Create a new Git repository 2 | #' 3 | #' Create a new Git repository or re-initialize an existing one, similar to 4 | #' \code{git init}. 5 | #' 6 | #' Will bad things happen if you \code{git_init()} in a directory that is 7 | #' already a Git repo? No, it's fine! To quote the 8 | #' \href{https://git-scm.com/docs/git-init}{git-init man page}, "running 9 | #' \code{git init} in an existing repository is safe. It will not overwrite 10 | #' things that are already there". A legitimate reason to do this is to pick up 11 | #' a new Git template, a topic which newcomers can safely ignore. 12 | #' 13 | #' \code{git_init()} will not create a Git repo in a subdirectory of an existing 14 | #' Git repo. The proper way to do this is via 15 | #' \href{https://git-scm.com/book/en/v2/Git-Tools-Submodules}{Git submodules}, 16 | #' which is beyond the current scope of the package. 17 | #' 18 | #' @param path Where to create the new Git repo. Defaults to current working 19 | #' directory. If the \code{path} doesn't exist, it will be created via 20 | #' \code{dir.create(path, recursive = TRUE)}. 21 | #' 22 | #' @template return-repo-path 23 | #' @export 24 | #' 25 | #' @examples 26 | #' repo <- git_init(tempfile("git-init-example-")) 27 | #' 28 | #' ## switch working directory to the repo 29 | #' owd <- setwd(repo) 30 | #' 31 | #' ## Config local user and make a commit 32 | #' git_config(user.name = "thelma", user.email = "thelma@example.org") 33 | #' write("I don't ever remember feeling this awake.", "thelma.txt") 34 | #' git_commit("thelma.txt", message = "thelma is awake") 35 | #' 36 | #' git_history() 37 | #' 38 | #' setwd(owd) 39 | git_init <- function(path = ".") { 40 | 41 | path <- normalize_path(path) 42 | led_path <- least_existing_dir(path) 43 | printable_path <- 44 | midlipsize(path, getOption("width") - 2, ellipsis = " \u2026 ") 45 | 46 | if (is_a_repo(path)) { 47 | message("'path' is already a Git repo:\n ", printable_path) 48 | } else if (is_in_repo(led_path)) { 49 | stop("Aborting git_init().\n", 50 | "'path' is or will be nested within an existing Git repo:\n ", 51 | printable_path, call. = FALSE) 52 | } 53 | 54 | if (!dir_exists(path)) { 55 | if (file_exists(path)) { 56 | stop("Aborting git_init().\n", 57 | "Can't create directory. File already exists at 'path':\n ", 58 | printable_path, call. = FALSE) 59 | } 60 | message("* Creating directory:\n ", printable_path) 61 | dir.create(path, recursive = TRUE) 62 | } 63 | 64 | message("* Initialising git repository in:\n ", printable_path) 65 | gr <- git2r::init(path) 66 | return(invisible(gr_path(gr))) 67 | 68 | ## TO CONSIDER: 69 | ## set any custom githug config vars here? note that githug did git_init()? 70 | ## add hooks? 71 | ## https://github.com/jennybc/githug0/issues/11 72 | ## set standard config vars to superior non-default values now? 73 | ## https://github.com/jennybc/githug0/issues/7 74 | 75 | } 76 | -------------------------------------------------------------------------------- /R/git_repository.R: -------------------------------------------------------------------------------- 1 | #' Open a Git repository, the git2r way 2 | #' 3 | #' Use this to convert a path into a \code{\linkS4class{git_repository}} object, 4 | #' which is how the \code{\link{git2r}} package handles Git repositories. This 5 | #' function is a slightly more flexible version of 6 | #' \code{\link[git2r:repository]{git2r::repository}()}. 7 | #' 8 | #' \code{githug} uses \code{\link{git2r}}, under the hood, to perform local Git 9 | #' operations. While \code{githug} always identifies the repository via its 10 | #' path, \code{\link{git2r}} handles repositories as objects of class 11 | #' \code{\linkS4class{git_repository}}. If you want to do a Git operation that 12 | #' isn't exposed via \code{githug}, this function helps you specify the 13 | #' repository \code{\link{git2r}}-style. 14 | #' 15 | #' @param x path that is or is in a Git repository; defaults to working 16 | #' directory 17 | #' @param ... additional arguments, such as \code{ceiling} from 18 | #' \code{\link[git2r]{discover_repository}()} 19 | #' 20 | #' @return An S4 \code{\linkS4class{git_repository}} object 21 | #' 22 | #' @examples 23 | #' repo <- git_init(tempfile("git-repository-example-")) 24 | #' 25 | #' ## you can specify the path explicitly 26 | #' as.git_repository(repo) 27 | #' 28 | #' ## switch working directory to the repo 29 | #' owd <- setwd(repo) 30 | #' 31 | #' ## as.git_repository() with no args consults working directory 32 | #' as.git_repository() 33 | #' 34 | #' dir.create("subdir") 35 | #' 36 | #' ## as.git_repository() walks up parents, looking for a repo 37 | #' as.git_repository("subdir") 38 | #' 39 | #' setwd("subdir") 40 | #' as.git_repository() 41 | #' ## unless you put a ceiling on the walk 42 | #' \dontrun{ 43 | #' as.git_repository("repo-path/subdir", ceiling = 0) 44 | #' } 45 | #' 46 | #' setwd(owd) 47 | #' 48 | #' \dontrun{ 49 | #' ## here's a rather exotic Git operation that githug is unlikely to expose: 50 | #' ## odb_blobs() lists "all blobs reachable from the commits in the object database" 51 | #' ## pre-process the repo with as_git_repository() to prepare for git2r 52 | #' git2r::odb_blobs(as.git_repository("path_to_a_git_repo")) 53 | #' } 54 | #' 55 | #' @export 56 | as.git_repository <- function(x, ...) UseMethod("as.git_repository") 57 | 58 | #' @export 59 | as.git_repository.character <- function(x, ...) { 60 | git2r::repository(repo_path(x, ...)) 61 | } 62 | 63 | #' @export 64 | as.git_repository.NULL <- function(x, ...) as.git_repository(x = ".", ...) 65 | 66 | 67 | 68 | repo_path <- function(x = ".", ...) { 69 | 70 | if (!dir_exists(x)) { 71 | stop("directory does not exist:\n", x, call. = FALSE) 72 | } 73 | 74 | ## why not use repository(..., discover = TRUE) on x? 75 | ## because it errors if can't discover repo 76 | ## whereas discover_repository() returns NULL 77 | ## also repository() silently ignores ceiling, which might be in ... 78 | xrepo <- git2r::discover_repository(x, ...) 79 | 80 | if (is.null(xrepo)) { 81 | stop("no git repo exists here:\n", x, call. = FALSE) 82 | } 83 | 84 | gr_path(git2r::repository(xrepo, discover = TRUE)) 85 | 86 | } 87 | 88 | is_in_repo <- function(x = ".", ...) { 89 | dir_exists(x) && !is.null(git2r::discover_repository(x, ...)) 90 | } 91 | 92 | is_a_repo <- function(x = ".") is_in_repo(x, ceiling = 0) 93 | 94 | gr_path <- function(gr) { 95 | stopifnot(inherits(gr, "git_repository")) 96 | normalize_path_strict(git2r::workdir(gr)) 97 | } 98 | -------------------------------------------------------------------------------- /R/git_revision.R: -------------------------------------------------------------------------------- 1 | #' Identify commits like a human 2 | #' 3 | #' Retrieve the SHA-1 for a specific commit via a human-friendly description, 4 | #' like 5 | #' \itemize{ 6 | #' \item \code{HEAD}: the most recent commit and the one that will be 7 | #' parent to the next commit. 8 | #' \item \code{master@{1 month ago}}: the tip commit of the 9 | #' \code{master} branch this time last month 10 | #' \item \code{bug-fix}: the tip commit of the \code{bug-fix} branch 11 | #' \item \code{feature^}: parent of the tip commit of the \code{feature} branch 12 | #' \item \code{master~2}: grandparent of the tip commit of the \code{master} 13 | #' branch 14 | #' \item \code{8675309}: commit with \code{8675309} as leading substring of SHA-1 15 | #' } 16 | #' Convenience wrapper around 17 | #' \code{\link[git2r:revparse_single]{git2r::revparse_single}()}, which 18 | #' implements functionality from \code{git rev-parse}. 19 | #' 20 | #' @template rev 21 | #' @template repo 22 | #' @template return-SHA-with-hint 23 | #' 24 | #' @references 25 | #' 26 | #' \href{https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection}{Revision 27 | #' Selection} from the Pro Git book by Scott Chacon and Ben Straub 28 | #' 29 | #' Specifying revisions section of the 30 | #' \href{https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{git-rev-parse 31 | #' man page} 32 | #' 33 | #' @name git-revision 34 | #' @aliases git_revision git_revision_exists 35 | #' @examples 36 | #' repo <- git_init(tempfile("githug-revisions-")) 37 | #' owd <- setwd(repo) 38 | #' 39 | #' ## no commits --> HEAD cannot be resolved 40 | #' git_revision_exists("HEAD") 41 | #' \dontrun{ 42 | #' git_revision() 43 | #' } 44 | #' 45 | #' ## commit and ... now HEAD exists 46 | #' write("Well, we're not in the middle of nowhere,", "nowhere.txt") 47 | #' git_commit(all = TRUE, message = "1ouise: not in the middle of nowhere") 48 | #' git_revision() 49 | #' git_revision_exists("HEAD") 50 | #' 51 | #' ## make a new commit then ask for parent of HEAD 52 | #' write("but we can see it from here.", "nowhere.txt", append = TRUE) 53 | #' git_commit(all = TRUE, message = "louise: but can see it") 54 | #' git_revision("HEAD^") 55 | #' 56 | #' ## create a new branch and find out what it points at 57 | #' git_switch("newbranch", create = TRUE) 58 | #' git_revision("newbranch") 59 | #' 60 | #' setwd(owd) 61 | NULL 62 | 63 | #' @section git_revision: 64 | #' 65 | #' If called with no arguments, this returns SHA for HEAD of repo associated 66 | #' with current working directory. 67 | #' 68 | #' @export 69 | #' @rdname git-revision 70 | git_revision <- function(rev = "HEAD", repo = ".") { 71 | git_revision_sha(rev = rev, repo = repo) 72 | } 73 | 74 | #' @section git_revision_exists: 75 | #' 76 | #' Tests if \code{rev} can be resolved to a specific commit. 77 | #' 78 | #' @export 79 | #' @rdname git-revision 80 | git_revision_exists <- function(rev, repo = ".") { 81 | git_revision_inherits(rev = rev, is = "git_commit", repo = repo) 82 | } 83 | 84 | git_revision_inherits <- function(rev, is = NULL, repo = ".") { 85 | x <- git_revision_resolve(rev = rev, repo = repo) 86 | !inherits(x, "try-error") && (is.null(is) || inherits(x, is)) 87 | } 88 | 89 | ## only differs from exported version in the flexibility to enhance the stop 90 | ## message; meant for internal use 91 | git_revision_sha <- function(rev, repo = ".", desc = NULL) { 92 | gco <- git_revision_gco(rev = rev, repo = repo, desc = desc) 93 | sha_with_hint(gco) 94 | } 95 | 96 | ## will error if rev does not resolve SPECIFICALLY to a git_commit 97 | git_revision_gco <- function(rev = "HEAD", repo = ".", desc = NULL) { 98 | gco <- git_revision_resolve(rev = rev, repo = repo) 99 | if (inherits(gco, "try-error")) { 100 | desc <- desc %||% paste0("the revision '", rev, "'") 101 | msg <- paste0("Can't find ", desc, " in this repo:\n", repo_path(repo)) 102 | stop(msg, call. = FALSE) 103 | } 104 | if (!git2r::is_commit(gco)) { 105 | ## if I'm getting a git_tag or git_branch here, it is not intentional! 106 | stop("rev '", rev, "' resolves to a ", class(gco), 107 | ", not a git_commit", call. = FALSE) 108 | } 109 | gco 110 | } 111 | 112 | ## goal here is to catch error when rev cannot be found 113 | ## and return to other functions, who can decide what to do about that 114 | git_revision_resolve <- function(rev = "HEAD", repo = ".") { 115 | stopifnot(is.character(rev), length(rev) == 1) 116 | try(git2r::revparse_single(as.git_repository(repo), rev), silent = TRUE) 117 | } 118 | 119 | ## stopifnot but for revisions and w/ a message in English 120 | stop_if_no_rev <- function(rev, repo = ".", 121 | desc = paste0("the revision '", rev, "'")) { 122 | if (git_revision_exists(rev = rev, repo = repo)) { 123 | return(invisible()) 124 | } 125 | msg <- paste0("Can't find ", desc, " in this repo:\n", repo_path(repo)) 126 | stop(msg, call. = FALSE) 127 | } 128 | 129 | ## constructor for specific bits of the extended SHA-1 syntax 130 | ## https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions 131 | ## usage: 132 | ## git_revision_spell(text = "thelma") --> HEAD^{/thelma} 133 | ## git_revision_spell(rev = "1234567", n = 5) --> 1234567^^^^^ 134 | git_revision_spell <- function(rev = "HEAD", n = 0, text = character()) { 135 | if (length(text) == 0L) { 136 | return(paste0(rev, strrep("^", n))) 137 | } 138 | paste0(rev, "^{/", text, "}") 139 | } 140 | -------------------------------------------------------------------------------- /R/git_stage-add.R: -------------------------------------------------------------------------------- 1 | #' Stage changes for the next commit. 2 | #' 3 | #' Stage changes to files in preparation for a commit. In an interactive 4 | #' session, you'll get the chance to "just stage everything: Y/N?", if you 5 | #' haven't pre-staged or specified any paths. To pre-authorize the staging of 6 | #' all current file additions, deletions, and modifications, use 7 | #' \code{git_stage(all = TRUE)}. 8 | #' 9 | #' \code{git_add()} and \code{git_stage()} are aliases for each other, so use 10 | #' what feels most natural, i.e. "add" a file to the repo and "stage" 11 | #' modifications. These are convenience wrappers around 12 | #' \code{\link[git2r:add]{git2r::add}()}. 13 | #' 14 | #' @param ... One or more paths or shell glob patterns that will be matched 15 | #' against files in the repo's working directory. Paths that match will be 16 | #' added to the set of changes staged for the next commit. 17 | #' @param all Logical, consulted if no paths are given. If \code{TRUE}, 18 | #' pre-authorizes the staging of all new files, file deletions, and file 19 | #' modifications. Emulates \code{git add -A}, which is equivalent to \code{git 20 | #' add .; git add -u}. 21 | #' @param force Logical, defaults to \code{FALSE}. Value \code{TRUE} is required 22 | #' if any of the to-be-staged paths are currently ignored. 23 | #' @template repo 24 | #' @return nothing 25 | #' @examples 26 | #' repo <- git_init(tempfile("githug-")) 27 | #' owd <- setwd(repo) 28 | #' write("Are these girls real smart or real real lucky?", "max.txt") 29 | #' write("You get what you settle for.", "louise.txt") 30 | #' git_status() 31 | #' ## try this interactively and accept the proposed auto-staging 32 | #' # git_add() 33 | #' git_add("max.txt", "louise.txt") 34 | #' git_status() 35 | #' write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 36 | #' "jd.txt") 37 | #' write("Is he your husband or your father?", "louise.txt", append = TRUE) 38 | #' git_status() 39 | #' git_stage(all = TRUE) 40 | #' git_status() 41 | #' git_commit(message = "Brains'll only get you so far and luck always runs out.") 42 | #' git_status() 43 | #' setwd(owd) 44 | #' @export 45 | git_stage <- function(..., all = FALSE, force = FALSE, repo = ".") { 46 | path <- as.character(c(...)) 47 | stopifnot(is_lol(all)) 48 | stopifnot(is_lol(force)) 49 | 50 | 51 | status_before <- git_status_check(repo = repo, ls = force) 52 | stageable <- c("unstaged", "untracked", if (force) "ignored") 53 | stageable_before <- status_before$status %in% stageable 54 | 55 | n_path <- length(path) 56 | n_stageable <- sum(stageable_before) 57 | 58 | if (sum(n_path + n_stageable) == 0L) { 59 | message("No changes to stage.") 60 | return(invisible()) 61 | } 62 | 63 | printed <- FALSE 64 | 65 | if (n_path == 0L && missing(all)) { 66 | if (!interactive()) { 67 | message("Either provide paths to stage or authorize auto-staging with ", 68 | "`all = TRUE`.\nNothing staged.") 69 | return(invisible()) 70 | } 71 | staged <- status_before$status == "staged" 72 | if (sum(staged) > 0) { 73 | message("Already staged for next commit:") 74 | print(status_before[staged, ]) 75 | message("") 76 | } 77 | message("Unstaged additions, deletions, and modifications:") 78 | print(status_before[stageable_before, ]) 79 | printed <- TRUE 80 | all <- yesno("\nStage all of this?") 81 | } 82 | 83 | if (n_path == 0L) { 84 | if (all) { 85 | path <- status_before$path[stageable_before] 86 | } else { 87 | message("No changes staged.") 88 | return(invisible()) 89 | } 90 | } 91 | 92 | git_add_do(repo = repo, path = path, force = force) 93 | 94 | status_after <- git_status_check(repo = repo, ls = force) 95 | stageable_after <- status_after$status %in% stageable 96 | staged_actual <- setdiff(status_before$path[stageable_before], 97 | status_after$path[stageable_after]) 98 | uhoh <- setdiff(path, staged_actual) 99 | if (length(uhoh) > 0) { 100 | message( 101 | "These requested paths may not have been staged:\n", 102 | paste(" *", uhoh, collapse = "\n"), 103 | "\nMaybe there were no stageable changes?", 104 | "\nOr these paths don't exist in this repo?", 105 | "\nIf trying to stage ignored files, use 'force = TRUE'.", 106 | "\nFinal caveat: Expect false positives if 'path' contained shell globs." 107 | ) 108 | } 109 | 110 | n_staged <- length(staged_actual) 111 | if (n_staged > 0) { 112 | if (printed) { 113 | message("Staged ", n_staged, " path(s).") 114 | } else { 115 | message("Staged these paths:\n", 116 | paste(" *", staged_actual, collapse = "\n")) 117 | } 118 | } 119 | 120 | return(invisible()) 121 | } 122 | 123 | #' @rdname git_stage 124 | #' @export 125 | git_add <- git_stage 126 | 127 | git_add_do <- function(repo = ".", path, force = FALSE) { 128 | git2r::add(repo = as.git_repository(repo), path = path, force = force) 129 | } 130 | -------------------------------------------------------------------------------- /R/git_status.R: -------------------------------------------------------------------------------- 1 | #' Get status of a Git repo 2 | #' 3 | #' The status of a Git repo is a set of paths, typically broken down like so: 4 | #' \describe{ 5 | #' \item{Staged changes}{Paths with modifications that are staged for inclusion 6 | #' in the next commit.} 7 | #' \item{Unstaged changes}{Paths that are tracked by Git but that have unstaged 8 | #' modifications.} 9 | #' \item{Untracked files}{Paths that are not yet tracked by Git but that are 10 | #' also not gitignored.} 11 | #' } 12 | #' What does that leave? Two kinds of paths 13 | #' \itemize{ 14 | #' \item Unchanged, tracked files. 15 | #' \item Ignored files. 16 | #' } 17 | #' Use \code{ls = TRUE} to request a status that includes these paths as well, 18 | #' i.e. a complete census of all the files in the repo. 19 | #' 20 | #' @param ls Logical, indicating whether to include unchanged, tracked files 21 | #' and gitignored files. Default is \code{FALSE}. 22 | #' @template repo 23 | #' 24 | #' @return a data frame where each row describes changes to a path, invisibly 25 | #' @export 26 | #' 27 | #' @examples 28 | #' repo <- git_init(tempfile("githug-")) 29 | #' owd <- setwd(repo) 30 | #' write("Add me", "add-me") 31 | #' write("Don't add me", "dont-add-me") 32 | #' git_status() 33 | #' git_add("add-me") 34 | #' git_status() 35 | #' git_commit(message = "first commit") 36 | #' git_status() 37 | #' git_status(ls = TRUE) 38 | #' setwd(owd) 39 | git_status <- function(repo = ".", ls = FALSE) { 40 | gbn <- git_branch(repo = repo) 41 | if (!is.null(gbn)) { 42 | ## this is cat(), not message(), to help auto-generate safety commit messages 43 | cat("On branch:\n *", gbn, "\n") 44 | ## TODO: maybe add sthg about last commit? 45 | } 46 | git_status_check(repo = repo, ls = ls) 47 | } 48 | 49 | git_status_check <- function(repo = ".", ls = FALSE) { 50 | 51 | stopifnot(is_lol(ls)) 52 | 53 | ## convert from git2r's git_status object (a list) 54 | ## to a tibble (maybe a githug_status object + print method? shelving for now) 55 | s <- git2r::status(repo = as.git_repository(repo), staged = TRUE, 56 | unstaged = TRUE, untracked = TRUE, ignored = ls) 57 | 58 | if (sum(lengths(s)) > 0) { 59 | 60 | stl <- purrr::map(s, tibble::enframe, name = "change", value = "path") 61 | 62 | ## we need to explicitly sort somewhere and it's easiest right here 63 | ## i.e. before renames get expanded 64 | STATII <- c("staged", "unstaged", "untracked", "ignored") # + tracked 65 | CHANGES <- c("new", "modified", "renamed", "deleted", 66 | "untracked", "ignored") # + none 67 | stl <- stl[order(match(names(stl), STATII))] 68 | sfun <- function(df) df[order(match(df$change, CHANGES)), ] 69 | stl <- purrr::map(stl, sfun) 70 | 71 | ## add index i to link paths associated with a rename 72 | ## set column order 73 | ifun <- function(df) { 74 | is_a_rename <- lengths(df$path) > 1 75 | df$i <- rep(NA_integer_, nrow(df)) 76 | df$i[is_a_rename] <- seq_len(sum(is_a_rename)) 77 | df[c("path", "change", "i")] 78 | } 79 | stl <- purrr::map(stl, ifun) 80 | 81 | ## unnest/flatten the path list-column 82 | jfun <- function(df) purrr::pmap_df(df, tibble::tibble) 83 | ## rowbind the status-specific tibbles + prepend status variable as .id 84 | st <- purrr::map_df(stl, jfun, .id = "status") 85 | } else { 86 | st <- tibble::tibble( 87 | status = character(), 88 | path = character(), 89 | change = character(), 90 | i = integer() 91 | ) 92 | } 93 | 94 | renamed <- st$change == "renamed" 95 | st$change[renamed] <- paste(st$change[renamed], c("from", "to"), sep = "_") 96 | 97 | ## get tracked but unchanged paths, a la git ls-files 98 | if (ls) { 99 | tracked <- setdiff(dir(repo), st$path) 100 | nt <- length(tracked) 101 | if (nt > 0) { 102 | ## use tibble::add_row() if gets fixed 103 | ## https://github.com/hadley/tibble/pull/142 104 | ## this seems crazy but what else to do w/o rbind for tibbles? 105 | st <- tibble::tibble( 106 | status = c(st$status, rep.int("tracked", nt)), 107 | path = c(st$path, tracked), 108 | change = c(st$change, rep.int("none", nt)), 109 | i = c(st$i, rep.int(NA_integer_, nt)) 110 | ) 111 | } 112 | } 113 | ## TO WORRY: this just seems misleading / weird 114 | st$change[st$change %in% c("untracked", "ignored")] <- "new" 115 | 116 | st 117 | 118 | } 119 | -------------------------------------------------------------------------------- /R/git_uncommit.R: -------------------------------------------------------------------------------- 1 | #' Undo a Git commit but leave files alone 2 | #' 3 | #' Make it as if the last Git commit never happened BUT LEAVE YOUR FILES ALONE. 4 | #' This function is "working directory safe" but "history unsafe". Think twice 5 | #' before uncommitting a commit that you have pushed (see Details). 6 | #' 7 | #' \code{git_uncommit()} will not change your files. It just reverses the act of 8 | #' making the most recent Git commit. Even the staged / unstaged status of your 9 | #' modifications is preserved. When might you use this? To undo the last commit 10 | #' so you can stage different changes or files and/or redo your commit, but with 11 | #' a better message. Note that \code{\link{git_amend}()} might be a more 12 | #' efficient way to do that. 13 | #' 14 | #' When might you NOT want to use this? If you have already pushed the most 15 | #' recent commit to a remote. It could still be OK if you're sure no one else 16 | #' has pulled. But be prepared to force push in this situation. 17 | #' 18 | #' \code{git_uncommit()} addresses the second most up-voted question on 19 | #' StackOverflow: 20 | #' \href{http://stackoverflow.com/questions/927358/how-to-undo-last-commits-in-git}{How 21 | #' to undo last commit(s) in Git?}, with over 3.6 million views. It is 22 | #' equivalent to \code{git reset --soft HEAD^}, i.e. a soft reset to the commit 23 | #' that is parent to the commit pointed to by current HEAD. 24 | #' 25 | #' @param ask Whether to confirm that user wants to change history 26 | #' @template repo 27 | #' @template return-SHA-with-hint 28 | #' @examples 29 | #' repo <- git_init(tempfile("githug-")) 30 | #' owd <- setwd(repo) 31 | #' write("Are these girls real smart or real real lucky?", "max.txt") 32 | #' git_commit("max.txt", 33 | #' message = "Brains'll only get you so far and luck always runs out.") 34 | #' write("Did I hear somebody say \"Peaches\"?", "jimmy.txt") 35 | #' git_commit("jimmy.txt", message = "That's the code word. I miss you, Peaches.") 36 | #' git_history() ## see? 2 commits 37 | #' git_status() ## see? nothing to stage 38 | #' git_uncommit() ## roll back most recent commit 39 | #' git_history() ## see? only 1st commit is in history 40 | #' git_status() ## see? jimmy.txt is a new, staged file 41 | #' ## re-do that 2nd commit but with message in ALL CAPS 42 | #' git_commit(message = "THAT'S THE CODE WORD. I MISS YOU, PEACHES.") 43 | #' git_history() ## see? back to 2 commits 44 | #' setwd(owd) 45 | #' @export 46 | git_uncommit <- function(ask = TRUE, repo = ".") { 47 | stopifnot(is_lol(ask)) 48 | stop_if_no_rev(rev = "HEAD", repo = repo, 49 | desc = "the most recent commit (a.k.a. HEAD)") 50 | ## temporary measure: abort now if HEAD^ doesn't exist 51 | ## https://github.com/jennybc/githug0/issues/32 52 | stop_if_no_rev(rev = "HEAD^", repo = repo, 53 | desc = "parent of the most recent commit (a.k.a. HEAD^)") 54 | 55 | just_do_it <- isFALSE(ask) 56 | 57 | ## TO WORRY: what if HEAD is detached? 58 | 59 | ## TO DO: once I can check status of remote tracking branch, refine this. 60 | if (is_not_FALSE(ask)) { 61 | message("Warning: changing history!\n\n", 62 | "git_uncommit() leaves your files intact,\n", 63 | " but removes a commit from the history.\n", 64 | "If you've already pushed to a remote,\n", 65 | " especially if others have already pulled,\n", 66 | " this will cause problems.") 67 | if (interactive()) { 68 | just_do_it <- yesno("\nDo you still want to uncommit?") 69 | } else { 70 | message("\nYou must explicitly authorize this with 'ask = FALSE'.") 71 | } 72 | } 73 | 74 | if (!just_do_it) { 75 | message("Aborting.") 76 | return(invisible()) 77 | } 78 | 79 | git_uncommit_do(repo = repo) 80 | 81 | } 82 | 83 | git_uncommit_do <- function(repo = ".") { 84 | 85 | current_head <- git_revision_sha("HEAD", repo = repo) 86 | message("Uncommit:\n", bulletize_sha(current_head)) 87 | 88 | ## TO DO: make the safety branch or stash RIGHT HERE 89 | 90 | new_head <- git_revision_gco(rev = "HEAD^", repo = repo) 91 | git2r::reset(new_head, reset_type = "soft") 92 | 93 | sha <- sha_with_hint(new_head) 94 | message("HEAD reset to:\n", bulletize_sha(sha)) 95 | invisible(sha) 96 | 97 | } 98 | -------------------------------------------------------------------------------- /R/git_unstage.R: -------------------------------------------------------------------------------- 1 | #' Unstage changes for the next commit. 2 | #' 3 | #' Remove file modifications from the staging area for the next commit, BUT 4 | #' LEAVE YOUR FILES ALONE. This function is "working directory safe". It will 5 | #' not change your files. It only unstages them. When would you use this? If 6 | #' you've staged changes by mistake and you don't want them in the next commit 7 | #' after all. 8 | #' 9 | #' \code{git_unstage()} addresses a popular question on StackOverflow: 10 | #' \href{http://stackoverflow.com/questions/348170/how-to-undo-git-add-before-commit}{How 11 | #' to undo 'git add' before commit?}, with over 1.3 million views. In terms of 12 | #' command line Git, this reverses \code{git add file.txt}. The call 13 | #' \code{git_unstage("file.txt")} is equivalent to \code{git reset file.txt}, 14 | #' which is short for \code{git reset --mixed HEAD file.txt}, i.e. a mixed reset 15 | #' of \code{file.txt} to the commit pointed to by current HEAD. 16 | #' 17 | #' @param ... One or more paths that will be matched against files with changes 18 | #' that are staged for the next commit. Paths that match will be unstaged, 19 | #' i.e. their changes will not be part of the next commit. 20 | #' @param all Logical, consulted if no paths are given. If \code{TRUE}, 21 | #' pre-authorizes the unstaging of all staged files. 22 | #' @template repo 23 | #' 24 | #' @return nothing 25 | #' @export 26 | #' @examples 27 | #' repo <- git_init(tempfile("githug-")) 28 | #' owd <- setwd(repo) 29 | #' write("Are these girls real smart or real real lucky?", "max.txt") 30 | #' git_commit(all = TRUE, message = "first commit") 31 | #' write("You get what you settle for.", "louise.txt") 32 | #' git_status() 33 | #' git_add("louise.txt") 34 | #' git_status() 35 | #' git_unstage("louise.txt") 36 | #' git_status() 37 | #' setwd(owd) 38 | git_unstage <- function(..., all = FALSE, repo = ".") { 39 | path <- as.character(c(...)) 40 | stopifnot(is_lol(all)) 41 | 42 | status_before <- git_status_check(repo = repo) 43 | staged_before <- status_before$status == "staged" 44 | n_staged_before <- sum(staged_before) 45 | 46 | if (n_staged_before == 0L) { 47 | message("Nothing to unstage.") 48 | return(invisible()) 49 | } 50 | 51 | printed <- FALSE 52 | n_path <- length(path) 53 | 54 | if (n_path == 0L && missing(all)) { 55 | if (!interactive()) { 56 | message("Either provide paths to unstage or authorize auto-unstaging ", 57 | "with `all = TRUE`.\nNothing unstaged.") 58 | return(invisible()) 59 | } 60 | message("Currently staged for next commit:") 61 | print(status_before[staged_before, ]) 62 | printed <- TRUE 63 | all <- yesno("\nUnstage all of this?") 64 | } 65 | 66 | staged <- status_before$path[staged_before] 67 | 68 | if (n_path == 0L) { 69 | if (all) { 70 | path <- staged 71 | } else { 72 | message("Nothing unstaged.") 73 | return(invisible()) 74 | } 75 | } 76 | 77 | git2r::reset(as.git_repository(repo), path) 78 | 79 | status_after <- git_status_check(repo = repo) 80 | staged_after <- status_after$status == "staged" 81 | unstaged_actual <- setdiff(staged, status_after$path[staged_after]) 82 | uhoh <- setdiff(path, unstaged_actual) 83 | if (length(uhoh) > 0) { 84 | message( 85 | "These requested paths may not have been unstaged:\n", 86 | paste(" *", uhoh, collapse = "\n"), 87 | "\nMaybe these paths were not staged to begin with?", 88 | "\nOr don't exist in this repo?", 89 | "\nAlso remember shell globs are not allowed for unstaging." 90 | ) 91 | } 92 | 93 | n_unstaged <- length(unstaged_actual) 94 | if (n_unstaged > 0) { 95 | if (printed) { 96 | message("Unstaged ", n_unstaged, " path(s).") 97 | } else { 98 | message("Unstaged these paths:\n", 99 | paste(" *", unstaged_actual, collapse = "\n")) 100 | } 101 | } 102 | 103 | return(invisible()) 104 | } 105 | -------------------------------------------------------------------------------- /R/githug-package.r: -------------------------------------------------------------------------------- 1 | #' githug. 2 | #' 3 | #' @importFrom purrr %||% 4 | #' @name githug 5 | #' @docType package 6 | NULL 7 | -------------------------------------------------------------------------------- /R/githug_list-class.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | print.githug_list <- function(x, ...) { 3 | ## NULLs need to be preserved in config because conveys variable did not exist 4 | ## necessary for roundtrips 5 | ## therefore NULLs also need to be printed 6 | xp <- vapply(x, `%||%`, character(1), y = "NULL") 7 | lapply(names(xp), function(nm) cat(sprintf("%s = %s\n", nm, xp[[nm]]))) 8 | invisible(x) 9 | } 10 | 11 | #' @export 12 | `[.githug_list` <- function(x, i) { 13 | structure(.subset(x, i), class = c("githug_list", "list")) 14 | } 15 | -------------------------------------------------------------------------------- /R/utils-git2r.R: -------------------------------------------------------------------------------- 1 | ## input = an S4 git_commit object 2 | ## output = SHA-1 as length 1 character, w/ other info as attributes 3 | sha_with_hint <- function(gco) { 4 | stopifnot(git2r::is_commit(gco)) 5 | structure(gco@sha, 6 | when = methods::as(gco@author@when, "POSIXct"), 7 | msg_start = substr(gco@message, 1, 72)) 8 | } 9 | 10 | ## input = SHA-1 11 | ## output = string suitable for bullet-list reporting 12 | bulletize_sha <- function(sha, format = "%Y-%m-%d") { 13 | stopifnot(is.character(sha)) 14 | sprintf(" * [%s] %s: %s", 15 | substr(sha, 1, 7), 16 | format(attr(sha, "when"), format = format), 17 | ellipsize(attr(sha, "msg_start"), 55)) 18 | } 19 | 20 | ## input = S4 git_commit object 21 | ## output = string suitable for bullet-list reporting 22 | bulletize_gco <- function(gco, format = "%Y-%m-%d") { 23 | stopifnot(git2r::is_commit(gco)) 24 | sha <- sha_with_hint(gco) 25 | bulletize_sha(sha, format = format) 26 | } 27 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | is_lol <- function(x) is.logical(x) && length(x) == 1L 2 | 3 | isFALSE <- function(x) identical(x, FALSE) 4 | is_not_TRUE <- function(x) !isTRUE(x) 5 | is_not_FALSE <- function(x) !isFALSE(x) 6 | 7 | 8 | dir_exists <- function(x) { 9 | stopifnot(is.character(x), length(x) == 1L) 10 | utils::file_test("-d", x) 11 | } 12 | 13 | file_exists <- function(x) { 14 | stopifnot(is.character(x), length(x) == 1L) 15 | utils::file_test("-f", x) 16 | } 17 | 18 | ## inspired by 19 | ## http://stackoverflow.com/questions/19734412/flatten-nested-list-into-1-deep-list 20 | renquote <- function(l) { 21 | if (is.list(l)) { 22 | lapply(l, renquote) } 23 | else if (length(l) > 1) { 24 | lapply(as.list(l), renquote) 25 | } else { 26 | enquote(l) 27 | } 28 | } 29 | ## make into a depth one list but: preserve NULLs + names and atomize vectors 30 | list_depth_one <- function(x) lapply(unlist(renquote(x)), eval) 31 | 32 | screen <- function(x, y) if (length(y)) stats::setNames(x[y], y) else x 33 | 34 | list_to_chr <- function(x) vapply(x, `[`, character(1)) 35 | 36 | is_named <- function(x) { 37 | nms <- names(x) 38 | !is.null(nms) && 39 | all(!is.na(nms)) && 40 | all(nzchar(nms)) 41 | } 42 | 43 | ## walk up parent dirs until you find one that exists 44 | least_existing_dir <- function(path) { 45 | stopifnot(length(path) == 1L, is.character(path), path != "") 46 | path <- normalize_path(path) 47 | #cat(path, "\n") 48 | if (dir.exists(path)) 49 | return(path) 50 | else 51 | least_existing_dir(dirname(path)) 52 | } 53 | 54 | normalize_path <- function(path) { 55 | normalizePath(path, winslash = "/", mustWork = FALSE) 56 | } 57 | 58 | normalize_path_strict <- function(path) { 59 | normalizePath(path, winslash = "/", mustWork = TRUE) 60 | } 61 | 62 | yesno <- function(..., yes = "yes", no = "no") { 63 | cat(paste0(..., collapse = "")) 64 | utils::menu(c(yes, no)) == 1 65 | } 66 | 67 | get_user_input <- function(...) { 68 | messages <- as.character(unlist(list(...))) 69 | lapply(utils::head(messages, -1), message) 70 | trimws(readline(paste0(messages[length(messages)], ": "))) 71 | } 72 | 73 | ellipsize <- function(x, n = 20, ellipsis = "\u2026") { 74 | if (length(x) == 0L || nchar(x) <= n) return(x) 75 | x <- replace_newlines(x) 76 | ifelse(nchar(x) > n, 77 | paste0(substr(x, start = 1, stop = n - nchar(ellipsis)), ellipsis), 78 | x) 79 | } 80 | 81 | midlipsize <- function(x, n = 20, ellipsis = "\u2026") { 82 | if (length(x) == 0L || nchar(x) <= n) return(x) 83 | x <- replace_newlines(x) 84 | half <- (n - nchar(ellipsis))/2 85 | paste0(substr(x, start = 1, stop = ceiling(half)), 86 | ellipsis, 87 | substr(x, start = nchar(x) - floor(half) + 1, stop = nchar(x))) 88 | } 89 | 90 | replace_newlines <- function(x) gsub("\n+", "; ", x) 91 | 92 | ## helpful for seeing non-interactive behavior in an interactive session, 93 | ## i.e. for development and test writing 94 | interactive <- function() { 95 | if (getOption("allow_interaction", default = TRUE)) { 96 | base::interactive() 97 | } else { 98 | FALSE 99 | } 100 | } 101 | prohibit_interaction <- function() options(allow_interaction = FALSE) 102 | allow_interaction <- function() options(allow_interaction = TRUE) 103 | 104 | no_string <- function(x) { 105 | stopifnot(is.character(x), length(x) <= 1) 106 | length(x) == 0L || x == "" 107 | } 108 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | [![Project Status: Wip - Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](http://www.repostatus.org/badges/0.1.0/wip.svg)](http://www.repostatus.org/#wip) 8 | 9 | ```{r, echo = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | fig.path = "README-", 14 | error = TRUE 15 | ) 16 | ``` 17 | 18 | # githug 19 | 20 | ### Welcome to Version Control! 21 | 22 | 23 | 24 | ![Demo](img/voldyhug-1440161473.gif) 25 | 26 | The goal of githug is to wrap you in the warm embrace of Git 🤗, from the comfort of R. 27 | 28 | *This a reboot of an earlier effort, which lives on in [branch `first-draft` ](https://github.com/jennybc/githug/tree/first-draft). That branch includes a function `githug_init()` to connect a new or existing R project (usually a RStudio Project) to a newly created GitHub remote. Currently plodding my way back to that level of functionality.* 29 | 30 | ## Installation 31 | 32 | You can install githug from github with: 33 | 34 | ```{r gh-installation, eval = FALSE} 35 | # install.packages("devtools") 36 | devtools::install_github("jennybc/githug") 37 | ``` 38 | 39 | ## Example 40 | 41 | Load dev version of the package. *This will become `library(githug)`.* 42 | 43 | ```{r} 44 | #library(githug) 45 | devtools::load_all(".") 46 | ``` 47 | 48 | Create a new Git repository and set that as working directory for the duration of this example. 49 | 50 | ```{r} 51 | repo <- git_init(tempfile("githug-example-")) 52 | knitr::opts_knit$set(root.dir = repo) 53 | ``` 54 | 55 | Set (local) config variables for user and email. 56 | Create two files and inspect Git status. 57 | 58 | ```{r} 59 | git_config_local(user.name = "louise", user.email = "louise@example.org") 60 | 61 | write("Are these girls real smart or real real lucky?", "max.txt") 62 | write("You get what you settle for.", "louise.txt") 63 | git_status() 64 | ``` 65 | 66 | Commit with `all = TRUE` to automatically accept all current changes. *In interactive use, if `all` is unspecified, you get an offer to just stage all current changes.* 67 | 68 | ```{r} 69 | git_commit(all = TRUE, 70 | message = "Brains'll only get you so far and luck always runs out.") 71 | ``` 72 | 73 | Add new file and commit it. Inspect commit history. 74 | 75 | ```{r} 76 | write("Did I hear somebody say \"Peaches\"?", "jimmy.txt") 77 | git_commit("jimmy.txt", message = "That's the code word. I miss you, Peaches.") 78 | git_history() 79 | ``` 80 | 81 | Uncommit, i.e. leave files as they are, but go back to parent of current commit. 82 | 83 | ```{r} 84 | git_uncommit(ask = FALSE) 85 | git_history() 86 | ``` 87 | 88 | Verify files and staging are OK. Unstage a file. 89 | 90 | ```{r} 91 | git_status() 92 | list.files() 93 | git_unstage("jimmy.txt") 94 | git_status() 95 | ``` 96 | 97 | See history. 98 | Create and checkout a branch. *In an interactive session, if `create` is unspecified and branch does not exist, you are asked if you want to create the branch.* Go back to master. 99 | 100 | ```{r} 101 | git_history() 102 | git_branch() 103 | git_switch("new_branch", create = TRUE) 104 | git_branch() 105 | git_switch() 106 | git_branch_list() 107 | ``` 108 | 109 | ## Overview of functions 110 | 111 | ```{r include = FALSE} 112 | fxn_table <- 113 | "fxn,description 114 | git_config(), Get and set Git configuration variables 115 | git_init(), Create a new repository 116 | git_status(), Get status of all files w/r/t Git 117 | git_history(), Get commit history (a.k.a. the log) 118 | git_stage(), Stage (changes to) a path for next commit 119 | git_add(), Synonym for git_stage() 120 | git_unstage(), Unstage (changes to) a path 121 | git_commit(), Make a commit 122 | git_uncommit(), Undo a Git commit but leave files alone 123 | git_amend(), Re-do the most recent commit 124 | git_file_rename(), Rename or move and file and stage it 125 | git_mv(), Synonym for git_file_rename() 126 | git_branch(), Report current branch or list all branches 127 | git_switch(), Switch to another branch 128 | git_branch_*(), \"Lower level functions to list, create, rename, delete, and checkout branches\" 129 | git_revision(), Identify a specific commit 130 | as.git_repository(), Open a Git repo in the style of the `git2r` package 131 | as.git_commit(), Get a specific commit in the style of the `git2r` package 132 | " 133 | ``` 134 | 135 | ```{r as.is = TRUE, echo = FALSE} 136 | knitr::kable(read.csv(text = fxn_table)) 137 | ``` 138 | 139 | *to be replaced by a proper test coverage badge* 140 | 141 | ```{r} 142 | Sys.time() 143 | git2r::repository("~/rrr/githug0") 144 | covr::package_coverage("~/rrr/githug0/") 145 | ``` 146 | -------------------------------------------------------------------------------- /githug.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /img/morguefile-P1070087.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/githug/675269266c06335365b8410a898fe3c1d08979f8/img/morguefile-P1070087.JPG -------------------------------------------------------------------------------- /img/voldyhug-1440161473.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/githug/675269266c06335365b8410a898fe3c1d08979f8/img/voldyhug-1440161473.gif -------------------------------------------------------------------------------- /inst/templates/template.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | -------------------------------------------------------------------------------- /internal/ddd-processing.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | 14 | #' I have some rather useful functions for processing very diverse `...` input 15 | #' to `git_config()` but damn if I remember how they work. And I may need this 16 | #' stuff again for `git_add()`. 17 | #' 18 | #' ### `renquote()` 19 | #' 20 | #' Here it is and apparently I was inspired by [a question from Jeroen on 21 | #' stackoverflow](http://stackoverflow.com/questions/19734412/flatten-nested-list-into-1-deep-list). 22 | 23 | renquote <- function(l) { 24 | if (is.list(l)) { 25 | lapply(l, renquote) } 26 | else if (length(l) > 1) { 27 | lapply(as.list(l), renquote) 28 | } else { 29 | enquote(l) 30 | } 31 | } 32 | 33 | x <- list(foo = TRUE, bar = 456, baz = NULL, 34 | pets = list(cat = "meeuw", dog = "woof"), 35 | letters = c(a = "a", b = "b", c = "c")) 36 | renquote(x) 37 | 38 | #' This appears to recurse through the input list and apply `enquote()` to each 39 | #' atom, by which I mean a list or vector element that is itself neither a list 40 | #' nor a vector of length greater than 1. `enquote()` is described as "a simple 41 | #' one-line utility which transforms a call of the form `Foo(....)` into the 42 | #' call `quote(Foo(....))`. This is typically used to protect a call from early 43 | #' evaluation." 44 | #' 45 | #' ### `list_depth_one()` 46 | #' 47 | #' I then use `renquote()` inside another function, `list_depth_one()`, like so: 48 | list_depth_one <- function(x) lapply(unlist(renquote(x)), eval) 49 | 50 | #' So here we go 51 | list_depth_one(x) 52 | 53 | #' And why wasn't I content with `unlist()`? 54 | unlist(x) 55 | 56 | #' The `NULL` is dropped and you get a vector, not a list, back. Both of which 57 | #' matter for `git_config()`. I also note that everything gets coerced to 58 | #' character, which doesn't happen to be a problem for `git_config()` but is 59 | #' still a big difference. 60 | #' 61 | #' I think `unlist()` might actually get the job done in `git_add()`. 62 | -------------------------------------------------------------------------------- /internal/ddd-processing.md: -------------------------------------------------------------------------------- 1 | ddd-processing.R 2 | ================ 3 | jenny 4 | Mon Jul 25 16:24:05 2016 5 | 6 | - [`renquote()`](#renquote) 7 | - [`list_depth_one()`](#list_depth_one) 8 | 9 | ``` r 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | error = TRUE 14 | ) 15 | ``` 16 | 17 | I have some rather useful functions for processing very diverse `...` input to `git_config()` but damn if I remember how they work. And I may need this stuff again for `git_add()`. 18 | 19 | ### `renquote()` 20 | 21 | Here it is and apparently I was inspired by [a question from Jeroen on stackoverflow](http://stackoverflow.com/questions/19734412/flatten-nested-list-into-1-deep-list). 22 | 23 | ``` r 24 | renquote <- function(l) { 25 | if (is.list(l)) { 26 | lapply(l, renquote) } 27 | else if (length(l) > 1) { 28 | lapply(as.list(l), renquote) 29 | } else { 30 | enquote(l) 31 | } 32 | } 33 | 34 | x <- list(foo = TRUE, bar = 456, baz = NULL, 35 | pets = list(cat = "meeuw", dog = "woof"), 36 | letters = c(a = "a", b = "b", c = "c")) 37 | renquote(x) 38 | #> $foo 39 | #> quote(TRUE) 40 | #> 41 | #> $bar 42 | #> quote(456) 43 | #> 44 | #> $baz 45 | #> quote(NULL) 46 | #> 47 | #> $pets 48 | #> $pets$cat 49 | #> quote("meeuw") 50 | #> 51 | #> $pets$dog 52 | #> quote("woof") 53 | #> 54 | #> 55 | #> $letters 56 | #> $letters$a 57 | #> quote("a") 58 | #> 59 | #> $letters$b 60 | #> quote("b") 61 | #> 62 | #> $letters$c 63 | #> quote("c") 64 | ``` 65 | 66 | This appears to recurse through the input list and apply `enquote()` to each atom, by which I mean a list or vector element that is itself neither a list nor a vector of length greater than 1. `enquote()` is described as "a simple one-line utility which transforms a call of the form `Foo(....)` into the call `quote(Foo(....))`. This is typically used to protect a call from early evaluation." 67 | 68 | ### `list_depth_one()` 69 | 70 | I then use `renquote()` inside another function, `list_depth_one()`, like so: 71 | 72 | ``` r 73 | list_depth_one <- function(x) lapply(unlist(renquote(x)), eval) 74 | ``` 75 | 76 | So here we go 77 | 78 | ``` r 79 | list_depth_one(x) 80 | #> $foo 81 | #> [1] TRUE 82 | #> 83 | #> $bar 84 | #> [1] 456 85 | #> 86 | #> $baz 87 | #> NULL 88 | #> 89 | #> $pets.cat 90 | #> [1] "meeuw" 91 | #> 92 | #> $pets.dog 93 | #> [1] "woof" 94 | #> 95 | #> $letters.a 96 | #> [1] "a" 97 | #> 98 | #> $letters.b 99 | #> [1] "b" 100 | #> 101 | #> $letters.c 102 | #> [1] "c" 103 | ``` 104 | 105 | And why wasn't I content with `unlist()`? 106 | 107 | ``` r 108 | unlist(x) 109 | #> foo bar pets.cat pets.dog letters.a letters.b letters.c 110 | #> "TRUE" "456" "meeuw" "woof" "a" "b" "c" 111 | ``` 112 | 113 | The `NULL` is dropped and you get a vector, not a list, back. Both of which matter for `git_config()`. I also note that everything gets coerced to character, which doesn't happen to be a problem for `git_config()` but is still a big difference. 114 | 115 | I think `unlist()` might actually get the job done in `git_add()`. 116 | -------------------------------------------------------------------------------- /internal/git-reset-checkout-visuals.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/githug/675269266c06335365b8410a898fe3c1d08979f8/internal/git-reset-checkout-visuals.key -------------------------------------------------------------------------------- /internal/git-reset-checkout-visuals.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/githug/675269266c06335365b8410a898fe3c1d08979f8/internal/git-reset-checkout-visuals.pdf -------------------------------------------------------------------------------- /internal/git-survival.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: github_document 3 | #' --- 4 | 5 | #' ### demo of functions for basic git survival 6 | 7 | library(githug) 8 | suppressPackageStartupMessages(library(dplyr)) 9 | 10 | #' #### git config 11 | 12 | #' see git config currently in effect, based on working directory 13 | git_config() # local > global, same as git_config(where = "de_facto") 14 | git_config_local() # same as git_config(where = "local") 15 | git_config_global() # same as git-config(where = "global") 16 | 17 | #' set, query, restore global config 18 | (ocfg <- 19 | git_config_global(user.name = "thelma", user.email = "thelma@example.org")) 20 | git_config_global("user.name", "user.email") 21 | ## complete the round trip 22 | git_config_global(ocfg) 23 | git_config_global("user.name", "user.email") 24 | 25 | #' a whole bunch of adding, commiting, ADDING, and COMMITTING 26 | ## conventional git add, status, commit 27 | repo <- git_init(tempfile("githug-commits-")) 28 | owd <- setwd(repo) 29 | writeLines("Are these girls real smart or real real lucky?", "max.txt") 30 | git_add("max.txt") 31 | git_status() 32 | git_commit("Brains'll only get you so far and luck always runs out.") 33 | git_status() 34 | setwd(owd) 35 | 36 | #' **THE SHOUTY COMMANDS** 37 | repo <- git_init(tempfile("GITHUG-SHOUTING-")) 38 | owd <- setwd(repo) 39 | writeLines("Change me", "change-me") 40 | writeLines("Delete me", "delete-me") 41 | git_status() 42 | git_add(c("change-me", "delete-me")) 43 | git_status() 44 | git_commit("initial") 45 | write("OK", "change-me", append = TRUE) 46 | file.remove("delete-me") 47 | writeLines("Add me", "add-me") 48 | git_status() 49 | git_ADD() 50 | git_status() 51 | ## TO DO: return here when commits and reset are wrapped 52 | ccc <- git2r::commits()[[1]] 53 | git2r::reset(ccc, "mixed") 54 | git_status() 55 | git_COMMIT("JUST DO IT.") 56 | git_status() 57 | setwd(owd) 58 | 59 | #' all the branch things ----------------------- 60 | repo <- git_init(tempfile("githug-branches-")) 61 | repo 62 | owd <- setwd(repo) 63 | getwd() 64 | ## **NOTE TO SELF:** I have no idea why this setwd() does not take effect. WTF? 65 | ## Temporary workaround: specify repo everywhere below. 66 | 67 | #' no commits --> no branches 68 | git_branch_list(repo = repo) 69 | 70 | #' commit and ... now we have master 71 | writeLines("Well, we're not in the middle of nowhere...", 72 | file.path(repo, "nowhere.txt")) 73 | git_COMMIT('1: not in the middle of nowhere', repo = repo) 74 | git_branch_list(repo = repo) 75 | git_branch_list(tips = TRUE, repo = repo) 76 | 77 | #' create new branch that points at HEAD 78 | git_branch_create("earlybranch", repo = repo) 79 | git_branch_list(repo = repo) 80 | 81 | #' another commit 82 | write("but we can see it from here.", 83 | file.path(repo, "nowhere.txt"), append = TRUE) 84 | git_COMMIT('2: but we can see it from here', repo = repo) 85 | 86 | #' create new branch that points at *first commit*, not HEAD 87 | (gl <- git_log(repo = repo)) 88 | git_branch_create("hindsight", commit = gl$commit[[2]], repo = repo) 89 | git_branch_list(repo = repo) 90 | git_branch_list(tips = TRUE, repo = repo) 91 | 92 | #' try to re-create an existing branch and fail 93 | #+ branch-create-will-error, error = TRUE 94 | git_branch_create("hindsight", repo = repo) 95 | 96 | #' try try again ... and use the force = TRUE 97 | git_branch_create("hindsight", force = TRUE, repo = repo) 98 | git_branch_list(tips = TRUE, repo = repo) 99 | 100 | #' checkout an existing branch 101 | git_checkout("earlybranch", repo = repo) 102 | git_branch(repo = repo) 103 | git_HEAD(repo = repo) 104 | 105 | #' checkout master 106 | git_checkout(repo = repo) 107 | git_HEAD(repo = repo) 108 | 109 | #' checkout AND CREATE all at once 110 | git_CHECKOUT("IMMEDIATE-GRATIFICATION", repo = repo) 111 | git_HEAD(repo = repo) 112 | 113 | #' delete a branch 114 | git_branch_delete("earlybranch", repo = repo) 115 | git_branch_list(repo = repo) 116 | 117 | setwd(owd) 118 | -------------------------------------------------------------------------------- /internal/git-switch-noninteractive.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | 14 | here <- rprojroot::find_package_root_file 15 | library(purrr) # for %>% 16 | devtools::load_all(here()) 17 | 18 | #' I need to see what happens when `git_switch()` gets called in a 19 | #' noninteractive session, in order to write the tests. 20 | #' 21 | #' No commits, no branches. 22 | tpath <- init_tmp_repo() 23 | git_switch(repo = tpath) 24 | 25 | #' Yes a commit, yes a branch exists, but not the one I'm asking for. 26 | write_file("a", dir = tpath) 27 | git_commit("a", message = "a", repo = tpath) 28 | git_switch("b", repo = tpath) 29 | -------------------------------------------------------------------------------- /internal/git-switch-noninteractive.md: -------------------------------------------------------------------------------- 1 | git-switch-noninteractive.R 2 | ================ 3 | jenny 4 | Tue Aug 16 22:57:47 2016 5 | 6 | ``` r 7 | knitr::opts_chunk$set( 8 | collapse = TRUE, 9 | comment = "#>", 10 | error = TRUE 11 | ) 12 | 13 | here <- rprojroot::find_package_root_file 14 | library(purrr) # for %>% 15 | devtools::load_all(here()) 16 | ``` 17 | 18 | ## Loading githug 19 | 20 | I need to see what happens when `git_switch()` gets called in a noninteractive session, in order to write the tests. 21 | 22 | No commits, no branches. 23 | 24 | ``` r 25 | tpath <- init_tmp_repo() 26 | #> * Creating directory: 27 | #> /var/folders/vt/4sdxy0rd1b3b65nqssx … iaPT2/githug-test-jenny-4ed5a1f23f9 28 | #> * Initialising git repository in: 29 | #> /var/folders/vt/4sdxy0rd1b3b65nqssx … iaPT2/githug-test-jenny-4ed5a1f23f9 30 | git_switch(repo = tpath) 31 | #> No branches to list. 32 | #> 'master' is not the name of any existing local branch. 33 | #> Error: Aborting. 34 | ``` 35 | 36 | Yes a commit, yes a branch exists, but not the one I'm asking for. 37 | 38 | ``` r 39 | write_file("a", dir = tpath) 40 | git_commit("a", message = "a", repo = tpath) 41 | #> Staged these paths: 42 | #> * a 43 | #> Commit: 44 | #> * [18ca18f] 2016-08-16: a 45 | git_switch("b", repo = tpath) 46 | #> 'b' is not the name of any existing local branch. 47 | #> Error: Aborting. 48 | ``` 49 | -------------------------------------------------------------------------------- /internal/git2r-reset-path-study.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | library(git2r) 14 | devtools::session_info("git2r")$packages[1, ] 15 | here <- rprojroot::find_package_root_file 16 | devtools::load_all(here()) 17 | 18 | #' ## Verifying what happens with `git2r::reset(object, path)`, where `object` 19 | #' is a `git_repository`. 20 | repo <- file.path("~", "tmp", "reset") 21 | 22 | #' Clean up any previous work. 23 | if (dir.exists(repo)) unlink(repo, recursive = TRUE) 24 | 25 | #' Set up a repo. 26 | repo <- git_init(repo) 27 | gr <- as.git_repository(repo) 28 | 29 | #' Create 2 files and commit them. 30 | write("1 one 1 one 1 one FIRST COMMIT", file.path(repo, "a.txt")) 31 | write("I will be deleted", file.path(repo, "b.txt")) 32 | git_commit(all = TRUE, message = "first commit", repo = repo) 33 | readLines(file.path(repo, "a.txt")) 34 | readLines(file.path(repo, "b.txt")) 35 | 36 | #' Modify the first file. Delete the second. Create another file. 37 | write("I'm new in the second commit", file.path(repo, "c.txt")) 38 | file.remove(file.path(repo, "b.txt")) 39 | write("2 two 2 two 2 two SECOND COMMIT", file.path(repo, "a.txt"), 40 | append = TRUE) 41 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 42 | cat(readLines(file.path(repo, "b.txt")), sep = "\n") 43 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 44 | (status_unstaged <- git_status(repo = repo)) 45 | 46 | #' Stage all of that. 47 | git_stage(all = TRUE, repo = repo) 48 | (status_staged <- git_status(repo = repo)) 49 | 50 | #' Unstage those paths. 51 | reset(gr, path = status_staged$path) 52 | (status_reset <- git_status(repo = repo)) 53 | 54 | #' Is it same as before? 55 | identical(status_unstaged, status_reset) 56 | 57 | #' Clean up. 58 | unlink(repo, recursive = TRUE) 59 | -------------------------------------------------------------------------------- /internal/git2r-reset-path-study.md: -------------------------------------------------------------------------------- 1 | git2r-reset-path-study.R 2 | ================ 3 | jenny 4 | Tue Aug 9 22:18:16 2016 5 | 6 | - [Verifying what happens with `git2r::reset(object, path)`, where `object`](#verifying-what-happens-with-git2rresetobject-path-where-object) 7 | 8 | ``` r 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | error = TRUE 13 | ) 14 | library(git2r) 15 | devtools::session_info("git2r")$packages[1, ] 16 | ``` 17 | 18 | ## package * version date source 19 | ## git2r * 0.15.0.9000 2016-08-09 Github (ropensci/git2r@87b7a9e) 20 | 21 | ``` r 22 | here <- rprojroot::find_package_root_file 23 | devtools::load_all(here()) 24 | ``` 25 | 26 | ## Loading githug 27 | 28 | Verifying what happens with `git2r::reset(object, path)`, where `object` 29 | ------------------------------------------------------------------------ 30 | 31 | is a `git_repository`. 32 | 33 | ``` r 34 | repo <- file.path("~", "tmp", "reset") 35 | ``` 36 | 37 | Clean up any previous work. 38 | 39 | ``` r 40 | if (dir.exists(repo)) unlink(repo, recursive = TRUE) 41 | ``` 42 | 43 | Set up a repo. 44 | 45 | ``` r 46 | repo <- git_init(repo) 47 | #> * Creating directory: 48 | #> /Users/jenny/tmp/reset 49 | #> * Initialising git repository in: 50 | #> /Users/jenny/tmp/reset 51 | gr <- as.git_repository(repo) 52 | ``` 53 | 54 | Create 2 files and commit them. 55 | 56 | ``` r 57 | write("1 one 1 one 1 one FIRST COMMIT", file.path(repo, "a.txt")) 58 | write("I will be deleted", file.path(repo, "b.txt")) 59 | git_commit(all = TRUE, message = "first commit", repo = repo) 60 | #> Staged these paths: 61 | #> * a.txt 62 | #> * b.txt 63 | #> Commit: 64 | #> * [5e24d67] 2016-08-09: first commit 65 | readLines(file.path(repo, "a.txt")) 66 | #> [1] "1 one 1 one 1 one FIRST COMMIT" 67 | readLines(file.path(repo, "b.txt")) 68 | #> [1] "I will be deleted" 69 | ``` 70 | 71 | Modify the first file. Delete the second. Create another file. 72 | 73 | ``` r 74 | write("I'm new in the second commit", file.path(repo, "c.txt")) 75 | file.remove(file.path(repo, "b.txt")) 76 | #> [1] TRUE 77 | write("2 two 2 two 2 two SECOND COMMIT", file.path(repo, "a.txt"), 78 | append = TRUE) 79 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 80 | #> 1 one 1 one 1 one FIRST COMMIT 81 | #> 2 two 2 two 2 two SECOND COMMIT 82 | cat(readLines(file.path(repo, "b.txt")), sep = "\n") 83 | #> Warning in file(con, "r"): cannot open file '/Users/jenny/tmp/reset/b.txt': 84 | #> No such file or directory 85 | #> Error in file(con, "r"): cannot open the connection 86 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 87 | #> I'm new in the second commit 88 | (status_unstaged <- git_status(repo = repo)) 89 | #> # A tibble: 3 x 4 90 | #> status path change i 91 | #> 92 | #> 1 unstaged a.txt modified NA 93 | #> 2 unstaged b.txt deleted NA 94 | #> 3 untracked c.txt new NA 95 | ``` 96 | 97 | Stage all of that. 98 | 99 | ``` r 100 | git_stage(all = TRUE, repo = repo) 101 | #> Staged these paths: 102 | #> * a.txt 103 | #> * b.txt 104 | #> * c.txt 105 | (status_staged <- git_status(repo = repo)) 106 | #> # A tibble: 3 x 4 107 | #> status path change i 108 | #> 109 | #> 1 staged c.txt new NA 110 | #> 2 staged a.txt modified NA 111 | #> 3 staged b.txt deleted NA 112 | ``` 113 | 114 | Unstage those paths. 115 | 116 | ``` r 117 | reset(gr, path = status_staged$path) 118 | (status_reset <- git_status(repo = repo)) 119 | #> # A tibble: 3 x 4 120 | #> status path change i 121 | #> 122 | #> 1 unstaged a.txt modified NA 123 | #> 2 unstaged b.txt deleted NA 124 | #> 3 untracked c.txt new NA 125 | ``` 126 | 127 | Is it same as before? 128 | 129 | ``` r 130 | identical(status_unstaged, status_reset) 131 | #> [1] TRUE 132 | ``` 133 | 134 | Clean up. 135 | 136 | ``` r 137 | unlink(repo, recursive = TRUE) 138 | ``` 139 | -------------------------------------------------------------------------------- /internal/git2r-reset-soft-study.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | library(git2r) 14 | devtools::session_info("git2r")$packages[1, ] 15 | here <- rprojroot::find_package_root_file 16 | devtools::load_all(here()) 17 | 18 | #' ## Verifying what happens with soft reset via `git2r::reset()`. 19 | repo <- file.path("~", "tmp", "reset") 20 | 21 | #' Clean up any previous work. 22 | if (dir.exists(repo)) unlink(repo, recursive = TRUE) 23 | 24 | #' Set up a repo. 25 | repo <- git_init(repo) 26 | gr <- as.git_repository(repo) 27 | 28 | #' Check HEAD. Should be `NULL` before the first commit. Points to an "unborn 29 | #' branch". 30 | head(gr) 31 | 32 | #' Create 2 files and commit them. 33 | write("1 one 1 one 1 one FIRST COMMIT", file.path(repo, "a.txt")) 34 | write("I will be deleted", file.path(repo, "b.txt")) 35 | git_commit(all = TRUE, message = "first commit", repo = repo) 36 | readLines(file.path(repo, "a.txt")) 37 | readLines(file.path(repo, "b.txt")) 38 | 39 | #' Check HEAD. Now it exists and `git2r::head()` return value is object of S4 40 | #' class `git_branch`. Check again with `githug:::git_HEAD()`. 41 | head(gr) 42 | class(head(gr)) 43 | git_HEAD(repo) 44 | 45 | #' Modify the first file. Delete the second. Create two more files. Commit. 46 | write("I'm new in the second commit", file.path(repo, "c.txt")) 47 | write("I'm also new in the second commit", file.path(repo, "d.txt")) 48 | file.remove(file.path(repo, "b.txt")) 49 | write("2 two 2 two 2 two SECOND COMMIT", file.path(repo, "a.txt"), 50 | append = TRUE) 51 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 52 | cat(readLines(file.path(repo, "b.txt")), sep = "\n") 53 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 54 | cat(readLines(file.path(repo, "d.txt")), sep = "\n") 55 | git_status(repo = repo) 56 | git_commit(all = TRUE, message = "second commit", repo = repo) 57 | 58 | #' Look at the commit history and check HEAD again. Store the commits for use 59 | #' in resets. Use `githug::git_log()`. 60 | (commits <- commits(gr)) 61 | git_log(repo) 62 | 63 | #' Which files exist now in the working tree? 64 | list.files(repo) 65 | 66 | #' Make a change to two of the three files but only stage one. Add two more 67 | #' files and stage one. 68 | write("3 three 3 three 3 three", file.path(repo, "a.txt"), append = TRUE) 69 | write("another line", file.path(repo, "c.txt"), append = TRUE) 70 | write("new after second commit", file.path(repo, "e.txt")) 71 | write("also new after second commit", file.path(repo, "f.txt")) 72 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 73 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 74 | cat(readLines(file.path(repo, "d.txt")), sep = "\n") 75 | cat(readLines(file.path(repo, "e.txt")), sep = "\n") 76 | cat(readLines(file.path(repo, "f.txt")), sep = "\n") 77 | git_status(repo, ls = TRUE) 78 | git_add("a.txt", "e.txt", repo = repo) 79 | (status_pre_reset <- git_status(repo, ls = TRUE)) 80 | 81 | #' That up right up there ^? That is the status I (sort of) expect to get 82 | #' back to after the reset. In any case, we'll want to compare back to this. 83 | #' 84 | #' Call `reset()` providing the first commit as a `git_commit` object as the 85 | #' first argument. HEAD will now point to this commit. 86 | reset(commits[[2]]) # soft is default 87 | list.files(repo) 88 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 89 | cat(readLines(file.path(repo, "b.txt")), sep = "\n") 90 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 91 | cat(readLines(file.path(repo, "d.txt")), sep = "\n") 92 | cat(readLines(file.path(repo, "e.txt")), sep = "\n") 93 | cat(readLines(file.path(repo, "f.txt")), sep = "\n") 94 | 95 | #' The working tree has not been changed. Compare current status to that right 96 | #' before the soft reset. 97 | git_status(repo, ls = TRUE) 98 | status_pre_reset 99 | #' I used SourceTree to confirm the stuff below as I haven't figured out diff 100 | #' inspection with `git2r`. 101 | #' 102 | #' * The accumulated modifications of "a.txt" are staged: the addition of the 103 | #' second and third lines. That makes sense because this is the staged state 104 | #' of "a.txt" at the time of reset. 105 | #' * "b.txt" does not exist and its deletion is staged. 106 | #' * The creation and first line of "c.txt" is staged (these were part of the 107 | #' commit that disappeared). The addition of the second line of "c.txt" is an 108 | #' unstaged modification, as it was unstaged at the time of reset. 109 | #' * The creation of "d.txt" is staged. At the time of reset, it was tracked 110 | #' but unchanged. 111 | #' * The creation of "e.txt" is staged. 112 | #' * "f.txt" has been created but it is not tracked and unstaged. 113 | 114 | git_HEAD(repo) 115 | git_log(repo) 116 | reflog(gr) 117 | #' Yes HEAD is pointing to the requested commit, here the first commit. The 118 | #' second commit disappears from the log. To get it back, you'd need to get it 119 | #' from reflog. So, if this is what `git_uncommit()` comes to mean, will I put 120 | #' something in place -- or message the SHA -- to document which commit has 121 | #' been peeled off? 122 | 123 | #' Clean up. 124 | unlink(repo, recursive = TRUE) 125 | 126 | #' Random question: what happens if you reset to a commit is not an ancestor of 127 | #' current HEAD? Leaving this for now. 128 | -------------------------------------------------------------------------------- /internal/git_config.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | here <- rprojroot::find_package_root_file 14 | devtools::load_all(here()) 15 | library(git2r) 16 | 17 | #' Showing `git_config()` usage 18 | 19 | setwd(here()) 20 | 21 | ## see git config currently in effect, based on working directory 22 | git_config() # local > global, same as git_config(where = "de_facto") 23 | git_config_local() # same as git_config(where = "local") 24 | git_config_global() # same as git_config(where = "global") 25 | 26 | ## different input formats to get config 27 | git_config_global("user.name", "user.email") 28 | git_config_global(list("user.name", "user.email")) 29 | git_config_global(c("user.name", "user.email")) 30 | 31 | ## get, set, get, restore, get global config 32 | git_config_global("user.name", "user.email") 33 | ocfg <- git_config_global(user.name = "thelma", user.email = "thelma@example.org") 34 | ## guess who's made several commits as thelma in the past :( 35 | git_config_global("user.name", "user.email") 36 | git_config_global(ocfg) 37 | git_config_global("user.name", "user.email") 38 | 39 | ## specify a Git repo 40 | (repo <- init_tmp_repo(slug = "git-config-demo")) 41 | git_config_local(repo = repo) 42 | 43 | ## switch working directory to the repo 44 | owd <- setwd(repo) 45 | 46 | ## set local variables for current repo 47 | git_config_local(user.name = "louise", user.email = "louise@example.org") 48 | 49 | ## get specific local variables, including a non-existent one 50 | git_config_local("user.name", "color.branch", "user.email") 51 | 52 | ## make sure we haven't changed global config, should be jenny not louise 53 | git_config_global("user.name", "user.email") 54 | 55 | ## set local variables, get, restore, get 56 | ocfg <- git_config_local(user.name = "oops", user.email = "oops@example.org") 57 | git_config_local("user.name", "user.email") 58 | git_config_local(ocfg) 59 | git_config_local("user.name", "user.email") 60 | 61 | ## set a custom variable, get, restore 62 | ocfg <- git_config_local(githug.lol = "wut") 63 | git_config_local("githug.lol") 64 | git_config_local(ocfg) 65 | 66 | ## restore wd 67 | setwd(owd) 68 | -------------------------------------------------------------------------------- /internal/git_init_status_stage_commit.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | here <- rprojroot::find_package_root_file 14 | library(purrr) # for %>% 15 | devtools::load_all(here()) 16 | 17 | #' ## Examples from `git_init()` 18 | 19 | repo <- git_init(tempfile("git-init-example-")) 20 | ## switch working directory to the repo 21 | owd <- setwd(repo) 22 | ## Config local user and make a commit 23 | git_config(user.name = "thelma", user.email = "thelma@example.org") 24 | write("I don't ever remember feeling this awake.", "thelma.txt") 25 | git_commit(all = TRUE, message = "thelma is awake") 26 | setwd(owd) 27 | 28 | #' ## Examples from `git_status()` 29 | 30 | repo <- git_init(tempfile("githug-")) 31 | owd <- setwd(repo) 32 | write("Change me", "change-me") 33 | write("Delete me", "delete-me") 34 | git_status() 35 | git_commit(all = TRUE, message = "first commit") 36 | git_status() 37 | write("OK", "change-me", append = TRUE) 38 | file.remove("delete-me") 39 | write("Add me", "add-me") 40 | git_status() 41 | git_commit(all = TRUE, message = "second commit") 42 | git_status() 43 | setwd(owd) 44 | 45 | #' ## Examples from `git_stage()` (practically same as for `git_commit()`) 46 | 47 | repo <- git_init(tempfile("githug-")) 48 | owd <- setwd(repo) 49 | write("Are these girls real smart or real real lucky?", "max.txt") 50 | write("You get what you settle for.", "louise.txt") 51 | git_status() 52 | ## explicit staging 53 | git_add("max.txt", "louise.txt") 54 | git_status() 55 | write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 56 | "jd.txt") 57 | write("Is he your husband or your father?", "louise.txt", append = TRUE) 58 | git_status() 59 | ## pre-authorize "stage everything" 60 | git_add(all = TRUE) 61 | git_status() 62 | git_commit(message = "Brains'll only get you so far and luck always runs out.") 63 | git_status() 64 | setwd(owd) 65 | 66 | #' ## Some interactive examples re: `git_stage()` and `git_commit()` 67 | #' 68 | #' None of this will be run when rendering, i.e. `eval = FALSE` here. 69 | #+ eval = FALSE 70 | 71 | repo <- git_init(tempfile("githug-")) 72 | owd <- setwd(repo) 73 | write("Change me", "change-me") 74 | write("Delete me", "delete-me") 75 | git_status() 76 | 77 | ## query about staging everything 78 | git_stage() ## say 'yes' 79 | 80 | ## elicit commit message 81 | git_commit() ## give a message (hello, autocomplete?!?) 82 | 83 | write("OK", "change-me", append = TRUE) 84 | file.remove("delete-me") 85 | write("Add me", "add-me") 86 | git_status() 87 | 88 | ## since nothing is staged, call git_stage() and query 89 | git_commit(message = "second commit") 90 | 91 | git_status() 92 | setwd(owd) 93 | 94 | -------------------------------------------------------------------------------- /internal/make-me-a-new-github-repo.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: github_document 3 | #' --- 4 | 5 | library(githug) 6 | 7 | ## Example 1: 8 | ## create a directory and local repo and remote repo, all at once 9 | ## go visit the new thing in the browser 10 | ## remove `private = TRUE` if you wish / must 11 | githug_init(path = tempfile("init-test-"), private = TRUE) 12 | 13 | ## Example 2: 14 | 15 | ## connect a pre-existing Git repo to GitHub 16 | repo <- git_init(tempfile("githug-init-example-")) 17 | 18 | ## switch working directory to the repo 19 | owd <- setwd(repo) 20 | 21 | ## Config local git user and make a commit 22 | git_config(user.name = "thelma", user.email = "thelma@example.org") 23 | writeLines("I don't ever remember feeling this awake.", "thelma.txt") 24 | git_COMMIT("thelma is awake") 25 | git_log() 26 | 27 | ## Connect it to GitHub! Visit the new repo in the browser. 28 | githug_init(private = TRUE) 29 | 30 | ## see that the 'origin' is now set to the GitHub remote 31 | ## TO DO: revise this when remote stuff done 32 | git2r::remotes() 33 | git2r::remote_url(as_git_repository()) 34 | 35 | ## see that local master is tracking remote master 36 | git2r::branch_get_upstream(git_HEAD()$git_branch) 37 | 38 | setwd(owd) 39 | 40 | ## Example 3: 41 | ## Turn an existing directory into a Git repo to and connect to GitHub 42 | repo <- tempfile("githug-init-example-") 43 | dir.create(repo) 44 | owd <- setwd(repo) 45 | githug_init(private = TRUE) 46 | setwd(owd) 47 | -------------------------------------------------------------------------------- /internal/pro-git-reset-cheat-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/githug/675269266c06335365b8410a898fe3c1d08979f8/internal/pro-git-reset-cheat-table.png -------------------------------------------------------------------------------- /internal/repo-path.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | here <- rprojroot::find_package_root_file 14 | devtools::load_all(here()) 15 | library(git2r) 16 | 17 | #' ## Find repo path 18 | #' 19 | #' One motivation of `githug` is to provide an interface to Git operations 20 | #' provided by `git2r` but with an API that is more consistent and more 21 | #' consistently helpful re: identifying the relevant Git repo. I'm inspired by 22 | #' `devtools`, which identifies the target package via its path. The typical 23 | #' user isn't aware of the `package` class. I'd like to similarly downplay 24 | #' `git2r`'s S4 `git_repository` class. 25 | #' 26 | #' This first commit adds internal functions to find and detect repo path: 27 | #' 28 | #' ```` 29 | #' user provides info about which repo (consciously or not) 30 | #' --> githug:::find_repo_path() normalizes it to a path to a repo 31 | #' --> git2r::repository() turns it into a git_repository 32 | #' that is pre-packaged as githug::as.git_repository() 33 | #' ```` 34 | #' 35 | #' The point will be more clear once I bring other functions back. 36 | #' 37 | #' ## Review: git2r functions for identifying a repo 38 | #' 39 | #' The `git2r` functions are somewhat inconsistent in terms of 40 | #' 41 | #' * whether the default behavior is to consult working directory 42 | #' * whether `ceiling` can be used to control walking up parents 43 | #' 44 | #' `discover_repository(path, ceiling)`: "used to identify the location of the 45 | #' repository" 46 | #' 47 | #' * path in, path out 48 | #' * output path will be like `~/foo/.git/` <-- note the terminating file separator 49 | #' * `discover_repository()` walks up parents unless `ceiling` is 0 or 1 50 | #' * DOES NOT default to `"."`; user must always supply a path 51 | 52 | setwd(here()) ## make sure wd is pkg root = a git repo 53 | discover_repository() ## too bad this does not default to "." 54 | discover_repository(".") 55 | discover_repository("./tests/testthat") 56 | discover_repository("./tests/testthat", ceiling = 1) 57 | discover_repository("./tests", ceiling = 1) 58 | discover_repository("./tests", ceiling = 0) 59 | 60 | #' `repository(path, ...)`: "open a repository" 61 | #' 62 | #' * path in, `git_repository` object out 63 | #' * `git2r::init()` also returns such objects 64 | #' * `repository()` walks up parents to find repo iff `discover = TRUE` 65 | #' * if `path` not given, DOES consult current working directory 66 | #' * silently ignores `ceiling` 67 | 68 | setwd(here()) 69 | repository() ## HEY this one does default to repo in wd! 70 | class(repository()) 71 | repository("./R") ## but it does not walk up, by default 72 | repository("./R", discover = TRUE) ## now we walk 73 | repository("./R", discover = TRUE, ceiling = 0) ## ceiling silently ignored 74 | 75 | #' `workdir(repo)` gets "workdir of repository" 76 | #' 77 | #' * `git_repository` in, path out 78 | #' * output path will be like `~/foo/` 79 | #' * if `repo` not given, DOES consult current working directory and walks up 80 | #' parents to find a git repo 81 | 82 | setwd(here()) 83 | workdir() ## HEY this also defaults to repo in wd! 84 | setwd(file.path(here(), "tests", "testthat")) 85 | workdir() ## always walks up 86 | 87 | #' ## Usage 88 | #' 89 | #' fiddling around 90 | setwd(here()) 91 | 92 | find_repo_path() 93 | as.git_repository() 94 | repository() 95 | 96 | find_repo_path("./.git") 97 | as.git_repository("./.git") 98 | repository("./.git") 99 | 100 | find_repo_path("./R") 101 | find_repo_path("./R", ceiling = 0) 102 | find_repo_path("./R", ceiling = 1) 103 | as.git_repository("./R") 104 | repository("./R") 105 | repository("./R", discover = TRUE) 106 | 107 | find_repo_path("./tests/testthat") 108 | find_repo_path("./tests/testthat/", ceiling = 0) 109 | find_repo_path("./tests/testthat/", ceiling = 1) 110 | repository("./tests/testthat/") 111 | repository("./tests/testthat/", discover = TRUE) 112 | 113 | find_repo_path("~") 114 | repository("~") 115 | 116 | is_in_repo(".") 117 | is_a_repo(".") 118 | is_in_repo("tests/testthat") 119 | is_a_repo("tests/testthat") 120 | is_in_repo("tests/testthat", ceiling = 1) 121 | is_in_repo("~") 122 | is_a_repo("~") 123 | -------------------------------------------------------------------------------- /internal/thelma-and-lousie-quotes.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Thelma and Louise Quotes" 3 | output: github_document 4 | --- 5 | 6 | ```{r setup, include=FALSE} 7 | knitr::opts_chunk$set( 8 | collapse = TRUE, 9 | comment = "#>", 10 | error = TRUE 11 | ) 12 | ``` 13 | 14 | It's so hard to come up with all the fake file names, committers, and messages! I need a inspiration. Hello, Thelma and Louise! 15 | 16 | 17 | 18 | > Louise Sawyer: You get what you settle for. 19 | 20 | > Thelma Dickinson: You said you and me was gonna get outta town and, for once, just really let our hair down. Well, darlin', look out, 'cause my hair is comin' down! 21 | 22 | > Thelma Dickinson: Louise, no matter what happens, I'm glad I came with you. 23 | 24 | > Louise Sawyer: You've always been crazy, this is just the first chance you've had to express yourself. 25 | 26 | > Thelma Dickinson: Something's, like, crossed over in me and I can't go back. I mean I just couldn't live. 27 | > Louise Sawyer: I know what you mean. Anyway, don't wanna end up on the damned Geraldo show. 28 | 29 | > Thelma Dickinson: I feel really awake. I don't recall ever feeling this awake. You know? Everything looks different now. You feel like that? You feel like you got something to live for now? 30 | 31 | > J.D.: Now, I’ve always believed if done properly armed robbery doesn't have to be a totally unpleasant experience. 32 | > Thelma Dickinson: You’re a real live outlaw aren’t you? 33 | > J.D.: Well, I may be the outlaw, darling, but you're the one stealin' my heart. 34 | 35 | > Thelma Dickinson: You’re not gonna give up on me, are ya? 36 | 37 | > Louise Sawyer: Is he your husband or your father? 38 | 39 | > Max: You know, the one thing I can't figure out are these girls real smart or real real lucky? 40 | > Hal Slocumb: Don't matter. Brains'll only get you so far and luck always runs out. 41 | 42 | > Louise: Jimmy, what color are my eyes? 43 | > Jimmy: They're brown. 44 | 45 | > Thelma: I don't ever remember feeling this awake. 46 | 47 | > Louise Sawyer: ... believe me, Texas ain't the place you want to get caught. 48 | 49 | > Louise: Well, we're not in the middle of nowhere, but we can see it from here. 50 | 51 | > Thelma: Hey Louise, better slow down, I'll just die if we get caught over a speeding ticket. Are you sure we should be driving like this, I mean in broad daylight and everything? 52 | > Louise Sawyer: No we shouldn't, but I want to put some distance between us and the SCENE OF OUR LAST GOD DAMNED CRIME! 53 | > Thelma: Oh man! You wouldn'ta believed it, it was like I was doing it all my life, nobody woulda believed it. 54 | > Louise Sawyer: Think you found your calling? 55 | > Thelma: May-be... may-be. 56 | > Thelma: The call of the wild! 57 | 58 | > Louise: [Thelma's tossing a bottle of Wild Turkey out of the car] Thelma, don't you litter. 59 | 60 | > Thelma Dickerson: Wait. What? You want to go to Mexico from Oklahoma and you don't want to go through Texas? 61 | > Louise: You know how I feel about Texas... We're not going that way. 62 | > Thelma Dickerson: I know, Louise, but we're running for our lives! Don't you think you could make an exception just this once? I mean, look at the map. The only thing between Oklahoma and Mexico is Texas! 63 | 64 | > Thelma: What is this? 65 | > Louise: I don't know, I think... I think it's the vast Grand Canyon! 66 | > Thelma: Isn't it beautiful? 67 | > Louise: Yeah, it's something. 68 | 69 | > Thelma: Let's see who wins a prize for keeping their cool. Now you sir, lay back down, thank you. Hey, throw in a couple bottles of Wild Turkey while you're at it. 70 | 71 | > Thelma: I've had it up to my ass with sedate. 72 | 73 | > Jimmy: It's a place called the Vagabond Motel. It's at 1921 North East 23rd. It's under "Peaches". 74 | > Louise: What? 75 | > Jimmy: That's the code word. I miss you, Peaches. 76 | 77 | > Jimmy: Did I hear somebody say "Peaches"? 78 | > Louise: Oh my God. Jimmy. 79 | > Jimmy: That's the secret word. Show the lady what she's won, Don. 80 | 81 | > Thelma: I had a plan, I said we should go to the police, but you didn't like that. 82 | > Louise: Well what's the rush, Thelma? If we wait long enough, they'll come to us. 83 | 84 | > Thelma Dickinson: OK, then listen: let's not get caught. 85 | > Louise Sawyer: What're you talkin' about? 86 | > Thelma Dickinson: Let's keep goin'! 87 | > Louise Sawyer: What d'you mean? 88 | > Thelma Dickinson: ... Go. 89 | -------------------------------------------------------------------------------- /internal/thelma-and-lousie-quotes.md: -------------------------------------------------------------------------------- 1 | Thelma and Louise Quotes 2 | ================ 3 | 4 | It's so hard to come up with all the fake file names, committers, and messages! I need a inspiration. Hello, Thelma and Louise! 5 | 6 | 7 | 8 | > Louise Sawyer: You get what you settle for. 9 | 10 | > Thelma Dickinson: You said you and me was gonna get outta town and, for once, just really let our hair down. Well, darlin', look out, 'cause my hair is comin' down! 11 | 12 | > Thelma Dickinson: Louise, no matter what happens, I'm glad I came with you. 13 | 14 | > Louise Sawyer: You've always been crazy, this is just the first chance you've had to express yourself. 15 | 16 | > Thelma Dickinson: Something's, like, crossed over in me and I can't go back. I mean I just couldn't live. 17 | > Louise Sawyer: I know what you mean. Anyway, don't wanna end up on the damned Geraldo show. 18 | 19 | > Thelma Dickinson: I feel really awake. I don't recall ever feeling this awake. You know? Everything looks different now. You feel like that? You feel like you got something to live for now? 20 | 21 | > J.D.: Now, I’ve always believed if done properly armed robbery doesn't have to be a totally unpleasant experience. 22 | > Thelma Dickinson: You’re a real live outlaw aren’t you? 23 | > J.D.: Well, I may be the outlaw, darling, but you're the one stealin' my heart. 24 | 25 | > Thelma Dickinson: You’re not gonna give up on me, are ya? 26 | 27 | > Louise Sawyer: Is he your husband or your father? 28 | 29 | > Max: You know, the one thing I can't figure out are these girls real smart or real real lucky? 30 | > Hal Slocumb: Don't matter. Brains'll only get you so far and luck always runs out. 31 | 32 | > Louise: Jimmy, what color are my eyes? 33 | > Jimmy: They're brown. 34 | 35 | > Thelma: I don't ever remember feeling this awake. 36 | 37 | > Louise Sawyer: ... believe me, Texas ain't the place you want to get caught. 38 | 39 | > Louise: Well, we're not in the middle of nowhere, but we can see it from here. 40 | 41 | > Thelma: Hey Louise, better slow down, I'll just die if we get caught over a speeding ticket. Are you sure we should be driving like this, I mean in broad daylight and everything? 42 | > Louise Sawyer: No we shouldn't, but I want to put some distance between us and the SCENE OF OUR LAST GOD DAMNED CRIME! 43 | > Thelma: Oh man! You wouldn'ta believed it, it was like I was doing it all my life, nobody woulda believed it. 44 | > Louise Sawyer: Think you found your calling? 45 | > Thelma: May-be... may-be. 46 | > Thelma: The call of the wild! 47 | 48 | > Louise: \[Thelma's tossing a bottle of Wild Turkey out of the car\] Thelma, don't you litter. 49 | 50 | > Thelma Dickerson: Wait. What? You want to go to Mexico from Oklahoma and you don't want to go through Texas? 51 | > Louise: You know how I feel about Texas... We're not going that way. 52 | > Thelma Dickerson: I know, Louise, but we're running for our lives! Don't you think you could make an exception just this once? I mean, look at the map. The only thing between Oklahoma and Mexico is Texas! 53 | 54 | > Thelma: What is this? 55 | > Louise: I don't know, I think... I think it's the vast Grand Canyon! 56 | > Thelma: Isn't it beautiful? 57 | > Louise: Yeah, it's something. 58 | 59 | > Thelma: Let's see who wins a prize for keeping their cool. Now you sir, lay back down, thank you. Hey, throw in a couple bottles of Wild Turkey while you're at it. 60 | 61 | > Thelma: I've had it up to my ass with sedate. 62 | 63 | > Jimmy: It's a place called the Vagabond Motel. It's at 1921 North East 23rd. It's under "Peaches". 64 | > Louise: What? 65 | > Jimmy: That's the code word. I miss you, Peaches. 66 | 67 | > Jimmy: Did I hear somebody say "Peaches"? 68 | > Louise: Oh my God. Jimmy. 69 | > Jimmy: That's the secret word. Show the lady what she's won, Don. 70 | 71 | > Thelma: I had a plan, I said we should go to the police, but you didn't like that. 72 | > Louise: Well what's the rush, Thelma? If we wait long enough, they'll come to us. 73 | 74 | > Thelma Dickinson: OK, then listen: let's not get caught. 75 | > Louise Sawyer: What're you talkin' about? 76 | > Thelma Dickinson: Let's keep goin'! 77 | > Louise Sawyer: What d'you mean? 78 | > Thelma Dickinson: ... Go. 79 | -------------------------------------------------------------------------------- /internal/uncommit-unstage-equals-mixed-reset.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: 3 | #' github_document: 4 | #' toc: true 5 | #' --- 6 | 7 | #+ setup 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | error = TRUE 12 | ) 13 | library(git2r) 14 | devtools::session_info("git2r")$packages[1, ] 15 | here <- rprojroot::find_package_root_file 16 | devtools::load_all(here()) 17 | 18 | #' ## Checking that `githug::uncommit()` + `git_unstage(all = TRUE)` leaves 19 | #' status same as `git2r::reset(, "mixed")`. 20 | repo <- file.path("~", "tmp", "reset") 21 | 22 | #' Clean up any previous work. 23 | if (dir.exists(repo)) unlink(repo, recursive = TRUE) 24 | 25 | #' Set up a repo. 26 | repo <- git_init(repo) 27 | gr <- as.git_repository(repo) 28 | 29 | #' Create 2 files and commit them. 30 | write("a line 1", file.path(repo, "a.txt")) 31 | write("I will be deleted", file.path(repo, "b.txt")) 32 | git_commit(all = TRUE, message = "first commit", repo = repo) 33 | readLines(file.path(repo, "a.txt")) 34 | readLines(file.path(repo, "b.txt")) 35 | 36 | #' Modify the first file. Delete the second. Create another file. Commit. 37 | write("I'm new in the second commit", file.path(repo, "c.txt")) 38 | file.remove(file.path(repo, "b.txt")) 39 | write("a line 2", file.path(repo, "a.txt"), append = TRUE) 40 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 41 | cat(readLines(file.path(repo, "b.txt")), sep = "\n") 42 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 43 | git_commit(all = TRUE, message = "second commit", repo = repo) 44 | git_log(repo) 45 | 46 | #' Uncommit and unstage. 47 | git_uncommit(repo = repo) 48 | git_unstage(all = TRUE, repo = repo) 49 | (status_unx2 <- git_status(repo = repo)) 50 | 51 | #' Restage and commit. 52 | git_commit(all = TRUE, message = "second commit, take two", repo = repo) 53 | git_log(repo) 54 | 55 | #' Mixed reset. 56 | reset(git_log(repo)$commit[[2]], reset_type = "mixed") 57 | (status_reset <- git_status(repo = repo)) 58 | 59 | #' Is it same as before? 60 | identical(status_unx2, status_reset) 61 | 62 | #' Clean up. 63 | unlink(repo, recursive = TRUE) 64 | -------------------------------------------------------------------------------- /internal/uncommit-unstage-equals-mixed-reset.md: -------------------------------------------------------------------------------- 1 | uncommit-unstage-equals-mixed-reset.R 2 | ================ 3 | jenny 4 | Tue Aug 9 23:06:30 2016 5 | 6 | - [Checking that `githug::uncommit()` + `git_unstage(all = TRUE)` leaves](#checking-that-githuguncommit-git_unstageall-true-leaves) 7 | 8 | ``` r 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | error = TRUE 13 | ) 14 | library(git2r) 15 | devtools::session_info("git2r")$packages[1, ] 16 | ``` 17 | 18 | ## package * version date source 19 | ## git2r * 0.15.0.9000 2016-08-09 Github (ropensci/git2r@87b7a9e) 20 | 21 | ``` r 22 | here <- rprojroot::find_package_root_file 23 | devtools::load_all(here()) 24 | ``` 25 | 26 | ## Loading githug 27 | 28 | Checking that `githug::uncommit()` + `git_unstage(all = TRUE)` leaves 29 | --------------------------------------------------------------------- 30 | 31 | status same as `git2r::reset(, "mixed")`. 32 | 33 | ``` r 34 | repo <- file.path("~", "tmp", "reset") 35 | ``` 36 | 37 | Clean up any previous work. 38 | 39 | ``` r 40 | if (dir.exists(repo)) unlink(repo, recursive = TRUE) 41 | ``` 42 | 43 | Set up a repo. 44 | 45 | ``` r 46 | repo <- git_init(repo) 47 | #> * Creating directory: 48 | #> /Users/jenny/tmp/reset 49 | #> * Initialising git repository in: 50 | #> /Users/jenny/tmp/reset 51 | gr <- as.git_repository(repo) 52 | ``` 53 | 54 | Create 2 files and commit them. 55 | 56 | ``` r 57 | write("a line 1", file.path(repo, "a.txt")) 58 | write("I will be deleted", file.path(repo, "b.txt")) 59 | git_commit(all = TRUE, message = "first commit", repo = repo) 60 | #> Staged these paths: 61 | #> * a.txt 62 | #> * b.txt 63 | #> Commit: 64 | #> * [f5e97db] 2016-08-09: first commit 65 | readLines(file.path(repo, "a.txt")) 66 | #> [1] "a line 1" 67 | readLines(file.path(repo, "b.txt")) 68 | #> [1] "I will be deleted" 69 | ``` 70 | 71 | Modify the first file. Delete the second. Create another file. Commit. 72 | 73 | ``` r 74 | write("I'm new in the second commit", file.path(repo, "c.txt")) 75 | file.remove(file.path(repo, "b.txt")) 76 | #> [1] TRUE 77 | write("a line 2", file.path(repo, "a.txt"), append = TRUE) 78 | cat(readLines(file.path(repo, "a.txt")), sep = "\n") 79 | #> a line 1 80 | #> a line 2 81 | cat(readLines(file.path(repo, "b.txt")), sep = "\n") 82 | #> Warning in file(con, "r"): cannot open file '/Users/jenny/tmp/reset/b.txt': 83 | #> No such file or directory 84 | #> Error in file(con, "r"): cannot open the connection 85 | cat(readLines(file.path(repo, "c.txt")), sep = "\n") 86 | #> I'm new in the second commit 87 | git_commit(all = TRUE, message = "second commit", repo = repo) 88 | #> Staged these paths: 89 | #> * a.txt 90 | #> * b.txt 91 | #> * c.txt 92 | #> Commit: 93 | #> * [c742d21] 2016-08-09: second commit 94 | git_log(repo) 95 | #> # A tibble: 2 x 6 96 | #> sha message when author 97 | #> 98 | #> 1 c742d21 second commit 2016-08-09 23:06 jennybc 99 | #> 2 f5e97db first commit 2016-08-09 23:06 jennybc 100 | #> # ... with 2 more variables: email , commit 101 | ``` 102 | 103 | Uncommit and unstage. 104 | 105 | ``` r 106 | git_uncommit(repo = repo) 107 | #> Uncommit: 108 | #> * [c742d21] 2016-08-09: second commit 109 | #> HEAD now points to (but no files were changed!): 110 | #> * [f5e97db] 2016-08-09: first commit 111 | git_unstage(all = TRUE, repo = repo) 112 | #> Unstaged these paths: 113 | #> * c.txt 114 | #> * a.txt 115 | #> * b.txt 116 | (status_unx2 <- git_status(repo = repo)) 117 | #> # A tibble: 3 x 4 118 | #> status path change i 119 | #> 120 | #> 1 unstaged a.txt modified NA 121 | #> 2 unstaged b.txt deleted NA 122 | #> 3 untracked c.txt new NA 123 | ``` 124 | 125 | Restage and commit. 126 | 127 | ``` r 128 | git_commit(all = TRUE, message = "second commit, take two", repo = repo) 129 | #> Staged these paths: 130 | #> * a.txt 131 | #> * b.txt 132 | #> * c.txt 133 | #> Commit: 134 | #> * [f4b5253] 2016-08-09: second commit, take two 135 | git_log(repo) 136 | #> # A tibble: 2 x 6 137 | #> sha message when author 138 | #> 139 | #> 1 f4b5253 second commit, take two 2016-08-09 23:06 jennybc 140 | #> 2 f5e97db first commit 2016-08-09 23:06 jennybc 141 | #> # ... with 2 more variables: email , commit 142 | ``` 143 | 144 | Mixed reset. 145 | 146 | ``` r 147 | reset(git_log(repo)$commit[[2]], reset_type = "mixed") 148 | (status_reset <- git_status(repo = repo)) 149 | #> # A tibble: 3 x 4 150 | #> status path change i 151 | #> 152 | #> 1 unstaged a.txt modified NA 153 | #> 2 unstaged b.txt deleted NA 154 | #> 3 untracked c.txt new NA 155 | ``` 156 | 157 | Is it same as before? 158 | 159 | ``` r 160 | identical(status_unx2, status_reset) 161 | #> [1] TRUE 162 | ``` 163 | 164 | Clean up. 165 | 166 | ``` r 167 | unlink(repo, recursive = TRUE) 168 | ``` 169 | -------------------------------------------------------------------------------- /man-roxygen/repo.R: -------------------------------------------------------------------------------- 1 | #' @param repo Path to a Git repo. If unspecified, current working directory is 2 | #' checked to see if it is or is inside a Git repo. 3 | -------------------------------------------------------------------------------- /man-roxygen/return-SHA-with-hint.R: -------------------------------------------------------------------------------- 1 | #' @return SHA of the commit. The \code{when} attribute holds the commit time as 2 | #' \code{POSIXct}. An excerpt of the commit message is in the \code{msg_start} 3 | #' attribute. 4 | -------------------------------------------------------------------------------- /man-roxygen/return-repo-path.R: -------------------------------------------------------------------------------- 1 | #' @return Path to the associated Git repo. 2 | -------------------------------------------------------------------------------- /man-roxygen/rev.R: -------------------------------------------------------------------------------- 1 | #' @param rev Target commit, as a 2 | #' \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 3 | #' string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 4 | #' substring thereof. 5 | -------------------------------------------------------------------------------- /man/add-and-commit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_add-commit.R 3 | \name{add-and-commit} 4 | \alias{add-and-commit} 5 | \alias{git_ADD} 6 | \alias{git_COMMIT} 7 | \alias{git_add} 8 | \alias{git_commit} 9 | \title{Stage and commit files} 10 | \usage{ 11 | git_add(path, repo = ".", ...) 12 | 13 | git_commit(message = NULL, repo = ".", ...) 14 | 15 | git_ADD(repo = ".") 16 | 17 | git_COMMIT(message = NULL, repo = ".") 18 | } 19 | \arguments{ 20 | \item{path}{Character vector with file names or shell glob 21 | patterns that will matched against files in the repository's 22 | working directory. Each file that matches will be added to the 23 | index (either updating an existing entry or adding a new entry).} 24 | 25 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 26 | checked to see if it is or is inside a Git repo.} 27 | 28 | \item{...}{Additional arguments to \code{\link[git2r]{add}} or 29 | \code{\link[git2r]{commit}} from \code{\link{git2r}}} 30 | 31 | \item{message}{The commit message.} 32 | } 33 | \value{ 34 | Path to the associated Git repo. 35 | } 36 | \description{ 37 | Stage files in preparation for a commit. And then commit them. Maybe even all 38 | at once! Convenience wrappers around \code{\link[git2r]{add}}, 39 | \code{\link[git2r]{commit}}, and \code{\link[git2r]{status}} from 40 | \code{\link{git2r}}. 41 | } 42 | \details{ 43 | \code{git_add} adds the current content of files identified via 44 | \code{path} to the index, using \code{\link[git2r]{add}} from 45 | \code{\link{git2r}}. These files are slated for inclusion in the next 46 | commit. What might go in \code{...}? You could set `force = TRUE` if you 47 | want to force add ignored files. 48 | 49 | \code{git_commit} stores the current contents of the index in a new 50 | commit along with a message describing the changes. What might go in 51 | \code{...}? Read up on the arguments to \code{\link[git2r]{commit}} from 52 | \code{\link{git2r}}, which this wraps. 53 | 54 | \code{git_ADD} says "JUST STAGE ALL THE THINGS." Use this when you 55 | want the next commit to reflect all new files, file deletions, and file 56 | modifications in your repo. Similar to the automatic staging behavior of 57 | \href{https://git-scm.com/book/en/v2/Git-in-Other-Environments-Graphical-Interfaces}{workflow-oriented 58 | Git clients} like \href{https://desktop.github.com}{GitHub Desktop}. The 59 | intent is to emulate \code{git add -A}, which is equivalent to \code{git 60 | add .; git add -u}. 61 | 62 | \code{git_COMMIT} says "JUST COMMIT ALL THE THINGS." Use this when 63 | you just want to commit the current state of your repo. It is 64 | \code{git_ADD} followed by \code{git_commit}. The intent is to emulate 65 | \code{git add -A && git commit}. 66 | } 67 | \examples{ 68 | ## conventional git add, status, commit 69 | repo <- git_init(tempfile("githug-")) 70 | owd <- setwd(repo) 71 | writeLines("Are these girls real smart or real real lucky?", "max.txt") 72 | git_add("max.txt") 73 | git_status() 74 | git_commit("Brains'll only get you so far and luck always runs out.") 75 | git_status() 76 | setwd(owd) 77 | 78 | if (require(dplyr)) { 79 | ## are pipes silly here? perhaps ... 80 | repo <- tempfile("githug-") \%>\% 81 | git_init() 82 | owd <- setwd(repo) 83 | writeLines("Are these girls real smart or real real lucky?", "max.txt") 84 | "max.txt" \%>\% 85 | git_add() \%>\% 86 | git_status() 87 | git_commit("Brains'll only get you so far and luck always runs out.") \%>\% 88 | git_status() 89 | setwd(owd) 90 | } 91 | 92 | ## THE SHOUTY COMMANDS 93 | repo <- git_init(tempfile("GITHUG-")) 94 | owd <- setwd(repo) 95 | writeLines("Change me", "change-me") 96 | writeLines("Delete me", "delete-me") 97 | git_status() 98 | git_add(c("change-me", "delete-me")) 99 | git_status() 100 | git_commit("initial") 101 | write("OK", "change-me", append = TRUE) 102 | file.remove("delete-me") 103 | writeLines("Add me", "add-me") 104 | git_status() 105 | git_ADD() 106 | git_status() 107 | ## TO DO: return here when commits and reset are wrapped 108 | ccc <- git2r::commits()[[1]] 109 | git2r::reset(ccc, "mixed") 110 | git_status() 111 | git_COMMIT("JUST DO IT.") 112 | git_status() 113 | setwd(owd) 114 | } 115 | 116 | -------------------------------------------------------------------------------- /man/as.git_commit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_as_git_commit.R 3 | \name{as.git_commit} 4 | \alias{as.git_commit} 5 | \title{Address a commit, the git2r way} 6 | \usage{ 7 | as.git_commit(x, repo = ".", ...) 8 | } 9 | \arguments{ 10 | \item{x}{Target commit, as a 11 | \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 12 | string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 13 | substring thereof.} 14 | 15 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 16 | checked to see if it is or is inside a Git repo.} 17 | 18 | \item{...}{additional arguments (none currently in use)} 19 | } 20 | \value{ 21 | An S4 \code{\linkS4class{git_commit}} object 22 | } 23 | \description{ 24 | Use this to convert a 25 | \href{https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 26 | string} into an object of class \code{\linkS4class{git_commit}}, which is how 27 | the \code{\link{git2r}} package handles Git commits. 28 | } 29 | \details{ 30 | \code{githug} uses \code{\link{git2r}}, under the hood, to perform local Git 31 | operations. While \code{githug} always identifies a commit via its SHA or a 32 | revision string, \code{\link{git2r}} handles repositories as objects of class 33 | \code{\linkS4class{git_commit}}. If you want to do a Git operation that isn't 34 | exposed via \code{githug}, this function helps you specify the commit 35 | \code{\link{git2r}}-style. 36 | } 37 | \examples{ 38 | repo <- git_init(tempfile("githug-")) 39 | owd <- setwd(repo) 40 | write("Are these girls real smart or real real lucky?", "max.txt") 41 | write("You get what you settle for.", "louise.txt") 42 | git_commit(all = TRUE, 43 | message = "Brains'll only get you so far and luck always runs out.") 44 | write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 45 | "jd.txt") 46 | git_commit("jd.txt", message = "J.D. is charming") 47 | 48 | ## refer to these commits 49 | as.git_commit("HEAD") 50 | as.git_commit("HEAD^") 51 | as.git_commit("master~1") 52 | 53 | setwd(owd) 54 | } 55 | 56 | -------------------------------------------------------------------------------- /man/as.git_repository.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_repository.R 3 | \name{as.git_repository} 4 | \alias{as.git_repository} 5 | \title{Open a Git repository, the git2r way} 6 | \usage{ 7 | as.git_repository(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{path that is or is in a Git repository; defaults to working 11 | directory} 12 | 13 | \item{...}{additional arguments, such as \code{ceiling} from 14 | \code{\link[git2r]{discover_repository}()}} 15 | } 16 | \value{ 17 | An S4 \code{\linkS4class{git_repository}} object 18 | } 19 | \description{ 20 | Use this to convert a path into a \code{\linkS4class{git_repository}} object, 21 | which is how the \code{\link{git2r}} package handles Git repositories. This 22 | function is a slightly more flexible version of 23 | \code{\link[git2r:repository]{git2r::repository}()}. 24 | } 25 | \details{ 26 | \code{githug} uses \code{\link{git2r}}, under the hood, to perform local Git 27 | operations. While \code{githug} always identifies the repository via its 28 | path, \code{\link{git2r}} handles repositories as objects of class 29 | \code{\linkS4class{git_repository}}. If you want to do a Git operation that 30 | isn't exposed via \code{githug}, this function helps you specify the 31 | repository \code{\link{git2r}}-style. 32 | } 33 | \examples{ 34 | repo <- git_init(tempfile("git-repository-example-")) 35 | 36 | ## you can specify the path explicitly 37 | as.git_repository(repo) 38 | 39 | ## switch working directory to the repo 40 | owd <- setwd(repo) 41 | 42 | ## as.git_repository() with no args consults working directory 43 | as.git_repository() 44 | 45 | dir.create("subdir") 46 | 47 | ## as.git_repository() walks up parents, looking for a repo 48 | as.git_repository("subdir") 49 | 50 | setwd("subdir") 51 | as.git_repository() 52 | ## unless you put a ceiling on the walk 53 | \dontrun{ 54 | as.git_repository("repo-path/subdir", ceiling = 0) 55 | } 56 | 57 | setwd(owd) 58 | 59 | \dontrun{ 60 | ## here's a rather exotic Git operation that githug is unlikely to expose: 61 | ## odb_blobs() lists "all blobs reachable from the commits in the object database" 62 | ## pre-process the repo with as_git_repository() to prepare for git2r 63 | git2r::odb_blobs(as.git_repository("path_to_a_git_repo")) 64 | } 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /man/as_git_repository.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rpath-class.R 3 | \name{as_git_repository} 4 | \alias{as_git_repository} 5 | \title{Open a Git repository, the git2r way} 6 | \usage{ 7 | as_git_repository(x = ".") 8 | } 9 | \arguments{ 10 | \item{x}{Git repository specified as a path. Or as an object of class 11 | \code{rpath} (S3 class used only internally in \code{\link{githug}}) or of 12 | class \code{\linkS4class{git_repository}} (from the \code{\link{git2r}} 13 | package).} 14 | } 15 | \value{ 16 | An S4 \code{\linkS4class{git_repository}} object 17 | } 18 | \description{ 19 | \code{\link{githug}} uses the \code{\link{git2r}} package, under the hood, to 20 | perform local Git operations. \code{\link{git2r}} handles Git repos as 21 | objects of class \code{\linkS4class{git_repository}}. Use this function to 22 | convert a path (or other way of referring to a Git repo) to the right sort of 23 | input. You might need this for more exotic Git operations, i.e. to call 24 | \code{\link{git2r}} functions that aren't exposed via \code{\link{githug}}. 25 | } 26 | \examples{ 27 | repo <- git_init(tempfile("githug-to-git2r-example-")) 28 | owd <- setwd(repo) 29 | git_config(user.name = "jd", user.email = "jd@example.org") 30 | writeLines(paste("Well, I've always believed that if done properly, armed", 31 | "robbery doesn't have to be an unpleasant experience."), 32 | "jd.txt") 33 | git_add("jd.txt") 34 | git_commit("jd is a smooth talker") 35 | 36 | ## here's a rather exotic Git operation that githug is unlikely to expose: 37 | ## odb_blobs() lists "all blobs reachable from the commits in the object database" 38 | ## pre-process the repo with as_git_repository() to prepare for git2r 39 | git2r::odb_blobs(as_git_repository()) 40 | setwd(owd) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /man/gh_pat.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gh_pat-username.R 3 | \name{gh_pat} 4 | \alias{gh_pat} 5 | \title{Retrieve GitHub personal access token.} 6 | \usage{ 7 | gh_pat(envvar = c("GITHUB_PAT", "GITHUB_TOKEN")) 8 | } 9 | \arguments{ 10 | \item{envvar}{Name of the environment variable in which the GitHub personal 11 | access token is stored. Can be a character vector, with names in order of 12 | preference.} 13 | } 14 | \value{ 15 | The GitHub personal access token, invisibly. 16 | } 17 | \description{ 18 | Consults environment variables for a GitHub personal access token (PAT). By 19 | default, looks for \code{GITHUB_PAT} and \code{GITHUB_TOKEN}, in that order. 20 | Conventions followed by other packages at the time of writing: 21 | \code{\link{git2r}} and \code{devtools} look for \code{GITHUB_PAT}, 22 | \code{\link{gh}} looks for \code{GITHUB_TOKEN}. Obtain a PAT from 23 | \url{https://github.com/settings/tokens}. Unless you have a specific reason 24 | to request otherwise, the default scopes are probably sufficient. 25 | } 26 | \details{ 27 | How to store your PAT in an environment variable? 28 | 29 | \itemize{ 30 | 31 | \item Identify your home directory. Not sure? Enter 32 | \code{normalizePath("~/")} in the R console. 33 | 34 | \item If you don't already have a file here named \code{.Renviron}, create 35 | one. If you have one already, open it for editing. 36 | 37 | \item Add a line like this: \code{GITHUB_PAT=blahblahblahblahblahblah}, where 38 | \code{blahblahblahblahblahblah} is your PAT. Make sure the last line in the 39 | file is empty. Otherwise R will \strong{silently} fail to load the file. 40 | Save. And yes you do want to use a filename that begins with a dot. 41 | 42 | \item Restart R. \code{.Renviron} is processed only during 43 | \code{\link{Startup}}. 44 | 45 | \item Check your work with \code{Sys.getenv("GITHUB_PAT")}. Your PAT should 46 | print to screen. 47 | 48 | } 49 | } 50 | \examples{ 51 | ## by default, the PAT is not printed to screen 52 | gh_pat() 53 | 54 | ## if you really want to see it, surround the call with extra parentheses 55 | (gh_pat()) 56 | } 57 | 58 | -------------------------------------------------------------------------------- /man/gh_username.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gh_pat-username.R 3 | \name{gh_username} 4 | \alias{gh_username} 5 | \title{Get authenticated username} 6 | \usage{ 7 | gh_username(pat = gh_pat()) 8 | } 9 | \arguments{ 10 | \item{pat}{A GitHub personal access token (PAT).} 11 | } 12 | \value{ 13 | character, a GitHub username 14 | } 15 | \description{ 16 | Get the authenticated username associated with a GitHub personal access 17 | token. Wraps the 18 | \href{https://developer.github.com/v3/users/#get-the-authenticated-user}{authenticated 19 | user} endpoint of the \href{https://developer.github.com/v3/}{GitHub API}. 20 | } 21 | \examples{ 22 | gh_username() 23 | } 24 | \seealso{ 25 | \code{\link{gh_pat}()} 26 | } 27 | 28 | -------------------------------------------------------------------------------- /man/git-revision.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_revision.R 3 | \name{git-revision} 4 | \alias{git-revision} 5 | \alias{git_revision} 6 | \alias{git_revision_exists} 7 | \title{Identify commits like a human} 8 | \usage{ 9 | git_revision(rev = "HEAD", repo = ".") 10 | 11 | git_revision_exists(rev, repo = ".") 12 | } 13 | \arguments{ 14 | \item{rev}{Target commit, as a 15 | \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 16 | string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 17 | substring thereof.} 18 | 19 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 20 | checked to see if it is or is inside a Git repo.} 21 | } 22 | \value{ 23 | SHA of the commit. The \code{when} attribute holds the commit time as 24 | \code{POSIXct}. An excerpt of the commit message is in the \code{msg_start} 25 | attribute. 26 | } 27 | \description{ 28 | Retrieve the SHA-1 for a specific commit via a human-friendly description, 29 | like 30 | \itemize{ 31 | \item \code{HEAD}: the most recent commit and the one that will be 32 | parent to the next commit. 33 | \item \code{master@{1 month ago}}: the tip commit of the 34 | \code{master} branch this time last month 35 | \item \code{bug-fix}: the tip commit of the \code{bug-fix} branch 36 | \item \code{feature^}: parent of the tip commit of the \code{feature} branch 37 | \item \code{master~2}: grandparent of the tip commit of the \code{master} 38 | branch 39 | \item \code{8675309}: commit with \code{8675309} as leading substring of SHA-1 40 | } 41 | Convenience wrapper around 42 | \code{\link[git2r:revparse_single]{git2r::revparse_single}()}, which 43 | implements functionality from \code{git rev-parse}. 44 | } 45 | \section{git_revision}{ 46 | 47 | 48 | If called with no arguments, this returns SHA for HEAD of repo associated 49 | with current working directory. 50 | } 51 | 52 | \section{git_revision_exists}{ 53 | 54 | 55 | Tests if \code{rev} can be resolved to a specific commit. 56 | } 57 | \examples{ 58 | repo <- git_init(tempfile("githug-revisions-")) 59 | owd <- setwd(repo) 60 | 61 | ## no commits --> HEAD cannot be resolved 62 | git_revision_exists("HEAD") 63 | \dontrun{ 64 | git_revision() 65 | } 66 | 67 | ## commit and ... now HEAD exists 68 | write("Well, we're not in the middle of nowhere,", "nowhere.txt") 69 | git_commit(all = TRUE, message = "1ouise: not in the middle of nowhere") 70 | git_revision() 71 | git_revision_exists("HEAD") 72 | 73 | ## make a new commit then ask for parent of HEAD 74 | write("but we can see it from here.", "nowhere.txt", append = TRUE) 75 | git_commit(all = TRUE, message = "louise: but can see it") 76 | git_revision("HEAD^") 77 | 78 | ## create a new branch and find out what it points at 79 | git_switch("newbranch", create = TRUE) 80 | git_revision("newbranch") 81 | 82 | setwd(owd) 83 | } 84 | \references{ 85 | \href{https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection}{Revision 86 | Selection} from the Pro Git book by Scott Chacon and Ben Straub 87 | 88 | Specifying revisions section of the 89 | \href{https://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{git-rev-parse 90 | man page} 91 | } 92 | 93 | -------------------------------------------------------------------------------- /man/git_HEAD.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_HEAD.R 3 | \name{git_HEAD} 4 | \alias{git_HEAD} 5 | \title{Get HEAD for a repository} 6 | \usage{ 7 | git_HEAD(repo = ".") 8 | } 9 | \arguments{ 10 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 11 | checked to see if it is or is inside a Git repo.} 12 | } 13 | \value{ 14 | A list with components: 15 | \describe{ 16 | \item{\code{branch_name}}{the name and ...} 17 | \item{\code{branch_type}}{type (local vs. remote) of the branch whose tip 18 | HEAD currently points to} 19 | \item{\code{head_sha}}{the SHA and ...} 20 | \item{\code{head_commit}}{\code{\linkS4class{git_commit}} object for the 21 | specific commit HEAD points to} 22 | \item{\code{repo}}{associated \code{\linkS4class{git_repository}} object} 23 | \item{\code{git_branch}}{associated \code{\linkS4class{git_branch}} object} 24 | } 25 | or \code{NULL} if \code{HEAD} does not exist. 26 | } 27 | \description{ 28 | Convenience wrapper around \code{\link[git2r]{head-methods}} from 29 | \code{\link{git2r}}. 30 | } 31 | \examples{ 32 | repo <- git_init(tempfile("githug-")) 33 | git_HEAD(repo = repo) 34 | } 35 | 36 | -------------------------------------------------------------------------------- /man/git_amend.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_amend.R 3 | \name{git_amend} 4 | \alias{git_amend} 5 | \title{Re-do the most recent commit} 6 | \usage{ 7 | git_amend(message = character(), ask = TRUE, repo = ".") 8 | } 9 | \arguments{ 10 | \item{message}{The commit message. If not provided and \code{ask = FALSE}, 11 | the original commit message is reused. If message is not given and 12 | \code{ask = TRUE} and session is interactive, you get a chance to supply 13 | the message, including an option to reuse the original message.} 14 | 15 | \item{ask}{Whether to confirm that user wants to change history} 16 | 17 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 18 | checked to see if it is or is inside a Git repo.} 19 | } 20 | \value{ 21 | SHA of the commit. The \code{when} attribute holds the commit time as 22 | \code{POSIXct}. An excerpt of the commit message is in the \code{msg_start} 23 | attribute. 24 | } 25 | \description{ 26 | Undo the most recent commit WHILE LEAVING ALL YOUR FILES ALONE, combine those 27 | changes with the currently staged changes, and make a new commit. If nothing 28 | is currently staged, this is just a way to edit the most recent commit 29 | message. This function is "working directory safe" but "history unsafe". 30 | Think twice before "amending" a commit that you have pushed (see Details). 31 | } 32 | \details{ 33 | \code{git_amend()} will not change your files. It gives you a do over on your 34 | most recent Git commit. When might you use this? If you realize the most 35 | recent commit included the wrong file changes or had a bad commit message. If 36 | you're not ready to re-commit yet, use \code{\link{git_uncommit}()} to just 37 | undo the commit. 38 | 39 | When might you NOT want to use this? If you have already pushed the most 40 | recent commit to a remote. It could still be OK if you're sure no one else 41 | has pulled. But be prepared to force push in this situation. 42 | 43 | \code{git_amend()} addresses the fourth most up-voted question on 44 | StackOverflow: 45 | \href{http://stackoverflow.com/questions/179123/edit-an-incorrect-commit-message-in-git}{Edit 46 | an incorrect commit message in Git}, with over 1.7 million views. It is 47 | equivalent to \code{git commit --amend -m "New commit message"}. 48 | } 49 | \examples{ 50 | repo <- git_init(tempfile("githug-")) 51 | owd <- setwd(repo) 52 | write("Are these girls real smart or real real lucky?", "max.txt") 53 | git_commit("max.txt", message = "lines from max") 54 | write("Did I hear somebody say \\"Peaches\\"?", "jimmy.txt") 55 | git_commit("jimmy.txt", message = "lines from some guy") 56 | git_history() ## note the SHA of the most recent commit 57 | 58 | ## fix the previous commit message 59 | git_amend(message = "lines from jimmy", ask = FALSE) 60 | git_history() ## note the SHA of most recent commit has changed 61 | 62 | setwd(owd) 63 | } 64 | 65 | -------------------------------------------------------------------------------- /man/git_branch_create.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_branch_create.R 3 | \name{git_branch_create} 4 | \alias{git_branch_create} 5 | \title{Create a new branch} 6 | \usage{ 7 | git_branch_create(name, repo = ".", rev = "HEAD") 8 | } 9 | \arguments{ 10 | \item{name}{Name for the new branch} 11 | 12 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 13 | checked to see if it is or is inside a Git repo.} 14 | 15 | \item{rev}{Target commit, as a 16 | \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 17 | string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 18 | substring thereof.} 19 | } 20 | \description{ 21 | Create a new local branch. You must specify the \code{name} of the new 22 | branch, at the very least. By default, the new branch will point at current 23 | HEAD. Optionally, you can specify another commit to base the branch on, via a 24 | \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 25 | string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 26 | substring thereof. 27 | } 28 | \details{ 29 | Convenience wrapper around 30 | \code{\link[git2r:branch_create]{git2r::branch_create}()}. 31 | } 32 | \examples{ 33 | repo <- git_init(tempfile("githug-branches-")) 34 | owd <- setwd(repo) 35 | 36 | ## first commit 37 | writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 38 | git_commit("nowhere.txt", message = "1: not in the middle of nowhere") 39 | git_branch_list() 40 | 41 | ## second commit 42 | write("but we can see it from here.", "nowhere.txt", append = TRUE) 43 | git_commit("nowhere.txt", message = "2: but we can see it from here") 44 | 45 | ## create new branch that points at HEAD = second commit 46 | git_branch_create("carpe_diem") 47 | git_branch_list() 48 | 49 | ## create new branch that points at *first commit*, via its SHA 50 | (gl <- git_history()) 51 | git_branch_create("hindsight", rev = gl$sha[[2]]) 52 | git_branch_list() 53 | 54 | \dontrun{ 55 | ## try to re-create an existing branch and fail 56 | git_branch_create("hindsight") 57 | } 58 | 59 | setwd(owd) 60 | } 61 | 62 | -------------------------------------------------------------------------------- /man/git_branch_delete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_branch_delete.R 3 | \name{git_branch_delete} 4 | \alias{git_branch_delete} 5 | \title{Delete a branch} 6 | \usage{ 7 | git_branch_delete(name, repo = ".") 8 | } 9 | \arguments{ 10 | \item{name}{Name of the branch} 11 | 12 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 13 | checked to see if it is or is inside a Git repo.} 14 | } 15 | \description{ 16 | Delete an existing local branch. You won't be allowed to delete the branch 17 | you're on. Switch to another branch, then delete. 18 | } 19 | \details{ 20 | Convenience wrapper around 21 | \code{\link[git2r:branch_delete]{git2r::branch_delete}()}. 22 | } 23 | \examples{ 24 | repo <- git_init(tempfile("githug-branches-")) 25 | owd <- setwd(repo) 26 | 27 | ## commit so that master branch exists 28 | writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 29 | git_commit("nowhere.txt", message = "... but we can see it from here.") 30 | 31 | ## create a branch off of master 32 | git_branch_create("doomed") 33 | git_branch_list() 34 | 35 | ## switch to doomed branch 36 | git_switch("doomed") 37 | git_branch() 38 | 39 | \dontrun{ 40 | ## try -- and fail -- to delete doomed branch 41 | git_branch_delete("doomed") 42 | } 43 | 44 | ## switch back to master 45 | git_switch() 46 | 47 | ## delete the new branch 48 | git_branch_delete("doomed") 49 | git_branch_list() 50 | 51 | setwd(owd) 52 | } 53 | 54 | -------------------------------------------------------------------------------- /man/git_branch_rename.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_branch_rename.R 3 | \name{git_branch_rename} 4 | \alias{git_branch_rename} 5 | \title{Rename a branch} 6 | \usage{ 7 | git_branch_rename(from, to, repo = ".") 8 | } 9 | \arguments{ 10 | \item{from}{Name of the existing branch} 11 | 12 | \item{to}{New name for the branch} 13 | 14 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 15 | checked to see if it is or is inside a Git repo.} 16 | } 17 | \description{ 18 | Rename an existing local branch. It's fine to rename the branch you're on. 19 | } 20 | \details{ 21 | Convenience wrapper around 22 | \code{\link[git2r:branch_create]{git2r::branch_rename}()}. 23 | } 24 | \examples{ 25 | repo <- git_init(tempfile("githug-branches-")) 26 | owd <- setwd(repo) 27 | 28 | ## commit so that master branch exists 29 | writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 30 | git_commit("nowhere.txt", message = "... but we can see it from here.") 31 | git_branch_list() 32 | 33 | ## rename master 34 | git_branch_rename("master", "louise") 35 | git_branch_list() 36 | 37 | setwd(owd) 38 | } 39 | 40 | -------------------------------------------------------------------------------- /man/git_commit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_commit.R 3 | \name{git_commit} 4 | \alias{git_commit} 5 | \title{Make a commit} 6 | \usage{ 7 | git_commit(..., all = FALSE, force = FALSE, message = character(), 8 | repo = ".") 9 | } 10 | \arguments{ 11 | \item{...}{One or more paths or shell glob patterns that will be matched 12 | against files in the repo's working directory. Paths that match will be 13 | added to the set of changes staged for the next commit.} 14 | 15 | \item{all}{Logical, consulted if no paths are given. If \code{TRUE}, 16 | pre-authorizes the staging of all new files, file deletions, and file 17 | modifications. Emulates \code{git add -A}, which is equivalent to \code{git 18 | add .; git add -u}.} 19 | 20 | \item{force}{Logical, defaults to \code{FALSE}. Value \code{TRUE} is required 21 | if any of the to-be-staged paths are currently ignored.} 22 | 23 | \item{message}{The commit message. Required. If not supplied, user will get a 24 | chance to provide the message in an interactive session.} 25 | 26 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 27 | checked to see if it is or is inside a Git repo.} 28 | } 29 | \value{ 30 | SHA of the commit. The \code{when} attribute holds the commit time as 31 | \code{POSIXct}. An excerpt of the commit message is in the \code{msg_start} 32 | attribute. 33 | } 34 | \description{ 35 | Write file changes to the repository. If there are staged changes, the simple 36 | call \code{git_commit(message = "my message")} makes a commit. If nothing's 37 | staged and it's an interactive session, you'll get an offer to "just stage & 38 | commit everything: Y/N?". To explicitly authorize "stage and commit" for all 39 | current file additions, deletions, and modifications, use 40 | \code{git_commit(all = TRUE)}, which emulates \code{git add -A && git 41 | commit}. In an interactive session, you will also get the chance to enter a 42 | missing commit message. 43 | } 44 | \details{ 45 | Convenience wrapper around \code{\link[git2r:commit]{git2r::commit}()} and, 46 | possibly, \code{\link{git_stage}()}. 47 | } 48 | \examples{ 49 | repo <- git_init(tempfile("githug-")) 50 | owd <- setwd(repo) 51 | write("Are these girls real smart or real real lucky?", "max.txt") 52 | write("You get what you settle for.", "louise.txt") 53 | git_status() 54 | git_commit(all = TRUE, 55 | message = "Brains'll only get you so far and luck always runs out.") 56 | write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 57 | "jd.txt") 58 | git_status() 59 | git_commit("jd.txt", message = "J.D. is charming") 60 | setwd(owd) 61 | } 62 | 63 | -------------------------------------------------------------------------------- /man/git_config.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_config.R 3 | \name{git_config} 4 | \alias{git_config} 5 | \alias{git_config_global} 6 | \alias{git_config_local} 7 | \title{Get and set Git configuration variables} 8 | \usage{ 9 | git_config(..., where = c("de_facto", "local", "global"), repo = ".") 10 | 11 | git_config_global(..., repo = ".") 12 | 13 | git_config_local(..., repo = ".") 14 | } 15 | \arguments{ 16 | \item{...}{The Git configuration variables to get or set. If unspecified, 17 | all are returned, i.e. the output should match the result of \code{git 18 | config --list}.} 19 | 20 | \item{where}{Specifies which variables. The default, \code{de_facto}, applies 21 | only to a query and requests the variables in force, i.e. where local repo 22 | variables override global user-level variables, when both are defined. 23 | \code{local} or \code{global} narrows the scope to the associated 24 | configuration file: for \code{local}, \code{.git/config} in the targetted 25 | \code{repo}, and for \code{global}, \code{~/.gitconfig} in user's home 26 | directory. When setting, if \code{where} is unspecified, the local 27 | configuration is modified.} 28 | 29 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 30 | checked to see if it is or is inside a Git repo.} 31 | } 32 | \value{ 33 | A named list of Git configuration variables, with class 34 | \code{githug_list} for pretty-printing purposes. 35 | } 36 | \description{ 37 | \code{git_config} and convenience wrappers \code{git_config_global} and 38 | \code{git_config_local} can be used to get or set Git configuration. All 39 | rely on \code{\link[git2r:config]{git2r::config}()}. 40 | } 41 | \details{ 42 | Variables can be listed by specifying the names as strings or in a unnamed 43 | list or vector of strings. Don't specify anything if you want to see them 44 | all. Non-existent variables will return value \code{NULL}. 45 | 46 | Variables can be set by specifying them as arguments in \code{variable = 47 | value} form or as a named list. To unset a variable, i.e. \code{git config 48 | --unset}, specify \code{NULL} as the value. 49 | 50 | When variables are set, the previous values are returned in an invisible 51 | named list, analogous to \code{\link{par}} or \code{\link{setwd}}. Such a 52 | list can be passed back in to \code{git_config_local} or 53 | \code{git_config_global} to restore the previous configuration. 54 | 55 | Consult the \href{https://git-scm.com/docs/git-config}{git-config man page} 56 | for a long yet non-comprehensive list of variables. 57 | 58 | For future GitHub happiness, it is highly recommended that you set 59 | \code{user.email} to an email address that is associated with your GitHub 60 | account: \url{https://help.github.com/articles/setting-your-email-in-git/}. 61 | } 62 | \section{Functions}{ 63 | \itemize{ 64 | \item \code{git_config_global}: Get or set global Git config, a la \code{git config 65 | --global} 66 | 67 | \item \code{git_config_local}: Get or set local Git config, a la \code{git config 68 | --local} 69 | }} 70 | \examples{ 71 | ## see git config currently in effect, based on working directory 72 | git_config() # local > global, same as git_config(where = "de_facto") 73 | git_config_local() # same as git_config(where = "local") 74 | git_config_global() # same as git_config(where = "global") 75 | 76 | ## set and get global config 77 | \dontrun{ 78 | ## set and list global config 79 | git_config_global(user.name = "thelma", user.email = "thelma@example.org") 80 | git_config_global("user.name", "user.email") 81 | } 82 | 83 | ## specify a Git repo 84 | repo <- git_init(tempfile("githug-config-example-")) 85 | git_config_local(repo = repo) 86 | 87 | ## switch working directory to the repo 88 | owd <- setwd(repo) 89 | 90 | ## set local variables for current repo 91 | git_config_local(user.name = "louise", user.email = "louise@example.org") 92 | 93 | ## get specific local variables, including a non-existent one 94 | git_config_local("user.name", "color.branch", "user.email") 95 | 96 | ## set local variables, then restore 97 | ocfg <- git_config_local(user.name = "oops", user.email = "oops@example.org") 98 | git_config_local("user.name", "user.email") 99 | git_config_local(ocfg) 100 | git_config_local("user.name", "user.email") 101 | 102 | ## set a custom variable 103 | ocfg <- git_config_local(githug.lol = "wut") 104 | git_config_local("githug.lol") 105 | 106 | setwd(owd) 107 | } 108 | \references{ 109 | \href{https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup}{Getting 110 | Started - First-Time Git Setup} from the Pro Git book by Scott Chacon and Ben 111 | Straub 112 | 113 | \href{https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration}{Customizing 114 | Git - Git Configuration} from the Pro Git book by Scott Chacon and Ben Straub 115 | 116 | \href{https://git-scm.com/docs/git-config}{git-config man page} 117 | } 118 | 119 | -------------------------------------------------------------------------------- /man/git_config_get.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_config.R 3 | \name{git_config_get} 4 | \alias{git_config_get} 5 | \title{Get Git config variables named in a character vector} 6 | \usage{ 7 | git_config_get(vnames = character(), where = c("de_facto", "local", 8 | "global"), repo = ".") 9 | } 10 | \description{ 11 | Get Git config variables named in a character vector 12 | } 13 | \keyword{internal} 14 | 15 | -------------------------------------------------------------------------------- /man/git_config_set.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_config.R 3 | \name{git_config_set} 4 | \alias{git_config_set} 5 | \title{Set Git config variables as given by a named list} 6 | \usage{ 7 | git_config_set(vars, where = c("de_facto", "local", "global"), repo = ".") 8 | } 9 | \description{ 10 | Set Git config variables as given by a named list 11 | } 12 | \keyword{internal} 13 | 14 | -------------------------------------------------------------------------------- /man/git_file_rename.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_file_rename.R 3 | \name{git_file_rename} 4 | \alias{git_file_rename} 5 | \alias{git_mv} 6 | \title{Rename or move a file and stage both ends} 7 | \usage{ 8 | git_file_rename(from, to, repo = ".") 9 | 10 | git_mv(from, to, repo = ".") 11 | } 12 | \arguments{ 13 | \item{from}{Path to an existing file, relative to the repo working directory} 14 | 15 | \item{to}{The desired new name, relative to the repo working directory} 16 | 17 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 18 | checked to see if it is or is inside a Git repo.} 19 | } 20 | \value{ 21 | Nothing 22 | } 23 | \description{ 24 | Rename a file and stage both ends of the transaction, i.e. the deletion of 25 | the file under its original name and the addition of the file under its new 26 | name. You still need to make the commit. If Git recognizes a rename event, 27 | then the history of the file will be preserved. This is worth striving for. 28 | Maximize the chance of this happy event by making the rename/move a distinct 29 | operation that is not muddled up with other changes to the file. 30 | } 31 | \details{ 32 | This is an extremely simple implementation of basic \code{git mv}. Why? True 33 | \code{git mv} is offered neither by libgit2 nor, therefore, by 34 | \code{\link{git2r}}. 35 | } 36 | \examples{ 37 | repo <- git_init(tempfile("githug-")) 38 | owd <- setwd(repo) 39 | write("Are these girls real smart or real real lucky?", "louise.txt") 40 | git_commit(all = TRUE, message = "filename is all wrong") 41 | git_mv(from = "louise.txt", to = "max.txt") 42 | git_commit(all = TRUE, message = "corrected filename") 43 | git_history() 44 | setwd(owd) 45 | } 46 | 47 | -------------------------------------------------------------------------------- /man/git_history.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_history.R 3 | \name{git_history} 4 | \alias{git_history} 5 | \title{Get the commit history} 6 | \usage{ 7 | git_history(repo = ".", ...) 8 | } 9 | \arguments{ 10 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 11 | checked to see if it is or is inside a Git repo.} 12 | 13 | \item{...}{Optional parameters passed through to 14 | \code{\link[git2r:commits]{git2r::commits}()}. Can include: 15 | \itemize{ 16 | \item \code{n} Max number of commits. 17 | \item \code{topological} Logical, requests topological sort, i.e. 18 | parent before child, defaults to \code{TRUE}. Can be combined with 19 | \code{time}. 20 | \item \code{time} Logical, requests chronological sort, defaults to 21 | \code{TRUE}. Can be combined with \code{topological}. 22 | \item \code{reverse} Logical, reverses the order, defaults to 23 | \code{FALSE}. 24 | }} 25 | } 26 | \value{ 27 | A data frame with S3 class \code{git_history}, solely for printing 28 | purposes. Variables: the \code{SHA}, commit \code{message}, \code{when} the 29 | commit happened, \code{author}, and \code{email}. 30 | } 31 | \description{ 32 | Get an overview of the last \code{n} commits. Convenience wrapper around 33 | \code{\link[git2r:commits]{git2r::commits}()}. The print method shows 34 | truncated versions of selected variables, e.g., the commit message, time, and 35 | SHA, but rest assured the full information is present in the returned object. 36 | } 37 | \examples{ 38 | repo <- git_init(tempfile("git-history-")) 39 | owd <- setwd(repo) 40 | line1 <- "Thelma: You're a real live outlaw, aren't ya?" 41 | line2 <- paste("J.D.: Well I may be an outlaw, darlin', but you're the one", 42 | "stealing my heart.") 43 | write(line1, "tl.txt") 44 | git_commit("tl.txt", message = "first commit") 45 | write(line2, "tl.txt", append = TRUE) 46 | git_commit("tl.txt", message = "second commit") 47 | git_history() 48 | setwd(owd) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /man/git_init.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_init.R 3 | \name{git_init} 4 | \alias{git_init} 5 | \title{Create a new Git repository} 6 | \usage{ 7 | git_init(path = ".") 8 | } 9 | \arguments{ 10 | \item{path}{Where to create the new Git repo. Defaults to current working 11 | directory. If the \code{path} doesn't exist, it will be created via 12 | \code{dir.create(path, recursive = TRUE)}.} 13 | } 14 | \value{ 15 | Path to the associated Git repo. 16 | } 17 | \description{ 18 | Create a new Git repository or re-initialize an existing one, similar to 19 | \code{git init}. 20 | } 21 | \details{ 22 | Will bad things happen if you \code{git_init()} in a directory that is 23 | already a Git repo? No, it's fine! To quote the 24 | \href{https://git-scm.com/docs/git-init}{git-init man page}, "running 25 | \code{git init} in an existing repository is safe. It will not overwrite 26 | things that are already there". A legitimate reason to do this is to pick up 27 | a new Git template, a topic which newcomers can safely ignore. 28 | 29 | \code{git_init()} will not create a Git repo in a subdirectory of an existing 30 | Git repo. The proper way to do this is via 31 | \href{https://git-scm.com/book/en/v2/Git-Tools-Submodules}{Git submodules}, 32 | which is beyond the current scope of the package. 33 | } 34 | \examples{ 35 | repo <- git_init(tempfile("git-init-example-")) 36 | 37 | ## switch working directory to the repo 38 | owd <- setwd(repo) 39 | 40 | ## Config local user and make a commit 41 | git_config(user.name = "thelma", user.email = "thelma@example.org") 42 | write("I don't ever remember feeling this awake.", "thelma.txt") 43 | git_commit("thelma.txt", message = "thelma is awake") 44 | 45 | git_history() 46 | 47 | setwd(owd) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /man/git_log.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_log.R 3 | \name{git_log} 4 | \alias{git_log} 5 | \title{Get a commit log} 6 | \usage{ 7 | git_log(repo = ".") 8 | } 9 | \arguments{ 10 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 11 | checked to see if it is or is inside a Git repo.} 12 | } 13 | \value{ 14 | A data frame (or tbl_df) with S3 class \code{git_log}, solely for 15 | printing purposes. Variables: the commit \code{message}, \code{when} the 16 | commit happened, \code{author}, \code{SHA}, \code{email}, \code{summary}, 17 | and a list-column of objects of class \code{\linkS4class{git_commit}}. 18 | } 19 | \description{ 20 | Get a commit log. Convenience wrapper around the \code{\link{git2r}} 21 | functions \code{\link[git2r]{coerce-git_repository-method}}, which coerces 22 | the commit log of the repository to a data frame, and 23 | \code{\link[git2r]{commits}}, which returns individual 24 | \code{\linkS4class{git_commit}} objects. The print method shows truncated 25 | versions of selected variables, e.g., the commit message, time, and SHA, but 26 | rest assured the full information is present in the returned object. 27 | } 28 | \examples{ 29 | require(dplyr, quietly = TRUE) 30 | repo <- git_init(tempfile("githug-")) 31 | owd <- setwd(repo) 32 | line1 <- "Thelma: You're a real live outlaw, aren't ya?" 33 | line2 <- paste("J.D.: Well I may be an outlaw, darlin', but you're the one", 34 | "stealing my heart.") 35 | writeLines(line1, "tl.txt") 36 | git_COMMIT(line1) 37 | write(line2, "tl.txt", append = TRUE) 38 | git_COMMIT(line2) 39 | git_log() 40 | setwd(owd) 41 | } 42 | 43 | -------------------------------------------------------------------------------- /man/git_stage.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_stage-add.R 3 | \name{git_stage} 4 | \alias{git_add} 5 | \alias{git_stage} 6 | \title{Stage changes for the next commit.} 7 | \usage{ 8 | git_stage(..., all = FALSE, force = FALSE, repo = ".") 9 | 10 | git_add(..., all = FALSE, force = FALSE, repo = ".") 11 | } 12 | \arguments{ 13 | \item{...}{One or more paths or shell glob patterns that will be matched 14 | against files in the repo's working directory. Paths that match will be 15 | added to the set of changes staged for the next commit.} 16 | 17 | \item{all}{Logical, consulted if no paths are given. If \code{TRUE}, 18 | pre-authorizes the staging of all new files, file deletions, and file 19 | modifications. Emulates \code{git add -A}, which is equivalent to \code{git 20 | add .; git add -u}.} 21 | 22 | \item{force}{Logical, defaults to \code{FALSE}. Value \code{TRUE} is required 23 | if any of the to-be-staged paths are currently ignored.} 24 | 25 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 26 | checked to see if it is or is inside a Git repo.} 27 | } 28 | \value{ 29 | nothing 30 | } 31 | \description{ 32 | Stage changes to files in preparation for a commit. In an interactive 33 | session, you'll get the chance to "just stage everything: Y/N?", if you 34 | haven't pre-staged or specified any paths. To pre-authorize the staging of 35 | all current file additions, deletions, and modifications, use 36 | \code{git_stage(all = TRUE)}. 37 | } 38 | \details{ 39 | \code{git_add()} and \code{git_stage()} are aliases for each other, so use 40 | what feels most natural, i.e. "add" a file to the repo and "stage" 41 | modifications. These are convenience wrappers around 42 | \code{\link[git2r:add]{git2r::add}()}. 43 | } 44 | \examples{ 45 | repo <- git_init(tempfile("githug-")) 46 | owd <- setwd(repo) 47 | write("Are these girls real smart or real real lucky?", "max.txt") 48 | write("You get what you settle for.", "louise.txt") 49 | git_status() 50 | ## try this interactively and accept the proposed auto-staging 51 | # git_add() 52 | git_add("max.txt", "louise.txt") 53 | git_status() 54 | write("If done properly armed robbery doesn't have to be a totally unpleasant experience.", 55 | "jd.txt") 56 | write("Is he your husband or your father?", "louise.txt", append = TRUE) 57 | git_status() 58 | git_stage(all = TRUE) 59 | git_status() 60 | git_commit(message = "Brains'll only get you so far and luck always runs out.") 61 | git_status() 62 | setwd(owd) 63 | } 64 | 65 | -------------------------------------------------------------------------------- /man/git_status.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_status.R 3 | \name{git_status} 4 | \alias{git_status} 5 | \title{Get status of a Git repo} 6 | \usage{ 7 | git_status(repo = ".", ls = FALSE) 8 | } 9 | \arguments{ 10 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 11 | checked to see if it is or is inside a Git repo.} 12 | 13 | \item{ls}{Logical, indicating whether to include unchanged, tracked files 14 | and gitignored files. Default is \code{FALSE}.} 15 | } 16 | \value{ 17 | a data frame where each row describes changes to a path, invisibly 18 | } 19 | \description{ 20 | The status of a Git repo is a set of paths, typically broken down like so: 21 | \describe{ 22 | \item{Staged changes}{Paths with modifications that are staged for inclusion 23 | in the next commit.} 24 | \item{Unstaged changes}{Paths that are tracked by Git but that have unstaged 25 | modifications.} 26 | \item{Untracked files}{Paths that are not yet tracked by Git but that are 27 | also not gitignored.} 28 | } 29 | What does that leave? Two kinds of paths 30 | \itemize{ 31 | \item Unchanged, tracked files. 32 | \item Ignored files. 33 | } 34 | Use \code{ls = TRUE} to request a status that includes these paths as well, 35 | i.e. a complete census of all the files in the repo. 36 | } 37 | \examples{ 38 | repo <- git_init(tempfile("githug-")) 39 | owd <- setwd(repo) 40 | write("Add me", "add-me") 41 | write("Don't add me", "dont-add-me") 42 | git_status() 43 | git_add("add-me") 44 | git_status() 45 | git_commit(message = "first commit") 46 | git_status() 47 | git_status(ls = TRUE) 48 | setwd(owd) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /man/git_uncommit.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_uncommit.R 3 | \name{git_uncommit} 4 | \alias{git_uncommit} 5 | \title{Undo a Git commit but leave files alone} 6 | \usage{ 7 | git_uncommit(ask = TRUE, repo = ".") 8 | } 9 | \arguments{ 10 | \item{ask}{Whether to confirm that user wants to change history} 11 | 12 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 13 | checked to see if it is or is inside a Git repo.} 14 | } 15 | \value{ 16 | SHA of the commit. The \code{when} attribute holds the commit time as 17 | \code{POSIXct}. An excerpt of the commit message is in the \code{msg_start} 18 | attribute. 19 | } 20 | \description{ 21 | Make it as if the last Git commit never happened BUT LEAVE YOUR FILES ALONE. 22 | This function is "working directory safe" but "history unsafe". Think twice 23 | before uncommitting a commit that you have pushed (see Details). 24 | } 25 | \details{ 26 | \code{git_uncommit()} will not change your files. It just reverses the act of 27 | making the most recent Git commit. Even the staged / unstaged status of your 28 | modifications is preserved. When might you use this? To undo the last commit 29 | so you can stage different changes or files and/or redo your commit, but with 30 | a better message. Note that \code{\link{git_amend}()} might be a more 31 | efficient way to do that. 32 | 33 | When might you NOT want to use this? If you have already pushed the most 34 | recent commit to a remote. It could still be OK if you're sure no one else 35 | has pulled. But be prepared to force push in this situation. 36 | 37 | \code{git_uncommit()} addresses the second most up-voted question on 38 | StackOverflow: 39 | \href{http://stackoverflow.com/questions/927358/how-to-undo-last-commits-in-git}{How 40 | to undo last commit(s) in Git?}, with over 3.6 million views. It is 41 | equivalent to \code{git reset --soft HEAD^}, i.e. a soft reset to the commit 42 | that is parent to the commit pointed to by current HEAD. 43 | } 44 | \examples{ 45 | repo <- git_init(tempfile("githug-")) 46 | owd <- setwd(repo) 47 | write("Are these girls real smart or real real lucky?", "max.txt") 48 | git_commit("max.txt", 49 | message = "Brains'll only get you so far and luck always runs out.") 50 | write("Did I hear somebody say \\"Peaches\\"?", "jimmy.txt") 51 | git_commit("jimmy.txt", message = "That's the code word. I miss you, Peaches.") 52 | git_history() ## see? 2 commits 53 | git_status() ## see? nothing to stage 54 | git_uncommit() ## roll back most recent commit 55 | git_history() ## see? only 1st commit is in history 56 | git_status() ## see? jimmy.txt is a new, staged file 57 | ## re-do that 2nd commit but with message in ALL CAPS 58 | git_commit(message = "THAT'S THE CODE WORD. I MISS YOU, PEACHES.") 59 | git_history() ## see? back to 2 commits 60 | setwd(owd) 61 | } 62 | 63 | -------------------------------------------------------------------------------- /man/git_unstage.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_unstage.R 3 | \name{git_unstage} 4 | \alias{git_unstage} 5 | \title{Unstage changes for the next commit.} 6 | \usage{ 7 | git_unstage(..., all = FALSE, repo = ".") 8 | } 9 | \arguments{ 10 | \item{...}{One or more paths that will be matched against files with changes 11 | that are staged for the next commit. Paths that match will be unstaged, 12 | i.e. their changes will not be part of the next commit.} 13 | 14 | \item{all}{Logical, consulted if no paths are given. If \code{TRUE}, 15 | pre-authorizes the unstaging of all staged files.} 16 | 17 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 18 | checked to see if it is or is inside a Git repo.} 19 | } 20 | \value{ 21 | nothing 22 | } 23 | \description{ 24 | Remove file modifications from the staging area for the next commit, BUT 25 | LEAVE YOUR FILES ALONE. This function is "working directory safe". It will 26 | not change your files. It only unstages them. When would you use this? If 27 | you've staged changes by mistake and you don't want them in the next commit 28 | after all. 29 | } 30 | \details{ 31 | \code{git_unstage()} addresses a popular question on StackOverflow: 32 | \href{http://stackoverflow.com/questions/348170/how-to-undo-git-add-before-commit}{How 33 | to undo 'git add' before commit?}, with over 1.3 million views. In terms of 34 | command line Git, this reverses \code{git add file.txt}. The call 35 | \code{git_unstage("file.txt")} is equivalent to \code{git reset file.txt}, 36 | which is short for \code{git reset --mixed HEAD file.txt}, i.e. a mixed reset 37 | of \code{file.txt} to the commit pointed to by current HEAD. 38 | } 39 | \examples{ 40 | repo <- git_init(tempfile("githug-")) 41 | owd <- setwd(repo) 42 | write("Are these girls real smart or real real lucky?", "max.txt") 43 | git_commit(all = TRUE, message = "first commit") 44 | write("You get what you settle for.", "louise.txt") 45 | git_status() 46 | git_add("louise.txt") 47 | git_status() 48 | git_unstage("louise.txt") 49 | git_status() 50 | setwd(owd) 51 | } 52 | 53 | -------------------------------------------------------------------------------- /man/githug-branch.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_branch.R 3 | \name{githug-branch} 4 | \alias{git_branch} 5 | \alias{git_branch_current} 6 | \alias{git_branch_list} 7 | \alias{githug-branch} 8 | \title{Report current or list all branches} 9 | \usage{ 10 | git_branch(where = NULL, repo = ".") 11 | 12 | git_branch_current(repo = ".") 13 | 14 | git_branch_list(where = c("local", "all", "remote"), repo = ".") 15 | } 16 | \arguments{ 17 | \item{where}{Which branches to list: \code{local} only (the default), 18 | \code{all}, or \code{remote} only.} 19 | 20 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 21 | checked to see if it is or is inside a Git repo.} 22 | } 23 | \description{ 24 | Report which branch you're currently on or list all local and/or remote 25 | branches in the repository. 26 | } 27 | \section{git_branch}{ 28 | 29 | 30 | If called with no arguments, or with only the \code{repo} argument, 31 | \code{git_branch()} reports the name of the current branch. This is 32 | equivalent to calling \code{git_branch_current()} directly. 33 | 34 | If the \code{where} argument is given, its value is passed through to 35 | \code{git_branch_list()}. 36 | } 37 | 38 | \section{git_branch_current}{ 39 | 40 | 41 | \code{git_branch()} tells which branch you're currently on. 42 | } 43 | 44 | \section{git_branch_list}{ 45 | 46 | 47 | \code{git_branch_list()} returns a data frame of information provided by 48 | \code{\link[git2r:branches]{git2r::branches}()}. 49 | 50 | How it corresponds to command line Git: 51 | 52 | \describe{ 53 | \item{\code{git_branch_list()}}{is like \code{git branch}. Lists local 54 | branches.} 55 | \item{\code{git_branch_list(where = "all")}}{is like \code{git branch -a}. 56 | Lists all branches, local and remote.} 57 | \item{\code{git_branch_list(where = "remote")}}{is like \code{git branch 58 | -r}. Lists remote branches.} 59 | } 60 | } 61 | \examples{ 62 | repo <- git_init(tempfile("githug-branches-")) 63 | owd <- setwd(repo) 64 | 65 | ## no commits --> no branches 66 | git_branch() 67 | git_branch_list() 68 | 69 | ## commit and ... now we have master 70 | write("Well, we're not in the middle of nowhere,", "nowhere.txt") 71 | git_commit(all = TRUE, message = "1ouise: not in the middle of nowhere") 72 | git_branch() 73 | git_branch_list() 74 | 75 | ## make a new commit then checkout initial commit, thereby detaching HEAD 76 | write("but we can see it from here.", "nowhere.txt", append = TRUE) 77 | git_commit(all = TRUE, message = "louise: but can see it") 78 | ## TODO: come back and make this nicer when more functions exist 79 | init_commit <- as.git_commit(git_revision("HEAD^")) 80 | git2r::checkout(init_commit) 81 | git_branch() 82 | git_branch_list() 83 | 84 | setwd(owd) 85 | } 86 | 87 | -------------------------------------------------------------------------------- /man/githug-switch.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_branch_checkout.R 3 | \name{githug-switch} 4 | \alias{git_branch_checkout} 5 | \alias{git_switch} 6 | \alias{githug-switch} 7 | \title{Switch to another branch} 8 | \usage{ 9 | git_switch(name = character(), create = NA, repo = ".", rev = "HEAD") 10 | 11 | git_branch_checkout(name = "master", create = FALSE, force = FALSE, 12 | repo = ".", rev = "HEAD") 13 | } 14 | \arguments{ 15 | \item{name}{Name of the branch} 16 | 17 | \item{create}{Whether to create the branch if it does not exist yet} 18 | 19 | \item{repo}{Path to a Git repo. If unspecified, current working directory is 20 | checked to see if it is or is inside a Git repo.} 21 | 22 | \item{rev}{Target commit, as a 23 | \href{http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions}{revision 24 | string}, e.g. \code{HEAD^}, \code{branchname}, \code{SHA-1} or a leading 25 | substring thereof.} 26 | 27 | \item{force}{Whether to overwrite current files with the versions in branch 28 | \code{name}, even if this means discarding uncommited changes. Default is 29 | \code{FALSE}.} 30 | } 31 | \description{ 32 | Switch to or "checkout" another branch. Optionally, create the branch if it 33 | doesn't exist yet and then check it out. 34 | } 35 | \details{ 36 | If you currently have uncommitted changes, it is possible -- though not 37 | inevitable -- to lose those changes when you switch to another branch. 38 | \code{git_switch()} will never let that happen. You should seriously consider 39 | committing or stashing those at-risk changes. However, if you really want to 40 | nuke them, call the lower-level function \code{git_branch_checkout(..., force 41 | = TRUE)}. 42 | 43 | Convenience wrappers around \code{\link{git2r}}'s 44 | \code{\link[git2r]{checkout,git_branch-method}}. 45 | } 46 | \section{git_switch}{ 47 | 48 | 49 | Designed for interactive use. Request a branch by \code{name} or, by 50 | default, switch to \code{master}. If the branch doesn't exist yet, you'll 51 | get an offer to create it and immediately check it out. 52 | 53 | \code{git_switch()} will not let you switch branches if you have 54 | uncommitted changes that would be lost. You should either: 55 | 56 | \enumerate{ 57 | \item Commit or stash these changes. Then call \code{git_switch()} again. 58 | \item Use \code{git_branch_checkout()} directly, with \code{force = TRUE}, 59 | if you are willing to lose these changes. 60 | } 61 | } 62 | 63 | \section{git_branch_checkout}{ 64 | 65 | 66 | Designed for non-interactive use. If the branch doesn't exist yet, you'll 67 | have to explicitly authorize its creation via \code{create = TRUE}. 68 | \code{force = TRUE} will checkout branch \code{name} even if it means 69 | losing uncommitted changes. In the future, githug will make a branch and a 70 | commit or a stash behind the scenes here, in case you later have regrets. 71 | } 72 | \examples{ 73 | repo <- git_init(tempfile("githug-branches-")) 74 | owd <- setwd(repo) 75 | 76 | ## first commit 77 | writeLines("Well, we're not in the middle of nowhere...", "nowhere.txt") 78 | git_commit("nowhere.txt", message = "1: not in the middle of nowhere") 79 | git_branch_list() 80 | 81 | \dontrun{ 82 | ## in an interactive session, try this to checkout and create at once 83 | git_switch("louise") 84 | } 85 | 86 | git_branch_checkout("louise", create = TRUE) 87 | git_branch_current() 88 | 89 | setwd(owd) 90 | } 91 | 92 | -------------------------------------------------------------------------------- /man/githug.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/githug-package.r 3 | \docType{package} 4 | \name{githug} 5 | \alias{githug} 6 | \alias{githug-package} 7 | \title{githug.} 8 | \description{ 9 | githug. 10 | } 11 | 12 | -------------------------------------------------------------------------------- /man/print.githug_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/githug_list-class.R 3 | \name{print.githug_list} 4 | \alias{print.githug_list} 5 | \title{Print a list JSON-style} 6 | \usage{ 7 | \method{print}{githug_list}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{The list to print.} 11 | 12 | \item{...}{Ignored.} 13 | } 14 | \value{ 15 | The input \code{x} is passed through invisibly. 16 | } 17 | \description{ 18 | Print a list JSON-style 19 | } 20 | 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(githug) 3 | 4 | test_check("githug") 5 | -------------------------------------------------------------------------------- /tests/testthat/git_log_print_output.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/githug/675269266c06335365b8410a898fe3c1d08979f8/tests/testthat/git_log_print_output.rds -------------------------------------------------------------------------------- /tests/testthat/helper00_git_set-up.R: -------------------------------------------------------------------------------- 1 | ## make sure current wd is or is inside a git repo 2 | ## why? so local git config exists 3 | ## matters even during local R CMD check, not to mention on travis, etc. 4 | if (!is_in_repo()) { 5 | git_init() 6 | } 7 | -------------------------------------------------------------------------------- /tests/testthat/helper01_funs.R: -------------------------------------------------------------------------------- 1 | tmp_repo_path <- 2 | function(x = "", slug = "githug-test", user = Sys.info()["user"]) { 3 | if (x != "") x <- paste0(x, "-") 4 | tempfile(paste(slug, user, x, sep = "-")) 5 | } 6 | 7 | init_tmp_repo <- function(x = "", 8 | slug = "githug-test", 9 | user = Sys.info()["user"]) { 10 | tpath <- tmp_repo_path(x = x, slug = slug, user = user) 11 | git_init(tpath) 12 | } 13 | 14 | read_git_config <- function(path) { 15 | alt_path <- git2r::discover_repository(path) 16 | path <- if (is.null(alt_path)) path else file.path(alt_path, "config") 17 | raw <- readLines(path) 18 | raw <- gsub("\t", "", raw) 19 | section <- cumsum(grepl("\\[.*\\]", raw)) 20 | sections <- split(raw, section) 21 | sections <- sections[lengths(sections) > 1L] 22 | names(sections) <- NULL 23 | as.list(unlist(lapply(sections, function(x) { 24 | s <- gsub('\\[|\\]', "", x[1]) 25 | s <- gsub('"', "", s) 26 | s <- gsub("\\s", ".", s) 27 | vars <- strsplit(x[-1], " = ") 28 | var_names <- vapply(vars, `[`, character(1), 1) 29 | var_names <- paste(s, var_names, sep = ".") 30 | as.list(setNames(vapply(vars, `[`, character(1), 2), var_names)) 31 | }))) 32 | } 33 | 34 | ## writes, e.g., "a" and "b" as the contents of files "a" and "b" 35 | write_file <- function(x, dir = NULL) { 36 | paths <- if (is.null(dir)) x else file.path(dir, x) 37 | invisible(purrr::map2(x, paths, write)) 38 | } 39 | 40 | ## limits comparison to columns you bothered to include in reference 41 | expect_status <- function(status, reference) { 42 | testthat::expect_identical(status[names(reference)], reference) 43 | } 44 | -------------------------------------------------------------------------------- /tests/testthat/helper02_backup-gitconfig.R: -------------------------------------------------------------------------------- 1 | file.copy(from = file.path("~", ".gitconfig"), 2 | to = file.path("~", ".gitconfig_BAK")) 3 | -------------------------------------------------------------------------------- /tests/testthat/test-as-git-commit.R: -------------------------------------------------------------------------------- 1 | context("as.git_commit") 2 | 3 | test_that("as.git_commit works", { 4 | tpath <- init_tmp_repo() 5 | write_file("a", dir = tpath) 6 | sha <- git_commit("a", message = "a", repo = tpath) 7 | expect_identical(sha, sha_with_hint(as.git_commit(repo = tpath))) 8 | expect_identical(sha, sha_with_hint(as.git_commit("HEAD", repo = tpath))) 9 | expect_identical(sha, sha_with_hint(as.git_commit("master", repo = tpath))) 10 | }) 11 | -------------------------------------------------------------------------------- /tests/testthat/test-classes.R: -------------------------------------------------------------------------------- 1 | context("rpath and githug_list classes") 2 | 3 | test_that("rpath doesn't accept garbage input", { 4 | expect_error(rpath(letters[1:2])) 5 | expect_error(rpath(1:2)) 6 | }) 7 | 8 | test_that("rpath returns path to enclosing git repo", { 9 | 10 | tpath <- tempfile("githug-test-") 11 | 12 | ## path does not exist 13 | expect_error(as.rpath(tpath), "no path exists") 14 | expect_null(as.rpath(tpath, raise = NULL)) 15 | expect_false(is_in_repo(tpath)) 16 | expect_false(is_a_repo(tpath)) 17 | 18 | ## path exists but there is no enclosing git repo 19 | dir.create(tpath) 20 | res <- rpath(tpath, raise = NULL) 21 | expect_null(res) 22 | expect_false(is_in_repo(tpath)) 23 | expect_false(is_a_repo(tpath)) 24 | unlink(tpath) 25 | 26 | ## path IS a git repo 27 | tpath <- git_init(tpath) 28 | res <- rpath(tpath) 29 | expect_identical(tpath, res) 30 | expect_true(is_in_repo(tpath)) 31 | expect_true(is_a_repo(tpath)) 32 | 33 | ## path is nested within a git repo 34 | nested_path <- file.path(tpath, "one", "two") 35 | dir.create(nested_path, recursive = TRUE) 36 | res <- rpath(nested_path) 37 | expect_identical(tpath, res) 38 | expect_error(rpath(nested_path, ceiling = 0), "no git repo exists") 39 | res <- rpath(nested_path, ceiling = 0, raise = NULL) 40 | expect_null(res) 41 | expect_error(rpath(nested_path, ceiling = 1), "no git repo exists") 42 | res <- rpath(nested_path, ceiling = 1, raise = NULL) 43 | expect_null(res) 44 | expect_true( is_in_repo(nested_path)) 45 | expect_false(is_in_repo(nested_path, ceiling = 0)) 46 | expect_false(is_in_repo(nested_path, ceiling = 1)) 47 | expect_false( is_a_repo(nested_path)) 48 | 49 | }) 50 | 51 | test_that("as.rpath works on a git_repository", { 52 | 53 | tpath <- init_tmp_repo() 54 | expect_identical(tpath, as.rpath(git2r::repository(tpath, discover = TRUE))) 55 | 56 | }) 57 | 58 | test_that("git_config returns objects of class githug_list", { 59 | ## querying 60 | cfg <- git_config() 61 | expect_is(cfg, "githug_list") 62 | ## setting 63 | tr <- init_tmp_repo() 64 | cfg_0 <- git_config_local(list(`blah.foo` = "a"), repo = tr) 65 | expect_is(cfg_0, "githug_list") 66 | cfg_1 <- git_config_local(list(`blah.foo` = "b"), repo = tr) 67 | expect_is(cfg_1, "githug_list") 68 | }) 69 | 70 | test_that("objects of class githug_list retain class after `[`", { 71 | cfg <- git_config() 72 | expect_is(cfg[1:2], "githug_list") 73 | expect_is(cfg[1], "githug_list") 74 | }) 75 | -------------------------------------------------------------------------------- /tests/testthat/test-gh-pat-username.R: -------------------------------------------------------------------------------- 1 | context("gh PATs & usernames") 2 | 3 | test_that("we can retrieve github PAT from env var", { 4 | 5 | ## default to GITHUB_PAT 6 | withr::with_envvar( 7 | c("GITHUB_PAT" = "GITHUB_PAT", "GITHUB_TOKEN" = "GITHUB_TOKEN"), { 8 | pat <- gh_pat() 9 | }) 10 | expect_identical(pat, c("GITHUB_PAT" = "GITHUB_PAT")) 11 | 12 | ## if no GITHUB_PAT, then GITHUB_TOKEN 13 | withr::with_envvar( 14 | c("GITHUB_PAT" = NA, "GITHUB_TOKEN" = "GITHUB_TOKEN"), { 15 | pat <- gh_pat() 16 | }) 17 | expect_identical(pat, c("GITHUB_TOKEN" = "GITHUB_TOKEN")) 18 | 19 | ## if user specifies, that takes precedence 20 | withr::with_envvar( 21 | c("GITHUB_PAT" = "GITHUB_PAT", "GITHUB_TOKEN" = "GITHUB_TOKEN", 22 | "a" = "a"), { 23 | pat <- gh_pat("a") 24 | }) 25 | expect_identical(pat, c("a" = "a")) 26 | 27 | }) 28 | 29 | test_that("we don't retrieve github PAT when we should not", { 30 | 31 | ## none of defaults exist? 32 | withr::with_envvar(c("GITHUB_PAT" = NA, "GITHUB_TOKEN" = NA), { 33 | pat <- gh_pat() 34 | }) 35 | expect_identical(pat, "") 36 | 37 | ## no defaults and user-specified env var doesn't exist either 38 | withr::with_envvar(c("GITHUB_PAT" = NA, "GITHUB_TOKEN" = NA), { 39 | pat <- gh_pat("a") 40 | }) 41 | expect_identical(pat, "") 42 | 43 | ## defaults exist but user is asking for something else 44 | withr::with_envvar(c("GITHUB_PAT" = "GITHUB_PAT", 45 | "GITHUB_TOKEN" = "GITHUB_TOKEN"), { 46 | pat <- gh_pat("a") 47 | }) 48 | expect_identical(pat, "") 49 | 50 | }) 51 | 52 | test_that("we can get authenticated username from valid PAT", { 53 | skip_if_no_GitHub_API 54 | ## TO PONDER: for tests to work for others, this should be less specific 55 | expect_true(gh_username() %in% c("jennybc", "githugci")) 56 | }) 57 | 58 | test_that("we can't get authenticated username from bad or empty PAT", { 59 | skip_if_no_GitHub_API 60 | expect_error(gh_username("nope"), "Bad credentials") 61 | expect_error(gh_username(gh_pat("nope")), "Requires authentication") 62 | }) 63 | -------------------------------------------------------------------------------- /tests/testthat/test-git-HEAD.R: -------------------------------------------------------------------------------- 1 | context("git HEAD") 2 | 3 | test_that("we retrieve info on HEAD", { 4 | 5 | tpath <- init_tmp_repo() 6 | writeLines('a', file.path(tpath, 'a')) 7 | git_COMMIT('a commit', repo = tpath) 8 | ghead <- git_HEAD(repo = tpath) 9 | expect_is(ghead, "git_HEAD") 10 | expect_identical(ghead[c("branch_name", "branch_type")], 11 | list(branch_name = "master", branch_type = "local")) 12 | expect_equivalent(lapply(ghead, class), 13 | list(branch_name = "character", branch_type = "character", 14 | head_sha = "character", head_commit = "git_commit", 15 | repo = "git_repository", git_branch = "git_branch")) 16 | 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test-git-add-commit.R: -------------------------------------------------------------------------------- 1 | context("git add and commit") 2 | 3 | test_that("add and ADD work, with repo elsewhere and in wd", { 4 | 5 | tpath <- init_tmp_repo() 6 | lapply(letters[1:4], function(x) writeLines(x, file.path(tpath, x))) 7 | expect_equivalent(git_status(repo = tpath), 8 | list(staged = list(), unstaged = list(), 9 | untracked = list(untracked = "a", untracked = "b", 10 | untracked = "c", untracked = "d"))) 11 | ga <- git_add(c("a", "c"), repo = tpath) 12 | expect_identical(tpath, ga) 13 | setwd(tpath) 14 | expect_equivalent(git_status(), 15 | list(staged = list(new = "a", new = "c"), 16 | unstaged = list(), 17 | untracked = list(untracked = "b", untracked = "d"))) 18 | git_ADD() 19 | expect_equivalent(git_status(), 20 | list(staged = list(new = "a", new = "b", 21 | new = "c", new = "d"), 22 | unstaged = list(), 23 | untracked = list())) 24 | 25 | }) 26 | 27 | test_that("commit and COMMIT work, with repo elsewhere and in wd", { 28 | 29 | tpath <- init_tmp_repo() 30 | lapply(letters[1:2], function(x) writeLines(x, file.path(tpath, x))) 31 | git_ADD(repo = tpath) 32 | expect_equivalent(git_status(repo = tpath), 33 | list(staged = list(new = "a", new = "b"), 34 | unstaged = list(), 35 | untracked = list())) 36 | gc <- git_commit("first commit", repo = tpath) 37 | expect_identical(gc, tpath) 38 | expect_output(git_status(repo = tpath), "working directory clean") 39 | setwd(tpath) 40 | lapply(letters[3:4], function(x) writeLines(x, file.path(tpath, x))) 41 | expect_equivalent(git_status(), 42 | list(staged = list(), 43 | unstaged = list(), 44 | untracked = list(untracked = "c", untracked = "d"))) 45 | git_COMMIT("second commit") 46 | expect_output(git_status(), "working directory clean") 47 | 48 | }) 49 | 50 | test_that("commit demands a commit message", { 51 | 52 | tpath <- init_tmp_repo() 53 | lapply(letters[1:2], function(x) writeLines(x, file.path(tpath, x))) 54 | git_ADD(repo = tpath) 55 | expect_error(git_commit(repo = tpath), "You must provide a commit message") 56 | 57 | }) 58 | 59 | test_that("ADD and COMMIT admit it when there's nothing to be done", { 60 | 61 | tpath <- init_tmp_repo() 62 | expect_message(git_ADD(repo = tpath), "nothing to ADD") 63 | expect_message(git_COMMIT("but there's nothing to commit!", repo = tpath), 64 | "nothing to ADD") 65 | 66 | }) 67 | -------------------------------------------------------------------------------- /tests/testthat/test-git-amend.R: -------------------------------------------------------------------------------- 1 | context("git amend") 2 | 3 | test_that("git_amend requires explicit permission to rewrite history", { 4 | prohibit_interaction() 5 | tpath <- init_tmp_repo() 6 | write_file("a", dir = tpath) 7 | gco <- git_commit("a", message = "commit 1", repo = tpath) 8 | write_file("b", dir = tpath) 9 | gco <- git_commit("b", message = "commit 2", repo = tpath) 10 | expect_message(am <- git_amend(repo = tpath), 11 | "You must explicitly authorize this") 12 | expect_null(am) 13 | expect_equivalent(gco, git_history(repo = tpath, n = 1)$sha) 14 | allow_interaction() 15 | }) 16 | 17 | test_that("git_amend aborts if HEAD^ does not exist", { 18 | tpath <- init_tmp_repo() 19 | write_file("a", dir = tpath) 20 | gco <- git_commit("a", message = "commit 1", repo = tpath) 21 | expect_error(git_amend(ask = FALSE, repo = tpath), 22 | "Can't find parent") 23 | expect_equivalent(gco, git_history(repo = tpath, n = 1)$sha) 24 | }) 25 | 26 | test_that("empty message is pre-empted by previous message", { 27 | tpath <- init_tmp_repo() 28 | write_file("a", dir = tpath) 29 | git_commit("a", message = "commit 1", repo = tpath) 30 | write_file("b", dir = tpath) 31 | git_commit("b", message = "commit 2", repo = tpath) 32 | git_amend(ask = FALSE, message = "", repo = tpath) 33 | expect_identical(git_history(repo = tpath)$message[1], "commit 2") 34 | }) 35 | 36 | 37 | test_that("git_amend results in new SHA, new message, new snapshot", { 38 | tpath <- init_tmp_repo() 39 | write_file("a", dir = tpath) 40 | git_commit("a", message = "commit 1", repo = tpath) 41 | write_file("b", dir = tpath) 42 | gco_old <- git_commit("b", message = "commit 2", repo = tpath) 43 | write("more b", file.path(tpath, "b"), append = TRUE) 44 | git_stage("b", repo = tpath) 45 | gco_new <- git_amend(message = "commit 2, take 2", ask = FALSE, repo = tpath) 46 | gl <- git_history(repo = tpath) 47 | expect_false(identical(gco_old, gco_new)) 48 | expect_equivalent(gco_new, gl$sha[1]) 49 | expect_identical(gl$message[1], "commit 2, take 2") 50 | expect_output(expect_identical(nrow(git_status(repo = tpath)), 0L)) 51 | expect_identical(readLines(file.path(tpath, "b")), c("b", "more b")) 52 | }) 53 | -------------------------------------------------------------------------------- /tests/testthat/test-git-branch-checkout.R: -------------------------------------------------------------------------------- 1 | context("git branch checkout") 2 | 3 | test_that("check out when no commits or branches exists", { 4 | prohibit_interaction() 5 | tpath <- init_tmp_repo() 6 | expect_error(git_switch(repo = tpath), "Specify the target branch") 7 | expect_error(git_branch_checkout(repo = tpath), "Aborting") 8 | allow_interaction() 9 | }) 10 | 11 | test_that("check out when requested branch does not exist", { 12 | prohibit_interaction() 13 | tpath <- init_tmp_repo() 14 | write_file("a", dir = tpath) 15 | git_commit("a", message = "a", repo = tpath) 16 | expect_message( 17 | expect_error(git_switch("b", repo = tpath), "Authorize its creation"), 18 | "not the name of any existing local branch" 19 | ) 20 | expect_error(git_branch_checkout("b", repo = tpath), "Aborting") 21 | allow_interaction() 22 | }) 23 | 24 | test_that("check out existing branch", { 25 | tpath <- init_tmp_repo() 26 | write_file("a", dir = tpath) 27 | git_commit("a", message = "a", repo = tpath) 28 | git_branch_create("new_branch", repo = tpath) 29 | 30 | git_branch_checkout("new_branch", repo = tpath) 31 | expect_identical(git_branch(repo = tpath), "new_branch") 32 | 33 | git_switch(repo = tpath) 34 | expect_identical(git_branch(repo = tpath), "master") 35 | 36 | git_switch("new_branch", repo = tpath) 37 | expect_identical(git_branch(repo = tpath), "new_branch") 38 | }) 39 | 40 | test_that("create and check out works from git_branch_checkout", { 41 | tpath <- init_tmp_repo() 42 | write_file("a", dir = tpath) 43 | git_commit("a", message = "a", repo = tpath) 44 | git_branch_checkout("new_branch", create = TRUE, repo = tpath) 45 | expect_identical(git_branch(repo = tpath), "new_branch") 46 | }) 47 | 48 | test_that("create and check out works from git_switch", { 49 | tpath <- init_tmp_repo() 50 | write_file("a", dir = tpath) 51 | git_commit("a", message = "a", repo = tpath) 52 | git_switch("new_branch", create = TRUE, repo = tpath) 53 | expect_identical(git_branch(repo = tpath), "new_branch") 54 | }) 55 | 56 | test_that("at risk files protected when 'force = FALSE' and not when 'force = TRUE'", { 57 | tpath <- init_tmp_repo() 58 | write_file("a", dir = tpath) 59 | git_commit("a", message = "a", repo = tpath) 60 | 61 | git_branch_checkout("new_branch", create = TRUE, repo = tpath) 62 | write("new_branch a", file = file.path(tpath, "a"), append = TRUE) 63 | git_commit("a", message = "new_branch a", repo = tpath) 64 | 65 | git_branch_checkout(repo = tpath) 66 | write("master a", file = file.path(tpath, "a"), append = TRUE) 67 | expect_status(git_status_check(repo = tpath), 68 | tibble::frame_data( 69 | ~status, ~path, ~change, 70 | "unstaged", "a", "modified" 71 | )) 72 | expect_error(git_branch_checkout("new_branch", repo = tpath), 73 | "conflict prevents checkout") 74 | expect_error(git_switch("new_branch", repo = tpath), 75 | "conflict prevents checkout") 76 | expect_message(git_branch_checkout("new_branch", repo = tpath, force = TRUE), 77 | "Switched to branch") 78 | expect_true("new_branch a" %in% readLines(file.path(tpath, "a"))) 79 | }) 80 | 81 | test_that("create and check out with specific rev works from git_branch_checkout", { 82 | tpath <- init_tmp_repo() 83 | write_file("a", dir = tpath) 84 | gc1 <- git_commit("a", message = "a", repo = tpath) 85 | write_file("b", dir = tpath) 86 | git_commit("b", message = "b", repo = tpath) 87 | git_branch_checkout("new_branch", create = TRUE, repo = tpath, rev = gc1) 88 | expect_identical(gc1, git_revision("HEAD", repo = tpath)) 89 | }) 90 | -------------------------------------------------------------------------------- /tests/testthat/test-git-branch-create.R: -------------------------------------------------------------------------------- 1 | context("git branch create") 2 | 3 | test_that("branch can be created from SHA", { 4 | tpath <- init_tmp_repo() 5 | write_file("a", dir = tpath) 6 | gc1 <- git_commit("a", message = "a", repo = tpath) 7 | write_file("b", dir = tpath) 8 | git_commit("b", message = "b", repo = tpath) 9 | gl <- git_history(repo = tpath) 10 | gb <- git_branch_create("new_branch", repo = tpath, rev = gl$sha[2]) 11 | expect_identical(gc1, gb) 12 | expect_true("new_branch" %in% git_branch_list(repo = tpath)$branch) 13 | }) 14 | 15 | test_that("can't create branch if name already taken", { 16 | tpath <- init_tmp_repo() 17 | write_file("a", dir = tpath) 18 | git_commit("a", message = "a", repo = tpath) 19 | expect_error(git_branch_create("master", repo = tpath), 20 | "name already exists") 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/test-git-branch-delete.R: -------------------------------------------------------------------------------- 1 | context("git branch delete") 2 | 3 | test_that("can delete", { 4 | tpath <- init_tmp_repo() 5 | write_file("a", dir = tpath) 6 | git_commit("a", message = "a", repo = tpath) 7 | git_branch_create("new_branch", repo = tpath) 8 | expect_true("new_branch" %in% git_branch_list(repo = tpath)$branch) 9 | expect_message(git_branch_delete("new_branch", repo = tpath), 10 | "Branch deleted") 11 | expect_false("new_branch" %in% git_branch_list(repo = tpath)$branch) 12 | }) 13 | 14 | test_that("can't delete non-existent branch", { 15 | tpath <- init_tmp_repo() 16 | write_file("a", dir = tpath) 17 | git_commit("a", message = "a", repo = tpath) 18 | expect_error(git_branch_delete("nope", repo = tpath), 19 | "No existing local branch") 20 | }) 21 | 22 | test_that("can't delete current branch", { 23 | tpath <- init_tmp_repo() 24 | write_file("a", dir = tpath) 25 | git_commit("a", message = "a", repo = tpath) 26 | expect_error(git_branch_delete("master", repo = tpath), 27 | "Cannot delete") 28 | }) 29 | -------------------------------------------------------------------------------- /tests/testthat/test-git-branch-rename.R: -------------------------------------------------------------------------------- 1 | context("git branch rename") 2 | 3 | test_that("rename when from doesn't exist", { 4 | tpath <- init_tmp_repo() 5 | write_file("a", dir = tpath) 6 | git_commit("a", message = "a", repo = tpath) 7 | expect_error(git_branch_rename(from = "nope", to = "yep", repo = tpath), 8 | "No existing local branch") 9 | }) 10 | 11 | test_that("rename when to already exists", { 12 | tpath <- init_tmp_repo() 13 | write_file("a", dir = tpath) 14 | git_commit("a", message = "a", repo = tpath) 15 | git_branch_create("new_branch", repo = tpath) 16 | expect_error(git_branch_rename(from = "new_branch", to = "master", repo = tpath), 17 | "name already exists") 18 | }) 19 | 20 | test_that("can rename", { 21 | tpath <- init_tmp_repo() 22 | write_file("a", dir = tpath) 23 | git_commit("a", message = "a", repo = tpath) 24 | git_branch_create("new_branch", repo = tpath) 25 | expect_message(git_branch_rename(from = "new_branch", to = "foo", repo = tpath), 26 | "Branch renamed") 27 | }) 28 | -------------------------------------------------------------------------------- /tests/testthat/test-git-branch.R: -------------------------------------------------------------------------------- 1 | context("git branch") 2 | 3 | test_that("current branch and list when no commits or branches", { 4 | tpath <- init_tmp_repo() 5 | expect_message(gb <- git_branch(repo = tpath), "Not on a branch") 6 | expect_null(gb) 7 | expect_message(gbl <- git_branch_list(repo = tpath), "No branches to list") 8 | expect_null(gbl) 9 | }) 10 | 11 | test_that("detached HEAD", { 12 | tpath <- init_tmp_repo() 13 | write_file("a", dir = tpath) 14 | c01 <- git_commit("a", message = "a", repo = tpath) 15 | write_file("b", dir = tpath) 16 | git_commit("b", message = "b", repo = tpath) 17 | git2r::checkout(as.git_commit(c01, repo = tpath)) 18 | expect_message(gb <- git_branch_current(repo = tpath), "Detached HEAD!") 19 | expect_identical(gb, NA_character_) 20 | gbl <- git_branch_list(repo = tpath) 21 | expect_false(any(gbl$HEAD)) 22 | }) 23 | 24 | test_that("current branch and list reporting when 1 branch exists", { 25 | ## why test for this explicitly? there were dimension dropping problems once 26 | tpath <- init_tmp_repo() 27 | write_file("a", dir = tpath) 28 | git_commit("a", message = "a", repo = tpath) 29 | expect_identical(git_branch(repo = tpath), "master") 30 | gbl <- git_branch_list(repo = tpath) 31 | gbl_ref <- tibble::tibble( 32 | HEAD = TRUE, 33 | full_name = "master", 34 | type = "local", 35 | branch = "master", 36 | remote = NA_character_ 37 | ) 38 | vars <- c("HEAD", "full_name", "type", "branch", "remote") 39 | expect_identical(gbl[vars], gbl_ref) 40 | expect_identical(gbl, git_branch(where = "local", repo = tpath)) 41 | expect_identical(gbl, git_branch(where = "all", repo = tpath)) 42 | }) 43 | 44 | test_that("branch listing when 2 branches exist", { 45 | tpath <- init_tmp_repo() 46 | write_file("a", dir = tpath) 47 | git_commit("a", message = "a", repo = tpath) 48 | git_branch_create("second", repo = tpath) 49 | gbl <- git_branch_list(repo = tpath) 50 | gbl_ref <- tibble::frame_data( 51 | ~HEAD, ~full_name, ~type, ~branch, ~remote, 52 | TRUE, "master", "local", "master", NA_character_, 53 | FALSE, "second", "local", "second", NA_character_ 54 | ) 55 | vars <- c("HEAD", "full_name", "type", "branch", "remote") 56 | expect_identical(gbl[vars], gbl_ref) 57 | expect_identical(gbl, git_branch(where = "local", repo = tpath)) 58 | expect_identical(gbl, git_branch(where = "all", repo = tpath)) 59 | }) 60 | -------------------------------------------------------------------------------- /tests/testthat/test-git-commit.R: -------------------------------------------------------------------------------- 1 | context("git commit") 2 | 3 | test_that("git_commit works, with repo elsewhere and in wd", { 4 | 5 | tpath <- init_tmp_repo() 6 | write_file(c("a", "b"), dir = tpath) 7 | gc <- git_commit(all = TRUE, message = "first commit", repo = tpath) 8 | expect_identical(nrow(git_status_check(repo = tpath)), 0L) 9 | owd <- setwd(tpath) 10 | write_file(c("c", "d")) 11 | expect_message(git_commit(all = TRUE, message = "second commit", repo = tpath), 12 | "Staged these paths:\n \\* c\n \\* d") 13 | expect_identical(nrow(git_status_check()), 0L) 14 | setwd(owd) 15 | }) 16 | 17 | test_that("commit demands a commit message", { 18 | prohibit_interaction() 19 | tpath <- init_tmp_repo() 20 | write_file("foo", dir = tpath) 21 | expect_error(git_commit(all = TRUE, repo = tpath), 22 | "You must provide a commit message") 23 | allow_interaction() 24 | }) 25 | 26 | test_that("explicit paths work in git_commit", { 27 | tpath <- init_tmp_repo() 28 | write_file(c("a", "b"), dir = tpath) 29 | expect_message(git_commit("a", "b", message = "commit", repo = tpath), 30 | "Staged these paths:\n \\* a\n \\* b") 31 | }) 32 | 33 | test_that("warning from git_commit if nothing staged", { 34 | tpath <- init_tmp_repo() 35 | write_file(c("a", "b"), dir = tpath) 36 | git_commit(all = TRUE, message = "commit", repo = tpath) 37 | expect_warning(git_commit(all = TRUE, message = "commit", repo = tpath), 38 | "Nothing staged for commit") 39 | }) 40 | -------------------------------------------------------------------------------- /tests/testthat/test-git-history.R: -------------------------------------------------------------------------------- 1 | context("git history") 2 | 3 | test_that("git_history messages when no commits yet", { 4 | tpath <- init_tmp_repo() 5 | expect_message(git_history(repo = tpath), "No commits yet") 6 | }) 7 | 8 | test_that("git_history passes, eg, n and reverse through", { 9 | tpath <- init_tmp_repo() 10 | write_file("a", dir = tpath) 11 | git_commit(all = TRUE, message = "a", repo = tpath) 12 | write_file("b", dir = tpath) 13 | git_commit(all = TRUE, message = "b", repo = tpath) 14 | write_file("c", dir = tpath) 15 | git_commit(all = TRUE, message = "c", repo = tpath) 16 | expect_identical(nrow(git_history(repo = tpath, n = 2)), 2L) 17 | expect_identical(nrow(git_history(repo = tpath, n = 10)), 3L) 18 | expect_identical(git_history(repo = tpath)$message, c("c", "b", "a")) 19 | expect_identical(git_history(reverse = TRUE, repo = tpath)$message, 20 | c("a", "b", "c")) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/test-git-init.R: -------------------------------------------------------------------------------- 1 | context("git init") 2 | 3 | test_that("init works in dir that does not exist yet", { 4 | tpath <- tmp_repo_path() 5 | expect_false(dir.exists(tpath)) 6 | expect_message(tpath <- git_init(tpath), "Creating directory") 7 | expect_true(dir.exists(tpath)) 8 | expect_true(is_a_repo(tpath)) 9 | }) 10 | 11 | test_that("init works in existing dir that is not a git repo", { 12 | tpath <- tmp_repo_path() 13 | dir.create(tpath) 14 | expect_message(tpath <- git_init(tpath), "Initialising git repository") 15 | expect_true(is_a_repo(tpath)) 16 | }) 17 | 18 | test_that("init works in existing dir that is already a git repo", { 19 | tpath <- init_tmp_repo() 20 | expect_message(tpath <- git_init(tpath), "is already a Git repo") 21 | }) 22 | 23 | test_that("init refuses to create a repo within a repo", { 24 | tpath <- init_tmp_repo() 25 | tpath_at_depth <- normalize_path(file.path(tpath, "dir1", "dir2")) 26 | expect_error(git_init(tpath_at_depth), "is or will be nested") 27 | }) 28 | 29 | test_that("init fails informatively on path to an existing file (not dir)", { 30 | tpath <- tmp_repo_path() 31 | dir.create(tpath) 32 | write_file("whatever", dir = tpath) 33 | expect_error(git_init(file.path(tpath, "whatever")), 34 | "File already exists") 35 | }) 36 | -------------------------------------------------------------------------------- /tests/testthat/test-git-log.R: -------------------------------------------------------------------------------- 1 | context("git log") 2 | 3 | test_that("git_log works, with repo elsewhere and in wd", { 4 | 5 | tpath <- init_tmp_repo() 6 | expect_message(gl <- git_log(repo = tpath), "no commits yet") 7 | expect_null(gl) 8 | 9 | writeLines("a", file.path(tpath, "a")) 10 | git_COMMIT("01", repo = tpath) 11 | gl <- git_log(repo = tpath) 12 | expect_identical(dim(gl), c(1L, 7L)) 13 | expect_identical(names(gl), 14 | c("message", "when", "author", "sha", "email", "summary", 15 | "commit")) 16 | 17 | setwd(tpath) 18 | writeLines("b", "b") 19 | git_COMMIT("02") 20 | gl <- git_log() 21 | expect_identical(dim(gl), c(2L, 7L)) 22 | expect_identical(gl$message, c("02", "01")) 23 | 24 | }) 25 | 26 | test_that("git_log returns sthg of class git_log, tbl_df", { 27 | 28 | tpath <- init_tmp_repo() 29 | writeLines("a", file.path(tpath, "a")) 30 | git_COMMIT("a", repo = tpath) 31 | gl <- git_log(repo = tpath) 32 | expect_is(gl, c("git_log", "tbl_df")) 33 | 34 | }) 35 | 36 | test_that("git_log warns if no git repo", { 37 | 38 | tpath <- tempfile("githug-test-") 39 | dir.create(tpath) 40 | expect_true(dir.exists(tpath)) 41 | expect_warning(res <- git_log(repo = tpath), "no git repo exists") 42 | expect_null(res) 43 | 44 | }) 45 | 46 | test_that("git_log printing", { 47 | 48 | tpath <- init_tmp_repo() 49 | writeLines("a", file.path(tpath, "a")) 50 | git_COMMIT("Goddamn it! I've never been lucky! Not one time!", repo = tpath) 51 | glo <- capture.output(git_log(repo = tpath)) 52 | expect_equal_to_reference(glo, "git_log_print_output.rds") 53 | 54 | }) 55 | -------------------------------------------------------------------------------- /tests/testthat/test-git-mv.R: -------------------------------------------------------------------------------- 1 | context("git mv") 2 | 3 | test_that("fail if from does not exist or to does exist", { 4 | tpath <- init_tmp_repo() 5 | write_file("a", dir = tpath) 6 | git_commit("a", message = "a", repo = tpath) 7 | expect_error(git_mv(from = "b", to = "z", repo = tpath), 8 | "file_exists\\(from_path\\) is not TRUE") 9 | write_file("z", dir = tpath) 10 | expect_error(git_mv(from = "a", to = "z", repo = tpath), 11 | "!file_exists\\(to_path\\) is not TRUE") 12 | }) 13 | 14 | test_that("file is renamed and staged", { 15 | tpath <- init_tmp_repo() 16 | write_file("a", dir = tpath) 17 | git_commit("a", message = "a", repo = tpath) 18 | git_mv(from = "a", to = "b", repo = tpath) 19 | expect_status(git_status_check(repo = tpath), 20 | tibble::frame_data( 21 | ~status, ~path, ~change, ~i, 22 | "staged", "a", "renamed_from", 1L, 23 | "staged", "b", "renamed_to", 1L 24 | )) 25 | expect_false(file.exists(file.path(tpath, "a"))) 26 | expect_true(file.exists(file.path(tpath, "b"))) 27 | }) 28 | -------------------------------------------------------------------------------- /tests/testthat/test-git-repository.R: -------------------------------------------------------------------------------- 1 | context("detect repo") 2 | 3 | test_that("repo_path detects invalid input", { 4 | expect_error(repo_path(letters[1:2]), "length\\(x\\) == 1L is not TRUE") 5 | expect_error(repo_path(1), "is.character\\(x\\) is not TRUE") 6 | }) 7 | 8 | test_that("nonexistent directory", { 9 | tpath <- tmp_repo_path() 10 | expect_error(repo_path(tpath), "directory does not exist") 11 | expect_false(is_in_repo(tpath)) 12 | expect_false(is_a_repo(tpath)) 13 | }) 14 | 15 | test_that("dir exists but is not a git repo", { 16 | tpath <- tmp_repo_path() 17 | dir.create(tpath) 18 | expect_error(repo_path(tpath), "no git repo exists here") 19 | expect_false(is_in_repo(tpath)) 20 | expect_false(is_a_repo(tpath)) 21 | }) 22 | 23 | test_that("dir is a git repo", { 24 | tpath <- init_tmp_repo() 25 | res <- repo_path(tpath) 26 | expect_identical(tpath, res) 27 | expect_true(is_in_repo(tpath)) 28 | expect_true(is_a_repo(tpath)) 29 | }) 30 | 31 | test_that("dir is descendant of a git repo", { 32 | tpath <- init_tmp_repo() 33 | nested_path <- file.path(tpath, "one", "two") 34 | dir.create(nested_path, recursive = TRUE) 35 | res <- repo_path(nested_path) 36 | expect_identical(tpath, res) 37 | expect_true(is_in_repo(nested_path)) 38 | expect_false(is_a_repo(nested_path)) 39 | 40 | ## does ceiling get honored? 41 | 42 | ## don't walk up at all 43 | expect_error(repo_path(nested_path, ceiling = 0), "no git repo exists") 44 | expect_false(is_in_repo(nested_path, ceiling = 0)) 45 | expect_false(is_in_repo(nested_path, ceiling = 1)) 46 | 47 | ## walk one level up 48 | expect_error(repo_path(nested_path, ceiling = 1), "no git repo exists") 49 | expect_false(is_in_repo(nested_path, ceiling = 1)) 50 | 51 | }) 52 | 53 | test_that("we default to the obvious repo", { 54 | tpath <- init_tmp_repo() 55 | gr <- as.git_repository(tpath) 56 | 57 | owd <- setwd(tpath) 58 | 59 | expect_identical(tpath, repo_path()) 60 | expect_identical(gr, as.git_repository()) 61 | 62 | nested_path <- file.path(tpath, "one", "two") 63 | dir.create(nested_path, recursive = TRUE) 64 | setwd(nested_path) 65 | expect_identical(tpath, repo_path()) 66 | expect_identical(gr, as.git_repository()) 67 | 68 | setwd(owd) 69 | }) 70 | -------------------------------------------------------------------------------- /tests/testthat/test-git-revision.R: -------------------------------------------------------------------------------- 1 | context("git-revision") 2 | 3 | test_that("error if rev does not exist", { 4 | tpath <- init_tmp_repo() 5 | expect_error(git_revision(repo = tpath), "Can't find the revision") 6 | expect_error(git_revision(rev = "nope", repo = tpath), "Can't find the revision") 7 | }) 8 | 9 | test_that("existence test works", { 10 | tpath <- init_tmp_repo() 11 | expect_false(git_revision_exists(rev = "nope", repo = tpath)) 12 | write_file("a", dir = tpath) 13 | git_commit("a", message = "a", repo = tpath) 14 | expect_true(git_revision_exists(rev = "HEAD", repo = tpath)) 15 | }) 16 | -------------------------------------------------------------------------------- /tests/testthat/test-git-stage-add.R: -------------------------------------------------------------------------------- 1 | context("git stage or add") 2 | 3 | test_that("git_stage works, with repo elsewhere and in wd", { 4 | 5 | tpath <- init_tmp_repo() 6 | write_file(letters[1:4], dir = tpath) 7 | expect_status(git_status_check(repo = tpath), 8 | tibble::frame_data( 9 | ~status, ~path, ~change, 10 | "untracked", "a", "new", 11 | "untracked", "b", "new", 12 | "untracked", "c", "new", 13 | "untracked", "d", "new" 14 | )) 15 | git_stage("a", "c", repo = tpath) 16 | owd <- setwd(tpath) 17 | expect_status(git_status_check(), 18 | tibble::frame_data( 19 | ~status, ~path, 20 | "staged", "a", 21 | "staged", "c", 22 | "untracked", "b", 23 | "untracked", "d" 24 | )) 25 | git_stage(all = TRUE) 26 | expect_status(git_status_check(), 27 | tibble::frame_data( 28 | ~status, ~path, 29 | "staged", "a", 30 | "staged", "b", 31 | "staged", "c", 32 | "staged", "d" 33 | )) 34 | setwd(owd) 35 | }) 36 | 37 | test_that("git_stage() admits when it can't do anything", { 38 | prohibit_interaction() 39 | tpath <- init_tmp_repo() 40 | expect_message(git_add(repo = tpath, all = TRUE), "No changes to stage") 41 | write_file("whatever", dir = tpath) 42 | expect_message(git_add(repo = tpath), "Nothing staged") 43 | expect_message(git_add(repo = tpath, all = FALSE), "No changes staged") 44 | allow_interaction() 45 | }) 46 | 47 | test_that("git_stage() correctly messages when re-staging a file", { 48 | tpath <- init_tmp_repo() 49 | write_file("whatever", dir = tpath) 50 | git_add("whatever", repo = tpath) 51 | write("something", file.path(tpath, "whatever"), append = TRUE) 52 | expect_message(git_stage("whatever", repo = tpath), "Staged.*whatever") 53 | }) 54 | 55 | test_that("git_stage() of an ignored file", { 56 | tpath <- init_tmp_repo() 57 | write_file("ignore-me", dir = tpath) 58 | write("ignore-me", file.path(tpath, ".gitignore")) 59 | git_commit(".gitignore", message = "commit", repo = tpath) 60 | expect_message(git_stage("ignore-me", repo = tpath), "not have been staged") 61 | expect_message(git_stage("ignore-me", force = TRUE, repo = tpath), 62 | "Staged these paths:\n \\* ignore-me") 63 | expect_status(git_status_check(repo = tpath), 64 | tibble::frame_data( 65 | ~status, ~path, ~change, ~i, 66 | "staged", "ignore-me", "new", NA_integer_ 67 | )) 68 | }) 69 | -------------------------------------------------------------------------------- /tests/testthat/test-git-status.R: -------------------------------------------------------------------------------- 1 | context("git status") 2 | 3 | empty_status <- tibble::tibble(status = character(), 4 | path = character(), 5 | change = character(), 6 | i = integer()) 7 | 8 | test_that("git_status() messages current branch", { 9 | tpath <- init_tmp_repo() 10 | write_file("a", dir = tpath) 11 | git_commit("a", message = "init", repo = tpath) 12 | expect_output(git_status(repo = tpath), "On branch:\n \\* master") 13 | }) 14 | 15 | test_that("status messages and returns NULL if not in git repo", { 16 | tpath <- tmp_repo_path() 17 | dir.create(tpath) 18 | expect_true(dir.exists(tpath)) 19 | expect_false(is_in_repo(tpath)) 20 | expect_error(git_status(repo = tpath), "no git repo exists here") 21 | }) 22 | 23 | test_that("status in empty repo", { 24 | tpath <- init_tmp_repo() 25 | expect_identical(git_status_check(repo = tpath), empty_status) 26 | }) 27 | 28 | test_that("status reports new files", { 29 | tpath <- init_tmp_repo() 30 | write_file(c("staged", "untracked", "tracked", "ignored"), dir = tpath) 31 | write("ignored", file.path(tpath, ".gitignore")) 32 | git_commit(c(".gitignore", "tracked"), message = "init", repo = tpath) 33 | git_add("staged", repo = tpath) 34 | expect_status(git_status_check(repo = tpath), 35 | tibble::frame_data( 36 | ~status, ~path, 37 | "staged", "staged", 38 | "untracked", "untracked" 39 | )) 40 | expect_status(git_status_check(repo = tpath, ls = TRUE), 41 | tibble::frame_data( 42 | ~status, ~path, 43 | "staged", "staged", 44 | "untracked", "untracked", 45 | "ignored", "ignored", 46 | "tracked", "tracked" 47 | )) 48 | }) 49 | 50 | test_that("status reports deleted files", { 51 | tpath <- init_tmp_repo() 52 | files <- c("staged", "unstaged") 53 | write_file(files, dir = tpath) 54 | git_commit(files, message = "init", repo = tpath) 55 | file.remove(file.path(tpath, files)) 56 | git_add("staged", repo = tpath) 57 | expect_status(git_status_check(repo = tpath), 58 | tibble::frame_data( 59 | ~status, ~path, ~change, 60 | "staged", "staged", "deleted", 61 | "unstaged", "unstaged", "deleted" 62 | )) 63 | }) 64 | 65 | test_that("status reports modified files", { 66 | tpath <- init_tmp_repo() 67 | files <- c("staged", "unstaged", "both") 68 | write_file(files, dir = tpath) 69 | git_commit(files, message = "init", repo = tpath) 70 | lapply(files, 71 | function(x) write("another line", file.path(tpath, x), append = TRUE)) 72 | git_add(c("staged", "both"), repo = tpath) 73 | write("yet another line", file.path(tpath, "both"), append = TRUE) 74 | expect_status(git_status_check(repo = tpath), 75 | tibble::frame_data( 76 | ~status, ~path, ~change, 77 | "staged", "both", "modified", 78 | "staged", "staged", "modified", 79 | "unstaged", "both", "modified", 80 | "unstaged", "unstaged", "modified" 81 | )) 82 | }) 83 | 84 | test_that("status reports renamed files", { 85 | tpath <- init_tmp_repo() 86 | write_file("from", dir = tpath) 87 | git_commit("from", message = "init", repo = tpath) 88 | file.rename(file.path(tpath, "from"), file.path(tpath, "to")) 89 | git_add(c("from", "to"), repo = tpath) 90 | expect_status(git_status_check(repo = tpath), 91 | tibble::frame_data( 92 | ~status, ~ path, ~change, ~i, 93 | "staged", "from", "renamed_from", 1L, 94 | "staged", "to", "renamed_to", 1L 95 | )) 96 | }) 97 | 98 | test_that("status reports tracked unchanged + ignored files when all = TRUE", { 99 | tpath <- init_tmp_repo() 100 | write_file(c("tracked", "ignored"), dir = tpath) 101 | write("ignored", file.path(tpath, ".gitignore")) 102 | git_commit(c(".gitignore", "tracked"), message = "init", repo = tpath) 103 | expect_status(git_status_check(repo = tpath, ls = TRUE), 104 | tibble::frame_data( 105 | ~status, ~path, ~change, 106 | "ignored", "ignored", "new", 107 | "tracked", "tracked", "none" 108 | )) 109 | }) 110 | 111 | test_that("status when git2r::status returns nothing but all = TRUE", { 112 | tpath <- init_tmp_repo() 113 | write_file("a_file", dir = tpath) 114 | git_commit("a_file", message = "init", repo = tpath) 115 | expect_status(git_status_check(repo = tpath, ls = TRUE), 116 | tibble::frame_data( 117 | ~status, ~path, ~change, 118 | "tracked", "a_file", "none" 119 | )) 120 | }) 121 | -------------------------------------------------------------------------------- /tests/testthat/test-git-uncommit.R: -------------------------------------------------------------------------------- 1 | context("git uncommit") 2 | 3 | test_that("git_uncommit requires explicit permission to rewrite history", { 4 | prohibit_interaction() 5 | tpath <- init_tmp_repo() 6 | write_file("a", dir = tpath) 7 | gco <- git_commit("a", message = "commit 1", repo = tpath) 8 | write_file("b", dir = tpath) 9 | gco <- git_commit("b", message = "commit 2", repo = tpath) 10 | expect_message(un <- git_uncommit(repo = tpath), 11 | "You must explicitly authorize this") 12 | expect_null(un) 13 | expect_equivalent(gco, git_history(repo = tpath, n = 1)$sha) 14 | allow_interaction() 15 | }) 16 | 17 | test_that("git_uncommit moves HEAD back to parent and leaves things staged", { 18 | 19 | tpath <- init_tmp_repo() 20 | write_file("a", dir = tpath) 21 | git_commit("a", message = "commit 1", repo = tpath) 22 | write_file("b", dir = tpath) 23 | git_commit("b", message = "commit 2", repo = tpath) 24 | gl <- git_history(repo = tpath) 25 | expect_identical(nrow(gl), 2L) 26 | gco <- git_uncommit(ask = FALSE, repo = tpath) 27 | expect_identical(nrow(git_history(repo = tpath)), 1L) 28 | expect_equivalent(gl$sha[[2]], gco) 29 | expect_status(git_status_check(ls = TRUE, repo = tpath), 30 | tibble::frame_data( 31 | ~status, ~path, ~change, 32 | "staged", "b", "new", 33 | "tracked", "a", "none" 34 | )) 35 | }) 36 | 37 | test_that("git_uncommit aborts if HEAD^ does not exist", { 38 | tpath <- init_tmp_repo() 39 | write_file("a", dir = tpath) 40 | git_commit("a", message = "commit 1", repo = tpath) 41 | expect_error(git_uncommit(ask = FALSE, repo = tpath), 42 | "Can't find parent") 43 | }) 44 | -------------------------------------------------------------------------------- /tests/testthat/test-git-unstage.R: -------------------------------------------------------------------------------- 1 | context("git unstage") 2 | 3 | test_that("git_unstage all = TRUE", { 4 | 5 | tpath <- init_tmp_repo() 6 | write_file(c("a", "b"), dir = tpath) 7 | git_commit(all = TRUE, message = "first commit", repo = tpath) 8 | write("more a", file.path(tpath, "a"), append = TRUE) 9 | file.remove(file.path(tpath, "b")) 10 | write_file("c", dir = tpath) 11 | status_before <- git_status_check(repo = tpath) 12 | git_add(all = TRUE, repo = tpath) 13 | expect_status(git_status_check(repo = tpath), 14 | tibble::frame_data( 15 | ~status, ~path, ~change, 16 | "staged", "c", "new", 17 | "staged", "a", "modified", 18 | "staged", "b", "deleted" 19 | )) 20 | git_unstage(all = TRUE, repo = tpath) 21 | expect_status(git_status_check(repo = tpath), status_before) 22 | }) 23 | 24 | test_that("git_unstage() admits when it can't do anything", { 25 | prohibit_interaction() 26 | tpath <- init_tmp_repo() 27 | expect_message(git_unstage(repo = tpath), "Nothing to unstage") 28 | write_file("a", dir = tpath) 29 | expect_message(git_unstage(repo = tpath), "Nothing to unstage") 30 | git_add("a", repo = tpath) 31 | expect_message(git_unstage(repo = tpath), "Either provide") 32 | git_commit(all = TRUE, message = "message", repo = tpath) 33 | expect_message(git_unstage(repo = tpath), "Nothing to unstage") 34 | allow_interaction() 35 | }) 36 | -------------------------------------------------------------------------------- /tests/testthat/test-githug-init.R: -------------------------------------------------------------------------------- 1 | context("githug init") 2 | 3 | # test_that("githug init works", { 4 | # skip_if_no_internet() 5 | # tpath <- githug_init(path = tmp_repo_path("githug-init")) 6 | # expect_true(dir.exists(tpath)) 7 | # expect_true(is_a_repo(tpath)) 8 | # expect_true(wd_is_clean(tpath)) 9 | # #expect_true(is_a_rsp(tpath)) 10 | # gcfg_local <- git_config_local(repo = tpath) 11 | # expect_identical(gcfg_local$githug.remotename, "origin") 12 | # ## use an explicit check for tracking, when it exists 13 | # expect_identical(gcfg_local$branch.master.remote, "origin") 14 | # githug_urls <- grep("githug\\..*url", names(gcfg_local), value = TRUE) 15 | # expect_identical(length(githug_urls), 3L) 16 | # }) 17 | -------------------------------------------------------------------------------- /tests/testthat/test-githug_list.R: -------------------------------------------------------------------------------- 1 | context("githug_list class") 2 | 3 | test_that("objects of class githug_list retain class after `[`", { 4 | cfg <- git_config() 5 | expect_is(cfg[1:2], "githug_list") 6 | expect_is(cfg[1], "githug_list") 7 | }) 8 | 9 | test_that("NULLs are printed in githug_list object", { 10 | tr <- init_tmp_repo() 11 | cfg <- git_config("blah.foo", repo = tr) 12 | expect_output(print(cfg), "blah.foo = NULL") 13 | }) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | context("utils") 2 | 3 | test_that("dir_exists, w/o and w/ trailing slash", { 4 | tpath <- tmp_repo_path() 5 | tpath <- gsub("/$", "", tpath) 6 | tpath2 <- paste0(tpath, "/") 7 | expect_false(dir_exists(tpath)) 8 | expect_false(dir_exists(tpath2)) 9 | dir.create(tpath) 10 | expect_true(dir_exists(tpath)) 11 | expect_true(dir_exists(tpath2)) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-zzz-restore-gitconfig.R: -------------------------------------------------------------------------------- 1 | if (file.copy(from = file.path("~", ".gitconfig_BAK"), 2 | to = file.path("~", ".gitconfig"), 3 | overwrite = TRUE)) 4 | file.remove(file.path("~", ".gitconfig_BAK")) 5 | 6 | -------------------------------------------------------------------------------- /vignettes/rstudio-to-github.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "From R/RStudio to GitHub" 3 | author: "Jenny Bryan" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{From R/RStudio to GitHub} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ### Current situation 13 | 14 | If your goal is to have a project that is: 15 | 16 | * a directory on your computer 17 | * an RStudio Project 18 | * a Git repository 19 | * associated with a remote repository on GitHub 20 | 21 | How can you achieve this? 22 | 23 | __GitHub First__: This is the workflow [we recommend in STAT 545](http://stat545-ubc.github.io/git07_git-github-rstudio.html), because it does not require going to the shell or explicit use of command line Git. Main steps: 24 | 25 | * *Prerequisite: Install Git. Git global config. Make sure RStudio has found Git executable.* 26 | * New repo on GitHub.YES to README. URL --> clipboard. 27 | * In RStudio: *File > New Project > Version Control > Git*. 28 | 29 | Why is this good for "my first repo"? 30 | 31 | * Easy to explain: not OS specific, never go to shell. 32 | * Based on clicking in RStudio or a browser: doesn't require 100% typing accuracy. 33 | * GitHub repo is set up as a remote for local repo. 34 | * Local master is tracking `origin/master`, so push and pull work immediately. In particular, the push and pull buttons in RStudio work. 35 | 36 | What are the downsides? 37 | 38 | * Requires system installation of Git and for RStudio to know where it is. 39 | * Must happen at the start of a project. 40 | * Based on clicking in RStudio or a browser, so must document with lots of words and screenshots versus R code. 41 | 42 | __RStudio First__: This is what we call [Plan B workflow in STAT 545](http://stat545-ubc.github.io/git07_git-github-rstudio.html). Main steps: 43 | 44 | * *Prerequisite: Install Git. Git global config. Make sure RStudio has found Git executable.* 45 | * In RStudio: *File > New Project > New Directory > Empty Project*. 46 | - Either at project start, say YES to "Create a git repository", or do so later with *Tools > Version Control > Project Setup > Version control system: Git,* YES init a repo, YES restart RStudio. 47 | - Do some work. Realize you want this thing on GitHub. 48 | * New repo on GitHub. URL --> clipboard or otherwise note it. 49 | * In shell, with wd in the repo, add GitHub repo as remote via `git remote add`. 50 | * In shell, pull if there's anything you need on GitHub, such as a README or LICENSE. 51 | * In shell, `git push -u origin master` to set upstream tracking. 52 | 53 | What are the downsides? 54 | 55 | * Requires system installation of Git and for RStudio to know where it is. 56 | * Requires cycling through RStudio, shell, and GitHub in the browser. Hard to write up as a lesson and students hate this in live demo, i.e. people get lost. 57 | 58 | ### Near term goals 59 | 60 | Delay Git installation + RStudio/Git handshake: I can't imagine anyone doing lots of Git work w/o installing Git on their system, i.e. *only* using what's exposed in `git2r` or `githug`. But we could hope to make this unnecessary for the first few hours or days. 61 | 62 | Git config: DONE. 63 | 64 | Git init in Project: DONE. 65 | 66 | Emulate `hub create jennybc/foo`. DONE. 67 | 68 | - init local repo, if not done already 69 | - add and commit 70 | - create new GitHub repo 71 | - add as remote 72 | - push the current branch 73 | - visit GitHub repo in the browser 74 | 75 | Replace the content of this vignette with an actual demo! 76 | --------------------------------------------------------------------------------