├── .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 |
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 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/docs/authors.html:
--------------------------------------------------------------------------------
1 |
2 | Authors and Citation • gitgadget
6 |
7 |
8 |
9 |
47 |
48 |
63 |
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 |
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 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
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 |
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 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
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 |
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 |
76 |
77 |
78 |
81 |
82 |
83 |
84 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/docs/reference/checkerr.html:
--------------------------------------------------------------------------------
1 |
2 | Check error status — checkerr • gitgadget
6 |
7 |
8 |
9 |
47 |
48 |
53 |
54 |
55 |
Check error status
56 |
57 |
58 |
61 |
62 |
63 |
Arguments
64 |
code
65 | Code returned by an API request
66 |
67 |
71 |
72 |
73 |
76 |
77 |
78 |
79 |
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 |
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 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
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 |
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 |
85 |
86 |
87 |
90 |
91 |
92 |
93 |
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 |
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 |
94 |
95 |
96 |
99 |
100 |
101 |
102 |
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 |
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 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
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 |
50 |
51 |
57 |
58 |
59 |
noRd
60 | export
61 |
62 |
63 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/docs/reference/gitgadget.html:
--------------------------------------------------------------------------------
1 |
2 | gitgadget — gitgadget • gitgadget
6 |
7 |
8 |
9 |
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 |
75 |
76 |
77 |
80 |
81 |
82 |
83 |
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 |
47 |
48 |
53 |
54 |
55 |
Launch gitgadget in a separate process
56 |
57 |
58 |
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 |
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 |
47 |
48 |
53 |
54 |
55 |
Start gitgadget and show url to open the application in an external browser
56 |
57 |
58 |
61 |
62 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/docs/reference/index.html:
--------------------------------------------------------------------------------
1 |
2 | Function reference • gitgadget
6 |
7 |
8 |
9 |
47 |
48 |
51 |
52 |
53 | All functions
54 |
55 |
56 |
57 | add_users_repo()
58 |
59 | Add users to a repo
60 |
61 | assign_work()
62 |
63 | Assign work to each student/team by creating a fork of the main repo
64 |
65 | check_tokens()
66 |
67 | Check student tokens
68 |
69 | checkerr()
70 |
71 | Check error status
72 |
73 | collect_work()
74 |
75 | Create merge requests for each student/team
76 |
77 | create_group()
78 |
79 | Create a group on gitlab using the API
80 |
81 | create_repo()
82 |
83 | Create the main repo from a local directory
84 |
85 | fetch_work()
86 |
87 | Fetch all merge requests as local branches and link to a remote
88 |
89 | get_port()
90 |
91 | noRd
92 | export
93 |
94 | gitgadget()
95 |
96 | gitgadget
97 |
98 | gitgadget_callr()
99 |
100 | Launch gitgadget in a separate process
101 |
102 | gitgadget_url()
103 |
104 | Start gitgadget and show url to open the application in an external browser
105 |
106 | projID()
107 |
108 | Find project ID
109 |
110 | read_ufile()
111 |
112 | Reach user file
113 |
114 | remove_project()
115 |
116 | Remove a project
117 |
118 | remove_users_repo()
119 |
120 | Remove users from a repo
121 |
122 |
123 |
126 |
127 |
128 |
129 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/docs/reference/projID.html:
--------------------------------------------------------------------------------
1 |
2 | Find project ID — projID • gitgadget
6 |
7 |
8 |
9 |
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 |
79 |
80 |
81 |
84 |
85 |
86 |
87 |
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 |
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 |
73 |
74 |
75 |
78 |
79 |
80 |
81 |
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 |
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 |
75 |
76 |
77 |
80 |
81 |
82 |
83 |
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 |
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 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
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("", input$repo_directory, " is not a git repo "))
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("No branches available to merge ")
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("No branches available ")
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("No branches available to delete ")
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 |
--------------------------------------------------------------------------------