190 lines
7.6 KiB
Racket
190 lines
7.6 KiB
Racket
#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)))
|
|
|
|
|
|
;; Help validates its topic before trying to open the installed documentation.
|
|
(check-exn exn:fail? (lambda () (git 'help 'not-a-git-command)))
|
|
(check-exn exn:fail:contract? (lambda () (git-help "grep")))
|