├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── CRAN-RELEASE ├── CRAN-SUBMISSION ├── DESCRIPTION ├── NAMESPACE ├── NEWS.md ├── R ├── aaa.R ├── gadget.R ├── git.R ├── group_management.R └── interactive_testing.R ├── README.md ├── cran-comments.md ├── docs ├── 404.html ├── articles │ ├── gitgadget.html │ ├── gitgadget_files │ │ ├── accessible-code-block-0.0.1 │ │ │ └── empty-anchor.js │ │ ├── header-attrs-2.3 │ │ │ └── header-attrs.js │ │ └── header-attrs-2.7 │ │ │ └── header-attrs.js │ └── index.html ├── authors.html ├── bootstrap-toc.css ├── bootstrap-toc.js ├── docsearch.css ├── docsearch.js ├── index.html ├── jquery.sticky-kit.min.js ├── link.svg ├── news │ └── index.html ├── pkgdown.css ├── pkgdown.js ├── pkgdown.yml ├── reference │ ├── add_users_repo.html │ ├── assign_work.html │ ├── check_tokens.html │ ├── checkerr.html │ ├── collect_work.html │ ├── create_group.html │ ├── create_repo.html │ ├── fetch_work.html │ ├── get_port.html │ ├── gitgadget.html │ ├── gitgadget_callr.html │ ├── gitgadget_url.html │ ├── index.html │ ├── projID.html │ ├── read_ufile.html │ ├── remove_project.html │ └── remove_users_repo.html └── sitemap.xml ├── gitgadget.Rproj ├── inst ├── app │ ├── app.R │ ├── components │ │ ├── branch.R │ │ ├── clone.R │ │ ├── collect.R │ │ ├── create.R │ │ ├── help.R │ │ ├── input-validation.R │ │ ├── intro.R │ │ ├── legacy.R │ │ ├── repo.R │ │ └── sync.R │ ├── data │ │ └── students.csv │ ├── gitgadget_ui.R │ ├── help │ │ └── help.md │ ├── init.R │ └── www │ │ └── style.css └── rstudio │ └── addins.dcf ├── man ├── add_users_repo.Rd ├── assign_work.Rd ├── check_tokens.Rd ├── checkerr.Rd ├── collect_work.Rd ├── create_group.Rd ├── create_repo.Rd ├── fetch_work.Rd ├── get_port.Rd ├── gitgadget.Rd ├── gitgadget_callr.Rd ├── gitgadget_url.Rd ├── projID.Rd ├── read_ufile.Rd ├── remove_project.Rd └── remove_users_repo.Rd ├── pkgbuild ├── build.R ├── build_mac.R └── build_win.R └── vignettes ├── gitgadget.Rmd └── gitgadget.html /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CRAN-RELEASE$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^.*\.sublime-project$ 5 | ^\.travis\.yml$ 6 | ^docs$ 7 | ^pkgbuild$ 8 | gitgadget.code-workspace 9 | cran-comments.md 10 | .vscode 11 | ^CRAN-SUBMISSION$ 12 | ^R/interactive_testing.R 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | .DS_Store 6 | .ipynb_checkpoints 7 | .mypy_cache 8 | .vscode 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | warnings_are_errors: true 3 | sudo: required 4 | 5 | r_github_packages: 6 | - rstudio/rstudioapi 7 | -------------------------------------------------------------------------------- /CRAN-RELEASE: -------------------------------------------------------------------------------- 1 | This package was submitted to CRAN on 2019-10-09. 2 | Once it is accepted, delete this file and tag the release (commit 37b0d9ea74). 3 | -------------------------------------------------------------------------------- /CRAN-SUBMISSION: -------------------------------------------------------------------------------- 1 | Version: 0.7.6 2 | Date: 2023-03-28 04:26:54 UTC 3 | SHA: c1f3b58a615fcf214d0cea0e4ad71d438de7c258 4 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: gitgadget 2 | Type: Package 3 | Title: 'Rstudio' Addin for Version Control and Assignment Management using Git 4 | Version: 0.8.2 5 | Date: 2025-4-6 6 | Authors@R: c( 7 | person("Vincent", "Nijs", , "vnijs@ucsd.edu", c("aut", "cre")), 8 | person("Sanjiv", "Erat", , "serat@ucsd.edu", "aut") 9 | ) 10 | Description: An 'Rstudio' addin for version control that allows users to clone 11 | repositories, create and delete branches, and sync forks on GitHub, GitLab, etc. 12 | Furthermore, the addin uses the GitLab API to allow instructors to create 13 | forks and merge requests for all students/teams with one click of a button. 14 | Depends: 15 | R (>= 4.0.0) 16 | Imports: 17 | shiny (>= 1.7.1), 18 | miniUI (>= 0.1.1.1), 19 | rstudioapi (>= 0.7), 20 | curl (>= 3.2), 21 | jsonlite (>= 1.5), 22 | dplyr (>= 0.8.3), 23 | shinyFiles (>= 0.7.5), 24 | callr (>= 2.0.4), 25 | usethis (>= 1.5.1), 26 | markdown 27 | Suggests: 28 | knitr, 29 | rmarkdown 30 | URL: https://github.com/vnijs/gitgadget 31 | BugReports: https://github.com/vnijs/gitgadget/issues 32 | License: GPL-3 33 | RoxygenNote: 7.3.2 34 | Encoding: UTF-8 35 | Language: en-US 36 | VignetteBuilder: knitr 37 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(add_users_repo) 4 | export(assign_work) 5 | export(check_tokens) 6 | export(checkerr) 7 | export(collect_work) 8 | export(create_group) 9 | export(create_repo) 10 | export(fetch_work) 11 | export(gitgadget) 12 | export(gitgadget_callr) 13 | export(gitgadget_url) 14 | export(projID) 15 | export(read_ufile) 16 | export(remove_project) 17 | export(remove_users_repo) 18 | import(curl) 19 | import(dplyr) 20 | import(miniUI) 21 | import(shiny) 22 | importFrom(callr,r_bg) 23 | importFrom(jsonlite,fromJSON) 24 | importFrom(markdown,markdownToHTML) 25 | importFrom(rstudioapi,getActiveProject) 26 | importFrom(rstudioapi,isAvailable) 27 | importFrom(rstudioapi,openProject) 28 | importFrom(rstudioapi,restartSession) 29 | importFrom(shinyFiles,parseDirPath) 30 | importFrom(shinyFiles,parseFilePaths) 31 | importFrom(shinyFiles,shinyDirButton) 32 | importFrom(shinyFiles,shinyDirChoose) 33 | importFrom(shinyFiles,shinyFileChoose) 34 | importFrom(shinyFiles,shinyFilesButton) 35 | importFrom(stats,na.omit) 36 | importFrom(stats,setNames) 37 | importFrom(usethis,edit_r_environ) 38 | importFrom(usethis,edit_r_profile) 39 | importFrom(usethis,use_git) 40 | importFrom(usethis,use_github) 41 | importFrom(utils,browseURL) 42 | importFrom(utils,capture.output) 43 | importFrom(utils,head) 44 | importFrom(utils,packageVersion) 45 | importFrom(utils,read.csv) 46 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # gitgadget 0.8.1.0 2 | 3 | * Fix URL specification in the DESCRIPTION file 4 | * Use "_PACKAGE" for documentation 5 | 6 | # gitgadget 0.7.5.0 7 | 8 | * Setting `main` as the default branch 9 | 10 | # gitgadget 0.6.9.0 11 | 12 | * Added option to specify the host ip to use. This makes is possible to use gitgadget with docker on ARM64 processors without requiring shiny-server 13 | 14 | # gitgadget 0.6.6.0 15 | 16 | * Updates to accommodate Shiny 1.7.1 17 | * Check if a user or ta file exists before loading 18 | 19 | # gitgadget 0.6.5.0 20 | 21 | * Fix to accommodate that GitLab now uses "/-/" in their profile URLs 22 | 23 | # gitgadget 0.6.4.0 24 | 25 | * Information provided through the "Introduce" was not being properly stored. Issue addressed in this release 26 | 27 | # gitgadget 0.6.0.0 28 | 29 | * By default, Create and Collect now _Hide_ (i.e., remove permissions) the main repo that was forked for each student 30 | * Allow setting an alternative api server (e.g., selfhosted gitlab) 31 | 32 | # gitgadget 0.5.5.0 33 | 34 | * Fix for fork synch 35 | 36 | # gitgadget 0.5.4.0 37 | 38 | * Various updates to allow using gitgadget with shiny server 39 | 40 | # gitgadget 0.5.3.0 41 | 42 | * Warning if user tries to `create` a repo from the base git directory 43 | * Code base restructured 44 | * Added inst/app/app.R for use with shiny server 45 | 46 | # gitgadget 0.5.2.0 47 | 48 | * Set `warn = FALSE` for `readLines` 49 | * Permissions are now set at the repo/projects level, rather than at the group level 50 | * Both Create and Collect now have the option to _Show_ (i.e., add permissions) or _Hide_ (i.e., remove permissions) from a repo 51 | * Both Create and Collect now have the option load a csv file with TA information. TAs are added as "Maintainer" to the class repo. Also, TAs are added as a "maintainer" to all repos forked for students (or teams). 52 | * The Collect tab also an option to _Hide_ and _Show_ forks created for students or teams. 53 | to _Show_ (i.e., add permissions) or _Hide_ (i.e., remove permissions) from a repo 54 | * Fix to address that on Windows the global .gitconfig file may be put in the Documents folder 55 | * Added a `Check` button to the Introduce tab so you can easily check and edit the .Renviron and .Rprofile settings using `usethis` functions 56 | * Allow both the "Owner" and the "Maintainer" of a repo to create merge requests with Collect & Fetch 57 | 58 | # gitgadget 0.4.4.0 59 | 60 | * `.gitignore` file was not picked up if it already existed. Fixed in this release 61 | * Fix for generating ssh key with alternative name when .ssh/config does not yet exist 62 | 63 | # gitgadget 0.4.2.0 64 | 65 | * Enhancement for initial setup with SSH 66 | * Add .gitlab-ci.yml to avoid warning emails about CI pipelines 67 | 68 | # gitgadget 0.3.3.0 69 | 70 | * Collect and Fetch assignments using either HTTPS or SSH 71 | 72 | # gitgadget 0.3.2.0 73 | 74 | * Use ssh keys with GitLab 75 | 76 | # gitgadget 0.3.0.0 77 | 78 | * Option to provide GitLab or GitHub personal access tokens in _Introduce_ 79 | * Create a repo on GitLab or GitHub in _Create_. `usethis::use_github` is used to create a repo on GitHub 80 | 81 | # gitgadget 0.2.9.0 82 | 83 | * Improved reporting when creating and forking repos 84 | * When credentials have not yet been stored in a keychain cloning (and creating) with throw an error in Rstudio (i.e., fatal: rpostback-askpass). If you start `gitgadget` to clone a repo it will switch to the terminal tab in Rstudio and insert a git clone command. That command will ask for credentials and clone the repo as requested. From then on, cloning and creating should work fine because credentials have been stored 85 | 86 | # gitgadget 0.2.8.5 87 | 88 | * Open project in new or existing session on clone 89 | * Open to remove local git repo, remote gitlab repo, and student forks after confirmation 90 | * Better error messages 91 | * Updated to work with V4 of the gitlab API 92 | * Added _Check tokens_ button in Create tab. Button is shown when file with student tokens specified. All student tokens will then be checked on GitLab 93 | 94 | # gitgadget 0.2.7.1 95 | 96 | * Upgraded dplyr dependency to 0.7.1 97 | 98 | # gitgadget 0.2.6.0 99 | 100 | - Added commit, push, pull, and reset, to Sync tab 101 | - Added placeholders for text inputs 102 | - Added choose.dir script for mac 103 | - Update documentation 104 | - Added confirmation dialog for destructive commands (red buttons) 105 | - Added help button to gadget header 106 | - Specify user type in Introduction tab 107 | - Hide inputs in Collect tab from students 108 | - Option to remove multiple (student) branches in Branch tab 109 | - Remove option to use SSH 110 | - Local branches will now be updated if student MR was updated 111 | 112 | # gitgadget 0.2.3.0 113 | 114 | ## Bug fixes 115 | 116 | - Fix for `create_repo` when group already exists 117 | - Updated links to source code and issue tracker 118 | - Export main functions 119 | - Avoid error when an account has multiple forks by picking the first 120 | - Specify specifically that merge requests should be Fetched 121 | - Improved regex for Rproj file creation on clone 122 | - Update branch list on collect and fetch 123 | - Get full remote origin list from .git/config 124 | 125 | ## Features 126 | 127 | - Automatically replace any number of "\\" by "/" in input file and directory paths 128 | - Added option to remove previous .git and remote repo before creating new local and remote repos 129 | -------------------------------------------------------------------------------- /R/aaa.R: -------------------------------------------------------------------------------- 1 | # to avoid 'no visible binding for global variable' NOTE 2 | globalVariables(c(".", "un", "directory", "base64_enc", "server", "team", "gitgadget_ui", "status", "name")) 3 | 4 | #' gitgadget 5 | #' 6 | #' @name gitgadget 7 | #' @import shiny miniUI curl dplyr 8 | #' @importFrom jsonlite fromJSON 9 | #' @importFrom rstudioapi isAvailable getActiveProject openProject restartSession 10 | #' @importFrom markdown markdownToHTML 11 | #' @importFrom utils read.csv capture.output 12 | #' @importFrom stats na.omit setNames 13 | #' @importFrom utils packageVersion browseURL head 14 | #' @importFrom usethis edit_r_environ edit_r_profile use_git use_github 15 | #' @importFrom shinyFiles shinyDirButton shinyFilesButton parseDirPath 16 | #' parseFilePaths shinyFileChoose shinyDirChoose 17 | "_PACKAGE" 18 | -------------------------------------------------------------------------------- /R/gadget.R: -------------------------------------------------------------------------------- 1 | ## copied from https://github.com/rstudio/shiny 2 | #' noRd 3 | #' export 4 | get_port <- function() { 5 | randomInt <- function(min, max) { 6 | min + sample(max - min, 1) - 1 7 | } 8 | 9 | while (TRUE) { 10 | port <- randomInt(3000, 8000) 11 | # Reject ports in this range that are considered unsafe by Chrome 12 | # http://superuser.com/questions/188058/which-ports-are-considered-unsafe-on-chrome 13 | # https://github.com/rstudio/shiny/issues/1784 14 | if (!port %in% c(3659, 4045, 6000, 6665:6669, 6697)) { 15 | break 16 | } 17 | } 18 | port 19 | } 20 | 21 | #' @description Run gitgadget in the Rstudio viewer if available 22 | #' 23 | #' @details See \url{https://github.com/vnijs/gitgadget} for documentation 24 | #' 25 | #' @param port Port to use for the app 26 | #' @param host Host ip to use 27 | #' @param launch.browser Launch app in viewer (browsers) or only show the URL 28 | #' 29 | #' @export 30 | gitgadget <- function(port = get_port(), host = "127.0.0.1", launch.browser = TRUE) { 31 | gitgadget_dir <- system.file(package = "gitgadget") 32 | source(file.path(gitgadget_dir, "app/init.R"), local = TRUE) 33 | source(file.path(gitgadget_dir, "app/gitgadget_ui.R"), local = TRUE) 34 | ui <- gitgadget_ui() 35 | server <- function(input, output, session) { 36 | source(file.path(gitgadget_dir, "app/components/legacy.R"), local = TRUE) 37 | source(file.path(gitgadget_dir, "app/components/help.R"), local = TRUE) 38 | source(file.path(gitgadget_dir, "app/components/input-validation.R"), local = TRUE) 39 | source(file.path(gitgadget_dir, "app/components/intro.R"), local = TRUE) 40 | source(file.path(gitgadget_dir, "app/components/create.R"), local = TRUE) 41 | source(file.path(gitgadget_dir, "app/components/clone.R"), local = TRUE) 42 | source(file.path(gitgadget_dir, "app/components/repo.R"), local = TRUE) 43 | source(file.path(gitgadget_dir, "app/components/branch.R"), local = TRUE) 44 | source(file.path(gitgadget_dir, "app/components/sync.R"), local = TRUE) 45 | source(file.path(gitgadget_dir, "app/components/collect.R"), local = TRUE) 46 | observeEvent(input$done, { 47 | if (!getOption("gitgadget.jupyter", default = FALSE)) { 48 | stopApp(cat("Stopped GitGadget")) 49 | } 50 | }) 51 | } 52 | 53 | if (rstudioapi::isAvailable() && launch.browser) { 54 | runGadget(shinyApp(ui, server), port = port, viewer = shiny::paneViewer(minHeight = 725)) 55 | } else { 56 | runApp(shinyApp(ui, server), port = port, host = host, launch.browser = launch.browser) 57 | } 58 | } 59 | 60 | 61 | #' Start gitgadget and show url to open the application in an external browser 62 | #' 63 | #' @details See \url{https://github.com/vnijs/gitgadget} for documentation 64 | #' 65 | #' @export 66 | gitgadget_url <- function() { 67 | message("Click on the link below to open gitgadget\nin your default browser") 68 | gitgadget::gitgadget(launch.browser = FALSE) 69 | } 70 | 71 | #' Launch gitgadget in a separate process 72 | #' 73 | #' @details Using the \code{callr} package to launch gitgadget in a separate process so 74 | #' the console is not blocked. Rstudio viewer is used if available. See 75 | #' \url{https://github.com/vnijs/gitgadget} for documentation 76 | #' 77 | #' @importFrom callr r_bg 78 | #' 79 | #' @export 80 | gitgadget_callr <- function() { 81 | port <- get_port() 82 | callr::r_bg(function(port) { 83 | gitgadget::gitgadget(port = port) 84 | }, args = list(port), user_profile = TRUE) 85 | Sys.sleep(1) 86 | getOption("viewer")(paste0("http://localhost:", port, "/")) 87 | } 88 | -------------------------------------------------------------------------------- /R/group_management.R: -------------------------------------------------------------------------------- 1 | ## code for adding and removing users from a group 2 | ## not currently used 3 | 4 | add_users_group <- function(user_ids, group_id, token, permission, server) { 5 | resp <- lapply(user_ids, function(user_id) { 6 | add_user_group(user_id, group_id, token, permission, server)$status 7 | }) 8 | if (resp[[1]] != "OKAY") stop("\nThere was an error adding users\n") 9 | } 10 | 11 | add_user_group <- function(user_id, group_id, token, permission, server) { 12 | h <- new_handle() 13 | handle_setopt(h, customrequest = "POST") 14 | handle_setheaders(h, "PRIVATE-TOKEN" = token) 15 | murl <- paste0(server, "groups/", group_id, "/members?user_id=", user_id, "&access_level=", permission) 16 | resp <- curl_fetch_memory(murl, h) 17 | if (checkerr(resp$status_code) == FALSE) { 18 | mess <- fromJSON(rawToChar(resp$content))$message 19 | if (length(mess) > 0 && grepl("already exists", mess, ignore.case = TRUE)) { 20 | return(list(status = "OKAY", message = mess)) 21 | } else { 22 | message("There was an error adding user", user_id, "to group", group_id, ":", mess, "\n") 23 | return(list(status = "SERVER_ERROR", message = mess)) 24 | } 25 | } 26 | 27 | resp$content <- fromJSON(rawToChar(resp$content)) 28 | list(status = "OKAY") 29 | } 30 | 31 | remove_members_group <- function(token, groupname = "", userfile = "", server = "https://api.github.com/") { 32 | resp <- connect(token, server) 33 | 34 | if (resp$status != "OKAY") { 35 | stop("Error connecting to server: check token/server") 36 | } 37 | 38 | token <- resp$token 39 | resp <- groupID(groupname, groupname, token, server) 40 | 41 | if (resp$status == "NOSUCHGROUP") { 42 | message("No group found: ", resp$message) 43 | return(invisible()) 44 | } 45 | 46 | ## must give users permission in order to fork repo for them 47 | if (!is_empty(userfile)) { 48 | course_id <- resp$group_id 49 | udat <- read_ufile(userfile) 50 | uids <- userIDs(udat$userid, token, server) 51 | remove_users_group(uids, course_id, token, server) 52 | } 53 | } 54 | 55 | remove_users_group <- function(user_ids, group_id, token, server) { 56 | resp <- lapply(user_ids, function(user_id) { 57 | remove_user_group(user_id, group_id, token, server)$status 58 | }) 59 | } 60 | 61 | remove_user_group <- function(user_id, group_id, token, server) { 62 | h <- new_handle() 63 | handle_setopt(h, customrequest = "DELETE") 64 | handle_setheaders(h, "PRIVATE-TOKEN" = token) 65 | murl <- paste0(server, "groups/", group_id, "/members/", user_id) 66 | resp <- curl_fetch_memory(murl, h) 67 | if (checkerr(resp$status_code) == FALSE) { 68 | message("User ", user_id, " was not a member of group ", group_id, "\n") 69 | list(status = "SERVER_ERROR") 70 | } else { 71 | list(status = "OKAY") 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /R/interactive_testing.R: -------------------------------------------------------------------------------- 1 | ## test section 2 | # main_git__ <- TRUE 3 | main_git__ <- FALSE 4 | if (main_git__) { 5 | library(curl) 6 | library(jsonlite) 7 | library(dplyr) 8 | source("./R/git.R") 9 | 10 | ## settings 11 | # server <- Sys.getenv("git.server", "https://rsm-gitlab.ucsd.edu/api/v4/") 12 | server <- Sys.getenv("git.server", "https://api.github.com/") 13 | username <- Sys.getenv("git.user") 14 | # token <- Sys.getenv("git.token") 15 | token <- Sys.getenv("GITHUB_PAT") 16 | # groupname <- "rady-mgta-bc-2016" 17 | groupname <- Sys.getenv("git.group") 18 | userfile <- "~/git/msba-test-gitlab.csv" 19 | stopifnot(file.exists(userfile)) 20 | 21 | ## to debug code 22 | permission <- 20 23 | 24 | ## important - name the assignment repo something unique because they will all reside 25 | ## in the student's namespace, i.e., two faculty might have assignment1 26 | assignment <- "assignment1" 27 | type <- "individual" 28 | # pre <- paste0(groupname,"-") 29 | # directory <- paste0("~/bc/", groupname) 30 | 31 | ## uncomment to cleanup 32 | # remove_group(token, groupname, server) 33 | 34 | ## uncomment to remove all student projects! 35 | ## highly destructive! 36 | userfile <- "~/git/msba-test-gitlab.csv" 37 | students <- gitgadget::read_ufile(userfile) 38 | gitgadget:::remove_student_projects(userfile, Sys.getenv("git.server", "https://rsm-gitlab.ucsd.edu/api/v4/")) 39 | 40 | ## repo <- "gitgadget-test-repo" 41 | # id <- projID(paste0("vnijs/",repo), token, server)$project_id 42 | # remove_project(token, id, server) 43 | 44 | ## To remove students from a group go to the group page, click on members, 45 | ## search for the students you want and click the delete icon 46 | 47 | ## To remove individual projects cloned to a student's account 48 | ## use the Create tab and load the file with student information 49 | 50 | ## check tokens 51 | userfile <- "~/msba-test-gitlab.csv" 52 | students <- gitgadget::read_ufile(userfile) 53 | 54 | ## testing if student tokens work 55 | for (i in seq_len(nrow(students))) { 56 | i <- 1 57 | token <- students[i, "token"] 58 | if (token != "") { 59 | id <- get_allprojects(token, server) 60 | } else { 61 | id$status <- "EMPTY" 62 | } 63 | 64 | if (id$status == "OKAY") { 65 | print(paste0("OKAY: ", students[i, "userid"], " ", token)) 66 | } else { 67 | print(paste0("NOT OKAY: ", students[i, "userid"], " ", token)) 68 | } 69 | } 70 | 71 | if (file.exists(file.path(directory, assignment))) { 72 | unlink(file.path(directory, assignment, ".git"), recursive = TRUE, force = TRUE) 73 | dir.exists(file.path(directory, assignment, ".git")) 74 | 75 | ## create a group for a course where all assignments and cases will be posted 76 | create_group( 77 | token, groupname, userfile, 78 | permission = permission, server = server 79 | ) 80 | 81 | ## get or create a repo for assignments and cases 82 | create_repo( 83 | username, token, assignment, directory, groupname, 84 | pre = pre, server = server 85 | ) 86 | 87 | assign_work( 88 | token, groupname, assignment, userfile, 89 | type = type, 90 | pre = pre, server = server 91 | ) 92 | } else { 93 | cat("Assignment does not exist") 94 | } 95 | 96 | ## same steps for a team assignment 97 | assignment <- "assignment2" 98 | type <- "team" 99 | if (file.exists(file.path(directory, assignment))) { 100 | unlink(file.path(directory, assignment, ".git"), recursive = TRUE, force = TRUE) 101 | dir.exists(file.path(directory, assignment, ".git")) 102 | 103 | create_repo( 104 | username, token, assignment, directory, groupname, 105 | pre = pre, server = server 106 | ) 107 | 108 | assign_work( 109 | token, groupname, assignment, userfile, 110 | type = type, 111 | pre = pre, server = server 112 | ) 113 | } else { 114 | cat("Assignment does not exist") 115 | } 116 | 117 | ## generate merge (pull) requests 118 | assignment <- "assignment1" 119 | type <- "individual" 120 | if (file.exists(file.path(directory, assignment))) { 121 | collect_work( 122 | token, groupname, assignment, userfile, 123 | type = type, 124 | pre = pre, server = server 125 | ) 126 | 127 | fetch_work( 128 | token, groupname, assignment, 129 | pre = pre, server = server 130 | ) 131 | } else { 132 | cat("Assignment does not exist") 133 | } 134 | 135 | ## create a repo on gitlab in the users own namespace 136 | groupname <- "" 137 | pre <- "" 138 | repo <- assignment 139 | # repo <- "gitgadget-test-repo" 140 | # directory <- "/Users/vnijs/Desktop/Github" 141 | if (file.exists(file.path(directory, repo))) { 142 | unlink(file.path(directory, assignment, ".git"), recursive = TRUE, force = TRUE) 143 | dir.exists(file.path(directory, assignment, ".git")) 144 | create_repo( 145 | username, token, repo, directory, groupname, 146 | pre = pre, server = server 147 | ) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Page not found (404) • gitgadget 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 |
24 |
64 | 65 | 66 | 67 | 68 |
69 |
70 | 73 | 74 | Content not found. Please use links in the navbar. 75 | 76 |
77 | 78 | 82 | 83 |
84 | 85 | 86 | 87 | 98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /docs/articles/gitgadget_files/accessible-code-block-0.0.1/empty-anchor.js: -------------------------------------------------------------------------------- 1 | // Hide empty tag within highlighted CodeBlock for screen reader accessibility (see https://github.com/jgm/pandoc/issues/6352#issuecomment-626106786) --> 2 | // v0.0.1 3 | // Written by JooYoung Seo (jooyoung@psu.edu) and Atsushi Yasumoto on June 1st, 2020. 4 | 5 | document.addEventListener('DOMContentLoaded', function() { 6 | const codeList = document.getElementsByClassName("sourceCode"); 7 | for (var i = 0; i < codeList.length; i++) { 8 | var linkList = codeList[i].getElementsByTagName('a'); 9 | for (var j = 0; j < linkList.length; j++) { 10 | if (linkList[j].innerHTML === "") { 11 | linkList[j].setAttribute('aria-hidden', 'true'); 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /docs/articles/gitgadget_files/header-attrs-2.3/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/articles/gitgadget_files/header-attrs-2.7/header-attrs.js: -------------------------------------------------------------------------------- 1 | // Pandoc 2.9 adds attributes on both header and div. We remove the former (to 2 | // be compatible with the behavior of Pandoc < 2.8). 3 | document.addEventListener('DOMContentLoaded', function(e) { 4 | var hs = document.querySelectorAll("div.section[class*='level'] > :first-child"); 5 | var i, h, a; 6 | for (i = 0; i < hs.length; i++) { 7 | h = hs[i]; 8 | if (!/^h[1-6]$/i.test(h.tagName)) continue; // it should be a header h1-h6 9 | a = h.attributes; 10 | while (a.length > 0) h.removeAttribute(a[0].name); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /docs/articles/index.html: -------------------------------------------------------------------------------- 1 | 2 | Articles • gitgadget 6 | 7 | 8 |
9 |
43 | 44 | 45 | 46 |
47 |
48 | 51 | 52 |
53 |

All vignettes

54 |

55 | 56 |
Using Git Gadget
57 |
58 |
59 |
60 |
61 | 62 | 63 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | Authors and Citation • gitgadget 6 | 7 | 8 |
9 |
43 | 44 | 45 | 46 |
47 |
48 |
49 | 52 | 53 | 54 |
  • 55 |

    Vincent Nijs. Author, maintainer. 56 |

    57 |
  • 58 |
  • 59 |

    Sanjiv Erat. Author. 60 |

    61 |
  • 62 |
63 |
64 |
65 |

Citation

66 | Source: DESCRIPTION 67 |
68 |
69 | 70 | 71 |

Nijs V, Erat S (2022). 72 | gitgadget: 'Rstudio' Addin for Version Control and Assignment Management using Git. 73 | URL: https://github.com/vnijs/gitgadget. 74 |

75 |
@Manual{,
 76 |   title = {gitgadget: 'Rstudio' Addin for Version Control and Assignment Management using Git},
 77 |   author = {Vincent Nijs and Sanjiv Erat},
 78 |   year = {2022},
 79 |   note = {URL: https://github.com/vnijs/gitgadget},
 80 | }
81 | 82 |
83 | 84 |
85 | 86 | 87 | 88 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | 6 | /* modified from https://github.com/twbs/bootstrap/blob/94b4076dd2efba9af71f0b18d4ee4b163aa9e0dd/docs/assets/css/src/docs.css#L548-L601 */ 7 | 8 | /* All levels of nav */ 9 | nav[data-toggle='toc'] .nav > li > a { 10 | display: block; 11 | padding: 4px 20px; 12 | font-size: 13px; 13 | font-weight: 500; 14 | color: #767676; 15 | } 16 | nav[data-toggle='toc'] .nav > li > a:hover, 17 | nav[data-toggle='toc'] .nav > li > a:focus { 18 | padding-left: 19px; 19 | color: #563d7c; 20 | text-decoration: none; 21 | background-color: transparent; 22 | border-left: 1px solid #563d7c; 23 | } 24 | nav[data-toggle='toc'] .nav > .active > a, 25 | nav[data-toggle='toc'] .nav > .active:hover > a, 26 | nav[data-toggle='toc'] .nav > .active:focus > a { 27 | padding-left: 18px; 28 | font-weight: bold; 29 | color: #563d7c; 30 | background-color: transparent; 31 | border-left: 2px solid #563d7c; 32 | } 33 | 34 | /* Nav: second level (shown on .active) */ 35 | nav[data-toggle='toc'] .nav .nav { 36 | display: none; /* Hide by default, but at >768px, show it */ 37 | padding-bottom: 10px; 38 | } 39 | nav[data-toggle='toc'] .nav .nav > li > a { 40 | padding-top: 1px; 41 | padding-bottom: 1px; 42 | padding-left: 30px; 43 | font-size: 12px; 44 | font-weight: normal; 45 | } 46 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 47 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 48 | padding-left: 29px; 49 | } 50 | nav[data-toggle='toc'] .nav .nav > .active > a, 51 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 52 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 53 | padding-left: 28px; 54 | font-weight: 500; 55 | } 56 | 57 | /* from https://github.com/twbs/bootstrap/blob/e38f066d8c203c3e032da0ff23cd2d6098ee2dd6/docs/assets/css/src/docs.css#L631-L634 */ 58 | nav[data-toggle='toc'] .nav > .active > ul { 59 | display: block; 60 | } 61 | -------------------------------------------------------------------------------- /docs/bootstrap-toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Table of Contents v0.4.1 (http://afeld.github.io/bootstrap-toc/) 3 | * Copyright 2015 Aidan Feldman 4 | * Licensed under MIT (https://github.com/afeld/bootstrap-toc/blob/gh-pages/LICENSE.md) */ 5 | (function() { 6 | 'use strict'; 7 | 8 | window.Toc = { 9 | helpers: { 10 | // return all matching elements in the set, or their descendants 11 | findOrFilter: function($el, selector) { 12 | // http://danielnouri.org/notes/2011/03/14/a-jquery-find-that-also-finds-the-root-element/ 13 | // http://stackoverflow.com/a/12731439/358804 14 | var $descendants = $el.find(selector); 15 | return $el.filter(selector).add($descendants).filter(':not([data-toc-skip])'); 16 | }, 17 | 18 | generateUniqueIdBase: function(el) { 19 | var text = $(el).text(); 20 | var anchor = text.trim().toLowerCase().replace(/[^A-Za-z0-9]+/g, '-'); 21 | return anchor || el.tagName.toLowerCase(); 22 | }, 23 | 24 | generateUniqueId: function(el) { 25 | var anchorBase = this.generateUniqueIdBase(el); 26 | for (var i = 0; ; i++) { 27 | var anchor = anchorBase; 28 | if (i > 0) { 29 | // add suffix 30 | anchor += '-' + i; 31 | } 32 | // check if ID already exists 33 | if (!document.getElementById(anchor)) { 34 | return anchor; 35 | } 36 | } 37 | }, 38 | 39 | generateAnchor: function(el) { 40 | if (el.id) { 41 | return el.id; 42 | } else { 43 | var anchor = this.generateUniqueId(el); 44 | el.id = anchor; 45 | return anchor; 46 | } 47 | }, 48 | 49 | createNavList: function() { 50 | return $(''); 51 | }, 52 | 53 | createChildNavList: function($parent) { 54 | var $childList = this.createNavList(); 55 | $parent.append($childList); 56 | return $childList; 57 | }, 58 | 59 | generateNavEl: function(anchor, text) { 60 | var $a = $(''); 61 | $a.attr('href', '#' + anchor); 62 | $a.text(text); 63 | var $li = $('
  • '); 64 | $li.append($a); 65 | return $li; 66 | }, 67 | 68 | generateNavItem: function(headingEl) { 69 | var anchor = this.generateAnchor(headingEl); 70 | var $heading = $(headingEl); 71 | var text = $heading.data('toc-text') || $heading.text(); 72 | return this.generateNavEl(anchor, text); 73 | }, 74 | 75 | // Find the first heading level (`

    `, then `

    `, etc.) that has more than one element. Defaults to 1 (for `

    `). 76 | getTopLevel: function($scope) { 77 | for (var i = 1; i <= 6; i++) { 78 | var $headings = this.findOrFilter($scope, 'h' + i); 79 | if ($headings.length > 1) { 80 | return i; 81 | } 82 | } 83 | 84 | return 1; 85 | }, 86 | 87 | // returns the elements for the top level, and the next below it 88 | getHeadings: function($scope, topLevel) { 89 | var topSelector = 'h' + topLevel; 90 | 91 | var secondaryLevel = topLevel + 1; 92 | var secondarySelector = 'h' + secondaryLevel; 93 | 94 | return this.findOrFilter($scope, topSelector + ',' + secondarySelector); 95 | }, 96 | 97 | getNavLevel: function(el) { 98 | return parseInt(el.tagName.charAt(1), 10); 99 | }, 100 | 101 | populateNav: function($topContext, topLevel, $headings) { 102 | var $context = $topContext; 103 | var $prevNav; 104 | 105 | var helpers = this; 106 | $headings.each(function(i, el) { 107 | var $newNav = helpers.generateNavItem(el); 108 | var navLevel = helpers.getNavLevel(el); 109 | 110 | // determine the proper $context 111 | if (navLevel === topLevel) { 112 | // use top level 113 | $context = $topContext; 114 | } else if ($prevNav && $context === $topContext) { 115 | // create a new level of the tree and switch to it 116 | $context = helpers.createChildNavList($prevNav); 117 | } // else use the current $context 118 | 119 | $context.append($newNav); 120 | 121 | $prevNav = $newNav; 122 | }); 123 | }, 124 | 125 | parseOps: function(arg) { 126 | var opts; 127 | if (arg.jquery) { 128 | opts = { 129 | $nav: arg 130 | }; 131 | } else { 132 | opts = arg; 133 | } 134 | opts.$scope = opts.$scope || $(document.body); 135 | return opts; 136 | } 137 | }, 138 | 139 | // accepts a jQuery object, or an options object 140 | init: function(opts) { 141 | opts = this.helpers.parseOps(opts); 142 | 143 | // ensure that the data attribute is in place for styling 144 | opts.$nav.attr('data-toggle', 'toc'); 145 | 146 | var $topContext = this.helpers.createChildNavList(opts.$nav); 147 | var topLevel = this.helpers.getTopLevel(opts.$scope); 148 | var $headings = this.helpers.getHeadings(opts.$scope, topLevel); 149 | this.helpers.populateNav($topContext, topLevel, $headings); 150 | } 151 | }; 152 | 153 | $(function() { 154 | $('nav[data-toggle="toc"]').each(function(i, el) { 155 | var $nav = $(el); 156 | Toc.init($nav); 157 | }); 158 | }); 159 | })(); 160 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /docs/jquery.sticky-kit.min.js: -------------------------------------------------------------------------------- 1 | /* Sticky-kit v1.1.2 | WTFPL | Leaf Corcoran 2015 | */ 2 | /* 3 | Source: https://github.com/leafo/sticky-kit 4 | License: MIT 5 | */ 6 | (function(){var b,f;b=this.jQuery||window.jQuery;f=b(window);b.fn.stick_in_parent=function(d){var A,w,J,n,B,K,p,q,k,E,t;null==d&&(d={});t=d.sticky_class;B=d.inner_scrolling;E=d.recalc_every;k=d.parent;q=d.offset_top;p=d.spacer;w=d.bottoming;null==q&&(q=0);null==k&&(k=void 0);null==B&&(B=!0);null==t&&(t="is_stuck");A=b(document);null==w&&(w=!0);J=function(a,d,n,C,F,u,r,G){var v,H,m,D,I,c,g,x,y,z,h,l;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);I=A.height();g=a.parent();null!=k&&(g=g.closest(k)); 7 | if(!g.length)throw"failed to find stick parent";v=m=!1;(h=null!=p?p&&a.closest(p):b("
    "))&&h.css("position",a.css("position"));x=function(){var c,f,e;if(!G&&(I=A.height(),c=parseInt(g.css("border-top-width"),10),f=parseInt(g.css("padding-top"),10),d=parseInt(g.css("padding-bottom"),10),n=g.offset().top+c+f,C=g.height(),m&&(v=m=!1,null==p&&(a.insertAfter(h),h.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(t),e=!0),F=a.offset().top-(parseInt(a.css("margin-top"),10)||0)-q, 8 | u=a.outerHeight(!0),r=a.css("float"),h&&h.css({width:a.outerWidth(!0),height:u,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),e))return l()};x();if(u!==C)return D=void 0,c=q,z=E,l=function(){var b,l,e,k;if(!G&&(e=!1,null!=z&&(--z,0>=z&&(z=E,x(),e=!0)),e||A.height()===I||x(),e=f.scrollTop(),null!=D&&(l=e-D),D=e,m?(w&&(k=e+u+c>C+n,v&&!k&&(v=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),eb&&!v&&(c-=l,c=Math.max(b-u,c),c=Math.min(q,c),m&&a.css({top:c+"px"})))):e>F&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(t),null==p&&(a.after(h),"left"!==r&&"right"!==r||h.append(a)),a.trigger("sticky_kit:stick")),m&&w&&(null==k&&(k=e+u+c>C+n),!v&&k)))return v=!0,"static"===g.css("position")&&g.css({position:"relative"}), 10 | a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")},y=function(){x();return l()},H=function(){G=!0;f.off("touchmove",l);f.off("scroll",l);f.off("resize",y);b(document.body).off("sticky_kit:recalc",y);a.off("sticky_kit:detach",H);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});g.position("position","");if(m)return null==p&&("left"!==r&&"right"!==r||a.insertAfter(h),h.remove()),a.removeClass(t)},f.on("touchmove",l),f.on("scroll",l),f.on("resize", 11 | y),b(document.body).on("sticky_kit:recalc",y),a.on("sticky_kit:detach",H),setTimeout(l,0)}};n=0;for(K=this.length;n 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body { 21 | position: relative; 22 | } 23 | 24 | body > .container { 25 | display: flex; 26 | height: 100%; 27 | flex-direction: column; 28 | } 29 | 30 | body > .container .row { 31 | flex: 1 0 auto; 32 | } 33 | 34 | footer { 35 | margin-top: 45px; 36 | padding: 35px 0 36px; 37 | border-top: 1px solid #e5e5e5; 38 | color: #666; 39 | display: flex; 40 | flex-shrink: 0; 41 | } 42 | footer p { 43 | margin-bottom: 0; 44 | } 45 | footer div { 46 | flex: 1; 47 | } 48 | footer .pkgdown { 49 | text-align: right; 50 | } 51 | footer p { 52 | margin-bottom: 0; 53 | } 54 | 55 | img.icon { 56 | float: right; 57 | } 58 | 59 | /* Ensure in-page images don't run outside their container */ 60 | .contents img { 61 | max-width: 100%; 62 | height: auto; 63 | } 64 | 65 | /* Fix bug in bootstrap (only seen in firefox) */ 66 | summary { 67 | display: list-item; 68 | } 69 | 70 | /* Typographic tweaking ---------------------------------*/ 71 | 72 | .contents .page-header { 73 | margin-top: calc(-60px + 1em); 74 | } 75 | 76 | dd { 77 | margin-left: 3em; 78 | } 79 | 80 | /* Section anchors ---------------------------------*/ 81 | 82 | a.anchor { 83 | display: none; 84 | margin-left: 5px; 85 | width: 20px; 86 | height: 20px; 87 | 88 | background-image: url(./link.svg); 89 | background-repeat: no-repeat; 90 | background-size: 20px 20px; 91 | background-position: center center; 92 | } 93 | 94 | h1:hover .anchor, 95 | h2:hover .anchor, 96 | h3:hover .anchor, 97 | h4:hover .anchor, 98 | h5:hover .anchor, 99 | h6:hover .anchor { 100 | display: inline-block; 101 | } 102 | 103 | /* Fixes for fixed navbar --------------------------*/ 104 | 105 | .contents h1, .contents h2, .contents h3, .contents h4 { 106 | padding-top: 60px; 107 | margin-top: -40px; 108 | } 109 | 110 | /* Navbar submenu --------------------------*/ 111 | 112 | .dropdown-submenu { 113 | position: relative; 114 | } 115 | 116 | .dropdown-submenu>.dropdown-menu { 117 | top: 0; 118 | left: 100%; 119 | margin-top: -6px; 120 | margin-left: -1px; 121 | border-radius: 0 6px 6px 6px; 122 | } 123 | 124 | .dropdown-submenu:hover>.dropdown-menu { 125 | display: block; 126 | } 127 | 128 | .dropdown-submenu>a:after { 129 | display: block; 130 | content: " "; 131 | float: right; 132 | width: 0; 133 | height: 0; 134 | border-color: transparent; 135 | border-style: solid; 136 | border-width: 5px 0 5px 5px; 137 | border-left-color: #cccccc; 138 | margin-top: 5px; 139 | margin-right: -10px; 140 | } 141 | 142 | .dropdown-submenu:hover>a:after { 143 | border-left-color: #ffffff; 144 | } 145 | 146 | .dropdown-submenu.pull-left { 147 | float: none; 148 | } 149 | 150 | .dropdown-submenu.pull-left>.dropdown-menu { 151 | left: -100%; 152 | margin-left: 10px; 153 | border-radius: 6px 0 6px 6px; 154 | } 155 | 156 | /* Sidebar --------------------------*/ 157 | 158 | #pkgdown-sidebar { 159 | margin-top: 30px; 160 | position: -webkit-sticky; 161 | position: sticky; 162 | top: 70px; 163 | } 164 | 165 | #pkgdown-sidebar h2 { 166 | font-size: 1.5em; 167 | margin-top: 1em; 168 | } 169 | 170 | #pkgdown-sidebar h2:first-child { 171 | margin-top: 0; 172 | } 173 | 174 | #pkgdown-sidebar .list-unstyled li { 175 | margin-bottom: 0.5em; 176 | } 177 | 178 | /* bootstrap-toc tweaks ------------------------------------------------------*/ 179 | 180 | /* All levels of nav */ 181 | 182 | nav[data-toggle='toc'] .nav > li > a { 183 | padding: 4px 20px 4px 6px; 184 | font-size: 1.5rem; 185 | font-weight: 400; 186 | color: inherit; 187 | } 188 | 189 | nav[data-toggle='toc'] .nav > li > a:hover, 190 | nav[data-toggle='toc'] .nav > li > a:focus { 191 | padding-left: 5px; 192 | color: inherit; 193 | border-left: 1px solid #878787; 194 | } 195 | 196 | nav[data-toggle='toc'] .nav > .active > a, 197 | nav[data-toggle='toc'] .nav > .active:hover > a, 198 | nav[data-toggle='toc'] .nav > .active:focus > a { 199 | padding-left: 5px; 200 | font-size: 1.5rem; 201 | font-weight: 400; 202 | color: inherit; 203 | border-left: 2px solid #878787; 204 | } 205 | 206 | /* Nav: second level (shown on .active) */ 207 | 208 | nav[data-toggle='toc'] .nav .nav { 209 | display: none; /* Hide by default, but at >768px, show it */ 210 | padding-bottom: 10px; 211 | } 212 | 213 | nav[data-toggle='toc'] .nav .nav > li > a { 214 | padding-left: 16px; 215 | font-size: 1.35rem; 216 | } 217 | 218 | nav[data-toggle='toc'] .nav .nav > li > a:hover, 219 | nav[data-toggle='toc'] .nav .nav > li > a:focus { 220 | padding-left: 15px; 221 | } 222 | 223 | nav[data-toggle='toc'] .nav .nav > .active > a, 224 | nav[data-toggle='toc'] .nav .nav > .active:hover > a, 225 | nav[data-toggle='toc'] .nav .nav > .active:focus > a { 226 | padding-left: 15px; 227 | font-weight: 500; 228 | font-size: 1.35rem; 229 | } 230 | 231 | /* orcid ------------------------------------------------------------------- */ 232 | 233 | .orcid { 234 | font-size: 16px; 235 | color: #A6CE39; 236 | /* margins are required by official ORCID trademark and display guidelines */ 237 | margin-left:4px; 238 | margin-right:4px; 239 | vertical-align: middle; 240 | } 241 | 242 | /* Reference index & topics ----------------------------------------------- */ 243 | 244 | .ref-index th {font-weight: normal;} 245 | 246 | .ref-index td {vertical-align: top; min-width: 100px} 247 | .ref-index .icon {width: 40px;} 248 | .ref-index .alias {width: 40%;} 249 | .ref-index-icons .alias {width: calc(40% - 40px);} 250 | .ref-index .title {width: 60%;} 251 | 252 | .ref-arguments th {text-align: right; padding-right: 10px;} 253 | .ref-arguments th, .ref-arguments td {vertical-align: top; min-width: 100px} 254 | .ref-arguments .name {width: 20%;} 255 | .ref-arguments .desc {width: 80%;} 256 | 257 | /* Nice scrolling for wide elements --------------------------------------- */ 258 | 259 | table { 260 | display: block; 261 | overflow: auto; 262 | } 263 | 264 | /* Syntax highlighting ---------------------------------------------------- */ 265 | 266 | pre, code, pre code { 267 | background-color: #f8f8f8; 268 | color: #333; 269 | } 270 | pre, pre code { 271 | white-space: pre-wrap; 272 | word-break: break-all; 273 | overflow-wrap: break-word; 274 | } 275 | 276 | pre { 277 | border: 1px solid #eee; 278 | } 279 | 280 | pre .img, pre .r-plt { 281 | margin: 5px 0; 282 | } 283 | 284 | pre .img img, pre .r-plt img { 285 | background-color: #fff; 286 | } 287 | 288 | code a, pre a { 289 | color: #375f84; 290 | } 291 | 292 | a.sourceLine:hover { 293 | text-decoration: none; 294 | } 295 | 296 | .fl {color: #1514b5;} 297 | .fu {color: #000000;} /* function */ 298 | .ch,.st {color: #036a07;} /* string */ 299 | .kw {color: #264D66;} /* keyword */ 300 | .co {color: #888888;} /* comment */ 301 | 302 | .error {font-weight: bolder;} 303 | .warning {font-weight: bolder;} 304 | 305 | /* Clipboard --------------------------*/ 306 | 307 | .hasCopyButton { 308 | position: relative; 309 | } 310 | 311 | .btn-copy-ex { 312 | position: absolute; 313 | right: 0; 314 | top: 0; 315 | visibility: hidden; 316 | } 317 | 318 | .hasCopyButton:hover button.btn-copy-ex { 319 | visibility: visible; 320 | } 321 | 322 | /* headroom.js ------------------------ */ 323 | 324 | .headroom { 325 | will-change: transform; 326 | transition: transform 200ms linear; 327 | } 328 | .headroom--pinned { 329 | transform: translateY(0%); 330 | } 331 | .headroom--unpinned { 332 | transform: translateY(-100%); 333 | } 334 | 335 | /* mark.js ----------------------------*/ 336 | 337 | mark { 338 | background-color: rgba(255, 255, 51, 0.5); 339 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 340 | padding: 1px; 341 | } 342 | 343 | /* vertical spacing after htmlwidgets */ 344 | .html-widget { 345 | margin-bottom: 10px; 346 | } 347 | 348 | /* fontawesome ------------------------ */ 349 | 350 | .fab { 351 | font-family: "Font Awesome 5 Brands" !important; 352 | } 353 | 354 | /* don't display links in code chunks when printing */ 355 | /* source: https://stackoverflow.com/a/10781533 */ 356 | @media print { 357 | code a:link:after, code a:visited:after { 358 | content: ""; 359 | } 360 | } 361 | 362 | /* Section anchors --------------------------------- 363 | Added in pandoc 2.11: https://github.com/jgm/pandoc-templates/commit/9904bf71 364 | */ 365 | 366 | div.csl-bib-body { } 367 | div.csl-entry { 368 | clear: both; 369 | } 370 | .hanging-indent div.csl-entry { 371 | margin-left:2em; 372 | text-indent:-2em; 373 | } 374 | div.csl-left-margin { 375 | min-width:2em; 376 | float:left; 377 | } 378 | div.csl-right-inline { 379 | margin-left:2em; 380 | padding-left:1em; 381 | } 382 | div.csl-indent { 383 | margin-left: 2em; 384 | } 385 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $('.navbar-fixed-top').headroom(); 6 | 7 | $('body').css('padding-top', $('.navbar').height() + 10); 8 | $(window).resize(function(){ 9 | $('body').css('padding-top', $('.navbar').height() + 10); 10 | }); 11 | 12 | $('[data-toggle="tooltip"]').tooltip(); 13 | 14 | var cur_path = paths(location.pathname); 15 | var links = $("#navbar ul li a"); 16 | var max_length = -1; 17 | var pos = -1; 18 | for (var i = 0; i < links.length; i++) { 19 | if (links[i].getAttribute("href") === "#") 20 | continue; 21 | // Ignore external links 22 | if (links[i].host !== location.host) 23 | continue; 24 | 25 | var nav_path = paths(links[i].pathname); 26 | 27 | var length = prefix_length(nav_path, cur_path); 28 | if (length > max_length) { 29 | max_length = length; 30 | pos = i; 31 | } 32 | } 33 | 34 | // Add class to parent
  • , and enclosing
  • if in dropdown 35 | if (pos >= 0) { 36 | var menu_anchor = $(links[pos]); 37 | menu_anchor.parent().addClass("active"); 38 | menu_anchor.closest("li.dropdown").addClass("active"); 39 | } 40 | }); 41 | 42 | function paths(pathname) { 43 | var pieces = pathname.split("/"); 44 | pieces.shift(); // always starts with / 45 | 46 | var end = pieces[pieces.length - 1]; 47 | if (end === "index.html" || end === "") 48 | pieces.pop(); 49 | return(pieces); 50 | } 51 | 52 | // Returns -1 if not found 53 | function prefix_length(needle, haystack) { 54 | if (needle.length > haystack.length) 55 | return(-1); 56 | 57 | // Special case for length-0 haystack, since for loop won't run 58 | if (haystack.length === 0) { 59 | return(needle.length === 0 ? 0 : -1); 60 | } 61 | 62 | for (var i = 0; i < haystack.length; i++) { 63 | if (needle[i] != haystack[i]) 64 | return(i); 65 | } 66 | 67 | return(haystack.length); 68 | } 69 | 70 | /* Clipboard --------------------------*/ 71 | 72 | function changeTooltipMessage(element, msg) { 73 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 74 | element.setAttribute('data-original-title', msg); 75 | $(element).tooltip('show'); 76 | element.setAttribute('data-original-title', tooltipOriginalTitle); 77 | } 78 | 79 | if(ClipboardJS.isSupported()) { 80 | $(document).ready(function() { 81 | var copyButton = ""; 82 | 83 | $("div.sourceCode").addClass("hasCopyButton"); 84 | 85 | // Insert copy buttons: 86 | $(copyButton).prependTo(".hasCopyButton"); 87 | 88 | // Initialize tooltips: 89 | $('.btn-copy-ex').tooltip({container: 'body'}); 90 | 91 | // Initialize clipboard: 92 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 93 | text: function(trigger) { 94 | return trigger.parentNode.textContent.replace(/\n#>[^\n]*/g, ""); 95 | } 96 | }); 97 | 98 | clipboardBtnCopies.on('success', function(e) { 99 | changeTooltipMessage(e.trigger, 'Copied!'); 100 | e.clearSelection(); 101 | }); 102 | 103 | clipboardBtnCopies.on('error', function() { 104 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 105 | }); 106 | }); 107 | } 108 | })(window.jQuery || window.$) 109 | -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 2.17.1.1 2 | pkgdown: 2.0.3 3 | pkgdown_sha: ~ 4 | articles: 5 | gitgadget: gitgadget.html 6 | last_built: 2022-06-11T22:00Z 7 | 8 | -------------------------------------------------------------------------------- /docs/reference/add_users_repo.html: -------------------------------------------------------------------------------- 1 | 2 | Add users to a repo — add_users_repo • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Add users to a repo

    56 |
    57 | 58 |
    59 |
    add_users_repo(
     60 |   token,
     61 |   repo,
     62 |   userfile,
     63 |   permission = 20,
     64 |   server = "https://gitlab.com/api/v4/"
     65 | )
    66 |
    67 | 68 |
    69 |

    Arguments

    70 |
    token
    71 |

    GitLab token

    72 |
    repo
    73 |

    Repo to update

    74 |
    userfile
    75 |

    A csv file with student information (i.e., username, token, and email)

    76 |
    permission
    77 |

    Permission setting for the repo (default is 20, i.e., reporter)

    78 |
    server
    79 |

    The gitlab API server

    80 |
    81 |
    82 |

    Details

    83 |

    See https://github.com/vnijs/gitgadget for additional documentation

    84 |
    85 | 86 |
    87 | 90 |
    91 | 92 | 93 |
    96 | 97 |
    98 |

    Site built with pkgdown 2.0.3.

    99 |
    100 | 101 |
    102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/reference/assign_work.html: -------------------------------------------------------------------------------- 1 | 2 | Assign work to each student/team by creating a fork of the main repo — assign_work • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Assign work to each student/team by creating a fork of the main repo

    56 |
    57 | 58 |
    59 |
    assign_work(
     60 |   token,
     61 |   groupname,
     62 |   assignment,
     63 |   userfile,
     64 |   tafile = "",
     65 |   type = "individual",
     66 |   pre = "",
     67 |   server = "https://gitlab.com/api/v4/"
     68 | )
    69 |
    70 | 71 |
    72 |

    Arguments

    73 |
    token
    74 |

    GitLab token

    75 |
    groupname
    76 |

    Group to create on gitlab (defaults to user's namespace)

    77 |
    assignment
    78 |

    Name of the assignment to assign

    79 |
    userfile
    80 |

    A csv file with student information (i.e., username, token, and email)

    81 |
    tafile
    82 |

    A optional csv file with TA information (i.e., username, token, and email)

    83 |
    type
    84 |

    Individual or Team work

    85 |
    pre
    86 |

    Pre-amble for the assignment name, usually groupname + "-"

    87 |
    server
    88 |

    The gitlab API server

    89 |
    90 |
    91 |

    Details

    92 |

    See https://github.com/vnijs/gitgadget for additional documentation

    93 |
    94 | 95 |
    96 | 99 |
    100 | 101 | 102 |
    105 | 106 |
    107 |

    Site built with pkgdown 2.0.3.

    108 |
    109 | 110 |
    111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/reference/check_tokens.html: -------------------------------------------------------------------------------- 1 | 2 | Check student tokens — check_tokens • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Check student tokens

    56 |
    57 | 58 |
    59 |
    check_tokens(
     60 |   userfile,
     61 |   server = Sys.getenv("git.server", "https://gitlab.com/api/v4/")
     62 | )
    63 |
    64 | 65 |
    66 |

    Arguments

    67 |
    userfile
    68 |

    A csv file with student information (i.e., username, token, and email)

    69 |
    server
    70 |

    The gitlab API server

    71 |
    72 |
    73 |

    Details

    74 |

    See https://github.com/vnijs/gitgadget for additional documentation

    75 |
    76 | 77 |
    78 | 81 |
    82 | 83 | 84 |
    87 | 88 |
    89 |

    Site built with pkgdown 2.0.3.

    90 |
    91 | 92 |
    93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/reference/checkerr.html: -------------------------------------------------------------------------------- 1 | 2 | Check error status — checkerr • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Check error status

    56 |
    57 | 58 |
    59 |
    checkerr(code)
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    code
    65 |

    Code returned by an API request

    66 |
    67 |
    68 |

    Details

    69 |

    See https://github.com/vnijs/gitgadget for additional documentation

    70 |
    71 | 72 |
    73 | 76 |
    77 | 78 | 79 |
    82 | 83 |
    84 |

    Site built with pkgdown 2.0.3.

    85 |
    86 | 87 |
    88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /docs/reference/collect_work.html: -------------------------------------------------------------------------------- 1 | 2 | Create merge requests for each student/team — collect_work • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Create merge requests for each student/team

    56 |
    57 | 58 |
    59 |
    collect_work(
     60 |   token,
     61 |   assignment,
     62 |   userfile,
     63 |   type = "individual",
     64 |   server = "https://gitlab.com/api/v4/"
     65 | )
    66 |
    67 | 68 |
    69 |

    Arguments

    70 |
    token
    71 |

    GitLab token

    72 |
    assignment
    73 |

    Name of the assignment (e.g., "class345/class345-assignment1")

    74 |
    userfile
    75 |

    A csv file with student information (i.e., username, token, and email)

    76 |
    type
    77 |

    Individual or Team work

    78 |
    server
    79 |

    The gitlab API server

    80 |
    81 |
    82 |

    Details

    83 |

    See https://github.com/vnijs/gitgadget for additional documentation

    84 |
    85 | 86 |
    87 | 90 |
    91 | 92 | 93 |
    96 | 97 |
    98 |

    Site built with pkgdown 2.0.3.

    99 |
    100 | 101 |
    102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/reference/create_group.html: -------------------------------------------------------------------------------- 1 | 2 | Create a group on gitlab using the API — create_group • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Create a group on gitlab using the API

    56 |
    57 | 58 |
    59 |
    create_group(
     60 |   token,
     61 |   groupname = "",
     62 |   userfile = "",
     63 |   permission = 20,
     64 |   server = "https://gitlab.com/api/v4/"
     65 | )
    66 |
    67 | 68 |
    69 |

    Arguments

    70 |
    token
    71 |

    GitLab token

    72 |
    groupname
    73 |

    Group to create on gitlab (defaults to user's namespace)

    74 |
    userfile
    75 |

    A csv file with student information (i.e., username, token, and email)

    76 |
    permission
    77 |

    Permission setting for the group (default is 20, i.e., reporter)

    78 |
    server
    79 |

    The gitlab API server

    80 |
    81 |
    82 |

    Details

    83 |

    See https://github.com/vnijs/gitgadget for additional documentation

    84 |
    85 | 86 |
    87 | 90 |
    91 | 92 | 93 |
    96 | 97 |
    98 |

    Site built with pkgdown 2.0.3.

    99 |
    100 | 101 |
    102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/reference/create_repo.html: -------------------------------------------------------------------------------- 1 | 2 | Create the main repo from a local directory — create_repo • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Create the main repo from a local directory

    56 |
    57 | 58 |
    59 |
    create_repo(
     60 |   username = Sys.getenv("git.user"),
     61 |   token = Sys.getenv("git.token"),
     62 |   repo = basename(getwd()),
     63 |   base_dir = dirname(getwd()),
     64 |   groupname = "",
     65 |   pre = "",
     66 |   ssh = FALSE,
     67 |   server = "https://gitlab.com/api/v4/"
     68 | )
    69 |
    70 | 71 |
    72 |

    Arguments

    73 |
    username
    74 |

    Username

    75 |
    token
    76 |

    Token (e.g., Sys.getenv("git.token") or Sys.getenv("GITHUB_PAT"))

    77 |
    repo
    78 |

    Name of the repo (assignment)

    79 |
    base_dir
    80 |

    Base directory for the repo. file.path(directory, assignment) should exist

    81 |
    groupname
    82 |

    Group to create on gitlab (defaults to user's namespace)

    83 |
    pre
    84 |

    Pre-amble for the repo (assignment) name

    85 |
    ssh
    86 |

    Use SSH for authentication

    87 |
    server
    88 |

    The gitlab API server

    89 |
    90 |
    91 |

    Details

    92 |

    See https://github.com/vnijs/gitgadget for additional documentation

    93 |
    94 | 95 |
    96 | 99 |
    100 | 101 | 102 |
    105 | 106 |
    107 |

    Site built with pkgdown 2.0.3.

    108 |
    109 | 110 |
    111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/reference/fetch_work.html: -------------------------------------------------------------------------------- 1 | 2 | Fetch all merge requests as local branches and link to a remote — fetch_work • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Fetch all merge requests as local branches and link to a remote

    56 |
    57 | 58 |
    59 |
    fetch_work(token, assignment, page = 1, server = "https://gitlab.com/api/v4/")
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    token
    65 |

    GitLab token

    66 |
    assignment
    67 |

    Name of the assignment (e.g., "class345/class345-assignment1")

    68 |
    page
    69 |

    Number of the results page to select

    70 |
    server
    71 |

    The gitlab API server

    72 |
    73 |
    74 |

    Details

    75 |

    See https://github.com/vnijs/gitgadget for additional documentation

    76 |
    77 | 78 |
    79 | 82 |
    83 | 84 | 85 |
    88 | 89 |
    90 |

    Site built with pkgdown 2.0.3.

    91 |
    92 | 93 |
    94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/reference/get_port.html: -------------------------------------------------------------------------------- 1 | 2 | noRd 3 | export — get_port • gitgadget 9 | 10 | 11 |
    12 |
    46 | 47 | 48 | 49 |
    50 |
    51 | 57 | 58 |
    59 |

    noRd 60 | export

    61 |
    62 | 63 |
    64 |
    get_port()
    65 |
    66 | 67 | 68 |
    69 | 72 |
    73 | 74 | 75 |
    78 | 79 |
    80 |

    Site built with pkgdown 2.0.3.

    81 |
    82 | 83 |
    84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/reference/gitgadget.html: -------------------------------------------------------------------------------- 1 | 2 | gitgadget — gitgadget • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Run gitgadget in the Rstudio viewer if available

    56 |
    57 | 58 |
    59 |
    gitgadget(port = get_port(), host = "127.0.0.1", launch.browser = TRUE)
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    port
    65 |

    Port to use for the app

    66 |
    host
    67 |

    Host ip to use

    68 |
    launch.browser
    69 |

    Launch app in viewer (browsers) or only show the URL

    70 |
    71 |
    72 |

    Details

    73 |

    See https://github.com/vnijs/gitgadget for documentation

    74 |
    75 | 76 |
    77 | 80 |
    81 | 82 | 83 |
    86 | 87 |
    88 |

    Site built with pkgdown 2.0.3.

    89 |
    90 | 91 |
    92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/reference/gitgadget_callr.html: -------------------------------------------------------------------------------- 1 | 2 | Launch gitgadget in a separate process — gitgadget_callr • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Launch gitgadget in a separate process

    56 |
    57 | 58 |
    59 |
    gitgadget_callr()
    60 |
    61 | 62 |
    63 |

    Details

    64 |

    Using the callr package to launch gitgadget in a separate process so 65 | the console is not blocked. Rstudio viewer is used if available. See 66 | https://github.com/vnijs/gitgadget for documentation

    67 |
    68 | 69 |
    70 | 73 |
    74 | 75 | 76 |
    79 | 80 |
    81 |

    Site built with pkgdown 2.0.3.

    82 |
    83 | 84 |
    85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/reference/gitgadget_url.html: -------------------------------------------------------------------------------- 1 | 2 | Start gitgadget and show url to open the application in an external browser — gitgadget_url • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Start gitgadget and show url to open the application in an external browser

    56 |
    57 | 58 |
    59 |
    gitgadget_url()
    60 |
    61 | 62 |
    63 |

    Details

    64 |

    See https://github.com/vnijs/gitgadget for documentation

    65 |
    66 | 67 |
    68 | 71 |
    72 | 73 | 74 |
    77 | 78 |
    79 |

    Site built with pkgdown 2.0.3.

    80 |
    81 | 82 |
    83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | Function reference • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 51 | 52 | 56 | 59 | 60 | 63 | 64 | 67 | 68 | 71 | 72 | 75 | 76 | 79 | 80 | 83 | 84 | 87 | 88 | 91 | 93 | 96 | 97 | 100 | 101 | 104 | 105 | 108 | 109 | 112 | 113 | 116 | 117 | 120 | 121 |
    53 |

    All functions

    54 |

    55 |
    57 |

    add_users_repo()

    58 |

    Add users to a repo

    61 |

    assign_work()

    62 |

    Assign work to each student/team by creating a fork of the main repo

    65 |

    check_tokens()

    66 |

    Check student tokens

    69 |

    checkerr()

    70 |

    Check error status

    73 |

    collect_work()

    74 |

    Create merge requests for each student/team

    77 |

    create_group()

    78 |

    Create a group on gitlab using the API

    81 |

    create_repo()

    82 |

    Create the main repo from a local directory

    85 |

    fetch_work()

    86 |

    Fetch all merge requests as local branches and link to a remote

    89 |

    get_port()

    90 |

    noRd 92 | export

    94 |

    gitgadget()

    95 |

    gitgadget

    98 |

    gitgadget_callr()

    99 |

    Launch gitgadget in a separate process

    102 |

    gitgadget_url()

    103 |

    Start gitgadget and show url to open the application in an external browser

    106 |

    projID()

    107 |

    Find project ID

    110 |

    read_ufile()

    111 |

    Reach user file

    114 |

    remove_project()

    115 |

    Remove a project

    118 |

    remove_users_repo()

    119 |

    Remove users from a repo

    122 | 123 | 126 |
    127 | 128 | 129 |
    132 | 133 |
    134 |

    Site built with pkgdown 2.0.3.

    135 |
    136 | 137 |
    138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/reference/projID.html: -------------------------------------------------------------------------------- 1 | 2 | Find project ID — projID • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Find project ID

    56 |
    57 | 58 |
    59 |
    projID(path_with_namespace, token, server, owned = TRUE, search = "")
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    path_with_namespace
    65 |

    Repo name together with the group or user namespace

    66 |
    token
    67 |

    GitLab token

    68 |
    server
    69 |

    The gitlab API server

    70 |
    owned
    71 |

    Restrict listing to only repos owned by the user? TRUE or FALSE

    72 |
    search
    73 |

    Search term to use to narrow down the set of projects

    74 |
    75 |
    76 |

    Details

    77 |

    See https://github.com/vnijs/gitgadget for additional documentation

    78 |
    79 | 80 |
    81 | 84 |
    85 | 86 | 87 |
    90 | 91 |
    92 |

    Site built with pkgdown 2.0.3.

    93 |
    94 | 95 |
    96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/reference/read_ufile.html: -------------------------------------------------------------------------------- 1 | 2 | Reach user file — read_ufile • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Reach user file

    56 |
    57 | 58 |
    59 |
    read_ufile(userfile, cols = c("userid", "team", "token"))
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    userfile
    65 |

    File with student information

    66 |
    cols
    67 |

    Column names that must exists in the file

    68 |
    69 |
    70 |

    Details

    71 |

    See https://github.com/vnijs/gitgadget for additional documentation

    72 |
    73 | 74 |
    75 | 78 |
    79 | 80 | 81 |
    84 | 85 |
    86 |

    Site built with pkgdown 2.0.3.

    87 |
    88 | 89 |
    90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/reference/remove_project.html: -------------------------------------------------------------------------------- 1 | 2 | Remove a project — remove_project • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Remove a project

    56 |
    57 | 58 |
    59 |
    remove_project(token, id, server)
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    token
    65 |

    GitLab token

    66 |
    id
    67 |

    Project ID

    68 |
    server
    69 |

    The gitlab API server

    70 |
    71 |
    72 |

    Details

    73 |

    See https://github.com/vnijs/gitgadget for additional documentation

    74 |
    75 | 76 |
    77 | 80 |
    81 | 82 | 83 |
    86 | 87 |
    88 |

    Site built with pkgdown 2.0.3.

    89 |
    90 | 91 |
    92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/reference/remove_users_repo.html: -------------------------------------------------------------------------------- 1 | 2 | Remove users from a repo — remove_users_repo • gitgadget 6 | 7 | 8 |
    9 |
    43 | 44 | 45 | 46 |
    47 |
    48 | 53 | 54 |
    55 |

    Remove users from a repo

    56 |
    57 | 58 |
    59 |
    remove_users_repo(token, repo, userfile, server = "https://gitlab.com/api/v4/")
    60 |
    61 | 62 |
    63 |

    Arguments

    64 |
    token
    65 |

    GitLab token

    66 |
    repo
    67 |

    Repo the update

    68 |
    userfile
    69 |

    A csv file with student information (i.e., username, token, and email)

    70 |
    server
    71 |

    The gitlab API server

    72 |
    73 |
    74 |

    Details

    75 |

    See https://github.com/vnijs/gitgadget for additional documentation

    76 |
    77 | 78 |
    79 | 82 |
    83 | 84 | 85 |
    88 | 89 |
    90 |

    Site built with pkgdown 2.0.3.

    91 |
    92 | 93 |
    94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /404.html 5 | 6 | 7 | /articles/gitgadget.html 8 | 9 | 10 | /articles/index.html 11 | 12 | 13 | /authors.html 14 | 15 | 16 | /index.html 17 | 18 | 19 | /news/index.html 20 | 21 | 22 | /reference/add_users_repo.html 23 | 24 | 25 | /reference/assign_work.html 26 | 27 | 28 | /reference/check_tokens.html 29 | 30 | 31 | /reference/checkerr.html 32 | 33 | 34 | /reference/collect_work.html 35 | 36 | 37 | /reference/create_group.html 38 | 39 | 40 | /reference/create_repo.html 41 | 42 | 43 | /reference/fetch_work.html 44 | 45 | 46 | /reference/get_port.html 47 | 48 | 49 | /reference/gitgadget.html 50 | 51 | 52 | /reference/gitgadget_callr.html 53 | 54 | 55 | /reference/gitgadget_url.html 56 | 57 | 58 | /reference/index.html 59 | 60 | 61 | /reference/projID.html 62 | 63 | 64 | /reference/read_ufile.html 65 | 66 | 67 | /reference/remove_project.html 68 | 69 | 70 | /reference/remove_users_repo.html 71 | 72 | 73 | -------------------------------------------------------------------------------- /gitgadget.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 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 | PackageCheckArgs: --as-cran 22 | PackageRoxygenize: rd,collate,namespace,vignette 23 | -------------------------------------------------------------------------------- /inst/app/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(gitgadget) 3 | library(dplyr) 4 | library(miniUI) 5 | 6 | is_not <- function(x) length(x) == 0 || (length(x) == 1 && is.na(x)) 7 | is_empty <- function(x, empty = "\\s*") { 8 | is_not(x) || (length(x) == 1 && grepl(paste0("^", empty, "$"), x)) 9 | } 10 | pressed <- function(x) !is.null(x) && (is.list(x) || x > 0) 11 | not_pressed <- function(x) !pressed(x) 12 | 13 | source("init.R", local = TRUE) 14 | source("./gitgadget_ui.R", local = TRUE) 15 | ui <- gitgadget_ui() 16 | server <- function(input, output, session) { 17 | source("components/legacy.R", local = TRUE) 18 | source("components/help.R", local = TRUE) 19 | source("components/input-validation.R", local = TRUE) 20 | source("components/intro.R", local = TRUE) 21 | source("components/create.R", local = TRUE) 22 | source("components/clone.R", local = TRUE) 23 | source("components/repo.R", local = TRUE) 24 | source("components/branch.R", local = TRUE) 25 | source("components/sync.R", local = TRUE) 26 | source("components/collect.R", local = TRUE) 27 | observeEvent(input$done, { 28 | if (!getOption("gitgadget.jupyter", default = FALSE)) { 29 | stopApp(cat("Stopped GitGadget")) 30 | } 31 | }) 32 | } 33 | 34 | shinyApp(ui = ui, server = server) 35 | -------------------------------------------------------------------------------- /inst/app/components/branch.R: -------------------------------------------------------------------------------- 1 | branches <- reactive({ 2 | input$branch_delete 3 | input$branch_create 4 | input$branch_checkout 5 | req(input$repo_directory) 6 | br <- suppressWarnings(system(paste("git -C", input$repo_directory, "branch -a"), intern = TRUE)) 7 | brs <- attr(br, "status") 8 | ## need both conditions because output on windows and mac differs 9 | if (length(br) == 0 || (!is.null(brs) && brs == 128)) { 10 | c() 11 | } else { 12 | br %>% gsub("[\\* ]+", "", .) %>% 13 | {.[!grepl("(^main$|^master$)|(^remotes/origin/main$)|(^remotes/origin/master$)|(^remotes/origin/HEAD)", .)]} 14 | } 15 | }) 16 | 17 | observeEvent(input$branch_create, { 18 | req(input$repo_directory) 19 | if (input$branch_create_name != "") { 20 | withProgress(message = "Creating branch", value = 0, style = "old", { 21 | mess <- suppressWarnings(system(paste("git -C", input$repo_directory, "checkout -b", input$branch_create_name), intern = TRUE)) 22 | }) 23 | if (is_empty(mess)) mess <- "No messages" 24 | showModal( 25 | modalDialog( 26 | title = "Branch create messages", 27 | span(HTML(paste0(mess, collapse = "
    "))) 28 | ) 29 | ) 30 | } 31 | }) 32 | 33 | observeEvent(input$branch_create_from_mr, { 34 | req(input$repo_directory) 35 | remote_fetch <- suppressWarnings(system(paste("git -C", input$repo_directory, "config --get-all remote.origin.fetch"), intern = TRUE)) 36 | if (!"+refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*" %in% remote_fetch) { 37 | mess <- system(paste("git -C", dir, "config --add remote.origin.fetch +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*"), intern = TRUE) 38 | if (is_empty(mess)) mess <- "No messages" 39 | showModal( 40 | modalDialog( 41 | title = "Branch create from MR messages", 42 | span(HTML(paste0(mess, collapse = "
    "))) 43 | ) 44 | ) 45 | } 46 | }) 47 | 48 | observeEvent(input$branch_merge, { 49 | from <- input$branch_merge_from 50 | into <- input$branch_merge_into 51 | req(input$repo_directory) 52 | if (!is.null(from) || !is.null(into)) { 53 | withProgress(message = "Merging branch", value = 0, style = "old", { 54 | mess1 <- suppressWarnings(system(paste("git -C", input$repo_directory, "checkout", into), intern = TRUE)) 55 | mess2 <- suppressWarnings(system(paste("git -C", input$repo_directory, "merge", from), inter = TRUE)) 56 | }) 57 | showModal( 58 | modalDialog( 59 | title = "Branch merge messages", 60 | span(HTML(paste0(c(mess1, mess2), collapse = "
    "))) 61 | ) 62 | ) 63 | } 64 | }) 65 | 66 | observeEvent(input$branch_abort, { 67 | req(input$repo_directory) 68 | mess <- suppressWarnings(system(paste("git -C", input$repo_directory, "merge --abort"), intern = TRUE)) 69 | if (is_empty(mess)) mess <- "No messages" 70 | showModal( 71 | modalDialog( 72 | title = "Branch merge abort messages", 73 | span(HTML(paste0(mess, collapse = "
    "))) 74 | ) 75 | ) 76 | }) 77 | 78 | observeEvent(input$branch_link, { 79 | req(input$repo_directory) 80 | if (input$branch_create_name != "") { 81 | ## would prefer to do this without 'push' -- however then I can't unlink for some reason 82 | mess <- suppressWarnings(paste("git -C", input$repo_directory, "push --set-upstream origin", input$branch_create_name) %>% 83 | system(intern = TRUE)) 84 | if (is_empty(mess)) mess <- "No messages" 85 | showModal( 86 | modalDialog( 87 | title = "Branch link messages", 88 | span(HTML(paste0(mess, collapse = "
    "))) 89 | ) 90 | ) 91 | } 92 | }) 93 | 94 | observeEvent(input$branch_unlink, { 95 | req(input$repo_directory) 96 | branch <- input$branch_delete_name 97 | if (is_empty(branch)) input$branch_create_name 98 | if (!is_empty(branch)) { 99 | mess <- c() 100 | for (ib in branch) { 101 | mess <- c(mess, system(paste0("git -C ", input$repo_directory, " branch -d -r origin/", ib), intern = TRUE)) 102 | } 103 | showModal( 104 | modalDialog( 105 | title = "Branch create messages", 106 | span(HTML(paste0(mess, collapse = "
    "))) 107 | ) 108 | ) 109 | } 110 | }) 111 | 112 | observeEvent(input$branch_delete, { 113 | req(input$repo_directory) 114 | if (!is.null(input$branch_delete_name)) { 115 | withProgress(message = "Deleting branch", value = 0, style = "old", { 116 | mess <- system(paste("git -C", input$repo_directory, "checkout main"), intern = TRUE) 117 | for (ib in input$branch_delete_name) { 118 | mess <- c(mess, system(paste("git -C", input$repo_directory, "branch -D", ib), intern = TRUE)) 119 | } 120 | }) 121 | showModal( 122 | modalDialog( 123 | title = "Branch create messages", 124 | span(HTML(paste0(mess, collapse = "
    "))) 125 | ) 126 | ) 127 | } 128 | }) 129 | 130 | output$ui_branch_create_name <- renderUI({ 131 | br <- rbranches() 132 | if (length(br) == 0) { 133 | HTML(paste0("
    ")) 134 | } else { 135 | init <- isolate(input$branch_create_name) 136 | init <- ifelse(is_empty(init), "", init) 137 | textInput("branch_create_name", NULL, value = init, placeholder = "Provide a name for the new branch") 138 | } 139 | }) 140 | 141 | output$ui_branch_merge_branches <- renderUI({ 142 | br <- c("main", branches()) %>% .[!grepl("origin/", .)] 143 | if (length(br) == 1) { 144 | HTML("") 145 | } else { 146 | tagList( 147 | fillRow(height = "70px", width = "300px", 148 | selectInput("branch_merge_from", "From:", choices = br, selected = br[2]), 149 | selectInput("branch_merge_into", "Into:", choices = br, selected = br[1]) 150 | ) 151 | ) 152 | } 153 | }) 154 | 155 | rbranches <- reactive({ 156 | input$sync; input$sync_unlink; input$branch_link; input$branch_unlink 157 | input$branch_create; input$branch_checkout; input$branch_delete; 158 | input$branch_merge; input$collect_fetch; input$collect 159 | req(input$repo_directory) 160 | 161 | br <- suppressWarnings(system(paste("git -C", input$repo_directory, "branch --all"), intern = TRUE)) 162 | brs <- attr(br, "status") 163 | ## need both conditions because output on windows and mac differs 164 | if (length(br) == 0 || (!is.null(brs) && brs == 128)) { 165 | c() 166 | } else { 167 | br %>% {unique(c(.[grepl("\\* ", .)], .))} %>% 168 | gsub("[\\* ]+", "", .) %>% 169 | {.[!grepl("(^remotes/origin/main$)|(^remotes/origin/main$)|(^remotes/origin/HEAD)", .)]} 170 | } 171 | }) 172 | 173 | output$ui_branch_checkout_name <- renderUI({ 174 | input$branch_create 175 | br <- rbranches() 176 | if (length(br) == 0) { 177 | HTML("") 178 | } else { 179 | selectInput("branch_checkout_name", NULL, choices = br) 180 | } 181 | }) 182 | 183 | observeEvent(input$branch_checkout, { 184 | req(input$repo_directory) 185 | if (!is.null(input$branch_checkout_name)) { 186 | ## based on solution #1 http://stackoverflow.com/a/29828320/1974918 187 | withProgress(message = "Checkout branch", value = 0, style = "old", { 188 | mess <- suppressWarnings(system(paste0("git -C ", input$repo_directory, " checkout ", sub("remotes/origin/", "", input$branch_checkout_name)), intern = TRUE)) 189 | }) 190 | showModal( 191 | modalDialog( 192 | title = "Branch checkout messages", 193 | span(HTML(paste0(mess, collapse = "
    "))) 194 | ) 195 | ) 196 | } 197 | }) 198 | 199 | output$ui_branch_delete_name <- renderUI({ 200 | resp <- branches() %>% .[!grepl("^remotes/origin", .)] 201 | if (length(resp) == 0) { 202 | HTML("") 203 | } else { 204 | selectizeInput("branch_delete_name", 205 | label = NULL, 206 | selected = resp[1], 207 | choices = resp, 208 | multiple = TRUE, 209 | options = list(placeholder = "Select branch(es) to delete", plugins = list("remove_button")) 210 | ) 211 | } 212 | }) 213 | -------------------------------------------------------------------------------- /inst/app/components/clone.R: -------------------------------------------------------------------------------- 1 | clone <- eventReactive(input$clone, { 2 | to_return <- c() 3 | if (input$clone_from != "") { 4 | if (input$clone_into != "") { 5 | owd <- setwd(input$clone_into) 6 | on.exit(setwd(owd)) 7 | } 8 | clone_from <- cmd_from <- input$clone_from 9 | cmd <- paste("git clone", clone_from) 10 | cmdclean <- paste("git clone", input$clone_from) 11 | 12 | clone_to <- input$clone_to 13 | if (!is_empty(clone_to)) { 14 | cmd <- paste(cmd, clone_to) 15 | cmdclean <- paste(cmdclean, clone_to) 16 | clone_to <- file.path(getwd(), clone_to) 17 | } else { 18 | clone_to <- file.path(getwd(), basename(clone_from) %>% tools::file_path_sans_ext()) 19 | } 20 | 21 | if (dir.exists(clone_to)) { 22 | showModal( 23 | modalDialog( 24 | title = "Directory already exists", 25 | span("The directory you are trying to clone to already exists") 26 | ) 27 | ) 28 | return(invisible()) 29 | } 30 | 31 | cat("Used:", cmdclean, "\n\n") 32 | 33 | withProgress(message = "Cloning repo", value = 0, style = "old", { 34 | if (os_type == "Windows") { 35 | ret <- suppressWarnings(system(cmd, intern = TRUE)) 36 | } else { 37 | ret <- suppressWarnings(system(paste(cmd, "2>&1"), intern = TRUE)) 38 | } 39 | if (any(grepl("rpostback-askpass", ret)) || any(grepl("could not read Username", ret))) { 40 | if (rstudioapi::isAvailable()) { 41 | rstudioapi::terminalActivate() 42 | tid <- c() 43 | slp <- 1 44 | while (length(tid) == 0) { 45 | Sys.sleep(1) 46 | tid <- rstudioapi::terminalVisible() 47 | if (slp > 10) break 48 | slp <- slp + 1 49 | } 50 | if (slp > 10) { 51 | cat("\nUnable to send commands to terminal. Please try again\n") 52 | } else { 53 | rstudioapi::terminalSend(tid, paste("git clone", clone_from, clone_to, "\n")) 54 | showModal( 55 | modalDialog( 56 | title = "Provide user name and password", 57 | span("Provide user name and password in Rstudio > Terminal to clone from GitLab (GitHub)") 58 | ) 59 | ) 60 | } 61 | } 62 | } else if (any(grepl("fatal:", ret))) { 63 | cat(ret, sep = "\n") 64 | } else { 65 | cat(ret, sep = "\n") 66 | to_return <- 0 67 | } 68 | }) 69 | } 70 | return(to_return) 71 | }) 72 | 73 | shinyFiles::shinyDirChoose(input, "clone_into_open", roots = gg_volumes) 74 | 75 | output$ui_clone_into <- renderUI({ 76 | if (!is.integer(input$clone_into_open)) { 77 | init <- shinyFiles::parseDirPath(gg_volumes, input$clone_into_open) 78 | } else { 79 | init <- Sys.getenv("git.home", basedir) 80 | } 81 | textInput("clone_into", "Base directory to clone repo into:", 82 | value = init, 83 | placeholder = "Choose directory to store repo" 84 | ) 85 | }) 86 | 87 | output$clone_output <- renderPrint({ 88 | input$clone 89 | ret <- clone() 90 | isolate({ 91 | if (is_empty(input$clone_from)) { 92 | cat("Nothing was returned. Make sure you specified a repo to clone from") 93 | } else if (length(ret) > 0 && ret == 0) { 94 | if (input$clone_to == "") { 95 | dir <- file.path(input$clone_into, gsub("\\.git\\s*$", "", basename(input$clone_from))) 96 | } else { 97 | dir <- file.path(input$clone_into, input$clone_to) 98 | } 99 | 100 | rproj <- list.files(path = dir, pattern = "*.Rproj") 101 | if (length(rproj) == 0) { 102 | cnt <- "Version: 1.0\n\nRestoreWorkspace: No\nSaveWorkspace: No\nAlwaysSaveHistory: Default\n\nEnableCodeIndexing: Yes\nUseSpacesForTab: Yes\nNumSpacesForTab: 2\nEncoding: UTF-8\n\nRnwWeave: knitr\nLaTex: pdfLaTex\n\nAutoAppendNewline: Yes\n\nBuildType: Package\nPackageUseDevtools: Yes\nPackageInstallArgs: --no-multiarch --with-keep.source\nPackageRoxygenize: rd,collate,namespace\n" 103 | cat(cnt, file = file.path(dir, paste0(basename(dir), ".Rproj"))) 104 | } 105 | 106 | vscode <- list.files(path = dir, pattern = "*.code-workspace") 107 | if (length(vscode) == 0) { 108 | cnt <- '{"folders": [{"path": "."}], "settings": {}}' 109 | cat(cnt, file = file.path(dir, paste0(basename(dir), ".code-workspace"))) 110 | } 111 | 112 | gitignore <- list.files(path = dir, all.files = TRUE, pattern = ".gitignore") 113 | if (length(gitignore) == 0) { 114 | cat(".Rproj.user\n.Rhistory\n.RData\n.Ruserdata\n.DS_Store\n.ipynb_checkpoints\n.mypy_cache\n.vscode\n", file = ".gitignore") 115 | } 116 | 117 | cat("Repo was sucessfully cloned into", dir) 118 | 119 | if (length(rproj) == 0) { 120 | rproj <- list.files(path = dir, pattern = "*.Rproj", full.names = TRUE)[1] 121 | } else { 122 | rproj <- file.path(dir, rproj[1]) 123 | } 124 | if (rstudioapi::isAvailable()) { 125 | rstudioapi::openProject(rproj, newSession = input$clone_proj == "new") 126 | } 127 | } else { 128 | cat("There was an error cloning the repo. Check the R console for output") 129 | } 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /inst/app/components/help.R: -------------------------------------------------------------------------------- 1 | ## Show remove_git modal when button is clicked. 2 | observeEvent(input$help, { 3 | ## See https://shiny.rstudio.com/reference/shiny/latest/modalDialog.html 4 | showModal( 5 | modalDialog(title = "GitGadget Help", 6 | markdown::markdownToHTML( 7 | file.path(system.file(package = "gitgadget"), "app/help/help.md"), 8 | fragment.only = TRUE, 9 | options = "", 10 | stylesheet = "" 11 | ) %>% 12 | gsub("", "
    ", .) %>% 13 | HTML, 14 | footer = tagList(modalButton("OK")), 15 | easyClose = TRUE 16 | ) 17 | ) 18 | }) 19 | -------------------------------------------------------------------------------- /inst/app/components/input-validation.R: -------------------------------------------------------------------------------- 1 | observeEvent(grep("([\\]+)|([/]{2,})", input$intro_git_home), { 2 | if (!is_empty(input$intro_git_home)) 3 | updateTextInput(session = session, "intro_git_home", value = gsub("([\\]+)|([/]{2,})", "/", input$intro_git_home)) 4 | }) 5 | 6 | observeEvent(grep("([\\]+)|([/]{2,})", input$create_directory), { 7 | updateTextInput(session = session, "create_directory", value = gsub("([\\]+)|([/]{2,})", "/", input$create_directory)) 8 | }) 9 | 10 | observeEvent(grep("([\\]+)|([/]{2,})", input$create_user_file), { 11 | updateTextInput(session = session, "create_user_file", value = gsub("([\\]+)|([/]{2,})", "/", input$create_user_file)) 12 | }) 13 | 14 | observeEvent(grep("([\\]+)|([/]{2,})", input$clone_into), { 15 | updateTextInput(session = session, "clone_into", value = gsub("([\\]+)|([/]{2,})", "/", input$clone_into)) 16 | }) 17 | 18 | observeEvent(grep("([\\]+)|([/]{2,})", input$clone_to), { 19 | updateTextInput(session = session, "clone_to", value = gsub("([\\]+)|([/]{2,})", "/", input$clone_to)) 20 | }) 21 | 22 | observeEvent(grep("([\\]+)|([/]{2,})", input$collect_user_file), { 23 | updateTextInput(session = session, "collect_user_file", value = gsub("([\\]+)|([/]{2,})", "/", input$collect_user_file)) 24 | }) 25 | -------------------------------------------------------------------------------- /inst/app/components/legacy.R: -------------------------------------------------------------------------------- 1 | if (!is.null(getOption("git.user"))) { 2 | showModal( 3 | modalDialog(title = "Move git settings to .Renviron", 4 | span("All git related settings should be moved from .Rprofile to .Renviron. Click on the 5 | \"edit .R files\" button to open both .Rprofile and .Renviron. Move all lines that 6 | contain 'git.' out of .Rprofile and to the .Renviron file. Then remove 'options' from the 7 | new lines in .Renviron. For example: \"options(git.user = 'abc123')\" in .Rprofile should 8 | be \"git.user = 'abc123'\" in .Renviron."), 9 | br(), br(), 10 | span("If you run into any problems, please 11 | post an issue to"), 12 | HTML("https://github.com/vnijs/gitgadget/issues"), 13 | footer = tagList( 14 | modalButton("Cancel"), 15 | actionButton("edit_r_files", "Edit .R files") 16 | ) 17 | ) 18 | ) 19 | } 20 | 21 | observeEvent(input$edit_r_files, { 22 | usethis::edit_r_environ() 23 | usethis::edit_r_profile() 24 | }) -------------------------------------------------------------------------------- /inst/app/components/repo.R: -------------------------------------------------------------------------------- 1 | shinyFiles::shinyDirChoose(input, "repo_directory_find", roots = gg_volumes) 2 | 3 | output$ui_repo_directory <- renderUI({ 4 | input$create 5 | input$remove_git 6 | if (!is_empty(projdir) && grepl(homedir, projdir)) { 7 | init <- projdir 8 | } else { 9 | init <- homedir 10 | } 11 | if (!is.integer(input$repo_directory_find)) { 12 | init <- shinyFiles::parseDirPath(gg_volumes, input$repo_directory_find) 13 | } 14 | textInput( 15 | "repo_directory", NULL, 16 | value = init, 17 | placeholder = "Repo directory" 18 | ) 19 | }) 20 | 21 | outputOptions(output, "ui_repo_directory", suspendWhenHidden = FALSE) 22 | 23 | is_repo <- reactive({ 24 | input$repo_refresh 25 | is_repo_fun(input$repo_directory) 26 | }) 27 | 28 | output$repo_output <- renderPrint({ 29 | req(input$repo_directory) 30 | input$repo_refresh 31 | if (is_repo()) { 32 | cat(paste(input$repo_directory, "is a git repo")) 33 | } else { 34 | cat(paste(input$repo_directory, "is not a git repo")) 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /inst/app/data/students.csv: -------------------------------------------------------------------------------- 1 | userid,team,email, pw 2 | id1,team1,student1@gmail.com, pw 3 | id2,team1,student2@gmail.com, pw 4 | id3,team2,student3@gmail.com, pw 5 | -------------------------------------------------------------------------------- /inst/app/init.R: -------------------------------------------------------------------------------- 1 | os_type <- Sys.info()["sysname"] 2 | 3 | find_home <- function(os_type = Sys.info()["sysname"]) { 4 | if (os_type == "Windows") { 5 | ## gives /Users/x and not /Users/x/Documents 6 | normalizePath( 7 | file.path( 8 | Sys.getenv("HOMEDRIVE"), 9 | Sys.getenv("HOMEPATH") 10 | ), 11 | winslash = "/" 12 | ) 13 | } else { 14 | Sys.getenv("HOME") 15 | } 16 | } 17 | 18 | homedir <- find_home() 19 | renvirdir <- Sys.getenv("HOME") 20 | 21 | ## setting up volumes for shinyFiles 22 | gg_volumes <- c(Home = homedir) 23 | git_home <- Sys.getenv("git.home") 24 | 25 | ## needed if using gitgadget with both native and docker version of Rstudio 26 | if (git_home != "" && !file.exists(git_home)) { 27 | git_alt <- file.path(homedir, basename(git_home)) 28 | if (file.exists(git_alt)) { 29 | git_home <- git_alt 30 | } else { 31 | git_alt <- file.path(homedir, basename(dirname(git_home)), basename(git_home)) 32 | if (file.exists(git_alt)) { 33 | git_home <- git_alt 34 | } else { 35 | git_home <- "" 36 | } 37 | } 38 | } 39 | 40 | ## setting up volumes for shinyFiles 41 | if (git_home != "") { 42 | gg_volumes <- setNames(c(git_home, gg_volumes), c(basename(git_home), names(gg_volumes))) 43 | } 44 | 45 | is_repo_fun <- function(dr) { 46 | is_empty(suppressWarnings(system(paste("git -C", dr, "rev-parse --is-inside-work-tree 2>/dev/null"), intern = TRUE))) == FALSE 47 | } 48 | 49 | projdir <- basedir <- git_home 50 | if (rstudioapi::isAvailable()) { 51 | pdir <- rstudioapi::getActiveProject() 52 | if (!is_empty(pdir)) { 53 | projdir <- basedir <- pdir 54 | } else { 55 | wd <- getwd() 56 | if (grepl("^/srv/", wd)) wd <- git_home 57 | if (is_repo_fun(wd)) { 58 | projdir <- basedir <- wd 59 | } 60 | } 61 | } else { 62 | wd <- getwd() 63 | if (grepl("^/srv/", wd)) wd <- git_home 64 | if (is_repo_fun(wd)) { 65 | projdir <- basedir <- wd 66 | } 67 | } 68 | 69 | if (is_empty(projdir)) { 70 | projdir <- basedir <- normalizePath(homedir, winslash = "/") 71 | } else { 72 | if (!projdir %in% gg_volumes) { 73 | gg_volumes <- setNames(c(projdir, gg_volumes), c(basename(projdir), names(gg_volumes))) 74 | } 75 | projdir <- basedir <- normalizePath(projdir, winslash = "/") 76 | } -------------------------------------------------------------------------------- /inst/app/www/style.css: -------------------------------------------------------------------------------- 1 | #create_file_find, #create_directory_find, #collect_file_find, #collect_list { 2 | margin-top: 25px; 3 | } 4 | 5 | /* adapted from https://jackolney.github.io/2016/shiny/*/ 6 | .shiny-progress .progress-text { 7 | height: 30px; 8 | width: 100%; 9 | background-color: #4682B4; 10 | padding-top: 2px; 11 | padding-right: 3px; 12 | padding-bottom: 2px; 13 | padding-left: 3px; 14 | opacity: 0.95; 15 | } 16 | 17 | .progress-text { 18 | top: 50px !important; 19 | color: #FFFFFF !important; 20 | text-align: center; 21 | } 22 | 23 | /* Bold Initial part of message */ 24 | .shiny-progress .progress-text .progress-message { 25 | padding-top: 0px; 26 | padding-right: 3px; 27 | padding-bottom: 3px; 28 | padding-left: 10px; 29 | font-weight: bold; 30 | font-size: 20px; 31 | } 32 | 33 | .sF-dirWindow { 34 | min-height: 30vh !important; 35 | } 36 | 37 | .sF-dirInfo>div { 38 | height: 28vh !important; 39 | } 40 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Git Gadget 2 | Description: Rstudio addin for version control and assignment management using git 3 | Binding: gitgadget 4 | Interactive: true 5 | -------------------------------------------------------------------------------- /man/add_users_repo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{add_users_repo} 4 | \alias{add_users_repo} 5 | \title{Add users to a repo} 6 | \usage{ 7 | add_users_repo( 8 | token, 9 | repo, 10 | userfile, 11 | permission = 20, 12 | server = "https://api.github.com/" 13 | ) 14 | } 15 | \arguments{ 16 | \item{token}{GitHub token} 17 | 18 | \item{repo}{Repo to update} 19 | 20 | \item{userfile}{A csv file with student information (i.e., username, token, and email)} 21 | 22 | \item{permission}{Permission setting for the repo (default is 20, i.e., reporter)} 23 | 24 | \item{server}{The GitLab API server} 25 | } 26 | \description{ 27 | Add users to a repo 28 | } 29 | \details{ 30 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 31 | } 32 | -------------------------------------------------------------------------------- /man/assign_work.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{assign_work} 4 | \alias{assign_work} 5 | \title{Assign work to each student/team by creating a fork of the main repo} 6 | \usage{ 7 | assign_work( 8 | token, 9 | groupname, 10 | assignment, 11 | userfile, 12 | tafile = "", 13 | type = "individual", 14 | pre = "", 15 | server = "https://api.github.com/" 16 | ) 17 | } 18 | \arguments{ 19 | \item{token}{GitLab token} 20 | 21 | \item{groupname}{Group to create on GitLab (defaults to user's namespace)} 22 | 23 | \item{assignment}{Name of the assignment to assign} 24 | 25 | \item{userfile}{A csv file with student information (i.e., username, token, and email)} 26 | 27 | \item{tafile}{A optional csv file with TA information (i.e., username, token, and email)} 28 | 29 | \item{type}{Individual or Team work} 30 | 31 | \item{pre}{Pre-amble for the assignment name, usually groupname + "-"} 32 | 33 | \item{server}{The GitLab API server} 34 | } 35 | \description{ 36 | Assign work to each student/team by creating a fork of the main repo 37 | } 38 | \details{ 39 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 40 | } 41 | -------------------------------------------------------------------------------- /man/check_tokens.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{check_tokens} 4 | \alias{check_tokens} 5 | \title{Check student tokens} 6 | \usage{ 7 | check_tokens( 8 | userfile, 9 | server = Sys.getenv("git.server", "https://api.github.com/") 10 | ) 11 | } 12 | \arguments{ 13 | \item{userfile}{A csv file with student information (i.e., username, token, and email)} 14 | 15 | \item{server}{The GitLab API server} 16 | } 17 | \description{ 18 | Check student tokens 19 | } 20 | \details{ 21 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 22 | } 23 | -------------------------------------------------------------------------------- /man/checkerr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{checkerr} 4 | \alias{checkerr} 5 | \title{Check error status} 6 | \usage{ 7 | checkerr(code) 8 | } 9 | \arguments{ 10 | \item{code}{Code returned by an API request} 11 | } 12 | \description{ 13 | Check error status 14 | } 15 | \details{ 16 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 17 | } 18 | -------------------------------------------------------------------------------- /man/collect_work.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{collect_work} 4 | \alias{collect_work} 5 | \title{Create merge requests for each student/team} 6 | \usage{ 7 | collect_work( 8 | token, 9 | assignment, 10 | userfile, 11 | type = "individual", 12 | server = "https://api.github.com/" 13 | ) 14 | } 15 | \arguments{ 16 | \item{token}{GitHub token} 17 | 18 | \item{assignment}{Name of the assignment (e.g., "class345/class345-assignment1")} 19 | 20 | \item{userfile}{A csv file with student information (i.e., username, token, and email)} 21 | 22 | \item{type}{Individual or Team work} 23 | 24 | \item{server}{The GitLab API server} 25 | } 26 | \description{ 27 | Create merge requests for each student/team 28 | } 29 | \details{ 30 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 31 | } 32 | -------------------------------------------------------------------------------- /man/create_group.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{create_group} 4 | \alias{create_group} 5 | \title{Create a group on GitLab using the API} 6 | \usage{ 7 | create_group( 8 | token, 9 | groupname = "", 10 | userfile = "", 11 | permission = 20, 12 | server = "https://api.github.com/" 13 | ) 14 | } 15 | \arguments{ 16 | \item{token}{GitLab token} 17 | 18 | \item{groupname}{Group to create on GitLab (defaults to user's namespace)} 19 | 20 | \item{userfile}{A csv file with student information (i.e., username, token, and email)} 21 | 22 | \item{permission}{Permission setting for the group (default is 20, i.e., reporter)} 23 | 24 | \item{server}{The GitLab API server} 25 | } 26 | \description{ 27 | Create a group on GitLab using the API 28 | } 29 | \details{ 30 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 31 | } 32 | -------------------------------------------------------------------------------- /man/create_repo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{create_repo} 4 | \alias{create_repo} 5 | \title{Create the main repo from a local directory} 6 | \usage{ 7 | create_repo( 8 | username = Sys.getenv("git.user"), 9 | token = Sys.getenv("GITHUB_PAT"), 10 | repo = basename(getwd()), 11 | base_dir = dirname(getwd()), 12 | groupname = "", 13 | pre = "", 14 | ssh = FALSE, 15 | server = "https://api.github.com/" 16 | ) 17 | } 18 | \arguments{ 19 | \item{username}{Username} 20 | 21 | \item{token}{Token (e.g., Sys.getenv("GITHUB_PAT") or Sys.getenv("GITHUB_PAT"))} 22 | 23 | \item{repo}{Name of the repo (assignment)} 24 | 25 | \item{base_dir}{Base directory for the repo. file.path(directory, assignment) should exist} 26 | 27 | \item{groupname}{Group to create on GitLab (defaults to user's namespace)} 28 | 29 | \item{pre}{Pre-amble for the repo (assignment) name} 30 | 31 | \item{ssh}{Use SSH for authentication} 32 | 33 | \item{server}{The GitLab API server} 34 | } 35 | \description{ 36 | Create the main repo from a local directory 37 | } 38 | \details{ 39 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 40 | } 41 | -------------------------------------------------------------------------------- /man/fetch_work.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{fetch_work} 4 | \alias{fetch_work} 5 | \title{Fetch all merge requests as local branches and link to a remote} 6 | \usage{ 7 | fetch_work(token, assignment, page = 1, server = "https://api.github.com/") 8 | } 9 | \arguments{ 10 | \item{token}{GitLab token} 11 | 12 | \item{assignment}{Name of the assignment (e.g., "class345/class345-assignment1")} 13 | 14 | \item{page}{Number of the results page to select} 15 | 16 | \item{server}{The GitLab API server} 17 | } 18 | \description{ 19 | Fetch all merge requests as local branches and link to a remote 20 | } 21 | \details{ 22 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 23 | } 24 | -------------------------------------------------------------------------------- /man/get_port.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gadget.R 3 | \name{get_port} 4 | \alias{get_port} 5 | \title{noRd 6 | export} 7 | \usage{ 8 | get_port() 9 | } 10 | \description{ 11 | noRd 12 | export 13 | } 14 | -------------------------------------------------------------------------------- /man/gitgadget.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/aaa.R, R/gadget.R 3 | \docType{package} 4 | \name{gitgadget} 5 | \alias{gitgadget-package} 6 | \alias{gitgadget} 7 | \title{gitgadget} 8 | \usage{ 9 | gitgadget(port = get_port(), host = "127.0.0.1", launch.browser = TRUE) 10 | } 11 | \arguments{ 12 | \item{port}{Port to use for the app} 13 | 14 | \item{host}{Host ip to use} 15 | 16 | \item{launch.browser}{Launch app in viewer (browsers) or only show the URL} 17 | } 18 | \description{ 19 | An 'Rstudio' addin for version control that allows users to clone repositories, create and delete branches, and sync forks on GitHub, GitLab, etc. Furthermore, the addin uses the GitLab API to allow instructors to create forks and merge requests for all students/teams with one click of a button. 20 | 21 | Run gitgadget in the Rstudio viewer if available 22 | } 23 | \details{ 24 | See \url{https://github.com/vnijs/gitgadget} for documentation 25 | } 26 | \seealso{ 27 | Useful links: 28 | \itemize{ 29 | \item \url{https://github.com/vnijs/gitgadget} 30 | \item Report bugs at \url{https://github.com/vnijs/gitgadget/issues} 31 | } 32 | 33 | } 34 | \author{ 35 | \strong{Maintainer}: Vincent Nijs \email{vnijs@ucsd.edu} 36 | 37 | Authors: 38 | \itemize{ 39 | \item Sanjiv Erat \email{serat@ucsd.edu} 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /man/gitgadget_callr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gadget.R 3 | \name{gitgadget_callr} 4 | \alias{gitgadget_callr} 5 | \title{Launch gitgadget in a separate process} 6 | \usage{ 7 | gitgadget_callr() 8 | } 9 | \description{ 10 | Launch gitgadget in a separate process 11 | } 12 | \details{ 13 | Using the \code{callr} package to launch gitgadget in a separate process so 14 | the console is not blocked. Rstudio viewer is used if available. See 15 | \url{https://github.com/vnijs/gitgadget} for documentation 16 | } 17 | -------------------------------------------------------------------------------- /man/gitgadget_url.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/gadget.R 3 | \name{gitgadget_url} 4 | \alias{gitgadget_url} 5 | \title{Start gitgadget and show url to open the application in an external browser} 6 | \usage{ 7 | gitgadget_url() 8 | } 9 | \description{ 10 | Start gitgadget and show url to open the application in an external browser 11 | } 12 | \details{ 13 | See \url{https://github.com/vnijs/gitgadget} for documentation 14 | } 15 | -------------------------------------------------------------------------------- /man/projID.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{projID} 4 | \alias{projID} 5 | \title{Find project ID} 6 | \usage{ 7 | projID(path_with_namespace, token, server, owned = TRUE, search = "") 8 | } 9 | \arguments{ 10 | \item{path_with_namespace}{Repo name together with the group or user namespace} 11 | 12 | \item{token}{GitLab token} 13 | 14 | \item{server}{The GitLab API server} 15 | 16 | \item{owned}{Restrict listing to only repos owned by the user? TRUE or FALSE} 17 | 18 | \item{search}{Search term to use to narrow down the set of projects} 19 | } 20 | \description{ 21 | Find project ID 22 | } 23 | \details{ 24 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 25 | } 26 | -------------------------------------------------------------------------------- /man/read_ufile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{read_ufile} 4 | \alias{read_ufile} 5 | \title{Reach user file} 6 | \usage{ 7 | read_ufile(userfile, cols = c("userid", "team", "token")) 8 | } 9 | \arguments{ 10 | \item{userfile}{File with student information} 11 | 12 | \item{cols}{Column names that must exists in the file} 13 | } 14 | \description{ 15 | Reach user file 16 | } 17 | \details{ 18 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 19 | } 20 | -------------------------------------------------------------------------------- /man/remove_project.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{remove_project} 4 | \alias{remove_project} 5 | \title{Remove a project} 6 | \usage{ 7 | remove_project(token, id, server) 8 | } 9 | \arguments{ 10 | \item{token}{GitLab token} 11 | 12 | \item{id}{Project ID} 13 | 14 | \item{server}{The GitLab API server} 15 | } 16 | \description{ 17 | Remove a project 18 | } 19 | \details{ 20 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 21 | } 22 | -------------------------------------------------------------------------------- /man/remove_users_repo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git.R 3 | \name{remove_users_repo} 4 | \alias{remove_users_repo} 5 | \title{Remove users from a repo} 6 | \usage{ 7 | remove_users_repo(token, repo, userfile, server = "https://api.github.com/") 8 | } 9 | \arguments{ 10 | \item{token}{GitLab token} 11 | 12 | \item{repo}{Repo the update} 13 | 14 | \item{userfile}{A csv file with student information (i.e., username, token, and email)} 15 | 16 | \item{server}{The GitLab API server} 17 | } 18 | \description{ 19 | Remove users from a repo 20 | } 21 | \details{ 22 | See \url{https://github.com/vnijs/gitgadget} for additional documentation 23 | } 24 | -------------------------------------------------------------------------------- /pkgbuild/build.R: -------------------------------------------------------------------------------- 1 | setwd(rstudioapi::getActiveProject()) 2 | curr <- getwd() 3 | pkg <- basename(curr) 4 | 5 | ## building radiant packages for mac and windows 6 | rv <- R.Version() 7 | rv <- paste(rv$major, substr(rv$minor, 1, 1), sep = ".") 8 | 9 | rvprompt <- readline(prompt = paste0("Running for R version: ", rv, ". Is that what you wanted y/n: ")) 10 | if (grepl("[nN]", rvprompt)) stop("Change R-version") 11 | 12 | dirsrc <- "../minicran/src/contrib" 13 | 14 | if (rv < "3.4") { 15 | dirmac <- fs::path("../minicran/bin/macosx/mavericks/contrib", rv) 16 | } else if (rv > "3.6") { 17 | dirmac <- fs::path("../minicran/bin/macosx/contrib", rv) 18 | } else { 19 | dirmac <- fs::path("../minicran/bin/macosx/el-capitan/contrib", rv) 20 | } 21 | 22 | dirwin <- file.path("../minicran/bin/windows/contrib", rv) 23 | 24 | if (!file.exists(dirsrc)) dir.create(dirsrc, recursive = TRUE) 25 | if (!file.exists(dirmac)) dir.create(dirmac, recursive = TRUE) 26 | if (!file.exists(dirwin)) dir.create(dirwin, recursive = TRUE) 27 | 28 | ## delete older version of radiant 29 | rem_old <- function(app) { 30 | unlink(paste0(dirsrc, "/", app, "*")) 31 | unlink(paste0(dirmac, "/", app, "*")) 32 | unlink(paste0(dirwin, "/", app, "*")) 33 | } 34 | 35 | sapply(pkg, rem_old) 36 | 37 | ## avoid 'loaded namespace' stuff when building for mac 38 | system(paste0(Sys.which("R"), " -e \"setwd('", getwd(), "'); app <- '", pkg, "'; source('build/build_mac.R')\"")) 39 | 40 | win <- readline(prompt = "Did you build on Windows? y/n: ") 41 | if (grepl("[yY]", win)) { 42 | 43 | ## move packages to radiant_miniCRAN. must package in Windows first 44 | path <- normalizePath("../") 45 | sapply(list.files(path, pattern = "*.tar.gz", full.names = TRUE), file.copy, dirsrc) 46 | unlink("../*.tar.gz") 47 | sapply(list.files(path, pattern = "*.tgz", full.names = TRUE), file.copy, dirmac) 48 | unlink("../*.tgz") 49 | sapply(list.files(path, pattern = "*.zip", full.names = TRUE), file.copy, dirwin) 50 | unlink("../*.zip") 51 | 52 | tools::write_PACKAGES(dirmac, type = "mac.binary") 53 | tools::write_PACKAGES(dirwin, type = "win.binary") 54 | tools::write_PACKAGES(dirsrc, type = "source") 55 | 56 | # commit to repo 57 | setwd("../minicran") 58 | system("git add --all .") 59 | mess <- paste0(pkg, " package update: ", format(Sys.Date(), format = "%m-%d-%Y")) 60 | system(paste0("git commit -m '", mess, "'")) 61 | system("git push") 62 | } 63 | 64 | setwd(curr) 65 | -------------------------------------------------------------------------------- /pkgbuild/build_mac.R: -------------------------------------------------------------------------------- 1 | ## build for mac 2 | app <- basename(getwd()) 3 | curr <- setwd("../") 4 | f <- devtools::build(app) 5 | system(paste0("R CMD INSTALL --build ", f)) 6 | setwd(curr) 7 | -------------------------------------------------------------------------------- /pkgbuild/build_win.R: -------------------------------------------------------------------------------- 1 | ## build for windows 2 | rv <- R.Version() 3 | rv <- paste0(rv$major,".", strsplit(rv$minor,".", fixed = TRUE)[[1]][1]) 4 | 5 | rvprompt <- readline(prompt = paste0("Running for R version: ", rv, ". Is that what you wanted y/n: ")) 6 | if (grepl("[nN]", rvprompt)) 7 | stop("Change R-version using Rstudio > Tools > Global Options > Rversion") 8 | 9 | ## build for windows 10 | setwd(rstudioapi::getActiveProject()) 11 | f <- devtools::build(binary = TRUE) 12 | devtools::install(upgrade = "never") 13 | -------------------------------------------------------------------------------- /vignettes/gitgadget.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Using Git Gadget" 3 | author: "Vincent Nijs" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Using Git Gadget} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | \usepackage[utf8]{inputenc} 10 | --- 11 | 12 | 13 | ```{r child='../README.md'} 14 | ``` 15 | --------------------------------------------------------------------------------