1## git2r, R bindings to the libgit2 library. 2## Copyright (C) 2013-2019 The git2r contributors 3## 4## This program is free software; you can redistribute it and/or modify 5## it under the terms of the GNU General Public License, version 2, 6## as published by the Free Software Foundation. 7## 8## git2r is distributed in the hope that it will be useful, 9## but WITHOUT ANY WARRANTY; without even the implied warranty of 10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11## GNU General Public License for more details. 12## 13## You should have received a copy of the GNU General Public License along 14## with this program; if not, write to the Free Software Foundation, Inc., 15## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16 17##' Coerce Git repository to a \code{data.frame} 18##' 19##' The commits in the repository are coerced to a \code{data.frame} 20##' 21##' 22##' The \code{data.frame} have the following columns: 23##' \describe{ 24##' 25##' \item{sha}{ 26##' The 40 character hexadecimal string of the SHA-1 27##' } 28##' 29##' \item{summary}{ 30##' the short "summary" of the git commit message. 31##' } 32##' 33##' \item{message}{ 34##' the full message of a commit 35##' } 36##' 37##' \item{author}{ 38##' full name of the author 39##' } 40##' 41##' \item{email}{ 42##' email of the author 43##' } 44##' 45##' \item{when}{ 46##' time when the commit happened 47##' } 48##' 49##' } 50##' @param x The repository \code{object} 51##' @param ... Additional arguments. Not used. 52##' @return \code{data.frame} 53##' @export 54##' @examples 55##' \dontrun{ 56##' ## Initialize a temporary repository 57##' path <- tempfile(pattern="git2r-") 58##' dir.create(path) 59##' repo <- init(path) 60##' 61##' ## Create a user 62##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 63##' 64##' ## Create three files and commit 65##' writeLines("First file", file.path(path, "example-1.txt")) 66##' writeLines("Second file", file.path(path, "example-2.txt")) 67##' writeLines("Third file", file.path(path, "example-3.txt")) 68##' add(repo, "example-1.txt") 69##' commit(repo, "Commit first file") 70##' add(repo, "example-2.txt") 71##' commit(repo, "Commit second file") 72##' add(repo, "example-3.txt") 73##' commit(repo, "Commit third file") 74##' 75##' ## Coerce commits to a data.frame 76##' df <- as.data.frame(repo) 77##' df 78##' } 79as.data.frame.git_repository <- function(x, ...) { 80 do.call("rbind", lapply(commits(x), as.data.frame)) 81} 82 83##' Open a repository 84##' 85##' @param path A path to an existing local git repository. 86##' @param discover Discover repository from path. Default is TRUE. 87##' @return A \code{git_repository} object with entries: 88##' \describe{ 89##' \item{path}{ 90##' Path to a git repository 91##' } 92##' } 93##' @export 94##' @examples 95##' \dontrun{ 96##' ## Initialize a temporary repository 97##' path <- tempfile(pattern="git2r-") 98##' dir.create(path) 99##' repo <- init(path) 100##' 101##' # Configure a user 102##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 103##' 104##' ## Create a file, add and commit 105##' writeLines("Hello world!", file.path(path, "test-1.txt")) 106##' add(repo, 'test-1.txt') 107##' commit_1 <- commit(repo, "Commit message") 108##' 109##' ## Make one more commit 110##' writeLines(c("Hello world!", "HELLO WORLD!"), 111##' file.path(path, "test-1.txt")) 112##' add(repo, 'test-1.txt') 113##' commit(repo, "Next commit message") 114##' 115##' ## Create one more file 116##' writeLines("Hello world!", 117##' file.path(path, "test-2.txt")) 118##' 119##' ## Brief summary of repository 120##' repo 121##' 122##' ## Summary of repository 123##' summary(repo) 124##' 125##' ## Workdir of repository 126##' workdir(repo) 127##' 128##' ## Check if repository is bare 129##' is_bare(repo) 130##' 131##' ## Check if repository is empty 132##' is_empty(repo) 133##' 134##' ## Check if repository is a shallow clone 135##' is_shallow(repo) 136##' 137##' ## List all references in repository 138##' references(repo) 139##' 140##' ## List all branches in repository 141##' branches(repo) 142##' 143##' ## Get HEAD of repository 144##' repository_head(repo) 145##' 146##' ## Check if HEAD is head 147##' is_head(repository_head(repo)) 148##' 149##' ## Check if HEAD is local 150##' is_local(repository_head(repo)) 151##' 152##' ## List all tags in repository 153##' tags(repo) 154##' } 155repository <- function(path = ".", discover = TRUE) { 156 if (isTRUE(discover)) { 157 path <- discover_repository(path) 158 if (is.null(path)) 159 stop("The 'path' is not in a git repository") 160 } else { 161 path <- normalizePath(path, winslash = "/", mustWork = TRUE) 162 if (!file.info(path)$isdir) 163 stop("'path' is not a directory") 164 } 165 166 if (!isTRUE(.Call(git2r_repository_can_open, path))) 167 stop("Unable to open repository at 'path'") 168 169 structure(list(path = path), class = "git_repository") 170} 171 172##' Init a repository 173##' 174##' @param path A path to where to init a git repository 175##' @param bare If TRUE, a Git repository without a working directory 176##' is created at the pointed path. If FALSE, provided path will 177##' be considered as the working directory into which the .git 178##' directory will be created. 179##' @return A \code{git_repository} object 180##' @export 181##' @seealso \link{repository} 182##' @examples 183##' \dontrun{ 184##' ## Initialize a repository 185##' path <- tempfile(pattern="git2r-") 186##' dir.create(path) 187##' repo <- init(path) 188##' is_bare(repo) 189##' 190##' ## Initialize a bare repository 191##' path_bare <- tempfile(pattern="git2r-") 192##' dir.create(path_bare) 193##' repo_bare <- init(path_bare, bare = TRUE) 194##' is_bare(repo_bare) 195##' } 196init <- function(path = ".", bare = FALSE) { 197 path <- normalizePath(path, winslash = "/", mustWork = TRUE) 198 if (!file.info(path)$isdir) 199 stop("'path' is not a directory") 200 .Call(git2r_repository_init, path, bare) 201 repository(path) 202} 203 204##' Clone a remote repository 205##' 206##' @param url The remote repository to clone 207##' @param local_path Local directory to clone to. 208##' @param bare Create a bare repository. Default is FALSE. 209##' @param branch The name of the branch to checkout. Default is NULL 210##' which means to use the remote's default branch. 211##' @param checkout Checkout HEAD after the clone is complete. Default 212##' is TRUE. 213##' @param credentials The credentials for remote repository 214##' access. Default is NULL. To use and query an ssh-agent for the 215##' ssh key credentials, let this parameter be NULL (the default). 216##' @param progress Show progress. Default is TRUE. 217##' @return A \code{git_repository} object. 218##' @seealso \link{repository}, \code{\link{cred_user_pass}}, 219##' \code{\link{cred_ssh_key}} 220##' @export 221##' @examples 222##' \dontrun{ 223##' ## Initialize repository 224##' path_repo_1 <- tempfile(pattern="git2r-") 225##' path_repo_2 <- tempfile(pattern="git2r-") 226##' dir.create(path_repo_1) 227##' dir.create(path_repo_2) 228##' repo_1 <- init(path_repo_1) 229##' 230##' ## Config user and commit a file 231##' config(repo_1, user.name = "Alice", user.email = "alice@@example.org") 232##' 233##' ## Write to a file and commit 234##' writeLines( 235##' "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", 236##' file.path(path_repo_1, "example.txt")) 237##' add(repo_1, "example.txt") 238##' commit(repo_1, "First commit message") 239##' 240##' ## Change file and commit 241##' lines <- c( 242##' "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", 243##' "eiusmod tempor incididunt ut labore et dolore magna aliqua.") 244##' writeLines(lines, file.path(path_repo_1, "example.txt")) 245##' add(repo_1, "example.txt") 246##' commit(repo_1, "Second commit message") 247##' 248##' ## Change file again and commit. 249##' lines <- c( 250##' "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", 251##' "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad", 252##' "minim veniam, quis nostrud exercitation ullamco laboris nisi ut") 253##' writeLines(lines, file.path(path_repo_1, "example.txt")) 254##' add(repo_1, "example.txt") 255##' commit(repo_1, "Third commit message") 256##' 257##' ## Clone to second repository 258##' repo_2 <- clone(path_repo_1, path_repo_2) 259##' 260##' ## List commits in repositories 261##' commits(repo_1) 262##' commits(repo_2) 263##' } 264clone <- function(url = NULL, 265 local_path = NULL, 266 bare = FALSE, 267 branch = NULL, 268 checkout = TRUE, 269 credentials = NULL, 270 progress = TRUE) { 271 .Call(git2r_clone, url, local_path, bare, 272 branch, checkout, credentials, progress) 273 repository(local_path) 274} 275 276##' Get HEAD for a repository 277##' 278##' @param x The repository \code{x} to check head 279##' @param ... Additional arguments. Unused. 280##' @return NULL if unborn branch or not found. A git_branch if not a 281##' detached head. A git_commit if detached head 282##' @importFrom utils head 283##' @export 284##' @examples 285##' \dontrun{ 286##' ## Create and initialize a repository in a temporary directory 287##' path <- tempfile(pattern="git2r-") 288##' dir.create(path) 289##' repo <- init(path) 290##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 291##' 292##' ## Create a file, add and commit 293##' writeLines("Hello world!", file.path(path, "example.txt")) 294##' add(repo, "example.txt") 295##' commit(repo, "Commit message") 296##' 297##' ## Get HEAD of repository 298##' repository_head(repo) 299##' } 300head.git_repository <- function(x, ...) { 301 .Deprecated("repository_head") 302 .Call(git2r_repository_head, x) 303} 304 305##' @export 306utils::head 307 308##' Get HEAD for a repository 309##' 310##' @template repo-param 311##' @return NULL if unborn branch or not found. A git_branch if not a 312##' detached head. A git_commit if detached head 313##' @export 314##' @examples 315##' \dontrun{ 316##' ## Create and initialize a repository in a temporary directory 317##' path <- tempfile(pattern="git2r-") 318##' dir.create(path) 319##' repo <- init(path) 320##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 321##' 322##' ## Create a file, add and commit 323##' writeLines("Hello world!", file.path(path, "example.txt")) 324##' add(repo, "example.txt") 325##' commit(repo, "Commit message") 326##' 327##' ## Get HEAD of repository 328##' repository_head(repo) 329##' } 330repository_head <- function(repo = ".") { 331 .Call(git2r_repository_head, lookup_repository(repo)) 332} 333 334##' Check if repository is bare 335##' 336##' @template repo-param 337##' @return \code{TRUE} if bare repository, else \code{FALSE} 338##' @seealso \link{init} 339##' @export 340##' @examples 341##' \dontrun{ 342##' ## Initialize a repository 343##' path <- tempfile(pattern="git2r-") 344##' dir.create(path) 345##' repo <- init(path) 346##' is_bare(repo) 347##' 348##' ## Initialize a bare repository 349##' path_bare <- tempfile(pattern="git2r-") 350##' dir.create(path_bare) 351##' repo_bare <- init(path_bare, bare = TRUE) 352##' is_bare(repo_bare) 353##' } 354is_bare <- function(repo = ".") { 355 .Call(git2r_repository_is_bare, lookup_repository(repo)) 356} 357 358##' Check if HEAD of repository is detached 359##' 360##' @template repo-param 361##' @return \code{TRUE} if repository HEAD is detached, else 362##' \code{FALSE}. 363##' @export 364##' @examples 365##' \dontrun{ 366##' ## Create and initialize a repository in a temporary directory 367##' path <- tempfile(pattern="git2r-") 368##' dir.create(path) 369##' repo <- init(path) 370##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 371##' 372##' ## Create a file, add and commit 373##' lines <- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do" 374##' writeLines(lines, file.path(path, "example.txt")) 375##' add(repo, "example.txt") 376##' commit_1 <- commit(repo, "Commit message 1") 377##' 378##' ## Change file, add and commit 379##' lines <- c( 380##' "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", 381##' "eiusmod tempor incididunt ut labore et dolore magna aliqua.") 382##' writeLines(lines, file.path(path, "example.txt")) 383##' add(repo, "example.txt") 384##' commit(repo, "Commit message 2") 385##' 386##' ## HEAD of repository is not detached 387##' is_detached(repo) 388##' 389##' ## Checkout first commit 390##' checkout(commit_1) 391##' 392##' ## HEAD of repository is detached 393##' is_detached(repo) 394##' } 395is_detached <- function(repo = ".") { 396 .Call(git2r_repository_head_detached, lookup_repository(repo)) 397} 398 399##' Check if repository is empty 400##' 401##' @template repo-param 402##' @return \code{TRUE} if repository is empty else \code{FALSE}. 403##' @export 404##' @examples 405##' \dontrun{ 406##' ## Initialize a temporary repository 407##' path <- tempfile(pattern="git2r-") 408##' dir.create(path) 409##' repo <- init(path) 410##' 411##' ## Create a user 412##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 413##' 414##' ## Check if it's an empty repository 415##' is_empty(repo) 416##' 417##' ## Commit a file 418##' writeLines("Hello world!", file.path(path, "example.txt")) 419##' add(repo, "example.txt") 420##' commit(repo, "First commit message") 421##' 422##' ## Check if it's an empty repository 423##' is_empty(repo) 424##' } 425is_empty <- function(repo = ".") { 426 .Call(git2r_repository_is_empty, lookup_repository(repo)) 427} 428 429##' Determine if a directory is in a git repository 430##' 431##' The lookup start from path and walk across parent directories if 432##' nothing has been found. 433##' @param path The path to the directory. 434##' @return TRUE if directory is in a git repository else FALSE 435##' @export 436##' @examples 437##' \dontrun{ 438##' ## Initialize a temporary repository 439##' path <- tempfile(pattern="git2r-") 440##' dir.create(path) 441##' repo <- init(path) 442##' 443##' ## Create a user 444##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 445##' 446##' ## Check if path is in a git repository 447##' in_repository(path) 448##' 449##' ## Check if working directory is in a git repository 450##' setwd(path) 451##' in_repository() 452##' } 453in_repository <- function(path = ".") { 454 !is.null(discover_repository(path)) 455} 456 457##' Determine if the repository is a shallow clone 458##' 459##' @template repo-param 460##' @return \code{TRUE} if shallow clone, else \code{FALSE} 461##' @export 462##' @examples 463##' \dontrun{ 464##' ## Initialize repository 465##' path_repo_1 <- tempfile(pattern="git2r-") 466##' path_repo_2 <- tempfile(pattern="git2r-") 467##' dir.create(path_repo_1) 468##' dir.create(path_repo_2) 469##' repo_1 <- init(path_repo_1) 470##' 471##' ## Config user and commit a file 472##' config(repo_1, user.name = "Alice", user.email = "alice@@example.org") 473##' 474##' ## Write to a file and commit 475##' lines <- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do" 476##' writeLines(lines, file.path(path_repo_1, "example.txt")) 477##' add(repo_1, "example.txt") 478##' commit(repo_1, "First commit message") 479##' 480##' ## Change file and commit 481##' lines <- c( 482##' "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", 483##' "eiusmod tempor incididunt ut labore et dolore magna aliqua.") 484##' writeLines(lines, file.path(path_repo_1, "example.txt")) 485##' add(repo_1, "example.txt") 486##' commit(repo_1, "Second commit message") 487##' 488##' ## Change file again and commit. 489##' lines <- c( 490##' "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do", 491##' "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad", 492##' "minim veniam, quis nostrud exercitation ullamco laboris nisi ut") 493##' writeLines(lines, file.path(path_repo_1, "example.txt")) 494##' add(repo_1, "example.txt") 495##' commit(repo_1, "Third commit message") 496##' 497##' ## Clone to second repository 498##' repo_2 <- clone(path_repo_1, path_repo_2) 499##' 500##' ## Check if it's a shallow clone 501##' is_shallow(repo_2) 502##' } 503is_shallow <- function(repo = ".") { 504 .Call(git2r_repository_is_shallow, lookup_repository(repo)) 505} 506 507##' Lookup 508##' 509##' Lookup one object in a repository. 510##' @template repo-param 511##' @param sha The identity of the object to lookup. Must be 4 to 40 512##' characters long. 513##' @return a \code{git_blob} or \code{git_commit} or \code{git_tag} 514##' or \code{git_tree} object 515##' @export 516##' @examples 517##' \dontrun{ 518##' ## Initialize a temporary repository 519##' path <- tempfile(pattern="git2r-") 520##' dir.create(path) 521##' repo <- init(path) 522##' 523##' ## Create a user and commit a file 524##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 525##' lines <- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do" 526##' writeLines(lines, file.path(path, "example.txt")) 527##' add(repo, "example.txt") 528##' commit_1 <- commit(repo, "First commit message") 529##' 530##' ## Create tag 531##' tag(repo, "Tagname", "Tag message") 532##' 533##' ## First, get SHAs to lookup in the repository 534##' sha_commit <- sha(commit_1) 535##' sha_tree <- sha(tree(commit_1)) 536##' sha_blob <- sha(tree(commit_1)["example.txt"]) 537##' sha_tag <- sha(tags(repo)[[1]]) 538##' 539##' ## SHAs 540##' sha_commit 541##' sha_tree 542##' sha_blob 543##' sha_tag 544##' 545##' ## Lookup objects 546##' lookup(repo, sha_commit) 547##' lookup(repo, sha_tree) 548##' lookup(repo, sha_blob) 549##' lookup(repo, sha_tag) 550##' 551##' ## Lookup objects, using only the first seven characters 552##' lookup(repo, substr(sha_commit, 1, 7)) 553##' lookup(repo, substr(sha_tree, 1, 7)) 554##' lookup(repo, substr(sha_blob, 1, 7)) 555##' lookup(repo, substr(sha_tag, 1, 7)) 556##' } 557lookup <- function(repo = ".", sha = NULL) { 558 .Call(git2r_object_lookup, lookup_repository(repo), sha) 559} 560 561##' Lookup the commit related to a git object 562##' 563##' Lookup the commit related to a git_reference, git_tag or 564##' git_branch object. 565##' @param object a git object to get the related commit from. 566##' @return A git commit object. 567##' @export 568##' @examples \dontrun{ 569##' ## Create a directory in tempdir 570##' path <- tempfile(pattern="git2r-") 571##' dir.create(path) 572##' 573##' ## Initialize a repository 574##' repo <- init(path) 575##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 576##' 577##' ## Create a file, add and commit 578##' lines <- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do" 579##' writeLines(lines, con = file.path(path, "test.txt")) 580##' add(repo, "test.txt") 581##' commit(repo, "Commit message 1") 582##' 583##' ## Get the commit pointed to by the 'master' branch 584##' lookup_commit(repository_head(repo)) 585##' 586##' ## Create a tag 587##' a_tag <- tag(repo, "Tagname", "Tag message") 588##' 589##' ## Get the commit pointed to by 'a_tag' 590##' lookup_commit(a_tag) 591##' } 592lookup_commit <- function(object) { 593 UseMethod("lookup_commit", object) 594} 595 596##' @rdname lookup_commit 597##' @export 598lookup_commit.git_branch <- function(object) { 599 lookup(object$repo, branch_target(object)) 600} 601 602##' @rdname lookup_commit 603##' @export 604lookup_commit.git_commit <- function(object) { 605 object 606} 607 608##' @rdname lookup_commit 609##' @export 610lookup_commit.git_tag <- function(object) { 611 lookup(object$repo, object$target) 612} 613 614##' @rdname lookup_commit 615##' @export 616lookup_commit.git_reference <- function(object) { 617 lookup_commit(lookup(object$repo, object$sha)) 618} 619 620##' @export 621print.git_repository <- function(x, ...) { 622 if (any(is_empty(x), is.null(repository_head(x)))) { 623 cat(sprintf("Local: %s\n", workdir(x))) 624 cat("Head: nothing commited (yet)\n") 625 } else { 626 if (is_detached(x)) { 627 cat(sprintf("Local: (detached) %s\n", workdir(x))) 628 629 h <- repository_head(x) 630 } else { 631 cat(sprintf("Local: %s %s\n", 632 repository_head(x)$name, 633 workdir(x))) 634 635 h <- repository_head(x) 636 u <- branch_get_upstream(h) 637 if (!is.null(u)) { 638 rn <- branch_remote_name(u) 639 cat(sprintf("Remote: %s @ %s (%s)\n", 640 substr(u$name, nchar(rn) + 2, nchar(u$name)), 641 rn, 642 branch_remote_url(u))) 643 } 644 645 h <- lookup(x, branch_target(repository_head(x))) 646 } 647 648 cat(sprintf("Head: [%s] %s: %s\n", 649 substring(h$sha, 1, 7), 650 substring(as.character(h$author$when), 1, 10), 651 h$summary)) 652 } 653 654 invisible(x) 655} 656 657##' Summary of repository 658##' 659##' @param object The repository \code{object} 660##' @param ... Additional arguments affecting the summary produced. 661##' @return None (invisible 'NULL'). 662##' @export 663##' @examples 664##' \dontrun{ 665##' ## Initialize a repository 666##' path <- tempfile(pattern="git2r-") 667##' dir.create(path) 668##' repo <- init(path) 669##' 670##' ## Config user 671##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 672##' 673##' ## Create a file 674##' writeLines("Hello world!", file.path(path, "test.txt")) 675##' summary(repo) 676##' 677##' ## Add file 678##' add(repo, "test.txt") 679##' summary(repo) 680##' 681##' ## Commit 682##' commit(repo, "First commit message") 683##' summary(repo) 684##' 685##' ## Change the file 686##' writeLines(c("Hello again!", "Here is a second line", "And a third"), 687##' file.path(path, "test.txt")) 688##' summary(repo) 689##' 690##' ## Add file and commit 691##' add(repo, "test.txt") 692##' commit(repo, "Second commit message") 693##' summary(repo) 694##'} 695summary.git_repository <- function(object, ...) { 696 print(object) 697 cat("\n") 698 699 n_branches <- sum(!is.na(unique(sapply(branches(object), 700 branch_target)))) 701 n_tags <- sum(!is.na(unique(vapply(tags(object), "[[", 702 character(1), "sha")))) 703 704 work <- commits(object) 705 n_commits <- length(work) 706 n_authors <- length(unique(vapply(lapply(work, "[[", "author"), 707 "[[", character(1), "name"))) 708 709 s <- .Call(git2r_status_list, object, TRUE, TRUE, TRUE, FALSE, TRUE) 710 n_ignored <- length(s$ignored) 711 n_untracked <- length(s$untracked) 712 n_unstaged <- length(s$unstaged) 713 n_staged <- length(s$staged) 714 715 n_stashes <- length(stash_list(object)) 716 717 ## Determine max characters needed to display numbers 718 n <- max(vapply(c(n_branches, n_tags, n_commits, n_authors, 719 n_stashes, n_ignored, n_untracked, 720 n_unstaged, n_staged), 721 nchar, 722 numeric(1))) 723 724 fmt <- paste0("Branches: %", n, "i\n", 725 "Tags: %", n, "i\n", 726 "Commits: %", n, "i\n", 727 "Contributors: %", n, "i\n", 728 "Stashes: %", n, "i\n", 729 "Ignored files: %", n, "i\n", 730 "Untracked files: %", n, "i\n", 731 "Unstaged files: %", n, "i\n", 732 "Staged files: %", n, "i\n") 733 cat(sprintf(fmt, n_branches, n_tags, n_commits, n_authors, 734 n_stashes, n_ignored, n_untracked, n_unstaged, 735 n_staged)) 736 737 cat("\nLatest commits:\n") 738 lapply(commits(object, n = 5), print) 739 740 invisible(NULL) 741} 742 743## Strip trailing slash or backslash, unless it's the current drive 744## root (/) or a Windows drive, for example, 'c:\'. 745strip_trailing_slash <- function(path) { 746 if (!is.null(path) && grep("^(/|[a-zA-Z]:[/\\\\]?)$", path, invert = TRUE)) 747 path <- sub("/?$", "", path) 748 path 749} 750 751##' Workdir of repository 752##' 753##' @template repo-param 754##' @return Character vector with the path of the workdir. If the 755##' repository is bare, \code{NULL} will be returned. 756##' @export 757##' @examples 758##' \dontrun{ 759##' ## Create a directory in tempdir 760##' path <- tempfile(pattern="git2r-") 761##' dir.create(path) 762##' 763##' ## Initialize a repository 764##' repo <- init(path) 765##' 766##' ## Get the path of the workdir for repository 767##' workdir(repo) 768##' } 769workdir <- function(repo = ".") { 770 path <- .Call(git2r_repository_workdir, lookup_repository(repo)) 771 strip_trailing_slash(path) 772} 773 774##' Find path to repository for any file 775##' 776##' @param path A character vector specifying the path to a file or 777##' folder 778##' @param ceiling The default is to not use the ceiling argument and 779##' start the lookup from path and walk across parent 780##' directories. When ceiling is 0, the lookup is only in 781##' path. When ceiling is 1, the lookup is in both the path and 782##' the parent to path. 783##' @return Character vector with path (terminated by a file 784##' separator) to repository or NULL if this cannot be 785##' established. 786##' @export 787##' @examples 788##' \dontrun{ 789##' ## Initialize a temporary repository 790##' path <- tempfile(pattern="git2r-") 791##' dir.create(path) 792##' repo <- init(path) 793##' 794##' ## Create a user and commit a file 795##' config(repo, user.name = "Alice", user.email = "alice@@example.org") 796##' lines <- "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do" 797##' writeLines(lines, file.path(path, "example-1.txt")) 798##' add(repo, "example-1.txt") 799##' commit(repo, "First commit message") 800##' 801##' ## Create a second file. The file is not added for version control 802##' ## in the repository. 803##' dir.create(file.path(path, "example")) 804##' file_2 <- file.path(path, "example/example-2.txt") 805##' writeLines("Not under version control", file_2) 806##' 807##' ## Find the path to the repository using the path to the second file 808##' discover_repository(file_2) 809##' 810##' ## Demonstrate the 'ceiling' argument 811##' wd <- workdir(repo) 812##' dir.create(file.path(wd, "temp")) 813##' 814##' ## Lookup repository in 'file.path(wd, "temp")'. Should return NULL 815##' discover_repository(file.path(wd, "temp"), ceiling = 0) 816##' 817##' ## Lookup repository in parent to 'file.path(wd, "temp")'. 818##' ## Should not return NULL 819##' discover_repository(file.path(wd, "temp"), ceiling = 1) 820##' } 821discover_repository <- function(path = ".", ceiling = NULL) { 822 if (identical(path, ".")) 823 path <- getwd() 824 path <- normalizePath(path) 825 826 if (!is.null(ceiling)) { 827 ceiling <- as.integer(ceiling) 828 if (identical(ceiling, 0L)) { 829 ceiling <- dirname(path) 830 } else if (identical(ceiling, 1L)) { 831 ceiling <- dirname(dirname(path)) 832 } else { 833 stop("'ceiling' must be either 0 or 1") 834 } 835 } 836 837 path <- .Call(git2r_repository_discover, path, ceiling) 838 strip_trailing_slash(path) 839} 840 841##' Internal utility function to lookup repository for methods 842##' 843##' @param repo repository \code{object} \code{git_repository}, or a 844##' path to a repository, or \code{NULL}. If the \code{repo} 845##' argument is \code{NULL}, the repository is searched for with 846##' \code{\link{discover_repository}} in the current working 847##' directory. 848##' @return git_repository 849##' @noRd 850lookup_repository <- function(repo = NULL) { 851 if (is.null(repo) || identical(repo, ".")) { 852 ## Try current working directory 853 repo <- discover_repository(getwd()) 854 if (is.null(repo)) 855 stop("The working directory is not in a git repository") 856 } else if (inherits(repo, "git_repository")) { 857 return(repo) 858 } 859 860 repository(repo) 861} 862