Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
305fb98
feat: implement basic Git functionality with init, cat-file, hash-obj…
ryan-gang Jun 25, 2025
766e0a1
feat: enhance cloneRepo function to support multiple expected commit …
ryan-gang Jun 25, 2025
2f731fc
feat: introduce cloneRepoNew function to streamline repository clonin…
ryan-gang Jun 25, 2025
beeb91e
refactor: remove cloneRepoNew function and integrate its logic direct…
ryan-gang Jun 25, 2025
be082e3
refactor: use go-git features as much as possible
ryan-gang Jun 25, 2025
4b2375f
chore: update go.mod and go.sum to include new dependencies and speci…
ryan-gang Jun 25, 2025
e1b46db
feat: add your_program.sh script to automate Go module tidy, build, a…
ryan-gang Jun 25, 2025
9718dc7
chore: remove obsolete debug configuration and scripts, and add new m…
ryan-gang Jun 25, 2025
402f52a
feat: implement mygit command-line tool with core Git functionalities…
ryan-gang Jun 25, 2025
6387bf3
test: add new test case for 'pass_all' scenario in stages_test.go
ryan-gang Jun 25, 2025
c52b1c6
feat: add platform-specific mygit binaries and enhance your_program.s…
ryan-gang Jun 25, 2025
bf8c085
feat: add build script for cross-platform compilation and simplify bi…
ryan-gang Jun 25, 2025
f9a638f
test: add pass_all fixture for comprehensive testing of your_program.…
ryan-gang Jun 25, 2025
6249244
test: modify test command to run without caching and enhance output n…
ryan-gang Jun 25, 2025
05f5802
chore: remove obsolete mygit binaries for darwin-arm64, linux-amd64, …
ryan-gang Jun 26, 2025
9052cb9
chore: update .gitignore to exclude ryan-git directory
ryan-gang Jun 26, 2025
b001f6a
feat: add Dockerfile and test script for local testing environment wi…
ryan-gang Jun 26, 2025
095238b
chore: remove build script for cross-platform compilation as it is no…
ryan-gang Jun 26, 2025
6bc0d52
feat: enhance testCloneRepository function to manage git executable i…
ryan-gang Jun 26, 2025
8be626d
fix: remove sudo from mv commands in testCloneRepository function to …
ryan-gang Jun 26, 2025
62e18a0
feat: implement GitTempDir to manage git executable in temporary dire…
ryan-gang Jun 26, 2025
8f1659a
fix: add sudo to mv commands in MoveGitToTemp and RestoreGit function…
ryan-gang Jun 26, 2025
15caef2
fix: suppress output of go build command in your_program.sh to reduce…
ryan-gang Jun 26, 2025
298beb2
refactor: unify gitTempDir management across test functions to improv…
ryan-gang Jun 26, 2025
d022f18
chore: update Dockerfile to use apt-get for package installation and …
ryan-gang Jun 26, 2025
591de8e
fix: comment out debug logging in MoveGitToTemp and RestoreGit functi…
ryan-gang Jun 26, 2025
1ec8426
ci: add regenerated fixtures
ryan-gang Jun 26, 2025
4b7376c
feat: add local testing script to build and run Docker container for …
ryan-gang Jun 26, 2025
99d8016
feat: implement MoveGitToTemp and RestoreGit methods for managing git…
ryan-gang Jun 26, 2025
389d950
fix: update RestoreGit documentation to clarify error handling behavior
ryan-gang Jun 26, 2025
b9c4bc7
feat: add configuration and script for Git management in testing envi…
ryan-gang Jun 26, 2025
5f04278
refactor: replace temporary directory creation in MoveGitToTemp with …
ryan-gang Jun 26, 2025
58007bc
feat: enhance your_program.sh to add support for git add command when…
ryan-gang Jun 26, 2025
893400e
fix: update your_program.sh to clarify git add requirement before wri…
ryan-gang Jun 26, 2025
8bedeae
test: rename and add new test cases for stages to improve coverage ag…
ryan-gang Jun 26, 2025
d935017
test: add new fixture files for comprehensive testing of git commands…
ryan-gang Jun 26, 2025
c503076
refactor: update MoveGitToTemp to use os.MkdirTemp for dynamic tempor…
ryan-gang Jun 26, 2025
eb00ede
refactor: simplify your_program.sh by removing redundant checks for g…
ryan-gang Jun 26, 2025
893e271
refactor: update your_program.sh to correctly match git binaries in t…
ryan-gang Jun 26, 2025
b357713
refactor: modify MoveGitToTemp to accept TestCaseHarness and register…
ryan-gang Jun 26, 2025
e06e574
refactor: update test functions to use modified MoveGitToTemp with Te…
ryan-gang Jun 26, 2025
e485d87
chore: remove ryan-git and associated tests
ryan-gang Jun 26, 2025
84d45ca
refactor: modify MoveGitToTemp to eliminate return values and use pan…
ryan-gang Jun 26, 2025
9255e68
refactor: streamline test functions by removing error handling for Mo…
ryan-gang Jun 26, 2025
9abf371
chore: fix lint issues
ryan-gang Jun 26, 2025
aa35a9f
refactor: enhance your_program.sh to correctly configure git settings…
ryan-gang Jul 3, 2025
c0a869c
refactor: remove sudo from MoveGitToTemp and restoreGit functions, re…
ryan-gang Jul 3, 2025
010f162
chore: update GitHub Actions workflow to run tests in a container wit…
ryan-gang Jul 3, 2025
e9c9389
chore: update GitHub Actions workflow to install dependencies and run…
ryan-gang Jul 3, 2025
aee5fa5
chore: comment out dependency installation in GitHub Actions workflow
ryan-gang Jul 3, 2025
7bae508
chore: remove error message for missing git binary in your_program.sh
ryan-gang Jul 3, 2025
ed4e488
refactor: simplify git configuration in your_program.sh by setting in…
ryan-gang Jul 3, 2025
2e5f3ae
refactor: add conditional check for init.defaultBranch in your_progra…
ryan-gang Jul 3, 2025
a709cef
refactor: update MoveGitToTemp to use oldGitPath directly for restori…
ryan-gang Jul 3, 2025
2a87dc3
fix: add error message for missing git binary in your_program.sh
ryan-gang Jul 3, 2025
c475b18
refactor: rename MoveGitToTemp to RelocateSystemGit for clarity and u…
ryan-gang Jul 7, 2025
96a1493
refactor: improve comments in your_program.sh for clarity and accuracy
ryan-gang Jul 7, 2025
3fa89a6
refactor: update comments in stage_write_tree.go to clarify the requi…
ryan-gang Jul 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
with:
python-version: '3.13'

- run: make test
- run: sudo make test
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build:
go build -o dist/main.out ./cmd/tester

test:
go test -v ./internal/
go test -v -count=1 ./internal/

test_with_git: build
CODECRAFTERS_REPOSITORY_DIR=$(shell pwd)/internal/test_helpers/pass_all \
Expand Down
3 changes: 2 additions & 1 deletion internal/stage_clone_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (r TestRepo) randomFile() TestFile {
return r.exampleFiles[random.RandomInt(0, len(r.exampleFiles))]
}

var testRepos []TestRepo = []TestRepo{
var testRepos = []TestRepo{
{
url: "https://github.com/codecrafters-io/git-sample-1",
exampleCommits: []string{
Expand Down Expand Up @@ -79,6 +79,7 @@ func randomRepo() TestRepo {
func testCloneRepository(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
RelocateSystemGit(harness, logger)

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/stage_create_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
func testCreateBlob(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
RelocateSystemGit(harness, logger)

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/stage_create_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
func testCreateCommit(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
RelocateSystemGit(harness, logger)

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/stage_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
func testInit(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
RelocateSystemGit(harness, logger)

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/stage_read_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
func testReadBlob(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
RelocateSystemGit(harness, logger)

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/stage_read_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
func testReadTree(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
RelocateSystemGit(harness, logger)

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions internal/stage_write_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
func testWriteTree(harness *test_case_harness.TestCaseHarness) error {
logger := harness.Logger
executable := harness.Executable
// This stage Requires the git binary for verifying the git object
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems weird behaviour to not do this here too. Can't we move the git binary back for verifying and then place it back again?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or can we just use the path of the temporary git to execute that?

// So, no relocation is done for this stage

tempDir, err := os.MkdirTemp("", "worktree")
if err != nil {
Expand Down
19 changes: 19 additions & 0 deletions internal/stages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"os"
"regexp"
"testing"

tester_utils_testing "github.com/codecrafters-io/tester-utils/testing"
Expand Down Expand Up @@ -102,11 +103,29 @@ func TestStages(t *testing.T) {
StdoutFixturePath: "./test_helpers/fixtures/write_tree",
NormalizeOutputFunc: normalizeTesterOutput,
},
"pass_all": {
UntilStageSlug: "mg6",
CodePath: "./test_helpers/pass_all",
ExpectedExitCode: 0,
StdoutFixturePath: "./test_helpers/fixtures/pass_all",
NormalizeOutputFunc: normalizeTesterOutput,
},
}

tester_utils_testing.TestTesterOutput(t, testerDefinition, testCases)
}

func normalizeTesterOutput(testerOutput []byte) []byte {
replacements := map[string][]*regexp.Regexp{
"initalize_line": {regexp.MustCompile(`Initialized empty Git repository in .*.git/`)},
"[your_program] commit-tree-sha": {regexp.MustCompile(`\[your_program\] .{4}[a-z0-9]{40}`)},
}

for replacement, regexes := range replacements {
for _, regex := range regexes {
testerOutput = regex.ReplaceAll(testerOutput, []byte(replacement))
}
}

return testerOutput
}
70 changes: 70 additions & 0 deletions internal/test_helpers/fixtures/pass_all
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Debug = true

[tester::#MG6] Running tests for Stage #MG6 (mg6)
[tester::#MG6] $ ./your_program.sh clone https://github.com/codecrafters-io/git-sample-1 <testDir>
[your_program] Cloning into 'test_dir'...
[tester::#MG6] $ git cat-file commit 3b0466d22854e57bf9ad3ccf82008a2d3f199550
[tester::#MG6] Commit contents verified
[tester::#MG6] Reading contents of a sample file
[tester::#MG6] File contents verified
[tester::#MG6] Test passed.

[tester::#JM9] Running tests for Stage #JM9 (jm9)
[tester::#JM9] Running git init
[tester::#JM9] Creating some files & directories
[tester::#JM9] Running git commit --all
[tester::#JM9] Creating another file
[tester::#JM9] $ ./your_program.sh commit-tree <tree_sha> -p <commit_sha> -m <message>
[your_program] 1b6618b6c3647ff62b3941c13245f89ff309f1d6
[tester::#JM9] Running git cat-file commit <sha>
[tester::#JM9] Test passed.

[tester::#FE4] Running tests for Stage #FE4 (fe4)
[tester::#FE4] $ ./your_program.sh init
[your_program] Initialized empty Git repository in /tmp/worktree576017363/.git/
[tester::#FE4] Creating some files & directories
[tester::#FE4] $ ./your_program.sh write-tree
[your_program] 5794e0cecb636294c4c6753742dcf616f21b7f8e
[tester::#FE4] Reading file at .git/objects/57/94e0cecb636294c4c6753742dcf616f21b7f8e
[tester::#FE4] Found git object file written at .git/objects/57/94e0cecb636294c4c6753742dcf616f21b7f8e.
[tester::#FE4] $ git ls-tree --name-only 5794e0cecb636294c4c6753742dcf616f21b7f8e
[tester::#FE4] Test passed.

[tester::#KP1] Running tests for Stage #KP1 (kp1)
[tester::#KP1] $ ./your_program.sh init
[your_program] Initialized empty Git repository in /tmp/worktree1592248827/.git/
[tester::#KP1] Writing a tree to git storage..
[tester::#KP1] $ ./your_program.sh ls-tree --name-only b8b2ce4032667654a481b94b38818b24fe84c460
[your_program] grape
[your_program] orange
[your_program] strawberry
[tester::#KP1] Test passed.

[tester::#JT4] Running tests for Stage #JT4 (jt4)
[tester::#JT4] $ ./your_program.sh init
[your_program] Initialized empty Git repository in /tmp/worktree1527710594/.git/
[tester::#JT4] $ echo "raspberry blueberry banana pear pineapple apple" > banana.txt
[tester::#JT4] $ ./your_program.sh hash-object -w banana.txt
[your_program] 27ece14445d59efd51d58d64afe55b2d912b2503
[tester::#JT4] Output is a 40-char SHA.
[tester::#JT4] Blob file contents are valid.
[tester::#JT4] Returned SHA matches expected SHA.
[tester::#JT4] Test passed.

[tester::#IC4] Running tests for Stage #IC4 (ic4)
[tester::#IC4] $ ./your_program.sh init
[your_program] Initialized empty Git repository in /tmp/worktree1802656906/.git/
[tester::#IC4] Added blob object to .git/objects: da6ad811e0463b5b347359cc21fb53a3e5010d10
[tester::#IC4] $ ./your_program.sh cat-file -p da6ad811e0463b5b347359cc21fb53a3e5010d10
[your_program] pineapple grape orange blueberry apple mango
[tester::#IC4] Output is valid.
[tester::#IC4] Test passed.

[tester::#GG4] Running tests for Stage #GG4 (gg4)
[tester::#GG4] $ ./your_program.sh init
[your_program] Initialized empty Git repository in /tmp/worktree164141277/.git/
[tester::#GG4] .git directory found.
[tester::#GG4] .git/objects directory found.
[tester::#GG4] .git/refs directory found.
[tester::#GG4] .git/HEAD file is valid.
[tester::#GG4] Test passed.
12 changes: 6 additions & 6 deletions internal/test_helpers/pass_all/codecrafters.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# This is the current stage that you're on.
#
# Whenever you want to advance to the next stage,
# bump this to the next number.
current_stage: 1

# Set this to true if you want debug logs.
#
# These can be VERY verbose, so we suggest turning them off
# unless you really need them.
debug: true

# Use this to change the Go version used to run your code
# on Codecrafters.
#
# Available versions: go-1.24
language_pack: go-1.24
8 changes: 0 additions & 8 deletions internal/test_helpers/pass_all/your_git.sh

This file was deleted.

35 changes: 35 additions & 0 deletions internal/test_helpers/pass_all/your_program.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/sh

# Check if git from PATH is working first
if command -v git >/dev/null 2>&1; then
if [ "$1" = "write-tree" ]; then
git add .
fi
exec git "$@"
fi

# Find git binary in /tmp locations
for tmpdir in /tmp/git-*/git; do
if [ -x "$tmpdir" ]; then
# If defaultBranch config is not set, we set it to main (doesn't work without global config)
if ! "$tmpdir" config --global --get init.defaultBranch >/dev/null 2>&1; then
"$tmpdir" config --global init.defaultBranch main
fi

# commit-tree stage doesn't use this script for init
# So we need to run this setup again
if [ "$1" = "commit-tree" ]; then
"$tmpdir" config --local user.email "hello@codecrafters.io"
"$tmpdir" config --local user.name "CodeCrafters-Bot"
fi

if [ "$1" = "write-tree" ]; then
"$tmpdir" add .
fi

exec "$tmpdir" "$@"
fi
done

echo "git binary not found in PATH or /tmp directories"
exit 1
55 changes: 55 additions & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package internal

import (
"fmt"
"io"
"os"
"os/exec"
"path"

"github.com/codecrafters-io/tester-utils/logger"
"github.com/codecrafters-io/tester-utils/test_case_harness"
)

// RelocateSystemGit moves the system git binary to a temporary directory
func RelocateSystemGit(harness *test_case_harness.TestCaseHarness, logger *logger.Logger) {
oldGitPath, err := exec.LookPath("git")
if err != nil {
panic(fmt.Sprintf("CodeCrafters Internal Error: git executable not found: %v", err))
}

tmpGitDir, err := os.MkdirTemp("/tmp", "git-*")
if err != nil {
panic(fmt.Sprintf("CodeCrafters Internal Error: create tmp git directory failed: %v", err))
}
tmpGitPath := path.Join(tmpGitDir, "git")

command := fmt.Sprintf("mv %s %s", oldGitPath, tmpGitPath)
moveCmd := exec.Command("sh", "-c", command)
moveCmd.Stdout = io.Discard
moveCmd.Stderr = io.Discard
if err := moveCmd.Run(); err != nil {
os.RemoveAll(tmpGitDir)
panic(fmt.Sprintf("CodeCrafters Internal Error: mv git to tmp directory failed: %v", err))
}

// Register teardown function to automatically restore git
harness.RegisterTeardownFunc(func() { restoreSystemGit(tmpGitPath, oldGitPath) })
}

// RestoreSystemGit moves the git binary back to its original location and cleans up
func restoreSystemGit(newPath string, originalPath string) error {
command := fmt.Sprintf("mv %s %s", newPath, originalPath)
moveCmd := exec.Command("sh", "-c", command)
moveCmd.Stdout = io.Discard
moveCmd.Stderr = io.Discard
if err := moveCmd.Run(); err != nil {
panic(fmt.Sprintf("CodeCrafters Internal Error: mv restore for git failed: %v", err))
}

if err := os.RemoveAll(path.Dir(newPath)); err != nil {
panic(fmt.Sprintf("CodeCrafters Internal Error: delete tmp git directory failed: %s", path.Dir(newPath)))
}

return nil
}
27 changes: 27 additions & 0 deletions local_testing/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM golang:1.24

# Install required packages
RUN apt-get update && apt-get install -y \
# Required for make test
make \
# Required to run bash tests
bash \
# Required for fixtures
python3 \
# Required for testing
git \
&& rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Starting from Go 1.20, the go standard library is no loger compiled.
# Setting GODEBUG to "installgoroot=all" restores the old behavior
RUN GODEBUG="installgoroot=all" go install std

# Copy go.mod and go.sum first to cache dependencies
COPY go.mod go.sum ./
RUN go mod download

# Default command
CMD ["/bin/bash"]
15 changes: 15 additions & 0 deletions local_testing/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

set -e

# Ensure we're in the correct directory
cd "$(dirname "$0")/.."

# Build and run
docker build -t local-git-tester -f local_testing/Dockerfile .
# Run make test
# docker run --rm -it -v $(pwd):/app local-git-tester make test
# Generate fixtures
# docker run --rm -it -e CODECRAFTERS_RECORD_FIXTURES=true -v $(pwd):/app local-git-tester make test
# Run test_with_git
docker run --rm -it -v $(pwd):/app local-git-tester make test_with_git