r-lib / gert

Simple git client for R
https://docs.ropensci.org/gert/
Other
146 stars 31 forks source link

`git_push()` silently succeeds with protected branches, expecting failure #236

Open krlmlr opened 3 weeks ago

krlmlr commented 3 weeks ago

Add a ruleset indicating that a status check must pass, or that PRs are required, for the main branch. Then:

gert::git_push()

info <- gert::git_info()
info$commit
#> [1] "0df1ef7c90ef5d4aa939e0d1aea56ab130d50d17"
gert::git_commit_id(info$upstream)
#> [1] "81680ff5d1608d1f64688e29c2852b41351b8fdb"

Created on 2024-08-18 with reprex v2.1.0

In interactive mode (can't reprex), I'm seeing:

d> gert::git_push()
Trying to authenticate 'git' using ssh-agent...
[status] refs/heads/main: push declined due to repository rule violations

I can work around by checking if <remote>/<branch> is the same as the local branch, but it would be better IMO if git_push() did that for me.

jeroen commented 3 weeks ago

Is this something that can be done with libgit2? https://libgit2.org/libgit2/#HEAD/search

krlmlr commented 3 weeks ago

Pushing a conflicting commit fails, but not with the technology GitHub uses (perhaps some hook on the remote repo?).

# Demo for failing push with conflicts
system("git init --bare remote-repo")
system("git clone remote-repo local-repo")
system("git -C local-repo commit --allow-empty -m 'Initial commit'")
system("git -C local-repo push")

system("git clone remote-repo local-repo2")

writeLines("This is a test file", "local-repo/test.txt")
system("git -C local-repo add test.txt")
system("git -C local-repo commit -m 'Add test file'")
system("git -C local-repo push")

writeLines("This is a conflicting test file", "local-repo2/test.txt")
system("git -C local-repo2 add test.txt")
system("git -C local-repo2 commit -m 'Add conflicting test file'")

# Fails
system("git -C local-repo2 push", intern = TRUE)
#> Warning in system("git -C local-repo2 push", intern = TRUE): running command
#> 'git -C local-repo2 push' had status 1
#> character(0)
#> attr(,"status")
#> [1] 1

# Fails too
withr::with_dir("local-repo2", gert::git_push())
#> Error in libgit2::git_remote_push: cannot push because a reference that you are trying to update on the remote contains commits that are not present locally.

Created on 2024-08-18 with reprex v2.1.0

krlmlr commented 3 weeks ago

I don't know too much about libgit2 to be helpful here, but could the above example be extended by such a hook in the remote repo?

krlmlr commented 3 weeks ago

Perhaps "Server-side hooks" in https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks ?

krlmlr commented 3 weeks ago

It's interesting that I can't even create an example with gert and hooks, these seem to be circumvented:

# Demo for failing push with conflicts
unlink("remote-repo", recursive = TRUE, force = TRUE)
unlink("local-repo", recursive = TRUE, force = TRUE)

run <- function(cmd, args) {
  invisible(processx::run(cmd, args, echo = TRUE))
}

run("git", c("init", "--bare", "remote-repo"))
#> Initialized empty Git repository in /private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/RtmpnNkFOo/reprex-c4aa7a5fbe6e-stout-scaup/remote-repo/
run("git", c("clone", "remote-repo", "local-repo"))
#> Cloning into 'local-repo'...
#> warning: You appear to have cloned an empty repository.
#> done.
run("git", c("-C", "local-repo", "commit", "--allow-empty", "-m", "Initial commit"))
#> [main (root-commit) 4cfc8d2] Initial commit
run("git", c("-C", "local-repo", "push", "-u", "origin", "HEAD"))
#> To /private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/RtmpnNkFOo/reprex-c4aa7a5fbe6e-stout-scaup/remote-repo
#>  * [new branch]      HEAD -> main
#> branch 'main' set up to track 'origin/main'.

writeLines(c("#!/bin/sh", "exit 1"), "remote-repo/hooks/update")
Sys.chmod("remote-repo/hooks/update", mode = as.octmode("755"))

writeLines("This is a test file", "local-repo/test.txt")
run("git", c("-C", "local-repo", "add", "test.txt"))
run("git", c("-C", "local-repo", "commit", "-m", "Add test file"))
#> [main d37ccf9] Add test file
#>  1 file changed, 1 insertion(+)
#>  create mode 100644 test.txt

# Fails as expected
run("git", c("-C", "local-repo", "push"))
#> remote: error: hook declined to update refs/heads/main        
#> To /private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/RtmpnNkFOo/reprex-c4aa7a5fbe6e-stout-scaup/remote-repo
#>  ! [remote rejected] main -> main (hook declined)
#> error: failed to push some refs to '/private/var/folders/dj/yhk9rkx97wn_ykqtnmk18xvc0000gn/T/RtmpnNkFOo/reprex-c4aa7a5fbe6e-stout-scaup/remote-repo'
#> Error in "processx::run(cmd, args, echo = TRUE)": ! System command 'git' failed

# Succeeds
withr::with_dir("local-repo", gert::git_push())

# WAT?
run("git", c("-C", "local-repo", "push"))
#> Everything up-to-date

Created on 2024-08-19 with reprex v2.1.0