Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,64 @@ jobs:
build/test/
build/Testing/
retention-days: 7

coverage:
name: Coverage (debian:stable / clang / Debug)

runs-on: ubuntu-latest

container:
image: debian:stable

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install dependencies
env:
DEBIAN_FRONTEND: noninteractive
run: bash dependencies.sh --compiler=clang

- name: Cache CMake FetchContent (spdlog, clipp, fmt)
uses: actions/cache@v5
with:
path: build/_deps
key: fetchcontent-coverage-${{ hashFiles('CMakeLists.txt') }}
restore-keys: |
fetchcontent-coverage-

- name: Configure git identity (needed by tests that create commits)
run: |
git config --global user.email "ci@github-actions"
git config --global user.name "GitHub Actions"
git config --global init.defaultBranch master

- name: Build with coverage
env:
CC: clang
CXX: clang++
CI: "1"
COVERAGE: "1"
run: make TYPE=Debug test

- name: Collect coverage data
run: |
gcovr --sonarqube coverage.xml \
--gcov-executable 'llvm-cov gcov' \
--gcov-ignore-errors=no_working_dir_found \
--exclude 'build/_deps/' \
--exclude 'test/' \
--root .

- name: Install codecov dependencies
env:
DEBIAN_FRONTEND: noninteractive
run: apt-get update && apt-get install -y curl gpg

- name: Upload to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
flags: unittests
name: debian-stable-clang-debug
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ build/
.opencode/
/git-wip

coverage-report/
coverage.xml

### C++
# Prerequisites
*.d
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ set(CMAKE_CXX_EXTENSIONS OFF)
# Enable generation of compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Code coverage options
option(WIP_COVERAGE "Enable code coverage instrumentation" OFF)
if(WIP_COVERAGE)
add_compile_options(-fprofile-arcs -ftest-coverage)
add_link_options(-fprofile-arcs -ftest-coverage)
endif()

include(FetchContent)
include(CheckCXXSourceCompiles)

Expand Down
22 changes: 14 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ TYPE ?= $(if ${CURRENT_TYPE},${CURRENT_TYPE},${DEFAULT_TYPE})
NPROC ?= $(shell nproc || echo 1)
CC ?= $(shell which clang gcc cc | head -n1)
CXX ?= $(shell which clang g++ c++ | head -n1)
$(info ## TYPE=${TYPE} CC=${CC} CXX=${CXX})
COVERAGE ?= false
$(info ## TYPE=${TYPE} CC=${CC} CXX=${CXX} COVERAGE=${COVERAGE})

# Coverage flag for CMake
COVERAGE_FLAG = $(if $(filter 1 yes true YES TRUE,${COVERAGE}),-DWIP_COVERAGE=ON,)

GIT_WIP = ${BUILD}/src/git-wip

all: ## [default] build the project (uses TYPE={Release,Debug})
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}"
all: ## [default] build the project (uses TYPE={Release,Debug}, COVERAGE={true,false})
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${COVERAGE_FLAG}
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
${Q}ln -fs "${BUILD}"/compile_commands.json compile_commands.json
${Q}ln -fs "${GIT_WIP}" .
Expand All @@ -50,19 +54,21 @@ distclean: ## remove build directory completely
help:
${Q}python3 -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)

test: ## run unit tests (with ctest, uses REBUILD={true,false})
test: ## run unit tests (with ctest, uses REBUILD={true,false}, COVERAGE={true,false})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}"
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" ${COVERAGE_FLAG}
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
${Q}cd "${BUILD}"/ && ctest -C "${TYPE}" $(if ${CI},--output-on-failure -VV)
${Q}echo " ✅ Unit tests complete."

coverage: ## check code coverage (with gcov, uses REBUILD={true,false})
coverage: ## check code coverage (with gcovr, uses REBUILD={true,false})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}"
${Q}${CMAKE} -G ${GENERATOR} -S. -B${BUILD} -DCMAKE_INSTALL_PREFIX="$(PREFIX)" -DCMAKE_BUILD_TYPE="${TYPE}" -DWIP_COVERAGE=ON
${Q}${CMAKE} --build "${BUILD}" --config "${TYPE}" --parallel "${NPROC}"
${Q}cd "${BUILD}"/ && ctest -C "${TYPE}" -VV
${Q}find "${BUILD}"/ -type f -name '*.gcno' -exec gcov -pb {} +
${Q}mkdir -p coverage-report
${Q}gcovr --html coverage-report/index.html --root . "${BUILD}"
${Q}echo " ✅ Coverage report generated in coverage-report/"

install: ## install the package (to the `PREFIX`, uses REBUILD={true,false})
${Q}$(if $(filter 1 yes true YES TRUE,${REBUILD}),rm -rf "${BUILD}"/)
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# About

[![CI](https://github.com/bartman/git-wip/actions/workflows/ci.yml/badge.svg)](https://github.com/bartman/git-wip/actions)
[![Codecov](https://codecov.io/gh/bartman/git-wip/branch/main/graph/badge.svg)](https://codecov.io/gh/bartman/git-wip)

`git-wip` manages **Work In Progress** (or **WIP**) branches.
WIP branches are mostly throw-away but capture points of development
between commits. The intent is to tie `git-wip` into your editor so
Expand Down Expand Up @@ -78,6 +81,15 @@ Old WIP commits are never deleted; they remain reachable through
Snapshot the working tree with the default message `"WIP"`.
Equivalent to `git wip save "WIP"`.

### `git wip [--version | -v | version]`

Show the version string (from `git describe --tags --dirty=-dirty` at build time).

```
$ git wip --version
v0.2-83-g95a6648-dirty
```

### `git wip save [<message>] [options] [-- <file>...]`

Create a new WIP commit.
Expand Down
44 changes: 44 additions & 0 deletions cmake/GitVersion.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# GitVersion.cmake - Integration for GitVersion.sh
#
# Usage:
# include(GitVersion)
# gitversion_generate(PREFIX GIT_WIP_ OUTPUT ${CMAKE_BINARY_DIR}/git_wip_version.h)

function(gitversion_generate)
set(options)
set(oneValueArgs PREFIX OUTPUT)
set(multiValueArgs)

cmake_parse_arguments(GV "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

if(NOT GV_PREFIX)
message(FATAL_ERROR "gitversion_generate: PREFIX is required")
endif()
if(NOT GV_OUTPUT)
message(FATAL_ERROR "gitversion_generate: OUTPUT is required")
endif()

# Run GitVersion.sh to generate the version header
execute_process(
COMMAND ${CMAKE_SOURCE_DIR}/cmake/GitVersion.sh ${GV_PREFIX} ${GV_OUTPUT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE GV_RESULT
)

if(NOT GV_RESULT EQUAL 0)
message(WARNING "gitversion_generate: Failed to run GitVersion.sh")
endif()

# Add a custom command to regenerate the version header
add_custom_command(
OUTPUT ${GV_OUTPUT}
COMMAND ${CMAKE_SOURCE_DIR}/cmake/GitVersion.sh ${GV_PREFIX} ${GV_OUTPUT}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/GitVersion.sh
COMMENT "Generating version header: ${GV_OUTPUT}"
VERBATIM
)

# Add a custom target that depends on the version header
add_custom_target(gitversion DEPENDS ${GV_OUTPUT})
endfunction()
31 changes: 31 additions & 0 deletions cmake/GitVersion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# GitVersion.sh - Generate version header from git describe
# Usage: GitVersion.sh PREFIX OUTPUT

set -e

PREFIX="$1"
OUTPUT="$2"

if [ -z "$PREFIX" ] || [ -z "$OUTPUT" ]; then
echo "Usage: $0 PREFIX OUTPUT" >&2
exit 1
fi

# Get git describe output
DESCRIBE="$(git describe --tags --dirty=-dirty 2>/dev/null || echo "unknown")"

# Generate temporary output file
OUTPUT_TMP="${OUTPUT}.tmp"

cat > "$OUTPUT_TMP" << EOF
#pragma once
#define ${PREFIX}VERSION "${DESCRIBE}"
EOF

# Only update the file if it changed
if [ -f "$OUTPUT" ] && cmp -s "$OUTPUT" "$OUTPUT_TMP"; then
rm -f "$OUTPUT_TMP"
else
mv "$OUTPUT_TMP" "$OUTPUT"
fi
3 changes: 3 additions & 0 deletions dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -205,19 +205,22 @@ case "$pkg_mgr" in
libgmock-dev
libgtest-dev
libgit2-dev
gcovr
)
;;
dnf)
packages+=(
gtest-devel
gmock-devel
libgit2-devel
gcovr
)
;;
pacman)
# Arch uses different package names
packages+=(
libgit2
gcovr
)
# Replace base packages with Arch equivalents
packages=( "${packages[@]/ninja-build/ninja}" )
Expand Down
8 changes: 8 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Generate version header
include(${CMAKE_SOURCE_DIR}/cmake/GitVersion.cmake)
gitversion_generate(PREFIX GIT_WIP_ OUTPUT ${CMAKE_BINARY_DIR}/git_wip_version.h)

add_executable(git-wip
color.cpp
main.cpp
Expand All @@ -8,11 +12,15 @@ add_executable(git-wip
cmd_status.cpp
)

# Ensure the executable is rebuilt when the version header changes
add_dependencies(git-wip gitversion)

install(TARGETS git-wip
RUNTIME DESTINATION bin
)

target_include_directories(git-wip PRIVATE
${CMAKE_BINARY_DIR}
${LIBGIT2_INCLUDE_DIRS}
)

Expand Down
8 changes: 7 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <cstdlib>
#include <iostream>
#include "git_wip_version.h"
#include "print_compat.hpp"
#include <format>
#include <map>
Expand All @@ -19,7 +20,7 @@ bool g_wip_debug = false;

void print_main_help(const std::vector<std::unique_ptr<Command>>& commands, std::ostream &os = std::cout) {
std::println(os, "Manage Work In Progress\n");
std::println(os, "git-wip <command> [ --help | command options ]\n");
std::println(os, "git-wip <command> [ --help | --version | command options ]\n");
for (const auto& cmd : commands) {
std::println(" git-wip {:20} # {}", cmd->name(), cmd->desc());
}
Expand Down Expand Up @@ -69,6 +70,11 @@ int main(int argc, char *argv[]) {
return 0;
}

if (command_name == "version" || command_name == "--version" || command_name == "-v") {
std::cout << GIT_WIP_VERSION << std::endl;
return 0;
}

// If the first argument looks like a file (not a known command and not an
// option), treat the whole invocation as "save WIP [files...]" — matching
// the old script behaviour where bare file paths fall through to save.
Expand Down