#lang racket/base (require rackunit racket/file git) (define tmp (make-temporary-file "racket-git-test~a" 'directory)) (dynamic-wind void (lambda () (make-directory (build-path tmp "sub")) (parameterize ([current-directory tmp]) (git 'init) (check-true (git-repository?)) (check-equal? (git-current-branch) "master") (check-equal? (git 'branch-current) "master") (check-true (git-clean?)) (git 'config "user.name" "Racket Git Test") (git 'config "user.email" "racket-git-test@example.invalid") (check-equal? (git-config "user.name") "Racket Git Test") (call-with-output-file ".gitignore" #:exists 'truncate/replace (lambda (out) (displayln "ignored.txt" out))) (call-with-output-file "ignored.txt" #:exists 'truncate/replace (lambda (out) (displayln "ignored" out))) (call-with-output-file (build-path "sub" "hello.txt") #:exists 'truncate/replace (lambda (out) (displayln "hello" out))) (check-equal? (git-status-lines) '("?? .gitignore" "?? sub/hello.txt")) (define status-output (open-output-string)) (define displayed-status (parameterize ([current-output-port status-output]) (dgit 'status))) (check-equal? displayed-status (git 'status)) (check-equal? (get-output-string status-output) "New - .gitignore\nNew - sub/hello.txt\n") (check-false (regexp-match? #rx"ignored[.]txt" (get-output-string status-output))) (parameterize ([current-directory (build-path tmp "sub")]) (git 'add "hello.txt")) (check-equal? (git-status-lines) '("?? .gitignore" "A sub/hello.txt")) (git 'add ".gitignore") (define first (git-commit "initial commit")) (check-equal? (string-length first) 40) (check-true (git-clean?)) (check-equal? (git 'diff) "") (check-equal? (git 'diff '--cached) "") ;; Grep always returns structured entries. -n, -l, and -c affect only ;; dgit presentation; -i and -v affect matching. (define grep-hello (git 'grep "hello")) (check-equal? grep-hello (list (git-grep-entry "sub/hello.txt" 1 "hello"))) (check-equal? (git 'grep '-n "hello") grep-hello) (check-equal? (git 'grep '-l "hello") grep-hello) (check-equal? (git 'grep '-c "hello") grep-hello) (check-equal? (git 'grep '-i "HELLO") grep-hello) (check-false (for/or ([entry (in-list (git 'grep '-v "hello"))]) (and (string=? (git-grep-entry-path entry) "sub/hello.txt") (string=? (git-grep-entry-line entry) "hello")))) (define grep-output (open-output-string)) (parameterize ([current-output-port grep-output]) (dgit 'grep '-n "hello")) (check-equal? (get-output-string grep-output) "sub/hello.txt:1:hello\n") (define grep-files-output (open-output-string)) (parameterize ([current-output-port grep-files-output]) (dgit 'grep '-l "hello")) (check-equal? (get-output-string grep-files-output) "sub/hello.txt\n") (define grep-count-output (open-output-string)) (parameterize ([current-output-port grep-count-output]) (dgit 'grep '-c "hello")) (check-equal? (get-output-string grep-count-output) "sub/hello.txt:1\n") ;; `restore --staged` removes a path from the index without deleting the ;; worktree file. Once the path is ignored, it disappears from status. (call-with-output-file "staged.txt" #:exists 'truncate/replace (lambda (out) (displayln "staged" out))) (git 'add "staged.txt") (call-with-output-file ".gitignore" #:exists 'append (lambda (out) (displayln "staged.txt" out))) (check-not-false (member "A staged.txt" (git-status-lines))) (git 'restore '--staged "staged.txt") (check-false (member "?? staged.txt" (git-status-lines))) (check-true (file-exists? "staged.txt")) ;; Restore the tracked .gitignore from the index and remove the now ;; untracked test file, returning to a clean repository. (git 'restore ".gitignore") (delete-file "staged.txt") (check-true (git-clean?)) (call-with-output-file (build-path "sub" "hello.txt") #:exists 'truncate/replace (lambda (out) (displayln "changed" out))) (define worktree-diff (git 'diff)) (check-equal? (git 'grep "hello" 'HEAD) (list (git-grep-entry "sub/hello.txt" 1 "hello"))) (check-equal? (git 'grep "changed") (list (git-grep-entry "sub/hello.txt" 1 "changed"))) (check-true (regexp-match? #rx"-hello" worktree-diff)) (check-true (regexp-match? #rx"[+]changed" worktree-diff)) (check-equal? (git 'diff '--cached) "") (define diff-output (open-output-string)) (parameterize ([current-output-port diff-output]) (dgit 'diff)) (check-equal? (get-output-string diff-output) worktree-diff) (git 'add "sub/hello.txt") (check-equal? (git 'diff) "") (define cached-diff (git 'diff '--cached)) (check-true (regexp-match? #rx"-hello" cached-diff)) (check-true (regexp-match? #rx"[+]changed" cached-diff)) ;; Path reset restores the index from HEAD but keeps the worktree edit. (git 'reset 'HEAD "--" "sub/hello.txt") (check-equal? (git 'diff '--cached) "") (check-true (regexp-match? #rx"[+]changed" (git 'diff))) (check-not-false (member " M sub/hello.txt" (git-status-lines))) (git 'add "sub/hello.txt") (git 'commit "prepare branches") (git 'checkout '-b "work") (call-with-output-file (build-path "sub" "hello.txt") #:exists 'truncate/replace (lambda (out) (displayln "work" out))) (git 'add) (git 'commit "work change") (check-equal? (file->string (build-path "sub" "hello.txt")) "work\n") (check-equal? (git 'branch-current) "work") (git 'switch "master") (check-equal? (file->string (build-path "sub" "hello.txt")) "changed\n") (check-equal? (git-current-branch) "master") (check-not-false (member "work" (git 'branch))) ;; Safe checkout must not overwrite an uncommitted tracked change. (call-with-output-file (build-path "sub" "hello.txt") #:exists 'truncate/replace (lambda (out) (displayln "dirty" out))) (check-exn exn:fail? (lambda () (git 'switch "work"))) (check-equal? (git-current-branch) "master") (check-equal? (file->string (build-path "sub" "hello.txt")) "dirty\n") (call-with-output-file (build-path "sub" "hello.txt") #:exists 'truncate/replace (lambda (out) (displayln "changed" out))) (git 'branch '-d "work") (check-false (member "work" (git 'branch))) (define tag-id (git 'tag "v0.1")) (check-equal? (string-length tag-id) 40) (check-equal? (git 'tag) '("v0.1")) (git 'checkout "v0.1") (check-false (git-current-branch)) (check-false (git 'branch-current)) (check-exn exn:fail? (lambda () (git 'switch "missing"))) (git 'switch "master") (check-equal? (git 'branch-current) "master") (git 'tag '-d "v0.1") (check-equal? (git 'tag) '()) (check-equal? (length (git 'log 10)) 2))) (lambda () (delete-directory/files tmp)))