Skip to content

Commit 4c538c7

Browse files
committed
feat: add git_config_get() and git_config_global_get()
Co-Authored-By: Claude Opus 4.6
1 parent 1ae6833 commit 4c538c7

6 files changed

Lines changed: 149 additions & 48 deletions

File tree

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ export(git_commit_id)
2828
export(git_commit_info)
2929
export(git_commit_stats)
3030
export(git_config)
31+
export(git_config_get)
3132
export(git_config_global)
33+
export(git_config_global_get)
3234
export(git_config_global_set)
35+
export(git_config_local_get)
3336
export(git_config_set)
3437
export(git_conflicts)
3538
export(git_diff)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# gert (development version)
22

3+
- Add `git_config_get()`, `git_config_local_get()`, and `git_config_global_get()` to retrieve a single named config option, returning `NULL` if unset (#267).
34
- `git_remote_set_pushurl()` gains an `add` argument to append push URLs instead of replacing them. (@robitalec, #128)
45
- Fix `git_info()` for the case when no upstream is configured (@mpage, #263)
56
- `git_branch_create()`: `force` now also applies to the checkout step, allowing branch creation even when local changes would be overwritten (@MichaelChirico, #177).

R/config.R

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
#' Get or set Git options, as `git config` does on the command line. **Global**
55
#' settings affect all of a user's Git operations (`git config --global`),
66
#' whereas **local** settings are scoped to a specific repository (`git config
7-
#' --local`). When both exist, local options always win. Four functions address
8-
#' the four possible combinations of getting vs setting and global vs. local.
7+
#' --local`). When both exist, local options always win.
98
#'
109
#' ```{r echo = FALSE, results = "asis"}
1110
#' dat <- data.frame(
12-
#' local = c("`git_config()`", "`git_config_set()`"),
13-
#' global = c("`git_config_global()`", "`git_config_global_set()`"),
14-
#' row.names = c("get", "set")
11+
#' local = c("`git_config()`", "`git_config_get()`", "`git_config_local_get()`", "`git_config_set()`"),
12+
#' global = c("`git_config_global()`", "`git_config_get()`", "`git_config_global_get()`", "`git_config_global_set()`"),
13+
#' row.names = c("get all", "get one (local+global)", "get one (local or global only)", "set")
1514
#' )
1615
#' knitr::kable(dat, col.names = paste0("**", colnames(dat), "**"))
1716
#' ```
@@ -21,6 +20,11 @@
2120
#' option is determined from global or local config.
2221
#' * `git_config_global()`: a `data.frame`, as for `git_config()`, except only
2322
#' for global Git options.
23+
#' * `git_config_get()`: the value of the named option considering both local and
24+
#' global config (local wins), or `NULL` if unset.
25+
#' * `git_config_local_get()`: as for `git_config_get()`, but restricted to
26+
#' local (repository-level) config only.
27+
#' * `git_config_global_get()`: as for `git_config_get()`, but for global config only.
2428
#' * `git_config_set()`, `git_config_global_set()`: The previous value(s) of
2529
#' `name` in local or global config, respectively. If this option was
2630
#' previously unset, returns `NULL`. Returns invisibly.
@@ -36,15 +40,16 @@
3640
#'
3741
#' previous <- git_config_set("aaa.bbb", "ccc", repo = r)
3842
#' previous
39-
#' cfg <- git_config(repo = r)
40-
#' subset(cfg, level == "local")
41-
#' cfg$value[cfg$name == "aaa.bbb"]
43+
#' git_config_local_get("aaa.bbb", repo = r)
4244
#'
4345
#' previous <- git_config_set("aaa.bbb", NULL, repo = r)
4446
#' previous
45-
#' cfg <- git_config(repo = r)
46-
#' subset(cfg, level == "local")
47-
#' cfg$value[cfg$name == "aaa.bbb"]
47+
#' git_config_local_get("aaa.bbb", repo = r)
48+
#'
49+
#' # Get a single named option (returns NULL if unset)
50+
#' git_config_get("aaa.bbb", repo = r)
51+
#' git_config_set("aaa.bbb", "ccc", repo = r)
52+
#' git_config_get("aaa.bbb", repo = r)
4853
#'
4954
#' unlink(r, recursive = TRUE)
5055
#'
@@ -53,6 +58,10 @@
5358
#' git_config_global_set("user.name", "Your Name")
5459
#' git_config_global_set("user.email", "your@email.com")
5560
#' git_config_global()
61+
#'
62+
#' # Get a single global option (returns NULL if unset)
63+
#' git_config_global_get("user.name")
64+
#' git_config_global_get("gert.nonexistent")
5665
#' }
5766
#' @export
5867
#' @family git
@@ -70,10 +79,41 @@ git_config_global <- function() {
7079
.Call(R_git_config_list, NULL)
7180
}
7281

82+
#' @export
83+
#' @rdname git_config
84+
#' @param name Name of the option to get or set
85+
git_config_get <- function(name, repo = '.') {
86+
cfg <- git_config(repo = repo)
87+
if (!name %in% cfg$name) {
88+
return(NULL)
89+
}
90+
cfg$value[cfg$name == name]
91+
}
92+
93+
#' @export
94+
#' @rdname git_config
95+
git_config_local_get <- function(name, repo = '.') {
96+
cfg <- git_config(repo = repo)
97+
cfg <- cfg[cfg$level == "local", ]
98+
if (!name %in% cfg$name) {
99+
return(NULL)
100+
}
101+
cfg$value[cfg$name == name]
102+
}
103+
104+
#' @export
105+
#' @rdname git_config
106+
git_config_global_get <- function(name) {
107+
cfg <- git_config_global()
108+
if (!name %in% cfg$name) {
109+
return(NULL)
110+
}
111+
cfg$value[cfg$name == name]
112+
}
113+
73114
#' @export
74115
#' @rdname git_config
75116
#' @useDynLib gert R_git_config_set
76-
#' @param name Name of the option to set
77117
#' @param value Value to set. Must be a string, logical, number or `NULL` (to
78118
#' unset).
79119
#' @param add if `TRUE`, append a new entry for `name` instead of replacing
@@ -85,8 +125,7 @@ git_config_set <- function(name, value, add = FALSE, repo = '.') {
85125
}
86126
repo <- git_open(repo)
87127
name <- as.character(name)
88-
orig_cfg <- git_config(repo = repo)
89-
out <- orig_cfg$value[orig_cfg$name == name & orig_cfg$level == "local"]
128+
out <- git_config_local_get(name, repo = repo)
90129
.Call(R_git_config_set, repo, name, value, add)
91130
if (length(out) > 0) {
92131
invisible(out)
@@ -98,8 +137,7 @@ git_config_set <- function(name, value, add = FALSE, repo = '.') {
98137
#' @export
99138
#' @rdname git_config
100139
git_config_global_set <- function(name, value, add = FALSE) {
101-
orig_cfg <- git_config_global()
102-
out <- orig_cfg$value[orig_cfg$name == name]
140+
out <- git_config_global_get(name)
103141
.Call(R_git_config_set, NULL, name, value, add)
104142
if (length(out) > 0) {
105143
invisible(out)
@@ -141,9 +179,8 @@ configure_global_user <- function() {
141179
}
142180

143181
global_user_is_configured <- function() {
144-
cfg <- git_config_global()
145-
user_name_exists <- any(cfg$name == "user.name")
146-
user_email_exists <- any(cfg$name == "user.email")
182+
user_name_exists <- !is.null(git_config_global_get("user.name"))
183+
user_email_exists <- !is.null(git_config_global_get("user.email"))
147184
user_name_exists && user_email_exists
148185
}
149186

@@ -164,11 +201,7 @@ global_user_is_configured <- function() {
164201
#' @examples
165202
#' user_is_configured()
166203
user_is_configured <- function(repo = ".") {
167-
cfg <- tryCatch(
168-
git_config(repo),
169-
error = function(e) git_config_global()
170-
)
171-
user_name_exists <- any(cfg$name == "user.name")
172-
user_email_exists <- any(cfg$name == "user.email")
204+
user_name_exists <- !is.null(git_config_get("user.name", repo = repo))
205+
user_email_exists <- !is.null(git_config_get("user.email", repo = repo))
173206
user_name_exists && user_email_exists
174207
}

man/git_config.Rd

Lines changed: 30 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-config.R

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1+
test_that("git_config_get returns value or NULL", {
2+
repo <- git_init(tempfile("gert-tests-config"))
3+
on.exit(unlink(repo, recursive = TRUE))
4+
5+
expect_null(git_config_get("aaa.bbb", repo = repo))
6+
git_config_set("aaa.bbb", "ccc", repo = repo)
7+
expect_equal(git_config_get("aaa.bbb", repo = repo), "ccc")
8+
})
9+
10+
test_that("git_config_global_get returns value or NULL", {
11+
expect_null(git_config_global_get("gert.nonexistent.option.xyzzy"))
12+
# Note: avoid setting/unsetting real global config in tests
13+
})
14+
15+
test_that("git_config_local_get returns local value or NULL", {
16+
repo <- git_init(tempfile("gert-tests-config"))
17+
on.exit(unlink(repo, recursive = TRUE))
18+
19+
expect_null(git_config_local_get("aaa.bbb", repo = repo))
20+
git_config_set("aaa.bbb", "ccc", repo = repo)
21+
expect_equal(git_config_local_get("aaa.bbb", repo = repo), "ccc")
22+
# global-only option should not be visible at local level
23+
expect_null(git_config_local_get(
24+
"gert.nonexistent.option.xyzzy",
25+
repo = repo
26+
))
27+
})
28+
129
test_that("local, custom config roundtrip", {
230
repo <- git_init(tempfile("gert-tests-config"))
331
on.exit(unlink(repo, recursive = TRUE))
432

533
orig <- git_config_set("aaa.bbb", "ccc", repo = repo)
634
expect_null(orig)
7-
cfg <- git_config(repo)
8-
expect_equal(cfg$value[cfg$name == "aaa.bbb"], "ccc")
35+
expect_equal(git_config_get("aaa.bbb", repo = repo), "ccc")
936

1037
orig <- git_config_set("aaa.bbb", NULL, repo = repo)
1138
expect_equal(orig, "ccc")
12-
cfg <- git_config(repo)
13-
expect_equal(cfg$value[cfg$name == "aaa.bbb"], character())
39+
expect_null(git_config_get("aaa.bbb", repo = repo))
1440
})

tests/testthat/test-remotes.R

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@ test_that("git_remote_set_pushurl with add = TRUE appends push URLs", {
55

66
git_remote_add("https://example.com/fetch", name = "origin", repo = repo)
77

8-
git_remote_set_pushurl("https://example.com/push1", remote = "origin", repo = repo)
9-
git_remote_set_pushurl("https://example.com/push2", remote = "origin", add = TRUE, repo = repo)
8+
git_remote_set_pushurl(
9+
"https://example.com/push1",
10+
remote = "origin",
11+
repo = repo
12+
)
13+
git_remote_set_pushurl(
14+
"https://example.com/push2",
15+
remote = "origin",
16+
add = TRUE,
17+
repo = repo
18+
)
1019

11-
cfg <- git_config(repo = repo)
12-
pushurls <- cfg$value[cfg$name == "remote.origin.pushurl"]
13-
expect_setequal(pushurls, c("https://example.com/push1", "https://example.com/push2"))
20+
pushurls <- git_config_get("remote.origin.pushurl", repo = repo)
21+
expect_setequal(
22+
pushurls,
23+
c("https://example.com/push1", "https://example.com/push2")
24+
)
1425
})
1526

1627
test_that("git_remote_set_pushurl without add replaces push URL", {
@@ -20,11 +31,18 @@ test_that("git_remote_set_pushurl without add replaces push URL", {
2031

2132
git_remote_add("https://example.com/fetch", name = "origin", repo = repo)
2233

23-
git_remote_set_pushurl("https://example.com/push1", remote = "origin", repo = repo)
24-
git_remote_set_pushurl("https://example.com/push2", remote = "origin", repo = repo)
34+
git_remote_set_pushurl(
35+
"https://example.com/push1",
36+
remote = "origin",
37+
repo = repo
38+
)
39+
git_remote_set_pushurl(
40+
"https://example.com/push2",
41+
remote = "origin",
42+
repo = repo
43+
)
2544

26-
cfg <- git_config(repo = repo)
27-
pushurls <- cfg$value[cfg$name == "remote.origin.pushurl"]
45+
pushurls <- git_config_get("remote.origin.pushurl", repo = repo)
2846
expect_equal(pushurls, "https://example.com/push2")
2947
})
3048

0 commit comments

Comments
 (0)