Compare commits

..

8 Commits

Author SHA1 Message Date
cce24d2f46 With checksum 2026-01-28 20:04:00 +01:00
9d7828a714 Release Workflow 2026-01-28 20:01:48 +01:00
Surek Gergely
3aab541d9c Fix: build with gcc 13 2026-01-24 13:21:48 +01:00
3682bbc1a9 Fix: Build on modern Clang (3.5<) 2026-01-24 11:36:07 +01:00
David J Patrick
ccfc96f1cb Update README.md 2024-12-11 12:45:33 -05:00
David J Patrick
7e1c8a3b5e Update README.md 2024-12-11 12:44:55 -05:00
David J Patrick
5fe78f5627 Update README.md
try to explain the intentions of the fork
2024-12-11 02:24:57 -05:00
David J Patrick
e3f81cf0e4 Update README.md
added fork explanation and updated uuid-dev install requirement
2024-12-10 18:51:52 -05:00
326 changed files with 4791 additions and 30520 deletions

View File

@@ -1,6 +0,0 @@
[advisories]
ignore = [
"RUSTSEC-2021-0124", # see https://github.com/GothenburgBitFactory/taskwarrior/issues/2830
"RUSTSEC-2020-0159", # segfault in localtime_r - low risk to TC
"RUSTSEC-2020-0071", # same localtime_r bug as above
]

View File

@@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"

View File

@@ -1,20 +0,0 @@
FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-22.04
ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="3.22.2"
# Optionally install the cmake for vcpkg
COPY ./reinstall-cmake.sh /tmp/
RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \
chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \
fi \
&& rm -f /tmp/reinstall-cmake.sh
RUN sudo apt update && sudo apt install uuid-dev
# [Optional] Uncomment this section to install additional vcpkg ports.
# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install <your-port-name-here>"
# [Optional] Uncomment this section to install additional packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>

View File

@@ -1,24 +0,0 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/cpp
{
"name": "Taskwarrior development environment",
"build": {
"dockerfile": "Dockerfile"
},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/rust:1": {}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "gcc -v",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -1,59 +0,0 @@
#!/usr/bin/env bash
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
#
set -e
CMAKE_VERSION=${1:-"none"}
if [ "${CMAKE_VERSION}" = "none" ]; then
echo "No CMake version specified, skipping CMake reinstallation"
exit 0
fi
# Cleanup temporary directory and associated files when exiting the script.
cleanup() {
EXIT_CODE=$?
set +e
if [[ -n "${TMP_DIR}" ]]; then
echo "Executing cleanup of tmp files"
rm -Rf "${TMP_DIR}"
fi
exit $EXIT_CODE
}
trap cleanup EXIT
echo "Installing CMake..."
apt-get -y purge --auto-remove cmake
mkdir -p /opt/cmake
architecture=$(dpkg --print-architecture)
case "${architecture}" in
arm64)
ARCH=aarch64 ;;
amd64)
ARCH=x86_64 ;;
*)
echo "Unsupported architecture ${architecture}."
exit 1
;;
esac
CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh"
CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt"
TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX)
echo "${TMP_DIR}"
cd "${TMP_DIR}"
curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O
curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O
sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}"
sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license
ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake
ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest

View File

@@ -0,0 +1,16 @@
on:
- push
jobs:
ReleaseSrc:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
submodules: True
- name: Release Archive
uses: https://gitea.com/actions/gitea-release-action
with:
files: ./*
sha256sum: True

View File

@@ -0,0 +1,9 @@
#### Description
Replace this text with a description of the PR.
#### Additional information...
- [ ] Have you run the test suite?
Many changes need to be tested. Please run the test suite
and include the output of ```cd test && ./problems```.

View File

@@ -0,0 +1,64 @@
name: tests
on: [push, pull_request]
jobs:
tests:
strategy:
fail-fast: false
matrix:
include:
- name: "Centos 8"
runner: ubuntu-latest
dockerfile: centos8
- name: "Fedora 32"
runner: ubuntu-latest
dockerfile: fedora32
- name: "Fedora 33"
runner: ubuntu-latest
dockerfile: fedora33
- name: "Fedora 34"
runner: ubuntu-latest
dockerfile: fedora34
- name: "Fedora 35"
runner: ubuntu-latest
dockerfile: fedora35
- name: "Debian Testing"
runner: ubuntu-latest
dockerfile: debiantesting
- name: "Ubuntu 18.04"
runner: ubuntu-latest
dockerfile: ubuntu1804
- name: "Ubuntu 20.04"
runner: ubuntu-latest
dockerfile: ubuntu2004
- name: "Ubuntu 21.04"
runner: ubuntu-latest
dockerfile: ubuntu2104
- name: "Ubuntu 21.10"
runner: ubuntu-latest
dockerfile: ubuntu2110
- name: "OpenSUSE 15"
runner: ubuntu-latest
dockerfile: opensuse15
- name: "Archlinux Base (Rolling)"
runner: ubuntu-latest
dockerfile: arch
- name: "Mac OS X 10.13"
runner: macos-latest
dockerfile: osx
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.continue-on-error == true }}
steps:
- uses: actions/checkout@v2
- name: Build ${{ matrix.name }}
env:
DOCKER_REGISTRY: docker.pkg.github.com
DOCKER_CACHE_IMAGE: docker.pkg.github.com/${{ github.repository }}/taskwarrior_cache
GITHUB_USER: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}
run: if [[ $CONTAINER != "osx" ]]; then docker-compose build test-$CONTAINER ; fi
- name: Test ${{ matrix.name }}
run: if [[ $CONTAINER != "osx" ]]; then docker-compose run test-$CONTAINER; else bash test/scripts/test_osx.sh ; fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}

3
.github/CODEOWNERS vendored
View File

@@ -1,3 +0,0 @@
taskchampion/* @dbr @djmitche
Cargo.toml @dbr @djmitche
Cargo.lock @dbr @djmitche

View File

@@ -1 +0,0 @@
Please see the ["Contributing to Taskwarrior"](https://github.com/GothenburgBitFactory/taskwarrior/tree/develop/doc/devel/contrib) section of the developer documentation.

View File

@@ -1,18 +0,0 @@
version: 2
updates:
# Enable version updates for GitHub actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Enable updates for Rust packages
- package-ecosystem: "cargo"
directory: "/" # Location of package manifests
schedule:
interval: "daily"
ignore:
# skip patch updates, as they can be quite noisy, but keep
# minor and major updates so that we don't fall too far
# behind
- dependency-name: "*"
update-types: ["version-update:semver-patch"]

View File

@@ -1,11 +0,0 @@
#### Description
Replace this text with a description of the PR.
#### Additional information...
- [ ] I changed C++ code or build infrastructure.
Please run the test suite and include the output of `cd test && ./problems`.
- [ ] I changed Rust code or build infrastructure.
Please run `cargo test` and address any failures before submitting.

View File

@@ -1,115 +0,0 @@
name: checks
on:
push:
branches:
- develop
pull_request:
types: [opened, reopened, synchronize]
jobs:
clippy:
runs-on: ubuntu-latest
name: "Check & Clippy"
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "1.70.0" # MSRV
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: check
- run: rustup component add clippy
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
name: "Clippy Results"
mdbook:
runs-on: ubuntu-latest
name: "Documentation"
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
with:
# if this changes, change it in .github/workflows/publish-docs.yml as well
mdbook-version: '0.4.10'
- run: mdbook test taskchampion/docs
- run: mdbook build taskchampion/docs
fmt:
runs-on: ubuntu-latest
name: "Formatting"
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
profile: minimal
components: rustfmt
toolchain: stable
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: fmt
args: --all -- --check
codegen:
runs-on: ubuntu-latest
name: "codegen"
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "1.70.0" # MSRV
override: true
- uses: actions-rs/cargo@v1.0.3
with:
command: run
args: --package xtask -- codegen
- name: check for changes
run: |
if ! git diff; then
echo "Generated code not up-to-date;
run `cargo run --package xtask -- codegen` and commit the result";
exit 1;
fi

View File

@@ -1,53 +0,0 @@
name: Taskwarrior Docker image
on:
workflow_dispatch:
workflow_run:
workflows: [tests]
branches:
- develop
- stable
types:
- completed
env:
REGISTRY: "ghcr.io"
jobs:
build-and-push-docker-image:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: "recursive"
- name: Install cosign
uses: sigstore/cosign-installer@v3.5.0
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Taskwarrior Docker image
id: build-and-push
uses: docker/build-push-action@v5.3.0
with:
context: .
file: "./docker/task.dockerfile"
push: true
tags: ${{ env.REGISTRY }}/${{ github.actor }}/task:${{ github.ref_name }}
- name: Sign the published Docker image
env:
COSIGN_EXPERIMENTAL: "true"
run: cosign sign ${{ env.REGISTRY }}/${{ github.actor }}/task@${{ steps.build-and-push.outputs.digest }}

View File

@@ -1,31 +0,0 @@
name: docs
on:
push:
branches:
- develop
permissions:
contents: write
jobs:
mdbook-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v2
with:
# if this changes, change it in .github/workflows/checks.yml as well
mdbook-version: '0.4.10'
- run: mdbook build taskchampion/docs
- name: Deploy
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./taskchampion/docs/book
destination_dir: taskchampion

View File

@@ -1,83 +0,0 @@
name: tests - rust
on:
push:
branches:
- develop
pull_request:
types: [opened, reopened, synchronize]
jobs:
## Run the `taskchampion` crate's tests with various combinations of features.
features:
strategy:
matrix:
features:
- ""
- "server-sync"
name: "taskchampion ${{ matrix.features == '' && 'with no features' || format('with features {0}', matrix.features) }}"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ubuntu-latest-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ubuntu-latest-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: test
run: cargo test -p taskchampion --no-default-features --features "${{ matrix.features }}"
## Run all TaskChampion crate tests, using both the minimum supported rust version
## and the latest stable Rust.
test:
strategy:
matrix:
rust:
- "1.70.0" # MSRV
- "stable"
os:
- ubuntu-latest
- macOS-latest
- windows-latest
name: "rust ${{ matrix.rust }} on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "${{ matrix.rust }}"
override: true
- name: test
run: cargo test

View File

@@ -1,20 +0,0 @@
name: security
on:
schedule:
- cron: '0 0 * * *'
push:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
jobs:
audit:
runs-on: ubuntu-latest
permissions: write-all
name: "Audit Rust Dependencies"
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@master
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,108 +0,0 @@
## Run the Taskwarrior tests, using stable rust to build TaskChampion.
name: tests
on: [push, pull_request]
jobs:
# MacOS tests do not run in Docker, and use the actions-rs Rust installaction
tests-macos-12:
name: tests (Mac OS 12.latest)
if: false # see #3242
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable"
override: true
- name: Test MacOS
run: bash test/scripts/test_macos.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tests-macos-13:
name: tests (Mac OS 13.latest)
if: false # see #3242
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo build
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable"
override: true
- name: Test MacOS
run: bash test/scripts/test_macos.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tests:
strategy:
fail-fast: false
matrix:
include:
- name: "Fedora 38"
runner: ubuntu-latest
dockerfile: fedora38
- name: "Fedora 39"
runner: ubuntu-latest
dockerfile: fedora39
- name: "Debian Testing"
runner: ubuntu-latest
dockerfile: debiantesting
- name: "Ubuntu 20.04"
runner: ubuntu-latest
dockerfile: ubuntu2004
- name: "Ubuntu 22.04"
runner: ubuntu-latest
dockerfile: ubuntu2204
- name: "OpenSUSE Tumbleweed (Rolling)"
runner: ubuntu-latest
dockerfile: opensuse
- name: "Archlinux Base (Rolling)"
runner: ubuntu-latest
dockerfile: arch
runs-on: ${{ matrix.runner }}
continue-on-error: ${{ matrix.continue-on-error == true }}
steps:
- uses: actions/checkout@v4
- name: Build ${{ matrix.name }}
env:
DOCKER_REGISTRY: docker.pkg.github.com
DOCKER_CACHE_IMAGE: docker.pkg.github.com/${{ github.repository }}/taskwarrior_cache
GITHUB_USER: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}
run: docker-compose build test-$CONTAINER
- name: Test ${{ matrix.name }}
run: docker-compose run test-$CONTAINER
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CONTAINER: ${{ matrix.dockerfile }}

15
.gitignore vendored
View File

@@ -1,15 +1,22 @@
cmake.h
commit.h
Makefile
src/task
src/taskd
src/libtask.a
src/commands/libcommands.a
src/columns/libcolumns.a
*~
.*.swp
Session.vim
package-config/osx/binary/task
/build*/
CMakeFiles
CMakeCache.txt
cmake_install.cmake
install_manifest.txt
_CPack_Packages
CPackConfig.cmake
CPackSourceConfig.cmake
patches
*.exe
tutorials
.prove
/target/
/.idea/

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "src/libshared"]
path = src/libshared
url = https://github.com/GothenburgBitFactory/libshared.git
[submodule "src/tc/corrosion"]
path = src/tc/corrosion
url = https://github.com/corrosion-rs/corrosion.git

View File

@@ -1,22 +1,16 @@
cmake_minimum_required (VERSION 3.22)
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (task
VERSION 3.0.1
DESCRIPTION "Taskwarrior - a command-line TODO list manager"
HOMEPAGE_URL https://taskwarrior.org/)
cmake_minimum_required (VERSION 3.5)
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
include (FetchContent)
include (CheckFunctionExists)
include (CheckStructHasMember)
set (HAVE_CMAKE true)
project (task)
include (CXXSniffer)
set (PROJECT_VERSION "2.6.2")
OPTION (ENABLE_WASM "Enable 'wasm' support" OFF)
if (ENABLE_WASM)
@@ -24,6 +18,15 @@ if (ENABLE_WASM)
set(CMAKE_EXECUTABLE_SUFFIX ".js")
endif (ENABLE_WASM)
OPTION (ENABLE_SYNC "Enable 'task sync' support" ON)
if (ENABLE_SYNC)
set (USE_GNUTLS ON CACHE BOOL "Build gnutls support." FORCE)
else (ENABLE_SYNC)
set (USE_GNUTLS OFF CACHE BOOL "Build gnutls support." FORCE)
message (WARNING "ENABLE_SYNC=OFF. Not building sync support.")
endif (ENABLE_SYNC)
message ("-- Looking for libshared")
if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src)
message ("-- Found libshared")
@@ -63,8 +66,19 @@ SET (TASK_DOCDIR share/doc/task CACHE STRING "Installation directory for doc fi
SET (TASK_RCDIR "${TASK_DOCDIR}/rc" CACHE STRING "Installation directory for configuration files")
SET (TASK_BINDIR bin CACHE STRING "Installation directory for the binary")
# rust libs require these
set (TASK_LIBRARIES dl pthread)
if (USE_GNUTLS)
message ("-- Looking for GnuTLS")
find_package (GnuTLS)
if (GNUTLS_FOUND)
set (HAVE_LIBGNUTLS true)
set (TASK_INCLUDE_DIRS ${TASK_INCLUDE_DIRS} ${GNUTLS_INCLUDE_DIR})
set (TASK_LIBRARIES ${TASK_LIBRARIES} ${GNUTLS_LIBRARIES})
endif (GNUTLS_FOUND)
endif (USE_GNUTLS)
if (ENABLE_SYNC AND NOT GNUTLS_FOUND)
message (FATAL_ERROR "Cannot find GnuTLS. Use -DENABLE_SYNC=OFF to build Taskwarrior without sync support. See INSTALL for more information.")
endif (ENABLE_SYNC AND NOT GNUTLS_FOUND)
check_function_exists (timegm HAVE_TIMEGM)
check_function_exists (get_current_dir_name HAVE_GET_CURRENT_DIR_NAME)
@@ -129,11 +143,6 @@ if (SOLARIS)
endif (NSL_LIBRARY)
endif (SOLARIS)
# Disable the Clang return-type-c-linkage warning globally. See #3225.
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-return-type-c-linkage")
endif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
message ("-- Configuring cmake.h")
configure_file (
${CMAKE_SOURCE_DIR}/cmake.h.in
@@ -141,7 +150,6 @@ configure_file (
add_subdirectory (src)
add_subdirectory (src/commands)
add_subdirectory (src/tc)
add_subdirectory (src/columns)
add_subdirectory (doc)
add_subdirectory (scripts)
@@ -152,7 +160,7 @@ if (EXISTS performance)
add_subdirectory (performance EXCLUDE_FROM_ALL)
endif (EXISTS performance)
set (doc_FILES ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
set (doc_FILES NEWS ChangeLog README.md INSTALL AUTHORS COPYING LICENSE)
foreach (doc_FILE ${doc_FILES})
install (FILES ${doc_FILE} DESTINATION ${TASK_DOCDIR})
endforeach (doc_FILE)
@@ -169,15 +177,15 @@ add_custom_command(OUTPUT run-reproduce
)
add_custom_target(reproduce DEPENDS run-reproduce)
add_custom_command(OUTPUT show-problems
COMMAND cd test && ./problems
)
add_custom_target(problems DEPENDS show-problems)
# ---
set (CPACK_SOURCE_GENERATOR "TGZ")
set (CPACK_SOURCE_PACKAGE_FILE_NAME ${PACKAGE_NAME}-${PACKAGE_VERSION})
set (CPACK_SOURCE_IGNORE_FILES "build" "test" "misc/*" "performance" "swp$" "src/lex$" "task-.*.tar.gz"
"commit.h" "cmake.h$" "\\\\.gitmodules" "src/libshared/\\\\.git" ".github/" ".*\\\\.gitignore$" "docker-compose.yml" "\\\\.git/")
set (CPACK_SOURCE_IGNORE_FILES "CMakeCache" "CMakeFiles" "CPackConfig" "CPackSourceConfig"
"_CPack_Packages" "cmake_install" "install_manifest" "Makefile$"
"test" "package-config" "misc/*" "src/task$" "src/calc$" "performance"
"src/libtask.a" "src/columns/libcolumns.a" "src/commands/libcommands.a"
"swp$" "src/lex$" "task-.*.tar.gz" "commit.h" "cmake.h$" "\\\\.gitmodules"
"src/libshared/\\\\.git" ".github/" ".*\\\\.gitignore$"
"src/liblibshared.a" "docker-compose.yml" "\\\\.git/")
include (CPack)

2334
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
[workspace]
members = [
"taskchampion/taskchampion",
"taskchampion/lib",
"taskchampion/integration-tests",
"taskchampion/xtask",
]
resolver = "2"
# src/tc/rust is just part of the TW build and not a public crate
exclude = [ "src/tc/rust" ]
# All Rust dependencies are defined here, and then referenced by the
# Cargo.toml's in the members with `foo.workspace = true`.
[workspace.dependencies]
anyhow = "1.0"
byteorder = "1.5"
cc = "1.0.73"
chrono = { version = "^0.4.22", features = ["serde"] }
ffizz-header = "0.5"
flate2 = "1"
google-cloud-storage = { version = "0.15.0", default-features = false, features = ["rustls-tls", "auth"] }
lazy_static = "1"
libc = "0.2.136"
log = "^0.4.17"
pretty_assertions = "1"
proptest = "^1.4.0"
ring = "0.17"
rstest = "0.17"
rusqlite = { version = "0.29", features = ["bundled"] }
serde_json = "^1.0"
serde = { version = "^1.0.147", features = ["derive"] }
strum = "0.25"
strum_macros = "0.25"
tempfile = "3"
tokio = { version = "1", features = ["rt-multi-thread"] }
thiserror = "1.0"
ureq = { version = "^2.9.0", features = ["tls"] }
uuid = { version = "^1.8.0", features = ["serde", "v4"] }
url = { version = "2" }

View File

@@ -1,71 +1,4 @@
------ current release ---------------------------
3.0.1 -
- Fix an error in creation of the 3.0.0 tarball which caused builds to fail (#3302)
- Improvements to `task news`, including notes for the 3.0.0 release
- Minor improvements to documentation and error handling
- Fix incorrect task ID of 0 when using hooks (#3339)
- Issue a warning if .data files remain (#3321)
------ old releases ------------------------------
3.0.0 -
- [BREAKING CHANGE] the sync functionality has been rewritten entirely, and
no longer supports taskserver/taskd. Instead, the recommended solution is
a cloud-storage backend, although `taskchampion-sync-server` is also
available.
See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to
Taskwarrior 3.0.
The following config options are no longer supported:
- `debug.tls`
- `taskd.ca`
- `taskd.certificate`
- `taskd.ciphers`
- `taskd.credentials`
- `taskd.key`
- `taskd.server`
- `taskd.trust`
The Taskwarrior build no longer requires GnuTLS. The build option
`ENABLE_SYNC=OFF` is also no longer supported; sync support is always built
in.
Deep thanks to the following for contributions to this work:
- Akash Shanmugaraj
- Andrew Savchenko
- Dathan Bennett
- Dustin Mitchell
- dbr/Ben
- Felix Schurk
- Isaac Wyatt
- Nathan Luong
- Nikos Koukis
- Pablo Baeyens
- Ravi Sawlani
- ryneeverett
- Simon Fraser
- TW #2732 Fix urgency inheritance for negative-urgency tasks.
Thanks to Jackson Abascal for contributing.
- TW #2763 `task show` now shows `hooks.location.
Thanks to rollniak for reporting and sec65 for contributing.
- TW #2765 Fix leading space before urgency value in `task info`.
Thanks to Dominik Rehák for contributing.
- TW #2780 Fix formatting of countdown-style dates
Thanks to Dominik Rehák for contributing.
- TW #2826 Fix issue with filter not applied correctly during `task _tags` command
Thanks to Nikos Koukis for contributing.
- TW #3052 Parsing of timestamp values now uses 64-bit integers, avoiding
issues in the year 2032.
Thanks to Bernhard M. Wiedemann for contributing.
- TW #3068 Fix fish completion.
Thanks to Michal Koutný for contributing.
2.6.2 -
- TW #502 Sequence of IDs doesn't work with attribute "depends"
@@ -242,6 +175,8 @@
Thanks to bharatvaj for contributing.
- TW #2581 Config entry with a trailing comment cannot be modified
------ old releases ------------------------------
2.5.3 (2021-01-05) - 2f47226f91f0b02f7617912175274d9eed85924f
- #2375 task hangs then dies when certain tasks are present in a report

View File

@@ -1 +1,146 @@
See [Developing Taskwarrior](./doc/devel/contrib/README.md).
# How to Build Taskwarrior
## Satisfy the Requirements:
* CMake 3.0 or later
* gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support
* libuuid (if not on macOS)
* gnutls (optional)
* python 3 (optional, for running the test suite)
## Obtain and build code:
```
$ git clone --recursive https://github.com/GothenburgBitFactory/taskwarrior taskwarrior.git
$ cd taskwarrior.git
$ git checkout develop # Latest dev branch
$ git submodule init # This is now done by cmake as a test
$ git submodule update # Update the libhsared.git submodule
$ cmake -DCMAKE_BUILD_TYPE=debug . # debug or release. Default: neither
$ make VERBOSE=1 -j4 # Shows details, builds using 4 jobs
# Alternately 'export MAKEFLAGS=-j 4'
```
## Running Test Suite:
```
$ cd test
$ make VERBOSE=1 # Shows details
$ ./run_all # Runs all tests silently > all.log
$ ./problems # Enumerate test failures in all.log
```
Note that any development should be performed using a git clone, and the
current development branch. The source tarballs do not reflect HEAD, and do
not contain the test suite.
If you send a patch (support@gothenburgbitfactory.org), make sure that patch is made
against git HEAD on the development branch. We cannot apply patches made
against the tarball source, or master.
# General Statement
This file is intended to convey the current efforts, priorities and needs of
the code base. It is for anyone looking for a way to start contributing.
Here are many ways to contribute that may not be obvious:
* Use Taskwarrior, become familiar with it, and make suggestions. There are
always ongoing discussions about new features and changes to existing
features.
* Join us in the #taskwarrior IRC channel on freenode.net or libera.chat.
Many great ideas, suggestions, testing and discussions have taken place
there. It is also the quickest way to get help, or confirm a bug.
* Review documentation: there are man pages, online articles, tutorials and
so on, and these may contain errors, or they may not convey ideas in the
best way. Perhaps you can help improve it. Contact us - documentation is
a separate effort from the code base, and includes all web sites, and all
are available as git repositories.
* Take a look at the bug database, and help triage the bug list. This is a
review process that involves confirming bugs, providing additional data,
information or analysis. Bug triage is very useful and much needed. You
could check to see that an old bug is still relevant - sometimes they are
not.
* Review the source code, and point out inefficiencies, problems, unreadable
functions, bugs and assumptions.
* Fix a bug. For this you'll need C++ and Git skills. We welcome all bug
fixes, provided the work is done well and doesn't create other problems or
introduce new dependencies. We recommend talking to us before starting.
Seriously.
* Add unit tests. Unit tests are possibly the most useful contributions of
all, because they not only improve the quality of the code, but prevent
future regressions, therefore maintaining quality of subsequent releases.
Plus, broken tests are a great motivator for us to fix the causal defect.
You'll need Python skills.
* Add a feature. Well, let's be very clear about this: adding a feature is
not usually well-received, and if you add a feature and send a patch, it
will most likely be rejected. The reason for this is that there are many
efforts under way, in various code branches. There is a very good chance
that the feature you add is either already in progress, or being done in a
way that is more fitting when considering other work in progress. So if
you want to add a feature, please don't. Start by talking to us, and find
out what is currently under way or planned. You might find that we've
already rejected such a feature for some very good reasons. So please
check first, so we don't duplicate effort or waste anyone's time.
* Spread the word. Help others become more effective at managing tasks.
* Encouragement. Tell us what works for you, and what doesn't. Tell us about
your methodology for managing tasks. It's all useful information.
* Request a feature. This not only tells us that you think something is
missing from the software, but gives us insights into how you use it.
Plus, you might get your feature implemented.
# Unit Tests Needed
There are always more unit tests needed. More specifically, better unit tests
are always needed. The convention is that there are four types of unit test:
1. High level tests that exercise large features, or combinations of commands.
For example, dependencies.t runs through a long list of commands that test
dependencies, but do so by using 'add', 'modify', 'done' and 'delete'.
1. Regression tests that ensure certain bugs are fixed and stay fixed. These
tests are named tw-NNNN.t where NNNN refers to the bug number. While it is
not worth creating tests for small fixes like typos, it is for logic
changes.
1. Small feature tests. When small features are added, we would like small,
low-level feature tests named feature.t, with a descriptive name and
focused tests.
1. Code tests. These are tests written in C++ that exercise C++ objects, or
function calls. These are the lowest level tests. It is important that
these kind of tests be extensive and thorough, because the software depends
on this code the most.
The tests are written in Python, Bash and C++, and all use TAP.
## Tests needed
* Take a look at the bug database (https://github.com/GothenburgBitFactory/taskwarrior/issues)
and notice that many issues, open and closed, have the "needsTest" label.
These are things that we would like to see in the test suite, as regression
tests.
All new unit tests should follow the test/template.t standard.
# Patches
Patches are encouraged and welcomed. Either send a pull request on Github or
email a patch to support@taskwarrior.org. A good patch:
* Maintains the MIT license, and does not contain code lifted from other
sources. You will have written 100% of the code in the patch, otherwise
we cannot maintain the license.
* Precisely addresses one issue only.
* Doesn't break unit tests. This means yes, run the unit tests.
* Doesn't introduce dependencies.
* Is accompanied by new or updated unit tests, where appropriate.
* Is accompanied by documentation changes, where appropriate.
* Conforms to the prevailing coding standards - in other words, it should
fit in with the existing code.
A patch may be rejected for violating any of the above rules, and more.
Bad patches may be accepted and modified depending on work load and mood. It
is possible that a patch may be rejected because it conflicts in some way with
plans or upcoming changes. Check with us first, before sinking time and effort
into a patch.

17
INSTALL
View File

@@ -20,6 +20,7 @@ You will need a C++ compiler that supports full C++17, which includes:
You will need the following libraries:
- libuuid (not needed for OSX)
- gnutls (can be optional - see '"sync" command' below)
Basic Installation
@@ -88,6 +89,20 @@ get absolute installation directories:
CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5
"sync" command
--------------
By default, GnuTLS support is required, which enables the "sync" command.
For Debian based distributions, installing "libgnutls-dev" is sufficient.
In order to build Taskwarrior without "sync" support, call cmake with the
"-DENABLE_SYNC=OFF" flag:
$ cmake . -DENABLE_SYNC=OFF
and proceed as described in "Basic Installation".
Uninstallation
--------------
@@ -146,7 +161,7 @@ OpenBSD
WASM
Using the Emscripten compiler, you can achieve it like this:
cmake -DCMAKE_CXX_COMPILER=emcc -DCMAKE_BUILD_TYPE=release -DENABLE_WASM=ON \
cmake -DCMAKE_CXX_COMPILER=emcc -DENABLE_SYNC=OFF -DCMAKE_BUILD_TYPE=release -DENABLE_WASM=ON \
-DCMAKE_EXE_LINKER_FLAGS="-m32 -s NO_DYNAMIC_EXECUTION=1 -s WASM=1 -s NO_EXIT_RUNTIME=1 -s INVOKE_RUN=0" \
-DCMAKE_CXX_FLAGS_RELEASE="-O2 -m32"

152
NEWS Normal file
View File

@@ -0,0 +1,152 @@
New Features in Taskwarrior 2.6.0
- The logic behind new-uuid verbosity option changed. New-uuid now overrides
new-id if set and will cause Taskwarrior to display UUIDs instead of IDs
for new tasks (machine friendly).
- If ~/.taskrc is not found, Taskwarrior will look for its configuration in
$XDG_CONFIG_HOME/task/taskrc (defaulting to ~/.config/task/taskrc). This
allows users to setup their Taskwarrior to follow XDG standard without
using config overrides.
- Newer Unicode characters, such as emojis are correctly handled and displayed.
Taskwarrior now supports all Unicode characters up to Unicode 12.
- Datetime values until year 9999 are now supported.
Duration values of up to 1 000 000 years are now supported.
- 64-bit numeric values (up to 9,223,372,036,854,775,807) are now supported.
- Later/someday named datetime values now resolve to 9999-12-30 (instead of
2038-01-18).
- Calendar now supports displaying due dates until year 9999.
- Calendar now displays waiting tasks with due dates on the calendar.
- Calendar supports highlighting days with scheduled tasks.
- Multi-day holidays are now supported.
- Holiday data files for fr-CA, hu-HU, pt-BR, sk-SK and sv-FI locales are now
generated and shipped with Taskwarrior.
- The task edit command can now handle multi-line annotations and UDAs in a
user friendly way, withouth having to handle with JSON escaping of special
chars.
- A large portion of currently known parser-related issues was fixed.
- The taskrc file now supports relative paths, which are evaluated with
respect to (a) current directory, (b) taskrc directory and (c) now also the
installation directory of configuration files.
- The currently selected context is now applied for "task add" and "task log"
commands. Section on contexts in the manpage was updated to describe this
functionality.
- Users can specify per-context specific overrides of configuration variables.
- The `task import` command can now accept annotations with missing entry
values. Current time will be assumed.
- The new 'by' filter attribute modifier compares using '<=' rather than '<'
as 'before' uses. This allows the last second of the day to match with
'due.by:eod', which it would not otherwise. It also works with
whole units like days, e.g. 'add test due:2021-07-17' would not match
'due.before:tomorrow' (on the 16th), but would match 'due.by:tomorrow'.
- Waiting is now an entirely "virtual" concept, based on a task's
'wait' property and the current time. Task is considered "waiting" if its
wait attribute is in the future. TaskWarrior no longer explicitly
"unwaits" a task (the wait attribute is not removed once its value is in
the past), so the "unwait' verbosity token is no longer available.
This allows for filtering for tasks that were waiting in the past
intervals, but are not waiting anymore.
- The configuration file now supports environment variables.
- Taskwarrior can now handle displaying tasks in windows with limited width,
even if columns contain long strings (like URLs).
- The nag message is emitted at most once per task command, even with bulk
operations. Additionally, the urgency of the task considered is taken
before the completion, not after.
- The export command now takes an optional argument that references an
existing report. As such, "task export <report>" command will export
the same tasks that "task <report>" prints, and in the same order.
- The burndown command now supports non-cumulative display, where tasks only
get plotted within the interval segment when they got completed.
New Commands in Taskwarrior 2.6.0
- The 'news' command will guide the user through important release notes
anytime a new version of Taskwarrior is installed. It provides personalized
feedback, deprecation warnings and usage advice, where applicable.
New Configuration Options in Taskwarrior 2.6.0
- The context definitions for reporting commmands are now stored in
"context.<name>.read". Context definitions for write commands are now
supported using "context.<name>.write" configuration variable.
- The context-specific configuration overrides are now supported. Use
context.<name>.rc.<key>=<value> to override, such as
context.work.rc.urgency.blocking=5.0 to override the value of urgency.blocking
when the 'work' context is active.
- Each report (and the timesheet command) can explicitly opt-out from the
currently active context by setting the report.<name>.context variable to 0
(defaults to 1). Useful for defining universal reports that ignore
currently set context, such as 'inbox' report for GTD methodology.
- Multi-day holidays are now supported. Use holiday.<name>.start=<date> and
holiday.<name>.end=<date> to specify a range-based holiday, such as a
vacation.
- Verbosity token 'default' was introduced in order to display information
about default actions.
- The new burndown.cumulative option can be used to toggle between
non-cumulative and cumulative version of the burndown command.
- The new color.calendar.scheduled setting can be used to control the
highlighting color of days in the calendar that have scheduled tasks.
Newly Deprecated Features in Taskwarrior 2.6.0
- The 'PARENT' and 'CHILD' virtual tags are replaced by 'TEMPLATE' and 'INSTANCE'.
- The 'waiting' status is now deprecated. We recommend using +WAITING virtual tag
or wait-attribute based filters, such as 'wait.before:eow' instead.
- The configuration variable 'monthsperline' is deprecated. Please use
'calendar.monthsperline' instead.
Fixed regressions in 2.6.0
- The "end of <date>" named dates ('eod', 'eow', ...) were pointing to the
first second of the next day, instead of last second of the referenced
interval. This was a regression introduced in 2.5.2.
- The "eow" and "eonw" were using a different weekday as a reference. This
was a regeression introduced in 2.5.2.
- The rc.verbose=<value> configuration override was applied only if it were
the first configuration override. In #2247, this manifested itself as
inability to supress footnotes about the overrides, and in #1953 as failure
to force task to display UUIDs of on task add. This was a regression
introduced in 2.5.2.
- The attribute values of the form "<attribute name>-<arbitrary string>", for
example "due-nextweek" or "scheduled-work" would fail to parse (see
#1913). This was a regression introduced in 2.5.1.
- The capitalized versions of named dates (such as Monday, February or
Tomorrow) are again supported. This was a regression introduced in 2.5.2.
- The duration periods are converted to datetime values using the
current time as the anchor, as opposed to the beginning of unix time.
This was a regression in 2.5.2.
- Filtering for attribute values containing dashes and numbers (such as
'vs.2021-01', see #2392) or spaces (such as "Home renovation", see #2388)
is again supported. This was a regression introduced in 2.4.0.
Removed Features in 2.6.0
-
Other notable changes in 2.6.0
- C++17 compatible compiler is now required (GCC 7.1 or older / clang 5.0 or older).
Known Issues
- https://github.com/GothenburgBitFactory/taskwarrior
Taskwarrior 2.6.0 has been built and tested on the following configurations:
* Archlinux
* OpenSUSE
* macOS 10.15
* Fedora (31, 32, 33, 34)
* Ubuntu (18.04, 20.04, 21.04)
* Debian (Stable, Testing)
* CentOS (7, 8)
However, we expect Taskwarrior to work on other platforms as well.
---
While Taskwarrior has undergone testing, bugs are sure to remain. If you
encounter a bug, please enter a new issue at:
https://github.com/GothenburgBitFactory/taskwarrior

View File

@@ -6,6 +6,7 @@
[![Release date](https://img.shields.io/github/release-date/GothenburgBitFactory/taskwarrior)](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/GothenburgBitFactory?color=green)](https://github.com/sponsors/GothenburgBitFactory/)
</br>
[![Twitter](https://img.shields.io/twitter/follow/taskwarrior?style=social)](https://twitter.com/taskwarrior)
</div>
## Taskwarrior
@@ -18,8 +19,16 @@ features](https://taskwarrior.org/docs/), developed as a portable open source pr
with an active and quite vast [ecosystem of tools, hooks and
extensions](https://taskwarrior.org/tools/).
### HEADS UP!
### This fork is to intended to preserve the taskwarrior 2.6.2 codebase, and to allow for further fixes and improvements!!
It's great to see that taskwarrior is still an active project and the active developers, working on version 3.x, have a vision that fuses taskchampion and an SQL database. It's cool, and in rust, and everything.. but they are decidedly uninterested in further work on the 2.x (data as text files) codebase.
It was the fact that task used a text-file to store data, that first hooked me to the project. I'm almost certainly first taskwarrior user, and technically the original taskwarrior Designer! I worked with the orginal programmer, Paul Beckingham, as he brilliantly implemented so many of my crazy ideas, like colors, urgency, reports, UDAs, attribute modifiers and SO much more!
Because I'm an old fart, set im my ways like that, and kind of squeamish about keeping my task data as a SQL database, this repo is somewhare I can try to apply fixes and cherry-pick from changes made after official 2.6.2 releases, and to curate some of the things that I think belong with it. That said, I'm a terrible programmer, a dubious developer, and would welcome input, ideas and contributions.
## Install
[![Arch](https://img.shields.io/archlinux/v/extra/x86_64/task)](https://archlinux.org/packages/extra/x86_64/task/)
[![Arch](https://img.shields.io/archlinux/v/community/x86_64/task)](https://archlinux.org/packages/community/x86_64/task/)
[![Debian](https://img.shields.io/debian/v/task/testing)](https://packages.debian.org/search?keywords=task&searchon=names&suite=all&section=all)
[![Fedora](https://img.shields.io/fedora/v/task)](https://bodhi.fedoraproject.org/updates/?packages=task)
[![Homebrew](https://img.shields.io/homebrew/v/task)](https://formulae.brew.sh/formula/task#default)
@@ -30,13 +39,17 @@ Windows](https://taskwarrior.org/download/). Check out the latest available
packages in repositories of your OS distribution of choice [on
Repology](https://repology.org/project/taskwarrior/versions).
Alternatively, you can [build Taskwarrior from source](doc/devel/contrib).
Alternatively, you can build Taskwarrior from source.
## Documentation
The [online documentation](https://taskwarrior.org/docs), downloads, news and
more are available on our website, [taskwarrior.org](https://taskwarrior.org).
We also recommend following [@taskwarrior on
Twitter](https://twitter.com/taskwarrior), where we share info about new
features, releases and various tips and tricks for new Taskwarrior users.
## Community
[![Twitter](https://img.shields.io/twitter/follow/taskwarrior?style=social)](https://twitter.com/taskwarrior)
[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/taskwarrior?style=social)](https://reddit.com/r/taskwarrior/)
@@ -51,8 +64,54 @@ Github](https://github.com/GothenburgBitFactory/taskwarrior/discussions). For
other support options, take a look at
[taskwarrior.org/support](https://taskwarrior.org/support)
For code contributions, please use pull requests.
See [Contributing to Taskwarrior](doc/devel/contrib) for more details.
For code contributions, please use pull requests, or alternately send your code patches to
[support@gothenburgbitfactory.org](mailto:support@gothenburgbitfactory.org)
## Branching Model
We use the following branching model:
* `stable` is a branch containing the content of the latest release. Building
from here is the same as building from the latest tarball, or installing a
binary package. No development is done on the `stable` branch.
* `develop` is the current development branch. All work is done here, and upon
release it will be merged to `stable`. While development branch is not
stable, we utilize CI to ensure we're at least not merging improvements that
break existing tests, and hence should be relatively safe. We still recommend
making backups when using the development branch.
## Installing
There are many binary packages available, but to install from source requires:
* git
* cmake
* make
* C++ compiler, currently gcc 7.1+ or clang 5.0+ for full C++17 support
* uuid-dev (was libuuid-dev)
* GnuTLS (optional, required for sync)
Download the tarball, and expand it:
$ curl -O https://taskwarrior.org/download/task-2.6.2.tar.gz
$ tar xzf task-2.6.2.tar.gz
$ cd task-2.6.2
Or clone this repository:
$ git clone --recursive -b stable https://github.com/GothenburgBitFactory/taskwarrior.git
$ cd taskwarrior
Then build:
$ cmake -DCMAKE_BUILD_TYPE=release .
...
$ make
...
[$ make test]
...
$ sudo make install
## Contributing
[![Contributors](https://img.shields.io/github/contributors/GothenburgBitFactory/taskwarrior)](https://github.com/GothenburgBitFactory/taskwarrior/graphs/contributors)
@@ -61,7 +120,8 @@ See [Contributing to Taskwarrior](doc/devel/contrib) for more details.
Your contributions are especially welcome.
Whether it comes in the form of code patches, ideas, discussion, bug reports, encouragement or criticism, your input is needed.
See further development documentation in [`doc/devel`](./doc/devel).
Visit [Github](https://github.com/GothenburgBitFactory/taskwarrior) and participate in the future of Taskwarrior.
## Sponsoring
[![GitHub Sponsors](https://img.shields.io/github/sponsors/GothenburgBitFactory?color=green)](https://github.com/sponsors/GothenburgBitFactory/)

View File

@@ -38,6 +38,9 @@
#cmakedefine GNUHURD
#cmakedefine UNKNOWN
/* Found the GnuTLS library */
#cmakedefine HAVE_LIBGNUTLS
/* Found tm_gmtoff */
#cmakedefine HAVE_TM_GMTOFF

View File

@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.22)
cmake_minimum_required (VERSION 3.0)
message ("-- Configuring man pages")
set (man_FILES task-color.5 task-sync.5 taskrc.5 task.1)
foreach (man_FILE ${man_FILES})

View File

@@ -1,9 +0,0 @@
# Documentation
This directory contains Taskwarrior documentation that is built and installed along with the executable:
* [`man`](man/) contains the source for the Taskwarrior manual pages.
* [`rc`](rc/) contains rcfiles that will be installed in `/usr/share/doc/task/rc` or equivalent.
* [`ref`](ref/) contains reference documentation that will be installed in `/usr/share/doc/task` or equivalent.
It also contains [developer documentation](devel/README.md) with a high-level view of how Taskwarrior development is done and how the pieces of the system fit together.

View File

@@ -1,20 +0,0 @@
# Development Documentation
This directory contains the _development_ documentation for Taskwarrior.
For all other documenation, see https://taskwarrior.org.
* [Contributing To Taskwarrior](contrib/README.md)
* [Taskwarrior RFCs](rfcs/README.md)
## Taskwarrior and TaskChampion
As of the 3.0 release, Taskwarrior uses TaskChampion to manage task data.
Find documentation of TaskChampion here:
* [TaskChampion README](../../taskchampion)
* [TaskChampion CONTRIBUTING guide](../../taskchampion/CONTRIBUTING.md)
* [TaskChampion Book](../../taskchampion/docs/src/SUMMARY.md)
* [TaskChampion API Documentation](https://docs.rs/taskchampion)
TaskChampion will [become its own
project](https://github.com/GothenburgBitFactory/taskwarrior/issues/3209) soon.

View File

@@ -1,8 +0,0 @@
# Contributing To Taskwarrior
* [Welcome, Open Source Contributor](first_time.md)
* [Developing Taskwarrior](development.md)
* [Coding Style](coding_style.md)
* [Branching Model](branching.md)
* [Rust and C++](rust-and-c++.md)
* [Releasing Taskwarrior](releasing.md)

View File

@@ -1,13 +0,0 @@
Software development typically requires a standardized branching model, to manage complexity and parallel efforts.
The branching model can be a source of confusion for developers, so this document describes how branching is used.
We use the following branching model:
* `develop` is the current development branch. All work is done here, and upon
release it will be branched to a release branch. While `develop` is not
stable, we utilize CI to ensure we're at least not merging improvements that
break existing tests, and hence should be relatively safe. We still recommend
making backups when using the development branch.
* The most recent minor release is in a branch named after the release, e.g., `2.7.0`.
This branch is used for bug-fixes of the latest release.

View File

@@ -1,31 +0,0 @@
# Coding Style
The coding style used for the Taskwarrior, Taskserver, and other codebases is deliberately kept simple and a little vague.
This is because there are many languages involved (C++, C, Python, sh, bash, HTML, troff and more), and specіfying those would be a major effort that detracts from the main focus which is improving the software.
Instead, the general guideline is simply this:
Make all changes and additions such that they blend in perfectly with the surrounding code, so it looks like only one person worked on the source, and that person is rigidly consistent.
To be a little more explicit:
## C++
- All functionality in C++17 is allowed.
- Indent C++ code using two spaces, no tabs
- Surround operators and expression terms with a space.
This includes a space between a function name and its list of arguments.
- No cuddled braces
- Class names are capitalized, variable names are not
## Python
Follow [PEP8](https://www.python.org/dev/peps/pep-0008/) as much as possible.
## Rust
Rust code should be formatted with `rustfmt` and generally follow Rust style guidelines.

View File

@@ -1,79 +0,0 @@
# Developing Taskwarrior
The following describes the process for developing Taskwarrior. If you are only
changing TaskChampion (Rust code), you can simply treat it like any other Rust
project: modify the source under `taskchampion/` and use `cargo test` to run
the TaskChampion tests.
See the [TaskChampion CONTRIBUTING guide](../../../taskchampion/CONTRIBUTING.md) for more.
## Satisfy the Requirements:
* CMake 3.0 or later
* gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support
* libuuid (if not on macOS)
* Rust 1.64.0 or higher (hint: use https://rustup.rs/ instead of using your system's package manager)
## Install Optional Dependencies:
* python 3 (for running the test suite)
* clangd or ccls (for C++ integration in many editors)
* rust-analyzer (for Rust integration in many editors)
## Obtain and Build Code:
The following documentation works with CMake 3.14 and later.
Here are the minimal steps to get started, using an out of source build directory and calling the underlying build tool over the CMake interface.
See the general CMake man pages or the [cmake-documentation](https://cmake.org/cmake/help/latest/manual/cmake.1.html) for more,
## Basic Building
```sh
git clone https://github.com/GothenburgBitFactory/taskwarrior
cd taskwarrior
cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build
```
Other possible build types can be `Release` and `Debug`.
This will build several executables, but the one you want is probably `src/task`, located in the `build` directory.
When you make changes, just run the last line again.
### Building a specific target
For **only** building the `task` executable, use
```sh
cmake --build build --target task_executable
```
### Building in parallel
If a parallel build is wanted use
```sh
cmake --build build -j <number-of-jobs>
```
### Building with clang as compiler
```sh
cmake -S . -B build-clang\
-DCMAKE_C_COMPILER=clang\
-DCMAKE_CXX_COMPILER=clang++
cmake --build build-clang
```
## Run the Test Suite:
First switch to the test directory:
```
$ cd build/test
```
Then you can run all tests, showing details, with
```
$ make VERBOSE=1
```
Alternately, run the tests with the details hidden in `all.log`:
```
$ ./run_all
```
Either way, you can get a summary of any test failures with:
```
$ ./problems
```
Note that any development should be performed using a git clone, and the current development branch.
The source tarballs do not reflect HEAD, and do not contain the test suite.
Follow the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) for creating a pull request.

View File

@@ -1,68 +0,0 @@
# Welcome, Open Source Contributor
Welcome, potential new Open Source contributor! This is a guide to show you exactly how to make a contribution, and will lead you through the entire process.
There are many people who wish to start contributing, but don't know how or where to start.
If this might be the case for you, then please read on, this guide is for you.
Because we want you to join in the fun with Open Source - it can be fun and rewarding, improve your skills, or just give you a way to contribute back to a project.
## How to Help
Help is needed in all areas of Taskwarrior development - design, coding, testing, support and marketing.
Applicants must be friendly.
Perhaps you are looking to help, but don't know where to start.
Perhaps you have skills we are looking for, here are ways you may be able to help:
- Use Taskwarrior, become familiar with it, and make suggestions.
We get great feedback from both new users and veteran users.
New users have a fresh approach that we can no longer achieve, while veteran users develop clever and crafty ways to use the product.
- Report bugs and odd behavior when you see it.
We don't necessarily know it's broken, unless you tell us.
- Suggest enhancements.
We get lots of these, and it's great.
Some really good ideas have been suggested and implemented.
Sure, some are out of scope, or plain crazy, but the stream of suggestions is fascinating to think about.
- Participate in the [bug tracking](https://github.com/GothenburgBitFactory/taskwarrior/issues) database, to help others and maybe learn something yourself.
- Proofread the documentation and man pages.
- Improve the documentation.
- Improve the man pages.
- Help improve the tutorials.
Make your own tutorial.
- Confirm a bug.
Nothing gets fixed without confirmation.
- Refine a bug.
Provide relevant details, elaborate on the behavior.
- Fix a bug.
Send a patch.
For this you'll need to know some C++ or Rust, and understand the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow).
See [Developing Taskwarrior](./development.md) for more information.
We welcome all bug fixes, provided the work is done well and doesn't create other problems or introduce new dependencies.
We recommend talking to us before starting: we are happy to help you out!
- Write a unit test.
Unit tests are possibly the most useful contributions of all, because they not only improve the quality of the code, but prevent future regressions, therefore maintaining quality of subsequent releases.
Plus, broken tests are a great motivator for us to fix the causal defect.
You'll need Python skills.
- Spread the word.
Help others become more effective at managing tasks.
Share your methodology, to inspire others.
- Encouragement.
Tell us what works for you, and what doesn't.
It's all good.
- Donate! Help offset costs.
Please remember that we need contributions from all skillsets, however small.
Every contribution helps.

View File

@@ -1,24 +0,0 @@
# Releasing Taskwarrior
To release Taskwarrior, follow this process:
- Examine the changes since the last version, and update `src/commands/CmdNews.cpp` accordingly.
There are instructions at the top of the file.
- Create a release PR
- Update version in CMakeLists.txt
- Update Changelog
- get this merged
- On `develop` after that PR merges, create a release tarball:
- `git clone . release-tarball`
- `cd release-tarball/`
- edit `Cargo.toml` to contain only `taskchampion` and `taskchampion-lib` in `members` (see https://github.com/GothenburgBitFactory/taskwarrior/issues/3294).
- `cmake -S. -Bbuild`
- `make -Cbuild package_source`
- copy build/task-*.tar.gz elsewhere and delete the `release-tarball` dir
- NOTE: older releases had a `test-*.tar.gz` but it's unclear how to generate this
- Update `stable` to the released commit and push upstream
- Tag the commit as vX.Y.Z and push the tag upstream
- Find the tag under https://github.com/GothenburgBitFactory/taskwarrior/tags and create a release from it
- Give it a clever title if you can think of one; refer to previous releases
- Include the tarball from earlier
- Add a new item in `content/news` on https://github.com/GothenburgBitFactory/tw.org

View File

@@ -1,42 +0,0 @@
# Rust and C++
Taskwarrior has historically been a C++ project, but as part of an [ongoing effort to replace its storage backend](https://github.com/GothenburgBitFactory/taskwarrior/issues/2770) it now includes a Rust library called TaskChampion.
To develop Taskwarrior, you will need both a [C++ compiler and tools](./development.md), and a [Rust toolchain](https://www.rust-lang.org/tools/install).
However, most tasks will only require you to be familiar with one of the two languages.
## TaskChampion
TaskChampion implements storage and access to "replicas" containing a user's tasks.
It defines an abstract model for this data, and also provides a simple Rust API for manipulating replicas.
It also defines a method of synchronizing replicas and provides an implementation of that method in the form of a sync server.
TaskChampion provides a C interface via the `taskchampion-lib` crate.
Other applications, besides Taskwarrior, can use TaskChampion to manage tasks.
Applications written in Rust can use the `taskchampion` crate, while those in other languages may use the `taskchampion-lib` crate.
Taskwarrior is just one application using the TaskChampion interface.
You can build TaskChampion locally by simply running `cargo build` in the root of this repository.
The implementation, including more documentation, is in the [`rust`](../../rust) subdirectory.
## Taskwarrior's use of TaskChampion
Taskwarrior's interface to TaskChampion has a few layers:
* The skeletal Rust crate in [`src/tc/rust`](../../src/tc/rust) brings the symbols from `taskchampion-lib` under CMake's management.
The corresponding header file is included from [`taskchampion/lib`](../../taskchampion/lib).
All of these symbols are placed in the C++ namespace, `tc::ffi`.
* C++ wrappers for the types from `taskchampion-lib` are defined in [`src/tc`](../../src/tc), ensuring memory safety (with `unique_ptr`) and adding methods corresponding to the Rust API's methods.
The wrapper types are in the C++ namespace, `tc`.
## WARNING About Dependency Tracking
CMake cannot detect changes to Rust files in under the `taskchampion/` directory.
Running `make` after these files are changed will not incorporate the changes into the resulting executables.
To force re-compilation of the Rust dependencies:
```
rm -rf src/tc/rust/x86_64-unknown-linux-gnu/debug/libtc_rust.a
make
```
You may need to adjust the `x86_64-unknown-linux-gnu` part of that command depending on what system you are using for development.

View File

@@ -1,18 +0,0 @@
# Taskwarrior RFCS
In the mid-2010's, Taskwarrior development was organized around RFCs, as a way to invite comment before designs were finalized.
Although these documents were less formal than [IETF RFCs](https://www.ietf.org/rfc) they serve a similar purpose.
In more recent years, use of RFCs has been discontinued, and the documents linked here should be considered historical.
Many were never completely implemented.
* [General Plans](plans.md)
* [Rules System](rules.md)
* [Full DOM Support ](dom.md)
* [Work Week Support](workweek.md)
* [Recurrence](recurrence.md)
* [Taskwarrior JSON Format](task.md)
* [CLI Updates ](cli.md)
* [Taskserver Sync Protocol](protocol.md)
* [Taskserver Message Format](request.md)
* [Taskserver Sync Algorithm](sync.md)
* [Taskserver Client](client.md)

View File

@@ -1,154 +0,0 @@
---
title: "Taskwarrior - Command Line Interface"
---
## Work in Progress
This design document is a work in progress, and subject to change. Once finalized, the feature will be scheduled for an upcoming release.
# CLI Syntax Update
The Taskwarrior command line syntax is being updated to allow more consistent and predictable results, while making room for new features.
Adding support for arbitrary expressions on the command line has become complicated because of the relaxed syntax of Taskwarrior. While the relaxed syntax allows for a very expressive command line, it also creates ambiguity for the parser, which needs to be reduced.
With some limited and careful changes it will be possible to have a clear and unambiguous command line syntax, which means a predictable and deterministic experience.
It should be stated that for straightforward and even current usage patterns, the command line will likely not change for you. Another goal is to not require changes to 3rd-party software, where possible. Only the more advanced and as-yet unintroduced features will require a more strict syntax. This is why now is an ideal time to tighten the requirements.
## Argument Types
The argument types supported remain the same, adding some new constructs.
* Config file override
* `rc:<file>`
* Configuration override
* `rc:<name>:<value>` Literal value
* `rc:<name>=<value>` Literal value
* `rc:<name>:=<value>` Calculated value
* Tag
* `+<tag>`
* `-<tag>`
* `'+tag one'` Multi-word tag
* Attribute modifier
* `rc:<name>.<modifier>:<value>`
* Modifier is one of:
* `before`
* `after`
* `under`
* `over`
* `above`
* `below`
* `none`
* `any`
* `is`
* `isnt`
* `equals`
* `not`
* `contains`
* `has`
* `hasnt`
* `left`
* `right`
* `startswith`
* `endswith`
* `word`
* `noword`
* Search pattern
* `/<pattern>/`
* Substitution
* `/<from>/<to>/`
* `/<from>/<to>/g`
* Command
* `add`
* `done`
* `delete`
* `list`
* etc.
* Separator
* `--`
* ID Ranges
* `<id>[-&ltid>][,<id>[-&ltid>]...]`
* UUID
* `<uuid>`
* Everything Else
* `<word>`
* `'<word> <word> ...'`
## New Command Line Rules
Certain command line constructs will no longer be supported, and this is imposed by the new rules:
1. Each command line argument may contain only one instance of one argument type, unless that type is `<word>`.
task add project:Home +tag Repair the thing # Good
task add project:Home +tag 'Repair the thing' # Good
task add 'project:Home +tag Repair the thing' # Bad
Putting two arguments into one quoted arg makes that arg a `<word>`.
2. If an argument type contains spaces, it must either be quoted or escaped.
task add project:'Home & Garden' ... # Good
task add 'project:Home & Garden' ... # Good
task add project:Home\ \&\ Garden ... # Good
task add project:Home' & 'Garden ... # Good
task add project:Home \& Garden ... # Bad
The parser will not combine multiple arguments, for example:
task '/one two/' list # Good
task /one two/ list # Bad
task /'one two'/ list # Bad, unless ' is part of the pattern
3. By default, *no* calculations are made, unless the `:=` eval operator is used, and if so, the whole argument may need to be quoted or escaped to satisfy Rule 1.
task add project:3.project+x # Literal
task add project:=3.project+x # DOM reference + concatenation
4. Bare word search terms are no longer supported.
Use the pattern type argument instead.
task /foo/ list # Good
task foo list # Bad
5. Expressions must be a series of arguments, not a quoted string.
task urgency \< 5.0 list # Good
task 'urgency < 5.0 list' # Bad
## Other Changes
Aside from the command line parser, there are other changes needed:
- Many online documents will need to be modified.
- Filters will be automatically parenthesized, so that every command line will now looke like:
task [overrides] [(cli-filter)] [(context-filter)] [(report-filter)] command [modifications]
- There will be more errors when the command line is not understood.
- Ambiguous ISO date formats are dropped.
YYYYMMDD # Bad
YYYY-MM-DD # Good
hhmmss # Bad
hh:mm:ss # Good
- The tutorial videos will be even more out of date, and will be replaced by a large number of smaller demo 'movies'.

View File

@@ -1,392 +0,0 @@
---
title: "Taskwarrior - Creating a Taskserver Client"
---
# Creating a Taskserver Client
A Taskserver client is a todo-list manager.
It may be as simple as a program that captures a single task, as complex as Taskwarrior, or anything in between.
It can be a mobile client, a web application, or any other type of program.
This document describes how such a client would interact with the server.
A client to the Taskserver is a program that manages a task list, and wishes to exchange data with the server so that the task list may be shared.
In order to do this, a client must store tasks locally, upload local changes, download remote changes, and apply remote changes to the local tasks.
The client must consider that there may be no network connectivity, or no desire by the user to synchronize.
The client will need proper credentials to talk to the server.
## Requirements
In this document, we adopt the convention discussed in Section 1.3.2 of [RFC1122](https://tools.ietf.org/html/rfc1122#page-16) of using the capitalized words MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, and OPTIONAL to define the significance of each particular requirement specified in this document.
In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement of the specification; "SHOULD" (or "RECOMMENDED") means there may exist valid reasons for ignoring this item, but the full implications should be understood before doing so; and "MAY" (or "OPTIONAL") means that this item is optional, and may be omitted without careful consideration.
## Taskserver Account
A Taskserver account must be created.
This process creates a storage area, and generates the necessary credentials.
## Credentials
A Taskserver client needs the following credentials in order to communicate with a server:
- Server address and port
- Organization name
- User name
- Password
- Certificate
- Key
The server address and port are the network location of the server.
An example of this value is:
foo.example.com:53589
In addition to a DNS name, this can be an IPv4 or IPv6 address.
The organization name is an arbitrary grouping, and is typically 'PUBLIC', reflecting the individual nature of server accounts.
Future capabilities will provide functionality that support groups of users, called an organization.
The user name is the full name.
This will be the name used to identify other users in an organization, in a future release.
Example 'John Doe'.
The password is a text string generated by the server at account creation time.
It should be considered a secret.
The certificate is an X.509 PEM file generated by the server at account creation time.
This is used for authentication.
It should be considered a secret.
The key is an X.509 PEM file generated by the server at account creation time.
This is used for encryption.
It should be considered a secret.
These credentials need to be stored on the client, and used during the sync operation.
## Description of a Taskserver Client
This section describes how a client might behave in order to facilitate integration with the Taskserver.
## Encryption
The Taskserver only communicates using encryption.
Therefore all user data is encrypted while in transit.
The Taskserver currently uses [GnuTLS](https://gnutls.org) to support this encryption, and therefore supports the following protocols:
- SSL 3.0
- TLS 1.0
- TLS 1.1
- TLS 1.2
The client may use any library that supports the above.
## Configuration
The client needs to store configuration, which matches the credentials needed for Taskserver communication.
See section 2.1 "Credentials".
The credentials may not be modified by the user without losing server access.
The server:port data may need to be changed automatically following a redirect response from the server.
See section 5 "Server Errors".
## Local Storage
The client needs to store task data locally.
The client will need to be able to find tasks by their UUID and overwrite them.
Uploaded and downloaded task changes will use the [Taskwarrior Data Interchange Format](/docs/design/task).
## Local Changes
Whenever local data is modified, that change MUST be synced with the server.
But this does not have to occur immediately, in fact the client SHOULD NOT assume connectivity at any time.
A client SHOULD NOT also assume that the server is available.
If the server is not available, the local changes should be retained, and the sync operation repeated later.
Ideally the client will give the user full control over sync operations.
Automatically syncing after all local modifications is not recommended.
If a client performs too many sync operations, the server MAY revoke the certificate.
Effectively, the client should maintain a separate list of tasks changed since the last successful sync operation.
Note that tasks have a "modified" attribute, which should be updated whenever a change is made.
This attribute contributes to conflict resolution on the server.
## Remote Changes
When a server sends remote changes to a client, in the response to a sync request, the changes have already been merged by the server, and therefore the client should simply store them intact.
Based on the UUID in the task, the client can determine whether a task is new (and should be added to the local list of tasks), or whether it represents a modification (and should overwrite it's existing entry).
The client MUST NOT perform any merges.
## Sync Key
Whenever a sync is performed, the server responds by sending a sync key and any remote changes.
The sync key is important, and should be included in the next sync request.
The client is REQUIRED to store the sync key in every server response message.
If a client omits the sync key in a sync message, the response will be a complete set of all tasks and modifications.
## Data Integrity
Although a task is guaranteed to contain at least 'entry', 'description' and 'uuid' attributes, it may also contain other known fields, and unknown user-defined fields.
An example might be an attribute named 'estimate'.
If a task is received via sync that contains an attribute named 'estimate', then a client has the responsibility of preserving the attribute intact.
If that data is shown, then it is assumed to be of type 'string', which is the format used by JSON for all values.
Conversely, if a client wishes to add a custom attribute, it is guaranteed that the server and other clients will preserve that attribute.
Using this rule, two clients of differing capabilities can exchange data and still maintain custom attributes.
This is a requirement.
Any client that does not obey this requirement is broken.
## Synchronizing
Synchronizing with the Taskserver consists of a single transaction.
Once an encrypted connection is made with the server, the client MUST compose a [sync request message](/docs/design/request).
This message includes credentials and local changes.
The response message contains status and remote changes, which MUST be stored locally.
## Establishing Encrypted Connection
All communication with the Taskserver is encrypted using the certificate and key provided to each user.
Using the 'server' configuration setting, establish a connection.
## Sync Request
See [sync request message](/docs/design/request).
A sync request MUST contain a sync key if one was provided by a previous sync.
A sync request MUST contain a list of modified tasks, in JSON format (see [Task JSON](/docs/design/task)), if local modifications have been made.
## Sync Response
A sync response WILL contain a 'code' and 'status' header variable, WILL contain a sync key in the payload, and MAY contain a list of tasks from the server in JSON format (see [Task JSON](/docs/design/task)).
## Server Messages
There are cases when the server needs to inform the user of some condition.
This may be anticipated server downtime, for example.
The response message is typically not present, but may be present in the header, containing a string:
...
message: Scheduled maintenance 2013-07-14 08:00UTC for 10 minutes.
...
If such a message is returned by the server, it SHOULD be made available to the user.
This is a recommendation, not a requirement.
## Server Errors
The server may generate many errors (See [Protocol](/docs/design/protocol)), but the following is a list of the ones most in need of special handling:
- 200 Success
- 201 No change
- 301 Redirect
- 430 Access denied
- 431 Account suspended
- 432 Account terminated
- 5xx Error
The 200 indicates success, and that a change was recorded.
The 201 indicates success but no changes were necessary.
The 301 is a redirect message indicating that the client MUST re-request from a new server.
The 43x series messages are account-related.
Any 5xx series code is a server error of some kind.
All errors consist of a code and a status message:
code: 200
status: Success
## Examples
Here are examples of properly formatted request and response messages.
Note that the messages are indented for clarity in this document, but is not the case in a properly formatted message.
Also note that newline characters U+000D are not shown, but are implied by the separate lines.
Because some messages have trailing newline characters, the text is delimited by the 'cut' markers:
foo
The example above illustrates text consisting of:
U+0066 f
U+006F o
U+006F o
U+000D newline
U+000D newline
Note that these values are left unspecified, but should be clear from the context, and the [message format](/docs/design/request) spec:
<size>
<organization>
<user>
<password>
## First Sync
The first time a client syncs, there is (perhaps) no data to upload, and no sync key from a previous sync.
<size>type: sync
org: <organization>
user: <user>
key: <password>
client: task 2.3.0
protocol: v1
Note the double newline character separating header from payload, with an empty payload.
## Request: Sync No Data
Ordinarily when a client syncs, there is a sync key from the previous sync response to send.
This example shows a sync with no local changes, but a sync key from a previous sync.
<size>type: sync
org: <organization>
user: <user>
key: <password>
client: task 2.3.0
protocol: v1
2e4685f8-34bc-4f9b-b7ed-399388e182e1
## Request: Sync Data
This sync request shows a sync key from the previous sync, and a locally modified task.
<size>type: sync
org: <organization>
user: <user>
key: <password>
client: task 2.3.0
protocol: v1
2e4685f8-34bc-4f9b-b7ed-399388e182e1
{"description":"An example","uuid":"8ad2e3db-914d-4832-b0e6-72fa04f6e331",...}
## Response: No Data
If a sync results in no downloads to the client, the response will look like this.
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 200
status: Ok
45da7110-1bcc-4318-d33e-12267a774e0f
Note that there is a sync key which must be stored and used in the next sync request, but there are no remote changes to store.
## Response: Remote Data
This shows a sync response providing a new sync key, and a remote change to two tasks.
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 200
status: Ok
45da7110-1bcc-4318-d33e-12267a774e0f
{"description":"Test data","uuid":"8ad2e3db-914d-4832-b0e6-72fa04f6e331",...}
{"description":"Test data2","uuid":"3b6218f9-726a-44fc-aa63-889ff52be442",...}
Note that the sync key must be stored for the next sync request.
Note that the two changed tasks must be stored locally, and if the UUID in the tasks matches local tasks, then the local tasks must be overwritten.
## Response: Error
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 431
status: Account suspended
Note the double newline character separating header from payload, with an empty payload.
## Response: Relocate
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 301
status: Redirect
info:
Note the 'info' field will contain a ':' string that should be used for all future sync requests.
This indicates that a user account was moved to another server.
Note the double newline character separating header from payload, with an empty payload.
## Response: Message
Occasionally the server will need to convey a message, and will include an additional header variable containing that message.
The server [protocol](/docs/design/protocol) states that the message SHOULD be shown to the user.
This message will be used for system event messages, used rarely, and never used for advertising or promotion.
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 200
status: Ok
message: Scheduled maintenance 2013-07-14 08:00UTC for 10 minutes.
45da7110-1bcc-4318-d33e-12267a774e0f
Note that the same message will likely be included in consecutive responses.
## Reference Implementation
The Taskserver 1.1.0 codebase contains a reference implementation of an SSL/TLS client and server program, which communicate text strings.
taskd.git/src/tls/Makefile # To build the example
taskd.git/src/tls/README # How to run the example
taskd.git/src/tls/TLSClient.cpp # TLS client code
taskd.git/src/tls/TLSClient.h
taskd.git/src/tls/TLSServer.cpp # TLS Server code
taskd.git/src/tls/TLSServer.h
taskd.git/src/tls/c.cpp # Client program
taskd.git/src/tls/s.cpp # Server program
taskd.git/src/tls/text.cpp # Text manipulation
taskd.git/src/tls/text.h # Text manipulation
The Taskwarrior codebase, version 2.4.0, is the reference implementation.
task.git/src/TLSClient.cpp # TLS client code
task.git/src/TLSClient.h
task.git/src/commands/CmdSync.cpp # Sync implementation
task.git/src/commands/CmdSync.h

View File

@@ -1,249 +0,0 @@
---
title: "Taskwarrior - Full DOM Support"
---
## Work in Progress
This design document is a work in progress, and subject to change.
Once finalized, the feature will be scheduled for an upcoming release.
# Full DOM Support
Taskwarrior currently supports DOM references that can access any stored data item.
The general forms supported are:
[ <id> | <uuid> ] <attribute> [ <part> ]
Examples include:
due
123.uuid
entry.month
123.annotations.0.entry.year
a87bc10f-931b-4558-a44a-e901a77db011.description
Additionally there are references for accessing configuration and system/program level items.
rc.<name>
context.program
context.args
context.width
context.height
system.version
system.os
While this is adequate for data retrieval, we have the possibility of extending it further to include data formats, higher-level constructs, and then to make use of DOM references in more locations.
This contributes to our goal of simplifying Taskwarrior.
## Proposed Format Support
When defining a custom report, the columns shown are defined like this:
report.x.columns=uuid.short,description.oneline ...
This syntax is:
<attribute> [ . <format> ]
If no `format` is specified, then `default` is assumed.
The `src/columns/ColΧ\*` objects are responsible for supporting and rendering these formats.
There is currently no consistency among these formats based on data type.
By incorporating formats into DOM references, we eliminate the need for a separate syntax for custom reports, and provide this:
123.due.iso
123.due.month.short
123.uuid.short
A standard set of formats per data type would be:
Type
Formats
Example
Numeric
default
`123 `
indicator
Based on `rc.<attribute>.indicator` which overrides `rc.numeric.indicator`.
json
`"<attribute>":"<value>"`
String
default
Buy milk
short
Feb
indicator
Based on `rc.<attribute>.indicator` which overrides `rc.string.indicator`.
json
`"<attribute>":"<value>"`
Date
default
Based on `rc.dateformat`
iso
2017-02-20T09:02:12
julian
2457805.12858
epoch
1234567890
age
2min
relative
-2min
remaining
0:02:04
countdown
0:02:04
indicator
Based on `rc.<attribute>.indicator` which overrides `rc.date.indicator`.
json
`"<attribute>":"<value>"`
Duration
default
1wk
iso
P1W
indicator
Based on `rc.<attribute>.indicator` which overrides `rc.duration.indicator`.
json
`"<attribute>":"<value>"`
There will also be a set of attribute-specific formats, similar to the currently supported set:
depends.list
depends.count
description.combined
description.desc
description.oneline
description.truncated
description.count
description.truncated_count
parent.default|long
parent.short
project.full
project.parent
project.indented
status.default|long
status.short
tags.default|list
tags.count
urgency.default|real
urgency.integer
uuid.default|long
uuid.short
Custom report sort criteria will also use DOM references.
This will be augmented by the `+`/`-` sort direction and `/` break indicator, which are not part of the DOM.
## High Level Construct Support
There need to be read-only DOM references that do not correspond directly to stored attributes.
Tasks have emergent properties represented by virtual tags, which will be accessible, in this case returning a `0` or `1`:
123.tags.OVERDUE
Using `rc.due` and the `due` attribute, the `OVERDUE` virtual tag is a combination of the two.
Other examples may include:
task.syncneeded
task.pending.count
task.hooks.installed
## Writable References
When a DOM reference refers to an attribute or RC setting, and does not extend further and reference a component or format, it may be writable.
For example:
rc.hooks # writable
123.description # writable
123.entry.month # not writable, not an attribute
## Data Interchange
The export command can be used to show a filtered set of tasks in JSON format, and this will also be available as a DOM format:
123.json
a87bc10f-931b-4558-a44a-e901a77db011.json
## RC File Support
The RC file (`~/.taskrc`) will support DOM references in values.
This will form a late-bound reference, which is evaluated at runtime, every time.
An example is to make two reports share the same description:
$ task config -- report.ls.description rc.report.list.description
This sets the description for the `ls` report to be a reference to the description of the `list` report.
This reference is not evaluated when the entry is written, but is evaluated every time the value is read, thus providing late-bound behavior.
Then if the description of the `list` report changes, so does that of the `ls` report automatically.
## Implementation Details
These notes list a series of anticipated changes to the codebase.
- The `src/columns/Col*` objects will implement type-specific and attribute-specific DOM support.
DOM reference lookup will defer to the column objects first.
- Some DOM references will be writable, permitting a `_set` command to complement the `_get` command.
- The `Config` object will recognize DOM references in values and perform lookup at read time.
This will require circularity detection.
- `src/DOM.cpp` will provide a memoized function to determine whether a DOM reference is valid.
- `src/DOM.cpp` will provide a function to obtain a DOM reference value, with supporting metadata (type, writable).

View File

@@ -1,436 +0,0 @@
---
title: "Plans"
---
There are many interconnected features and technologies in Taskwarrior, Taskserver, Tasksh and Timewarrior, each piece having it's own goals.
This matrix allows a simple reading of where things are, and where they are going.
This is a low-resolution time line.
It is subject to change.
It does not constitute a concrete plan.
This is an all-volunteer effort, and scheduling is difficult.
[Last updated 2016-08-08.]
<table class="table table-bordered table-striped">
<tr>
<th>Taskwarrior<br />Technology/Feature</th>
<th>
<span class="label label-success">2.5.1</span><br />
Current<br /><br />
Released 2016-02-24
</th>
<th>
<span class="label label-danger">2.6.0</span><br />
Next<br /><br />
2017
</th>
<th>
<span class="label label-info">2.x</span><br />
Future
</th>
</tr>
<tr>
<td>Core</td>
<td>
<a href="/docs/dom.html">DOM</a><br />
Filters<br />
Expressions<br />
Color Rules<br />
Custom Reports<br />
Annotations<br />
Tags / Virtual Tags<br />
<a href="/docs/context.html">Context</a><br />
</td>
<td>
<a href="/docs/design/recurrence.html">Recurrence</a><br />
Shared library<br />
<code>purge</code> command<br />
</td>
<td>
True Color
</td>
</tr>
<tr>
<td>API</td>
<td>
<a href="/docs/design/task.html">JSON</a><br />
Import<br />
Export<br />
<a href="/docs/hooks.html">Hooks</a><br />
<a href="/docs/hooks2.html">Hooks v2</a><br />
<a href="/docs/dom.html">DOM</a><br />
Helper commands<br />
</td>
<td>
</td>
<td>
<code>on-sync</code> hook<br />
Full DOM<br />
DOM access in rc<br />
<code>$ENV</code> access in rc<br />
Report columns as DOM refs<br />
</td>
</tr>
<tr>
<td>
Attributes<br />
<a href="/docs/udas.html">User Defined Attributes (UDA)</a>
</td>
<td>
<code>modified</code><br />
<code>priority</code> as a UDA<br />
</td>
<td>
<code>template</code><br />
<code>rtype</code><br />
Remove <code>mask</code><br />
Remove <code>imask</code><br />
Remove <code>parent</code><br />
</td>
<td>
<code>org</code><br />
<code>group</code><br />
</td>
</tr>
<tr>
<td>Reports</td>
<td>
Improved layouts<br />
Improved Themes
</td>
<td>
Daily, Weekly reports (history, ghistory)<br />
</td>
<td>
</td>
</tr>
<tr>
<td>Synchronization</td>
<td>
<code>task sync</code><br />
<code>task sync init</code> (all tasks)<br />
</td>
<td>
</td>
<td>
<code>task sync reset</code><br />
</td>
</tr>
<tr>
<td>TDB (task database)</td>
<td>
Local file locking<br />
Single file set<br />
Single user
</td>
<td>
</td>
<td>
Threaded file load<br />
Read-only mode
</td>
</tr>
<tr>
<td>I18N / L10N</td>
<td>
UTF-8 support<br />
<code>deu-DEU</code><br />
<code>eng-USA</code><br />
<code>epo-RUS</code><br />
<code>esp-ESP</code><br />
<code>fra-FRA</code><br />
<code>ita-ITA</code><br />
<code>pol-POL</code><br />
<code>por-PRT</code><br />
</td>
<td>
No I18N / L10N
</td>
<td>
Migrate to <a href="https://www.gnu.org/software/gettext/">gettext</a><br />
</td>
</tr>
<tr>
<td>Documentation</td>
<td>
man: task<br />
man: taskrc<br />
man: task-color<br />
man: task-sync<br />
youtube: various<br />
<a href="https://taskwarrior.org">taskwarrior.org</a><br />
taskwarrior.com: Support Site<br />
</td>
<td>
</td>
<td>
New video tutorials<br />
</td>
</tr>
<tr>
<td>Testing</td>
<td>
C++ tests<br />
Python tests<br />
Sync tests<br />
Parallel tests<br />
</td>
<td>
Migration to Flod2<br />
</td>
<td>
</td>
</tr>
<tr>
<td>Tool Chain</td>
<td>
GCC 4.7 / Clang 3.3<br />
C++11 support<br />
CMake<br />
</td>
<td>
GCC 4.9 / Clang 3.4<br />
Full C++11 support<br />
</td>
<td>
Full C++14 support<br />
Full C++17 support<br />
</td>
</tr>
</table>
<table class="table table-bordered table-striped">
<tr>
<th>Tasksh<br />Technology/Feature</th>
<th>
<span class="label label-success">1.1.0</span><br />
Current<br /><br />
Released 2016-09-05
</th>
<th>
<span class="label label-danger">1.2.0</span><br />
Next<br /><br />
2017
</th>
<th>
<span class="label label-info">1.x</span><br />
Future
</th>
</tr>
<tr>
<td>Core</td>
<td>
<a href="/docs/review.html">Review</a><br />
libreadline<br />
Shared library<br />
</td>
<td>
</td>
<td>
Pomodoro timer<br />
</td>
</tr>
<tr>
<td>Tool Chain</td>
<td>
CMake<br />
GCC 4.7 / Clang 3.3<br />
</td>
<td>
GCC 4.9 / Clang 3.4<br />
Full C++11 support<br />
</td>
<td>
Full C++14 support<br />
Full C++17 support<br />
</td>
</tr>
</table>
<table class="table table-bordered table-striped">
<tr>
<th>Taskserver<br />Technology/Feature</th>
<th>
<span class="label label-success">1.1.0</span><br />
Current<br /><br />
Released 2015-05-10
</th>
<th>
<span class="label label-danger">1.2.0</span><br />
Next<br /><br />
2017
</th>
<th>
<span class="label label-info">1.x</span><br />
Future
</th>
</tr>
<tr>
<td>Core</td>
<td>
Serial server
</td>
<td>
Shared library<br />
</td>
<td>
Threaded server
</td>
</tr>
<tr>
<td>Protocol</td>
<td>
v1
</td>
<td>
v1.1 - client reset request<br />
</td>
<td>
v1.2
</td>
</tr>
<tr>
<td>DB (Data Storage)</td>
<td>
</td>
<td>
</td>
<td>
GC
</td>
</tr>
<tr>
<td>Security</td>
<td>
Validation
</td>
<td>
</td>
<td>
UUID:Cert Verification<br />
Combined Certs
</td>
</tr>
<tr>
<td>Tool Chain</td>
<td>
GCC 4.7 / Clang 3.3<br />
CMake<br />
</td>
<td>
GCC 4.9 / Clang 3.4<br />
Full C++11 support<br />
</td>
<td>
Full C++14 support<br />
Full C++17 support<br />
</td>
</tr>
</table>
<table class="table table-bordered table-striped">
<tr>
<th>Timewarrior<br />Technology/Feature</th>
<th>
<span class="label label-success">1.0.0</span><br />
Current<br /><br />
Released 2016-08-20
</th>
<th>
<span class="label label-danger">1.1.0</span><br />
Next<br /><br />
2017
</th>
<th>
<span class="label label-info">1.x</span><br />
Future
</th>
</tr>
<tr>
<td>Core</td>
<td>
Shared library<br />
</td>
<td>
</td>
<td>
True Color
</td>
</tr>
<tr>
<td>Reports</td>
<td>
<code>summary</code> report<br />
<code>gaps</code> report<br />
<code>day</code> chart<br />
<code>week</code> chart<br />
<code>month</code> chart<br />
<code>totals.py</code> extension<br />
</td>
<td>
</td>
<td>
</td>
</tr>
<tr>
<td>Rules</td>
<td>
Simple configuration rules
</td>
<td>
</td>
<td>
Rule System<br />
</td>
</tr>
<tr>
<td>Integration</td>
<td>
Taskwarrior <code>on-modify</code> hook script
</td>
<td>
</td>
<td>
</td>
</tr>
<tr>
<td>Tool Chain</td>
<td>
CMake<br />
GCC 4.7 / Clang 3.3<br />
C++11 support<br />
</td>
<td>
GCC 4.9 / Clang 3.4<br />
Full C++11 support<br />
</td>
<td>
Full C++14 support<br />
Full C++17 support<br />
</td>
</tr>
</table>

View File

@@ -1,171 +0,0 @@
---
title: "Taskwarrior - Sync Protocol"
---
# Sync Protocol
## Introduction
Taskwarrior data has typically been shared in several ways.
Those include SCM (source code management) systems, directory synchronizing software (such as DropBox), and by use of the 'push', 'pull' and 'merge' commands introduced in version 1.9.3.
While these methods work, they each have problems associated with the merging of data.
In the case of directory synchronizing software, there is no merging at all - just simple file overwrite, despite many people believing that the data is somehow combined and preserved.
The Taskserver is a solution.
It is an online/cloud storage and sync service for taskwarrior data.
It performs conflict-free data merging, and minimizes bandwidth use.
The Taskserver also provides multi-client access, so that a task added using a web client could be immediately viewed using a mobile client, or modified using taskwarrior.
Choice of clients is important - people have widely varying behaviors and tastes.
The Taskserver also provides multi-user access, which introduces new capabilities, such as list sharing and delegation.
These will require later modification to this protocol.
The Taskserver protocol will be implemented by the taskd project and first used in taskwarrior 2.3.0.
Other clients will follow.
## Requirements
In this document, we adopt the convention discussed in Section 1.3.2 of [RFC1122](https://tools.ietf.org/html/rfc1122#page-16) of using the capitalized words MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, and OPTIONAL to define the significance of each particular requirement specified in this document.
In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement of the specification; "SHOULD" (or "RECOMMENDED") means there may exist valid reasons for ignoring this item, but the full implications should be understood before doing so; and "MAY" (or "OPTIONAL") means that this item is optional, and may be omitted without careful consideration.
## Link Level
The Taskserver protocol assumes a reliable data stream such as provided by TCP.
When TCP is used, a Taskserver listens on a single predetermined port *for the given client* only.
This means the server may be using multiple ports to serve distinct sets of clients.
This server is only an interface between programs and the task data.
It does not perform any user interaction or presentation-level functions.
## Transactions
Each transaction is a single incoming message, with a single response message.
All communication therefore consists of a single 'send', followed by a single 'receive', then termination.
There are no sessions, and no continuously open connections.
The message format is described in the [Taskserver Message Format](/docs/design/request) document.
## Responsibilities of the Server
The server will maintain a set of transactions, in the original sequence, punctuated by sync keys which are UUIDs.
Each sync key represents a non- trivial sync operation by a client.
Each transaction is a [JSON-formatted task](/docs/design/task), followed by a newline (\\n) character.
The result is a single file that contains interleaved lines of two types: tasks and sync keys.
This design allows the server to maintain a set of deltas such that multiple clients may request a minimal set of changes since their last sync.
## Responsibilities of the Client
This describes how Taskwarrior implements sync.
All modifications to tasks (add, modify, done, delete \...) are recorded in the form of a fully-composed [JSON-formatted task](/docs/design/task).
The formatted task is added to a local backlog.data file.
If a task is modified a second time, it is added again to the backlog.data file - the lines are not combined.
Each task SHALL have a 'modified' date attribute that will help resolve conflicts.
On sync:
* Send a 'sync' type message with the entire contents of the backlog.data, unmodified, as the message payload.
* Receive one of the following response codes:
* 201: This means 'no change', and there is no further action to be taken.
* 200: This means 'success', and the message payload contains a set of tasks and a sync key:
* The formatted tasks are to be stored as-is.
These tasks will either be appended to the client data or will overwrite existing client data, based on the UUID of the task.
No merge logic is necessary.
* The sync key will be written to the backlog.data file, overwriting the previous contents, such that it will now contain only one line.
* 301: Redirect to : found in the 'info' response header, will force the client to resubmit the request to the new server.
* 3xx, 4xx, 5xx: The 'status' field contains an error message.
* If the response contained any error or warning, the error should be shown to the user.
This provides an opportunity for the server to announce downtime, or relocation.
If no sync key is sent, the server cannot provide an incremental delta, and so will send all task data, which should be stored as above.
This should be the case for a client making its first sync call.
If an unrecognized attribute is present in the task data, the client MUST preserve the attribute unmodified, and assume it is of type 'string'.
This permits individual clients to augment the task data without other clients stripping it meaningful data.
This is how UDAs (user defined attributes) are handled.
## Extensions
This protocol was designed so that extensions to the protocol will take the form of additional message types and status codes.
## Summary of Response Codes
Status responses indicate the server's response to the last command received from the client.
The codes consist of a 3 digit numeric code.
The first digit of the response broadly indicates the success, failure, or progress of the previous command (based generally on [RFC640](https://tools.ietf.org/html/rfc640) [RFC821](https://tools.ietf.org/html/rfc821)):
| 1yz | Positive Preliminary reply |
| 2yz | Positive Completion reply |
| 3yz | Positive Intermediate reply |
| 4yz | Transient Negative Completion reply |
| 5yz | Permanent Negative Completion reply |
The next digit in the code indicates the response category:
| x0z | Syntax |
| x1z | Information (e.g., help) |
| x2z | Connections |
| x3z | Authentication |
| x4z | Unspecified as yet |
| x5z | Taskd System (\...) |
| x8z | Nonstandard (private implementation) extensions |
A summary of all status response are:
| 200 | Success |
| 201 | No change |
| 300 | Deprecated message type. This message will not be supported in future Taskserver releases. |
| 301 | Redirect. Further requests should be made to the specified server/port. |
| 302 | Retry. The client is requested to wait and retry the same request. The wait time is not specified, and further retry responses are possible. |
| 400 | Malformed data |
| 401 | Unsupported encoding |
| 420 | Server temporarily unavailable |
| 421 | Server shutting down at operator request |
| 430 | Access denied |
| 431 | Account suspended |
| 432 | Account terminated |
| 500 | Syntax error in request |
| 501 | Syntax error, illegal parameters |
| 502 | Not implemented |
| 503 | Command parameter not implemented |
| 504 | Request too big |
## Security Considerations
All communication with the Taskserver uses SSL 3.0 or TLS 1.0, 1.1 or 1.2.
Encryption is mandatory.
Data is never transmitted in plain text.
## Limitations and Guidelines
Some limitations exists to reduce bandwidth and load on the server.
They are:
- A client may only connect to a single server.
Synchronization among a set of servers is not supported.
- A client should attempt to minimize data bandwidth usage by maintaining a local data store, and properly using sync keys.
- A client should minimize data transfers by limiting the frequency of sync requests.

View File

@@ -1,195 +0,0 @@
---
title: "Taskwarrior - Recurrence"
---
# Draft
This is a draft design document.
Your [feedback](mailto:support@taskwarrior.org?Subject=Feedback) is welcomed.
Recurrence
----------
Recurrence needs an overhaul to improve weaknesses and add new features.
# Terminology
- The hidden 'parent' task is called the template.
- Synthesis is the name for the generation of new recurring task instances when necessary.
- The synthesized tasks are called instances.
- The index is the zero-based monotonically increasing number of the instance.
- Drift is the accumulated errors in time that cause a due date to slowly change for each recurring task.
# Criticism of Current Implementation
- The `mask` attribute grows unbounded.
- Only strict recurrence cycles are supported.
The example of mowing the lawn is that you want to mow the lawn every seven days, but when you are four days late mowing the lawn, the next mowing should be in seven days, not in three.
- Intances generated on one machine and then synced, may collide with equivalent unsynced instances tasks on another device, because the UUIDs are different.
- You cannot `wait` a recurring task and have that wait period propagate to all other child tasks.
- Task instances cannot individually expire.
# Proposals
## Proposal: Eliminate `mask`, `imaѕk` Attributes
The `mask` attribute in the template is replaced by `last`, which indicates the most recent instance index synthesized.
Because instances are never synthesized out of order, we only need to store the most recent index.
The `imask` attribute in the instance is no longer needed.
## Proposal: Rename `parent` to `template`
The name `parent` implies subtasks, and confuses those who inspect the internals.
The value remains the UUID of the template.
This frees up the namespace for future use with subtasks.
## Proposal: New 'rtype' attribute
To indicate the flavor of recurrence, support the following values:
* `periodic` - Instances are created on a regular schedule.
Example: send birthday flowers.
It must occur on a regular schedule, and doesn't matter if you were late last year.
This is the default value.
* `chained` - Instances are created back to back, so when one instance ends, the next begins, with the same recurrence.
Example: mow the lawn.
If you mow two days late, the next instance is not two days early to compensate.
## Proposal: Use relative offsets
The delta between `wait` and `due` date in the template should be reflected in the delta between `wait` and `due` date in the instance.
Similarly, 'scheduled' must be handled the same way.
## Proposal: On load, auto-upgrade legacy tasks
Upgrade template:
- Add `rtype:periodic`
- Add `last:N` where `N` is the length of `mask`
- Delete `mask`
Upgrade instance:
- Rename `parent` to `template`
- Delete `imask`
- Update `wait` if not set to: `wait:due + (template.due - template.wait)`
- Update `scheduled` if not set to: `scheduled:due + (template.due - template.scheduled)`
## Proposal: Deleting a chained instance
Deleting a `rtype:chained` instance causes the next chained instance to be synthesized.
This gives the illusion that the due date is simply pushed out to `(now + template.recur)`.
## Proposal: Modification Propagation
TBD
## Proposal: Exotic Dates
Expand date specifications to use pattern phrases:
- `4th thursday in November`
- `4th thursday of November`
- `Friday before easter`
- `next Tuesday`
- `last Tuesday`
- `last July`
- `weekend`
- `3 days before eom`
- `in the morning`
- `4pm`
- `noon`
- `midnight`
Got suggestions?
## Proposal: User-Defined Week Start
TBD
# Implementation
## Implementation: Adding a new `periodic` template
When adding a new periodic template:
task add ... due:D recur:R wait:D-1wk scheduled:D-1wk until:U
Creates:
template.uuid: NEW_UUID
template.description: ...
template.entry: now
template.modified: now
template.due: D
template.recur: R (stored in raw form, ie 'P14D')
template.wait: D-1wk
template.scheduled: D-1wk
template.until: U
template.rtype: periodic
template.last:
Creating the Nth instance (index N):
Clone instance from template.
instance.uuid: NEW_UUID
instance.modified: now
instance.due: template.due + (N * template.recur)
instance.wait: instance.due + (template.due - template.wait)
instance.scheduled: instance.due + (template.due - template.scheduled)
instance.start:
template.last: N
## Implementation: Adding a new `chained` template
When adding a new chained template:
task add ... due:D recur:R wait:D-1wk scheduled:D-1wk until:U rtype:chained
Creates:
template.uuid: NEW_UUID
template.description: ...
template.entry: now
template.modified: now
template.due: D
template.recur: R (stored in raw form, ie 'P14D')
template.wait: D-1wk
template.scheduled: D-1wk
template.until: U
template.rtype: chained
Creating the Nth instance (index N):
Clone instance from template.
instance.uui d: NEW_UUID
instance.mod ified: now
instance.due : instance[N-1].end + template.recur
instance.wai t: instance.due + (template.due - template.wait)
instance.sch eduled: instance.due + (template.due - template.scheduled)
instance.sta rt:
Chained tasks do not obey `rc.recurrence.limit`, and show only one pending task
at a time.
## Implementation: Special handling for months
Certain recurrence periods are inexact:
- P1M
- P1Y
- P1D
When the recurrence period is `P1M` the number of days in a month varies and causes drift.
When the recurrence period is `P1Y` the number of days in a year varies and causes drift.
When the recurrence period is `P1D` the number of hours in a day varies due to daylight savings, and causes drift.
Drift should be avoided by carefully implementing:
instance.due: template.due + (N * template.recur)

View File

@@ -1,198 +0,0 @@
---
title: "Taskwarrior - Request"
---
# Taskserver Message Format
The Taskserver accepts and emits only messages.
These messages look somewhat like email, as defined in [RFC821](https://tools.ietf.org/html/rfc821), [RFC2822](https://tools.ietf.org/html/rfc2822).
The message format allows for data, metadata, and extensibility.
This combination allows the Taskserver to accommodate current and future needs.
This document describes the message format, and the supported message types.
## Requirements
In this document, we adopt the convention discussed in Section 1.3.2 of [RFC1122](https://tools.ietf.org/html/rfc1122#page-16) of using the capitalized words MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, and OPTIONAL to define the significance of each particular requirement specified in this document.
In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement of the specification; "SHOULD" (or "RECOMMENDED") means there may exist valid reasons for ignoring this item, but the full implications should be understood before doing so; and "MAY" (or "OPTIONAL") means that this item is optional, and may be omitted without careful consideration.
## Encoding
All messages are UTF8-encoded text.
## Message Format
This format is based on [RFC2822](https://tools.ietf.org/html/rfc2822), 'Internet Message Format'.
Here is an example of the format:
<SIZE>
name: value
name2: value2
payload
There are three sections.
The first is the size, which is a 4-byte, big- Endian, binary byte count of the length of the message, including the 4 bytes for the size.
The header section is a set of name/value pairs separated by newline characters (U+000D).
The name is separated from the value by ': ' (colon U+003A, space U+0020) The header section is terminated by two consecutive newline (U+000D) characters.
All text is UTF8-encoded.
The payload section is arbitrary, and message type-specific.
However, it is still UTF8-encoded text.
## Message Requirements
Messages SHALL contain particular headers.
Those are:
- type
- protocol
- client
The 'type' value is what determines the interpretation of the payload.
The 'protocol' value should be 'v1', or any subsequently published protocol version.
The 'client' represent the client identifier, so that any special cases can be handled.
For example, an emergency fix that is client version-specific could be released, to support users that have not updated their client, or perhaps the client has not released a fix.
The form of the 'version' value is:
<product identifier> <version number>
As an example:
taskwarrior 2.3.0
DO NOT spoof any other software using this client value.
If another client is spoofed, then patches addressing protocol errors may break working software.
## Auth Data
Every request from the client SHALL contain "auth" information, which involves these header entries:
org: <organization>
user: <user>
key: <key>
The user and org fields uniquely identify a user.
The key field is generated when a new server account is set up.
It is a shared secret, equivalent to a password, and should be protected.
Authentication failure can result in these errors:
- 430 Authentication failed
- 431 Account suspended
## Status Data
Every response from the Taskserver SHALL contain status data:
code: <code>
status: <status text>
The code is a numeric status indicator defined in the [Sync Protocol](/docs/design/protocol).
## Payload Data
Payload data is optional, arbitrary and message type dependent.
It is always UTF8-encoded text.
## Message Types
The Taskserver supports several message types, thus providing a set of primitives for use by clients.
It is expected that the number of supported ticket types will increase over time.
## Sync Message
The "sync" message always originates from the client, but the response will contain data from the server.
A sync is therefore a single request with a single response.
The "sync" message type MUST contain the following headers:
- type
- org
- user
- key
- client
- protocol
The "sync" message payload has this format:
<uuid>
<JSON task 1>
<JSON task 2>
...
<JSON task N>
Here is an example of a sync message:
<size>type: sync
org: <organization>
user: <user>
key: <key>
client: task 2.3.0
protocol: v1
2e4685f8-34bc-4f9b-b7ed-399388e182e1
{"description":"Test data","entry":"20130602T002341Z","status":"pending"}
The request contains the proper auth section, and the body contains the current sync key followed by a newline characters (U+000D), then a list of JSON-formatted tasks \[2\] each separated by a newline character (U+000D).
An example response message might be:
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 200
status: Ok
45da7110-1bcc-4318-d33e-12267a774e0f
The status indicates success, and the payload contains zero remote task modifications, followed by a sync key.
## Statistics Message
The message format іs simply:
<size>type: statistics
org: <Organization>
user: <User>
key: <Key>
client: taskd 1.0.0
protocol: v1
There is no payload.
An example response message might be:
<size>type: response
client: taskd 1.0.0
protocol: v1
code: 200
status: Ok
average request bytes: 0
average response bytes: 0
average response time: 0.000000
errors: 0
idle: 1.000000
maximum response time: 0.000000
total bytes in: 0
total bytes out: 0
tps: 0.000000
transactions: 1
uptime: 28
There is no payload, and the results are in the header variables.
Note that the statistics gathered by the server are growing, which means new values are occasionally added to the response message.
Existing values will not be removed.

View File

@@ -1,240 +0,0 @@
---
title: "Taskwarrior - Rule System"
---
## Work in Progress
This design document is a work in progress, and subject to change.
Once finalized, the feature will be scheduled for an upcoming release.
# Rule System
The rule system is a framework that supports highly configurable features, with runtime evaluation, DOM access and an internal API.
Implementing a rule system meets the goal of shrinking and stabilizing the product core, while adding new features, and enabling many more.
## Required Enhancements
To prepare for a Rules System, various subsystems must first be enhanced:
- DOM references need to be unambiguous, and will all have the `dom.` prefix.
- DOM references need to be able to access any Taskwarrior data, in any
- Custom reports will change from referencing `<column>[.<format>]` to simply
`<domref>`
- RC file syntax needs to be enhanced, so support rule definitions, which are
multi-line blocks that are indentation-sensitive
- RC file syntax will support two ways of specifying the same data:
a.b.c=...
a:
b:
c=...
- RC file syntax will allow the use of environment variables inline:
name=${TERM}
include ${HOME}/.taskrc_local
- The `Variant` object will migrate to `libshared`
- The expression evaluator `Eval` object will migrate to `libshared`
- The column objects will gain a more structured base class, and will serve as
providers for DOM references
- The 'exec' command will be able to run a rule, if the reference is correct
- Taskwarrior will store state data in a new `state.data` file
- `Config` object needs to use the `rat` parser, to tackle the more complex
syntax
- The RC file will support environment variable expansion, where `${NAME}`
will be replaced by its corresponding value at launch time
At that point, the rules system can be implemented in `libshared`, and will use a pluggable architecture to allow its integration into several projects.
## DOM Enhancements
DOM references will be enhanced, with many more references supported.
All DOM references will begin with `dom.`, yielding unambiguous references.
References will have a type.
Types will support sub-references (`<date>.<month>`, `<tags>.<N>`, `<annotation>.<description>`), and display formats included.
dom . [<id> .] <attribute> [. <sub-reference>] . <format>
dom . 123 . entry . year . yyyy
dom . 123 . entry
dom . 123 . tags
dom . 123 . tags . count
dom . 123 . tags . 1
In addition to direct attribute access, DOM references will also support tw references beyond the current set: dom.rc.<name>
dom.cli.args
dom.terminal.width
dom.terminal.height
dom.system.version
dom.system.oѕ
And will also support higher-level constructs that do not directly correlate to attributes, for example:
dom.active Boolean indicator of any active tasks
dom.synced Boolean indicator of the need to sync
dom.rc.path String path of .taskrc file (or override)
dom.data.path String path of data directory
dom.hooks.path String path of hooks directory
Finally, access to state:
dom.state.program
dom.state.sync.last
dom.state.sync.configured
dom.state.run.last
dom.state.context
## RC Syntax Changes
The current configuration system supports only two different forms of syntax:
<name> = [ <value> ]
include <file>
A rule is a new form of syntax that consists of the rule keyword, a name, optional trigger, followed by indented actions in the form of API calls and flow control.
For example:
rule myRule() on_launch:
# Some code here
A rule definition will appear in the RC file, alongside all the existing settings.
The rule syntax will require a blank line to terminate the rule definition, the result being that the RC file should be quite readable, although it will look like Python.
## Hook Scripts
While this functionality can also be implemented using hook scripts, rules will run in-process, and therefore do not require external interpreters to be launched every time.
This creates the potential to run faster than a hook script.
For complex processing, hook scripts will be the preferred mechanism, but as the rules system matures, rules will be made to run more quickly.
With adequate performance, a rule will be the preferred implementation over a hook script.
This is not expected to be the case at first.
Hook scripts are not likely to be extended beyond their current form, and with greater DOM access and a growing API, rules should be able to supplant most hook script use cases.
## Rule Triggers
The set of supported rule types will include:
* `on_launch` - Triggered on program launch.
* `on_add` - Triggered when a task is added.
A context task will be provided.
The rule can modify the task, and approve or reject it.
* `on_modify` - Triggered when a task is modified.
A before and after context task will be provided.
The rule can modify the task, and approve or reject it.
* `on_exit` - Triggered on program exit.
* `color` - Triggered when colors are being determined.
* `virtual tag` - Defines a new virtual tag.
* `format` - Triggered when an attribute needs formatting, defines are new format.
More rules types will be added for more capabilities in future releases.
## API
The API is a simple set of actions that may be taken by a rule.
* `debug(<string>)` - Displays the string in debug mode only and continues processing.
* `warn(<string>)` - Displays the string as a warning continues processing.
* `error(<string>)` - Displays the string as an error and terminates processing.
* `exec(<binary> [ <args> ... ])` - Executes the external program and passes arguments to it.
If the program exits with non-zero status, it is treated as an error.
* `return <value>` - Provides a result value for the rule, when necessary.
This is a very limited set at first, and more API calls will be added to support capabilities in future releases.
## Grammar
The grammar closely tracks that of Python.
Blocks are indented consistently.
* `if <condition>: ... else: ...` - The condition is a full Algebraic expression, and supports none of the command line conveniences.
Terms must be combined with logical operators.
The condition is an expression that is evaluated and converted to a Boolean value.
* `for <name> in <collection>:` - There is no native type for a collection, but there are DOM references (`tags` \...) that reference collections.
This provides a way to iterate.
* `set <name> = <expression>` - Writes to a named type.
The name may be a writable DOM object (`dom...`) or temporary variable storage (`tmp...`).
Writing to a read-only DOM reference is an error.
* `<function>([<args>])` - A function is either a rule or an API call.
Calling an undefined function is an error.
## Examples
Here are some example rules which illustrate the syntax and API.
The replacement for the nag feature:
rule Nag(before, after) on-modify:
if before.urgency < tasks.max.urgency:
warn You have more urgent tasks
if after.status == 'completed' and before.urgency < (dom.urgency.max - 2.0):
warn 'You have more urgent tasks!'
Correct commonly misspelled word:
rule CorrectSpelling(task) on_add:
set task.description = substitute(task.description, 'teh', 'the')
Abbreviation expansion:
rule ExpandAbbreviation(task) on_modify:
set task.description = substitute(task.description, '/TW-\d+/', 'https:\/\/github.com\/GothenburgBitFactory\/taskwarrior\/issues\/\1')
Warn on missing project:
rule WarnOnMissingProject(task) on_add:
if task.project == :
warn(Project not specified)
Color rule:
rule ColorizeDue(task) color:
if task.due > now:
if task.due < (now + 5d):
return dom.rc.color.due
else:
return dom.rc.color.due.later
Policy:
rule policyProject(task) on_add:
if task.project == '':
if rc.default.project == '':
error('You must specify a project')
set task.project = rc.default.project

View File

@@ -1,242 +0,0 @@
---
title: "Taskwarrior - Taskserver Sync Algorithm"
---
# Taskserver Sync Algorithm
This document describes how task changes are merged by the Taskserver.
It does not describe [the protocol](/docs/design/protocol) used by the Taskserver.
The Taskserver merges tasks from multiple sources, resulting in conflict- free syncing of data.
The algorithm used to achieve this is simple and effective, paralleling what SCM systems do to perform a rebase.
## Requirements
In this document, we adopt the convention discussed in Section 1.3.2 of
[RFC1122](https://tools.ietf.org/html/rfc1122#page-16) of using the capitalized words MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, and OPTIONAL to define the significance of each particular requirement specified in this document.
In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement of the specification; "SHOULD" (or "RECOMMENDED") means there may exist valid reasons for ignoring this item, but the full implications should be understood before doing so; and "MAY" (or "OPTIONAL") means that this item is optional, and may be omitted without careful consideration.
## Problem Definition
The sync algorithm considers a single task, with multiple changes occurring in two separate locations that must be resolved.
The two locations are the local machine and the server.
This results in two parallel change sequences.
Examples using multiple clients collapse down to the simple two-branch case because the clients are merged serially.
## Change Sequence
A sequence of changes to the same task is represented as:
T0 --> T1 --> T2
Although all examples are of the two-branch variety, some involve trivial branches.
Going through these examples will illustrate the algorithm.
First the legend:
T0 Represents the original task, the base.
T1 Represents the task with a non-trivial set of changes.
T2 Represents the task with further changes.
## Deltas
The transition from T0 \--\> T1 can be seen as a transform applied to T0, resulting in T1.
That transform is the delta (d1) between T0 and T1, which is a subtractive term:
d1 = (T1 - T0)
Therefore:
T0 --> T1 = T0 + d1
= T0 + (T1 - T0)
This states that the transition from T0 to T1 is the application of a delta to the original, T0, which results in T1.
Applying this to the whole change sequence yields:
T0 --> T1 --> T2 = T0 + d1 + d2
= T0 + (T1 - T0) + (T2 - T1)
## Use Case Classification
Because clients sync requests are processed serially, there is no need to consider the multiple client cases.
This means there is only ever the case with two parallel change sequences = the two branch case.
## Two Branch Case
The two branch case represents changes made to the same task in two locations, resulting in two deltas that must be applied to the same base.
T0 --> T1
T0 --> T2
This reduces to a base with two deltas, but the order in which the deltas are applied is important.
For example:
T0 + d1 + d2 =/= T0 + d2 + d1
The application of deltas is not commutative, except in the trivial case where the two deltas are identical, or the deltas do not overlap.
The deltas therefore need to be applied in the correct sequence.
Tasks have metadata that indicates the last modified time, which dictates the sequence.
Assuming d1 occurred before d2, this neatly collapses down to a single branch sequence:
T0 + d1 + d2 = T3
Note that the result in this case is T3, because it will be neither T1 nor T2, unless the deltas are identical.
## Two Branch, Multiple Changes Case
The two branch case can be complicated by multiple changes per branch:
T0 --> T1 --> T3 --> T5
T0 --> T2 --> T4
Note that the numbers were chosen to represent the order in which the changes were made.
First a list of deltas is generated:
T0 --> T1 = d1
T1 --> T3 = d3
T3 --> T5 = d5
T0 --> T2 = d2
T0 --> T4 = d4
d1, d3, d5, d2, d4
Then the deltas are sorted by modified time:
d1, d2, d3, d4, d5
Then epplied to the base, yielding T6:
T0 + d1 + d2 + d3 + d4 +d5 = T6
## Two Branch Case Example
Suppose the base task looks like this:
T0 project:ONE due:tomorrow priority:H +tag1 Original description
The first branch looks like this:
T1 project:TWO due:23rd priority:H +tag1 Original description
The second branch looks like this:
T2 project:ONE due:tomorrow priority:H +tag1 Modified description
Delta d1 is:
T0 project:ONE due:tomorrow priority:H +tag1 Original description
T1 project:TWO due:23rd priority:H +tag1 Original description
----------------------------------------------------------------------
d1 project:TWO due:23rd
Delta d2 is:
T0 project:ONE due:tomorrow priority:H +tag1 Original description
T2 project:ONE due:tomorrow priority:H +tag1 Modified description
----------------------------------------------------------------------
d2 Modified description
If d1 occurred before d2, the result is:
T3 = T0 + d1 + d2
= T0 + (project:TWO due:23rd) + (Modified description)
T3 = project:TWO due:23rd priority:H +tag1 Modified description
## Use Cases
A range of illustrated use cases, from the trivial to the complex will show the algorithm in use.
## Use Case 1: New Local Task
Initial state:
Server: -
Client: T0
The server has no data, and so T0 is stored.
The result is now:
Server: T0
Client: T0
## Use Case 2: Local Change
Initial state:
Server: T0
Client: T0 --> T1
The server resolves the change:
T0 --> T1 = T0 + d1
= T1
T1 is stored.
The result is now:
Server: T0 --> T1
Client: T1
## Use Case 3: Local and Remote Change
Initial state:
Server: T0 --> T1
Client: T0 --> T2
This is the two branch case, and the deltas are generated:
T0 --> T1 = T0 + d1
T0 --> T2 = T0 + d2
The order of change is determine to be d1, d2, yielding T3:
T3 = T0 + d1 + d2
T3 is stored on the server, and returned to the client.
The result is now:
Server: T0 --> T1 --> T2 --> T3
Client: T3
## Use Case 4: Multiple Local and Remote Changes
Initial state:
Server: T0 --> T1 --> T3
Client: T0 --> T2 --> T4
This is the two branch case, and the deltas are generated:
T0 --> T1 = T0 + d1
T1 --> T3 = T0 + d3
T0 --> T2 = T0 + d2
T2 --> T4 = T0 + d4
d1, d3, d2, d4
The order of change is determine to be d1, d2, d3, d4, yielding T5:
T5 = T0 + d1 + d2 + d3 + d4
T5 is stored on the server, and returned to the client.
The result is now:
Server: T0 --> T1 --> T2 --> T3 --> T4 --> T5
Client: T5

View File

@@ -1,469 +0,0 @@
---
title: "Taskwarrior - Taskwarrior JSON Format"
---
# Taskwarrior JSON Format
When Taskwarrior exchanges data, it uses [JSON](https://www.json.org/).
This document describes the structure and semantics for tasks exported from Taskwarrior, imported to Taskwarrior, or synced with the Taskserver.
Any client of the Taskserver will need to communicate task information.
This document describes the format of a single task.
It does not describe the communication and sync protocol between client and server.
This document is subject to change.
The data attributes are also subject to change.
## Requirements
In this document, we adopt the convention discussed in Section 1.3.2 of [RFC1122](https://tools.ietf.org/html/rfc1122#page-16) of using the capitalized words MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, and OPTIONAL to define the significance of each particular requirement specified in this document.
In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement of the specification; "SHOULD" (or "RECOMMENDED") means there may exist valid reasons for ignoring this item, but the full implications should be understood before doing so; and "MAY" (or "OPTIONAL") means that this item is optional, and may be omitted without careful consideration.
## General Format
The format is JSON, specifically a JSON object as a single line of text, terminated by a newline (U+000D).
The JSON looks like this:
{"description":"One two three","status":"pending", ... }
While this is not a valid task (there are missing fields), the format is illustrated.
All attribute names are quoted with " (U+0022).
A name will always have a corresponding value, and if a value is blank, then the name/value pair is omitted from the line.
Newline characters are not permitted within the value, meaning that a task consists of a single line of text.
All data is UTF8.
## Data Types
There are five data types used in the task format.
## Data Type: String
Strings may consist of any UTF8 encoded characters.
## Data Type: Fixed String
A fixed string is one value from a set of acceptable values, such as a priority level, where the values may only be "", "L", "M" or "H".
## Data Type: UUID
A UUID is a 32-hex-character lower case string, formatted in this way:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
An example:
296d835e-8f85-4224-8f36-c612cad1b9f8
## Data Type: Integer
Integers are rendered in a simple fashion:
123
## Data Type: Date
Dates are rendered in ISO 8601 combined date and time in UTC format using the template:
YYYYMMDDTHHMMSSZ
An example:
20120110T231200Z
No other formats are supported.
## Data Type: Duration
Duration values represent a time period.
They take the form:
[[<sign>] <number>] <unit>
Some examples include:
- -3days
- annual
- 4hrs
The supported units are:
- annual
- biannual
- bimonthly
- biweekly
- biyearly
- daily
- days
- day
- d
- fortnight
- hours
- hour
- hrs
- hr
- h
- minutes
- mins
- min
- monthly
- months
- month
- mnths
- mths
- mth
- mos
- mo
- quarterly
- quarters
- qrtrs
- qtrs
- qtr
- q
- seconds
- secs
- sec
- s
- semiannual
- sennight
- weekdays
- weekly
- weeks
- week
- wks
- wk
- w
- yearly
- years
- year
- yrs
- yr
- y
Note that some values lack precision, for example "2q" means two quarters, or half a year.
Note that not all combinations of number and unit make sense, for example "3annual" makes no sense, but evaluates to "3years".
## The Attributes
Here are the standard attributes that may comprise a task:
| Name | Type |
|--------------|---------|
| status | String |
| uuid | UUID |
| entry | Date |
| description | String |
| start | Date |
| end | Date |
| due | Date |
| until | Date |
| wait | Date |
| modified | Date |
| scheduled | Date |
| recur | String |
| mask | String |
| imask | Integer |
| parent | UUID |
| project | String |
| priority | String |
| depends | String |
| tags * | String |
| annotation * | String |
| (UDA) | ? |
\* Both tags and annotations are lists of strings and objects.
Any UDA fields are assumed to be of type string.
There are other forms, which are conditional upon the state of a task:
| Status Value | Pending | Deleted | Completed | Waiting | Recurring Parent | Recurring Child |
|--------------|---------|---------|-----------|---------|------------------|-----------------|
| status | Reqd | Reqd | Reqd | Reqd | Reqd | Reqd |
| uuid | Reqd | Reqd | Reqd | Reqd | Reqd | Reqd |
| entry | Reqd | Reqd | Reqd | Reqd | Reqd | Reqd |
| description | Reqd | Reqd | Reqd | Reqd | Reqd | Reqd |
| start | Opt | Opt | Opt | Opt | Opt | Opt |
| end | | Reqd | Reqd | | | |
| due | Opt | Opt | Opt | Opt | Reqd | Opt |
| until | Opt | Opt | Opt | Opt | Opt | Opt |
| scheduled | Opt | Opt | Opt | Opt | Opt | Opt |
| wait | | | | Reqd | | |
| recur | | | | | Reqd | Reqd |
| mask | | | | | Intrn | |
| imask | | | | | | Intrn |
| parent | | | | | | Reqd |
| annotation | Opt | Opt | Opt | Opt | Opt | Opt |
| project | Opt | Opt | Opt | Opt | Opt | Opt |
| tags | Opt | Opt | Opt | Opt | Opt | Opt |
| priority | Opt | Opt | Opt | Opt | Opt | Opt |
| depends | Opt | Opt | Opt | Opt | Opt | Opt |
| modified | Intrn | Intrn | Intrn | Intrn | Intrn | Intrn |
| UDA | Opt | Opt | Opt | Opt | Opt | Opt |
(Legend: Reqd = required, Opt = optional, Intrn = Internally generated)
All tasks have four required fields.
There are other states in which a task may exist, and the requirements change.
At a minimum, a valid task contains:
- uuid
- status
- entry
- description
*Deleted* - A deleted task MUST also have "status":"deleted", an "end" date and a "modified" date.
*Completed* - A completed task MUST also have "status":"completed", an "end" date and a "modified" date.
*Waiting* - A waiting task MUST also have "status":"waiting" and a "wait" date.
The task is hidden from the user, until that "wait" date has passed, whereupon the status reverts to "pending", and the "wait" date is removed.
*Recurring Parent* - When a recurring task is entered, it MUST have "status":"recurring", a "recur" period and a "due" date.
It MAY also have an "until" date.
Recurring parent tasks are hidden from the user.
*Recurring Child* - A recurring child task is not created by the user, but is cloned from the recurring parent task by the Taskserver.
It may be modified by the user.
On completion, there is special handling to be done.
See section 3.11.
## Additional Attributes
There MAY be other fields than those listed above in a task definition.
Such fields MUST be preserved intact by any client, which means that if a task is downloaded that contains an unrecognized field, that field MUST not be modified, and MUST continue to exist in the task..
User Defined Attributes (UDAs) are additional fields.
## Attribute Details
The individual fields convey important information about a task, and in some cases work only in collusion with other fields.
All such details are listed here.
## Attribute: status
The status field describes the state of the task, which may ONLY be one of these literal strings:
"status":"pending"
"status":"deleted"
"status":"completed"
"status":"waiting"
"status":"recurring"
A pending task is a task that has not yet been completed or deleted.
This is the typical state for a task.
A deleted task is one that has been removed from the pending state, and MUST have an "end" field specified.
Given the required "entry" and "end" field, it can be determined how long the task was pending.
A completed task is one that has been removed from the pending state by completion, and MUST have an "end" field specified.
Given the required "entry" and "end" fields, it can be determine how long the task was pending.
A waiting task is ostensibly a pending task that has been hidden from typical view, and MUST have a "wait" field containing the date when the task is automatically returned to the pending state.
If a client sees a task that is in the waiting state, and the "wait" field is earlier than the current date and time, the client MUST remove the "wait" field and set the "status" field to "pending".
A recurring task is essentially a parent template task from which child tasks are cloned.
The parent remains hidden from view, and contains a "mask" field that represents the recurrences.
Each cloned child task has an "imask" field that indexes into the parent "mask" field, as well as a "parent" field that lists the UUID of the parent.
## Attribute: uuid
When a task is created, it MUST be assigned a new UUID by the client.
Once assigned, a UUID field MUST NOT be modified.
UUID fields are permanent.
## Attribute: entry
When a task is created, it MUST be assigned an "entry" date by the client.
This is the creation date of the task.
## Attribute: description
When a task is created, it MUST have a "description" field value, which contains UTF8 characters.
A "description" field may not contain newline characters, but may contain other characters, properly escaped.
See <https://json.org> for details.
## Attribute: start
To indicate that a task is being worked on, it MAY be assigned a "start" field.
Such a task is then considered Active.
## Attribute: end
When a task is deleted or completed, is MUST be assigned an "end" field.
It is not valid for a task to have an "end" field unless the status is also "completed" or "deleted".
If a completed task is restored to the "pending" state, the "end" field is removed.
## Attribute: due
A task MAY have a "due" field, which indicates when the task should be completed.
## Attribute: until
A recurring task MAY have an "until" field, which is the date after which no more recurring tasks should be generated.
At that time, the parent recurring task is set to "completed".
## Attribute: wait
A task MAY have a "wait" field date, in conjunction with a "status" of "waiting".
A waiting task is one that is not typically shown on reports until it is past the wait date.
An example of this is a birthday reminder.
A task may be entered for a birthday reminder in 10 months time, but can have a "wait" date 9 months from now, which means the task remains hidden until 1 month before the due date.
This prevents long-term tasks from cluttering reports until they become relevant.
## Attribute: recur
The "recur" field is for recurring tasks, and specifies the period between child tasks, in the form of a duration value.
The value is kept in the raw state (such as "3wks") as a string, so that it may be evaluated each time it is needed.
## Attribute: mask
A parent recurring task has a "mask" field that is an array of child status indicators.
Suppose a task is created that is due every week for a month.
The "mask" field will look like:
"----"
This mask has four slots, indicating that there are four child tasks, and each slot indicates, in this case, that the child tasks are pending ("-").
The possible slot indicators are:
* `-` - Pending
* `+` - Completed
* `X` - Deleted
* `W` - Waiting
Suppose the first three tasks has been completed, the mask would look like this:
"+++-"
If there were only three indicators in the mask:
"+-+"
This would indicate that the second task is pending, the first and third are complete, and the fourth has not yet been generated.
## Attribute: imask
Child recurring tasks have an "imask" field instead of a "mask" field like their parent.
The "imask" field is a zero-based integer offset into the "mask" field of the parent.
If a child task is completed, one of the changes that MUST occur is to look up the parent task, and using "imask" set the "mask" of the parent to the correct indicator.
This prevents recurring tasks from being generated twice.
## Attribute: parent
A recurring task instance MUST have a "parent" field, which is the UUID of the task that has "status" of "recurring".
This linkage between tasks, established using "parent", "mask" and "imask" is used to track the need to generate more recurring tasks.
## Attribute: annotation\_\...
Annotations are strings with timestamps.
Each annotation itself has an "entry" field and a "description" field, similar to the task itself.
Annotations form an array named "annotations".
For example (lines broken for clarity):
"annotations":[
{"entry":"20120110T234212Z","description":"Remember to get the mail"},
{"entry":"20120110T234559Z","description":"Pay the bills"}
]
## Attribute: project
A project is a single string.
For example:
"project":"Personal Taxes"
Note that projects receive special handling, so that when a "." (U+002E) is used, it implies a hierarchy, which means the following two projects:
"Home.Kitchen"
"Home.Garden"
are both considered part of the "Home" project.
## Attribute: tags
The "tags" field is an array of string, where each string is a single word containing no spaces.
For example:
"tags":["home","garden"]
## Attribute: priority
The "priority" field, if present, MAY contain one of the following strings:
"priority":"H"
"priority":"M"
"priority":"L"
These represent High, Medium and Low priorities.
An absent priority field indicates no priority.
## Attribute: depends
The "depends" field is a string containing a comma-separated unique set of UUIDs.
If task 2 depends on task 1, then it is task 1 that must be completed first.
Task 1 is considered a "blocking" tasks, and task 2 is considered a "blocked" task.
For example:
"depends":",, ..."
Note that in a future version of this specification, this will be changed to a JSON array of strings, like the "tags" field.
## Attribute: modified
A task MUST have a "modified" field set if it is modified.
This field is of type "date", and is used as a reference when merging tasks.
## Attribute: scheduled
A task MAY have a "scheduled" field, which indicates when the task should be available to start.
A task that has passed its "scheduled" data is said to be "ready".
## User Defined Attributes
A User Defined Attribute (UDA) is a field that is defined via configuration.
Given that the configuration is not present in the JSON format of a task, any fields that are not recognized are to be treated as UDAs.
This means that if a task contains a UDA, unless the meaning of it is understood, it MUST be preserved.
UDAs may have one of four types: string, numeric, date and duration.

View File

@@ -1,29 +0,0 @@
---
title: "Taskwarrior - Work Week Support"
---
## Work in Progress
This design document is a work in progress, and subject to change.
Once finalized, the feature will be scheduled for an upcoming release.
# Work Week Support
Taskwarrior supports the idea that a week starts on either a Sunday or a Monday, as determined by configuration.
This was added eight years ago, simply for display purposes in the `calendar` report.
Since then its use has propagated and it influences the `sow` date reference.0
Further requests have been made to make this more flexible, so that the notion of 'weekend' can be defined.
Furthermore, the idea that every week has a weekend has also been questioned.
It has become clear that a `weekstart` setting, and the notion of a weekend are no longer useful.
## Proposed Support
One option is to allow the user to completely define a work week in the following way:
workweek=1,2,3,4,5
With Sunday as day zero, this states that the work week is the typical Monday - Friday.
From this setting, the meaning of `soww` and `eoww` can be determined, as well as `recur:weekday`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,176 +1,51 @@
.TH task-sync 5 2016-02-24 "${PACKAGE_STRING}" "User Manuals"
.SH NAME
task-sync \- A discussion and tutorial for the various
.BR task (1)
data synchronization capabilities.
task-sync \- A discussion and tutorial for the various task(1) data
synchronization capabilities.
.SH INTRODUCTION
Taskwarrior has several sync options, both external and built in. If you wish
to sync your data, choose one method only; mixing methods is going to lead to
problems. Each of the methods discussed have their own strengths.
Taskwarrior can synchronize your tasks to a server. This has a few benefits:
.SH ALTERNATIVES
There are three alternatives for syncing data, which are:
1) Version control systems, such as git, hg, svn
.br
- Makes your tasks accessible from multiple systems, called "replicas".
2) File sharing systems, such as DropBox, Google Drive
.br
- Provides a backup of your tasks.
3) Using the Taskserver and the 'sync' command
.SH OPTION 1: VERSION CONTROL SYSTEMS
There are several good, distributed VCS systems (git, hg, ...) and centralized
VCS systems (svn, cvs ...), and they all function in a similar fashion for our
purposes.
Setup is straightforward. You place your .task directory under revision
control. You then need to perform a regular commit/push/pull/update to make
sure that the data is propagated when needed. You can even do this using shell
scripts so that every task command is preceded by a 'pull' and followed by
a 'push'.
Strengths:
.br
- Saves disk space.
For example, you might want a replica of your tasks on your laptop and on your phone.
NOTE: A side-effect of synchronization is that once changes have been
synchronized, they cannot be undone. This means that each time synchronization
is run, it is no longer possible to undo previous operations.
.SH MANAGING SYNCHRONIZATION
.SS Adding a Replica
To add a new replica, configure a new, empty replica identically to
the existing replica, and run `task sync`.
.SS When to Synchronize
Taskwarrior can perform a sync operation at every garbage collection (gc) run.
This is the default, and is appropriate for local synchronization.
For synchronization to a server, a better solution is to run
$ task sync
periodically, such as via
.BR cron (8) .
.SH CONFIGURATION
Taskwarrior provides several options for synchronizing your tasks:
- To a server specifically designed to handle Taskwarrior data.
+ To a cloud storage provider. Currently only GCP is supported.
- To a local, on-disk file.
For most of these, you will need an encryption secret used to encrypt and
decrypt your tasks. This can be any secret string, and must match for all
replicas sharing tasks.
$ task config sync.encryption_secret <encryption_secret>
Tools such as
.BR pwgen (1)
can generate suitable secret values.
.SS Sync Server
To synchronize your tasks to a sync server, you will need the following
information from the server administrator:
- Good data transport mechanisms
.br
- The server's URL ("origin", such as "https://tw.example.com")
- Secure transport options
Weaknesses:
.br
- A client ID ("client_id") identifying your tasks
Configure Taskwarrior with these details:
$ task config sync.server.origin <origin>
$ task config sync.server.client_id <client_id>
Note that the origin must include the scheme, such as 'http://' or 'https://'.
.SS Google Cloud Platform
To synchronize your tasks to GCP, use the GCP Console to create a new project,
and within that project a new Cloud Storage bucket. The default settings for
the bucket are adequate.
Authenticate to the project with:
$ gcloud config set project $PROJECT_NAME
$ gcloud auth application-default login
Then configure Taskwarrior with:
$ task config sync.gcp.bucket <bucket-name>
However you can bring your own service account credentials if your
`application-default` is already being used by some other application
To begin, navigate to the "IAM and Admin" section in the Navigation Menu, then select "Roles."
On the top menu bar within the "Roles" section, click "CREATE ROLE."
Provide an appropriate name and description for the new role.
Add permissions to your new role using the filter "Service:storage" (not the "Filter permissions by role" input box).
Select the following permissions:
- storage.buckets.create
- storage.buckets.get
- storage.buckets.update
- storage.objects.create
- storage.objects.get
- storage.objects.list
- storage.objects.update
Create your new role.
On the left sidebar, navigate to "Service accounts."
On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT."
Provide an appropriate name and description for the new service account.
Select the role you just created and complete the service account creation process.
Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar.
Click on "ADD KEY" to create and download a new key (a JSON key).
- You need proficiency with VCS tools
.br
- You will need to manually resolve conflicts frequently
.br
- You need to provide the mechanism for making sure copies are up to date
Then configure Taskwarrior with:
$ task config sync.gcp.bucket <bucket-name>
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
.SS Local Synchronization
In order to take advantage of synchronization's side effect of saving disk
space without setting up a remote server, it is possible to sync tasks locally.
To configure local sync:
$ task config sync.local.server_dir /path/to/sync
The default configuration is to sync to a database in the task directory
("data.location").
.SH RUNNING TASKCHAMPION-SYNC-SERVER
The TaskChampion sync server is an HTTP server supporting multiple users.
Users are identified by a client ID, and users with different client IDs are
entirely independent. Task data is encrypted by Taskwarrior, and the sync
server never sees un-encrypted data.
The server is developed in
https://github.com/GothenburgBitFactory/taskchampion-sync-server.
.SS Adding a New User
To add a new user to the server, invent a new client ID with a tool like
`uuidgen` or an online UUID generator. There is no need to configure the server
for this new client ID: the sync server will automatically create a new user
whenever presented with a new client ID. Supply the ID, along with the
origin, to the user for inclusion in their Taskwarrior config. The user should
invent their own "encryption_secret".
.SH AVOIDING DUPLICATE RECURRING TASKS
If you run multiple clients that sync to the same server, you will need to run
this command on your primary client (the one you use most often):
$ task config recurrence on
And on the other clients, run:
$ task config recurrence off
This protects you against the effects of a sync/duplication bug.
.SH ALTERNATIVE: FILE SHARING SERVICES
.SH OPTION 2: FILE SHARING SERVICES
There are many file sharing services, such as DropBox, Amazon S3, Google Drive,
SkyDrive and more. This technique involves storing your .task directory in a
shared directory under the control of the file hosting services.
@@ -199,6 +74,78 @@ Weaknesses:
- Tasks are not properly merged
.SH OPTION 3: TASKSERVER
The Taskserver was designed for this purpose to be secure, fast and conflict-
free, allowing data interchange between assorted Taskwarrior clients, and
tolerant of network connectivity problems.
There is a 'sync' command built in to Taskwarrior (provided the GnuTLS library
is installed), and with a server account and client configuration, syncing is
done on demand.
Setup is a matter of creating an account on a Taskserver (see your Taskserver
provider or operate your own - see
https://taskwarrior.org/docs/taskserver/setup.html)
Once you have an account, you'll receive a certificate, key, and credentials.
You'll need to put the certificate and key somewhere like this:
$ cp <name>.cert.pem ~/.task
$ cp <name>.key.pem ~/.task
Then you configure Taskwarrior, using the provided details:
$ task config taskd.certificate ~/.task/<name>.cert.pem
$ task config taskd.key ~/.task/<name>.key.pem
$ task config taskd.credentials <organization>/<name>/<UUID>
$ task config taskd.server <server domain>:<port>
If you are using a private server, you are likely also using a self-signed
certificate, which means you will need one of the following additional entries:
$ task config taskd.ca ~/.task/ca.cert.pem
The CA (Certificate Authority) will be used to verify the server certificate.
After setup, you run a one-time sync initialization, like this:
$ task sync init
This will make sure your client and the server are properly in sync to begin
with. From this point on, you never run the 'initialize' command again, just
go about your business, and when you want to sync, run this:
$ task sync
You'll see a summary of how many tasks were uploaded and downloaded. You can
safely run the command as often as you like. When there are no changes to sync,
nothing happens. If you do not have connectivity, your task changes accumulate
so that when you next run 'sync' with proper connectivity, the changes are
properly handled, in the right order.
If you run multiple clients that sync to the same server, you will need to run
this command on your primary client (the one you use most often):
$ task config recurrence on
And on the other clients, run:
$ task config recurrence off
This protects you against the effects of a sync/duplication bug.
Strengths:
.br
- Secure communication
.br
- Minimal bandwidth
.br
- Tolerates connectivity outage
Weaknesses:
.br
- You need to manage your own server, or gain access to a hosted server.
.SH "CREDITS & COPYRIGHTS"
Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez.

View File

@@ -401,6 +401,15 @@ Modifies the existing task with provided information.
.B task <filter> prepend <mods>
Prepends description text to an existing task. Is affected by the context.
.TP
.B task <filter> purge
Permanently removes the specified tasks from the data files. Only
tasks that are already deleted can be purged. This command has a
local-only effect and changes introduced by it are not synced.
Is affected by the context.
Warning: causes permanent, non-revertible loss of data.
.TP
.B task <filter> start <mods>
Marks the specified tasks as started. Is affected by the context.
@@ -538,9 +547,13 @@ Shows statistics of the tasks defined by the filter. Is affected by the context.
Shows a report of aggregated task status by project. Is affected by the context.
.TP
.B task sync
.B task sync [init]
The sync command synchronizes data with the Taskserver, if configured.
The init subcommand should only ever be run once, and only on one client, because
it sends all data to the Taskserver. This allows all the subsequent sync commands
to only send small deltas.
Note: If you use multiple sync clients, make sure this setting (which is the default)
is on your primary client:
@@ -1129,11 +1142,11 @@ task ... due:juhannus
.SS FREQUENCIES
Recurrence periods. Taskwarrior supports several ways of specifying the
.I frequency
of recurring tasks. Note that frequencies can be abbreviated.
of recurring tasks.
.RS
.TP
daily, day, 1day, 1days, 2day, 2days, 1da, 2da, ...
daily, day, 1da, 2da, ...
Every day or a number of days.
.TP
@@ -1187,6 +1200,7 @@ active context. Here is a list of the commands that are affected:
log
prepend
projects
purge
start
stats
stop

View File

@@ -516,7 +516,7 @@ debug output can be useful. It can also help explain how the command line is
being parsed, but the information is displayed in a developer-friendly, not a
user-friendly way.
Turning debug on automatically sets debug.hooks=1 and debug.parser=1
Turning debug on automatically sets debug.hooks=1, debug.parser=1 and debug.tls=2
if they do not already have assigned values. Defaults to "0".
.TP
@@ -531,6 +531,11 @@ Level 1 shows the final parse tree.
Level 2 shows the parse tree from all phases of the parse.
Level 3 shows expression evaluation details.
.TP
.B debug.tls=0
Controls the GnuTLS diagnostic level. For 'sync' debugging. Level 0 means no
diagnostics. Level 9 is the highest. Level 2 is a good setting for debugging.
.TP
.B obfuscate=0
When set to '1', will replace all report text with 'xxx'.
@@ -1522,6 +1527,58 @@ context.home.rc.default.command=home_report
These configuration settings are used to connect and sync tasks with the task
server.
.TP
.B taskd.server=<host>:<port>
.RS
Specifies the hostname and port of the Taskserver. Hostname may be an IPv4 or
IPv6 address, or domain. Port is an integer.
.RE
.TP
.B taskd.credentials=<organization>/<user>/<key>
.RS
User identification for the Taskserver, which includes a private key.
.RE
.TP
.B taskd.certificate=<path>
.RS
Specifies the path to the client certificate used for identification with the
Taskserver.
.RE
.TP
.B taskd.key=<path>
.RS
Specifies the path to the client key used for encrypted communication with the
Taskserver.
.RE
.TP
.B taskd.ca=<path>
.RS
Specifies the path to the CA certificate in the event that your Taskserver is
using a self-signed certificate. Optional.
.RE
.TP
.B taskd.trust=strict|ignore hostname|allow all
.RS
This settings allows you to override the trust level when server certificates
are validated. With "allow all", the server certificate is trusted
automatically. With "ignore hostname", the server certificate is verified but
the hostname is ignored. With "strict", the server certificate is verified.
Default is "strict", which requires full validation.
.RE
.TP
.B taskd.ciphers=NORMAL
Override of the cipher selection. The set of ciphers used by TLS may be
controlled by both server and client. There must be some overlap between
client and server supported ciphers, or communication cannot occur.
Default is "NORMAL". See GnuTLS documentation for full details.
.RE
.SH "CREDITS & COPYRIGHTS"
Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez.

View File

@@ -1,17 +1,57 @@
version: '3'
services:
test-fedora38:
test-centos7:
build:
context: .
dockerfile: test/docker/fedora38
dockerfile: test/docker/centos7
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-fedora39:
test-centos8:
build:
context: .
dockerfile: test/docker/fedora39
dockerfile: test/docker/centos8
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-fedora32:
build:
context: .
dockerfile: test/docker/fedora32
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-fedora33:
build:
context: .
dockerfile: test/docker/fedora33
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-fedora34:
build:
context: .
dockerfile: test/docker/fedora34
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-fedora35:
build:
context: .
dockerfile: test/docker/fedora35
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-ubuntu1804:
build:
context: .
dockerfile: test/docker/ubuntu1804
network_mode: "host"
security_opt:
- label=type:container_runtime_t
@@ -24,10 +64,26 @@ services:
security_opt:
- label=type:container_runtime_t
tty: true
test-ubuntu2204:
test-ubuntu2104:
build:
context: .
dockerfile: test/docker/ubuntu2204
dockerfile: test/docker/ubuntu2104
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-ubuntu2110:
build:
context: .
dockerfile: test/docker/ubuntu2110
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-debianstable:
build:
context: .
dockerfile: test/docker/debianstable
network_mode: "host"
security_opt:
- label=type:container_runtime_t
@@ -40,10 +96,18 @@ services:
security_opt:
- label=type:container_runtime_t
tty: true
test-opensuse:
test-gentoo:
build:
context: .
dockerfile: test/docker/opensuse
dockerfile: test/docker/gentoo
network_mode: "host"
security_opt:
- label=type:container_runtime_t
tty: true
test-opensuse15:
build:
context: .
dockerfile: test/docker/opensuse15
network_mode: "host"
security_opt:
- label=type:container_runtime_t

View File

@@ -1,42 +0,0 @@
FROM ubuntu:22.04 AS base
FROM base AS builder
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && \
apt-get install -y \
build-essential \
cmake \
curl \
git \
libgnutls28-dev \
uuid-dev
# Setup language environment
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8
# Add source directory
ADD .. /root/code/
WORKDIR /root/code/
# Setup Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
sh rustup.sh -y --profile minimal --default-toolchain stable --component rust-docs
# Build Taskwarrior
RUN git clean -dfx && \
git submodule init && \
git submodule update && \
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release . && \
cmake --build build -j 8
FROM base AS runner
# Install Taskwarrior
COPY --from=builder /root/code/build/src/task /usr/local/bin
# Initialize Taskwarrior
RUN ( echo "yes" | task ) || true

View File

@@ -1,3 +0,0 @@
# misc/
This directory contains bits and bobs that do not belong elsewhere.

View File

@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.22)
cmake_minimum_required (VERSION 3.0)
add_custom_target (performance ./run_perf
DEPENDS task_executable

View File

@@ -31,18 +31,18 @@ while (my $line = <$fh>)
if ($. % 20 == 19)
{
my $anno_id = $id - 1;
qx{../build/src/task rc:perf.rc rc.gc=off $anno_id annotate $line};
qx{../src/task rc:perf.rc rc.gc=off $anno_id annotate $line};
print "[$.] task rc:perf.rc rc.gc=off $anno_id annotate $line\n" if $?;
}
elsif ($. % 4 == 1)
{
qx{../build/src/task rc:perf.rc rc.gc=off add $line};
qx{../src/task rc:perf.rc rc.gc=off add $line};
print "[$.] task rc:perf.rc rc.gc=off add $line\n" if $?;
++$id;
}
else
{
qx{../build/src/task rc:perf.rc rc.gc=off log $line};
qx{../src/task rc:perf.rc rc.gc=off log $line};
print "[$.] task rc:perf.rc rc.gc=off log $line\n" if $?;
}
}

View File

@@ -16,7 +16,7 @@ fi
# Allow override.
if [[ -z $TASK ]]
then
TASK=../build/src/task
TASK=../src/task
fi
# Run benchmarks.

View File

@@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.22)
cmake_minimum_required (VERSION 3.0)
install (DIRECTORY bash fish vim hooks
DESTINATION ${TASK_DOCDIR}/scripts)
install (FILES zsh/_task

View File

@@ -123,10 +123,6 @@ function __fish.task.need_to_complete.config
contains (__fish.task.current.command) 'config' 'show'
end
function __fish.task.need_to_complete.context
contains (__fish.task.current.command) 'context'
end
function __fish.task.need_to_complete.filter
__fish.task.before_command
end
@@ -221,10 +217,6 @@ function __fish.task.list.config
task _config
end
function __fish.task.list.context
task _context
end
function __fish.task.list.depends
__fish.task.list.id with_description
end
@@ -295,9 +287,8 @@ end
function __fish.task.list.tag
set -l tags (task _tags)
printf '+%s\n' $tags
# compatibility, older fish won't allow - in format
printf ' %s\n' $tags | tr ' ' '-'
printf -- '+%s\n' $tags
printf -- '-%s\n' $tags
end
function __fish.task.list.task
@@ -355,10 +346,10 @@ end
# static variables that won't changes even when taskw's data is modified
set __fish_task_static_commands_with_desc (__fish.task.zsh commands | sort | string collect)
set __fish_task_static_commands (echo -e $__fish_task_static_commands_with_desc | cut -d ' ' -f 1 | string collect)
set __fish_task_static_command_mods (printf '%s\n' 'add' 'annotate' 'append' 'delete' 'done' 'duplicate' 'log' 'modify' 'prepend' 'start' 'stop' | string collect)
set __fish_task_static_mod (printf '%s\n' 'before' 'after' 'over' 'under' 'none' 'is' 'isnt' 'has' 'hasnt' 'startswith' 'endswith' 'word' 'noword' | string collect)
set __fish_task_static_status (printf '%s\tstatus\n' 'pending' 'completed' 'deleted' 'waiting' | string collect)
set __fish_task_static_priority (printf '%s\n' 'H\tHigh' 'M\tMiddle' 'L\tLow' | string collect)
set __fish_task_static_command_mods (printf -- '%s\n' 'add' 'annotate' 'append' 'delete' 'done' 'duplicate' 'log' 'modify' 'prepend' 'start' 'stop' | string collect)
set __fish_task_static_mod (printf -- '%s\n' 'before' 'after' 'over' 'under' 'none' 'is' 'isnt' 'has' 'hasnt' 'startswith' 'endswith' 'word' 'noword' | string collect)
set __fish_task_static_status (printf -- '%s\tstatus\n' 'pending' 'completed' 'deleted' 'waiting' | string collect)
set __fish_task_static_priority (printf -- '%s\n' 'H\tHigh' 'M\tMiddle' 'L\tLow' | string collect)
set __fish_task_static_freq 'daily:Every day' \
'day:Every day' \
@@ -373,17 +364,17 @@ set __fish_task_static_freq 'daily:Every day' \
'yearly:Every year' \
'biannual:Every two years' \
'biyearly:Every two years'
set __fish_task_static_freq (printf '%s\n' $__fish_task_static_freq | sed 's/:/\t/' | string collect)
set __fish_task_static_freq (printf -- '%s\n' $__fish_task_static_freq | sed 's/:/\t/' | string collect)
set __fish_task_static_freq_numeric 'd:days' \
'w:weeks' \
'q:quarters' \
'y:years'
set __fish_task_static_freq_numeric (printf '%s\n' $__fish_task_static_freq_numeric | sed 's/:/\t/' | string collect)
set __fish_task_static_freq_numeric (printf -- '%s\n' $__fish_task_static_freq_numeric | sed 's/:/\t/' | string collect)
set __fish_task_static_freq_numeric 'd:days' \
'w:weeks' \
'q:quarters' \
'y:years'
set __fish_task_static_freq_numeric (printf '%s\n' $__fish_task_static_freq_numeric | sed 's/:/\t/' | string collect)
set __fish_task_static_freq_numeric (printf -- '%s\n' $__fish_task_static_freq_numeric | sed 's/:/\t/' | string collect)
set __fish_task_static_dates 'today:Today' \
'yesterday:Yesterday' \
'tomorrow:Tomorrow' \
@@ -415,7 +406,7 @@ set __fish_task_static_dates 'today:Today' \
'midsommarafton:Midsommarafton' \
'later:Later' \
'someday:Some Day'
set __fish_task_static_dates (printf '%s\n' $__fish_task_static_dates | sed 's/:/\t/' | string collect)
set __fish_task_static_dates (printf -- '%s\n' $__fish_task_static_dates | sed 's/:/\t/' | string collect)
set __fish_task_static_reldates 'hrs:n hours' \
'day:n days' \
# '1st:first' \
@@ -423,7 +414,7 @@ set __fish_task_static_reldates 'hrs:n hours' \
# '3rd:third' \
# 'th:4th, 5th, etc.' \
'wks:weeks'
set __fish_task_static_reldates (printf '%s\n' $__fish_task_static_reldates | sed 's/:/\t/' | string collect)
set __fish_task_static_reldates (printf -- '%s\n' $__fish_task_static_reldates | sed 's/:/\t/' | string collect)
# the followings are actually not used for autocomplete, but to retrieve friendly description that aren't present in internal command
set __fish_task_static_attr_desc_keys 'description' 'status' 'project' \
'priority' 'due' 'recur' \
@@ -438,7 +429,6 @@ set __fish_task_static_attr_desc_vals 'Task description text' 'Status of task -
# fish's auto-completion when multiple `complete` have supplied with '-k' flag, the last will be displayed first
__fish.task.complete config
__fish.task.complete context
__fish.task.complete attr_value
__fish.task.complete attr_name
__fish.task.complete tag

View File

@@ -5,7 +5,7 @@ FROM centos:8
RUN dnf update -y
RUN yum install epel-release -y
RUN dnf install python38 vim git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man gdb -y
RUN dnf install python38 vim git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime sudo man gdb -y
RUN useradd warrior
RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior

View File

@@ -3,13 +3,9 @@
FROM centos:8
# Workaround to the location of the repos
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
RUN dnf update -y
RUN yum install epel-release -y
RUN dnf install python38 git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man -y
RUN dnf install python38 git gcc gcc-c++ cmake make gnutls-devel libuuid-devel libfaketime sudo man -y
RUN useradd warrior
RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior
@@ -17,10 +13,6 @@ RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior
USER warrior
WORKDIR /home/warrior/
# Setup Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \
sh rustup.sh -y --profile minimal --default-toolchain stable --component rust-docs
# Setup taskwarrior
# The purpose is to speed up subsequent re-installs due to Docker layer caching
RUN git clone https://github.com/GothenburgBitFactory/taskwarrior.git

View File

@@ -38,15 +38,12 @@ syn match taskrcGoodKey '^\s*\V_forcecolor='he=e-1
syn match taskrcGoodKey '^\s*\Vabbreviation.minimum='he=e-1
syn match taskrcGoodKey '^\s*\Vactive.indicator='he=e-1
syn match taskrcGoodKey '^\s*\Valias.\S\{-}='he=e-1
syn match taskrcGoodKey '^\s*\Vallow.empty.filter='he=e-1
syn match taskrcGoodKey '^\s*\Vavoidlastcolumn='he=e-1
syn match taskrcGoodKey '^\s*\Vbulk='he=e-1
syn match taskrcGoodKey '^\s*\Vburndown.cumulative='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.details='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.details.report='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.holidays='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.legend='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.monthsperline='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.offset='he=e-1
syn match taskrcGoodKey '^\s*\Vcalendar.offset.value='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor='he=e-1
@@ -61,13 +58,10 @@ syn match taskrcGoodKey '^\s*\Vcolor.calendar.due='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.due.today='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.holiday='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.overdue='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.scheduled='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.today='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.weekend='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.calendar.weeknumber='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.completed='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.debug='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.deleted='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.due='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.due.today='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.error='he=e-1
@@ -76,8 +70,6 @@ syn match taskrcGoodKey '^\s*\Vcolor.header='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.history.add='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.history.delete='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.history.done='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.label='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.label.sort='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.overdue='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.uda.priority.H='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.uda.priority.L='he=e-1
@@ -93,16 +85,11 @@ syn match taskrcGoodKey '^\s*\Vcolor.tagged='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.undo.after='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.undo.before='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.until='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.warning='he=e-1
syn match taskrcGoodKey '^\s*\Vcolor.\(tag\|project\|keyword\|uda\).\S\{-}='he=e-1
syn match taskrcGoodKey '^\s*\Vcolumn.padding='he=e-1
syn match taskrcGoodKey '^\s*\Vcomplete.all.tags='he=e-1
syn match taskrcGoodKey '^\s*\Vconfirmation='he=e-1
syn match taskrcGoodKey '^\s*\Vcontext='he=e-1
syn match taskrcGoodKey '^\s*\Vcontext.\S\{-}.\(read\|write\)='he=e-1
syn match taskrcGoodKey '^\s*\Vcontext.\S\{-}.rc.\S\{-}='he=e-1
syn match taskrcGoodKey '^\s*\Vdata.location='he=e-1
syn match taskrcGoodKey '^\s*\Vdate.iso='he=e-1
syn match taskrcGoodKey '^\s*\Vdateformat='he=e-1
syn match taskrcGoodKey '^\s*\Vdateformat.annotation='he=e-1
syn match taskrcGoodKey '^\s*\Vdateformat.edit='he=e-1
@@ -110,8 +97,7 @@ syn match taskrcGoodKey '^\s*\Vdateformat.holiday='he=e-1
syn match taskrcGoodKey '^\s*\Vdateformat.info='he=e-1
syn match taskrcGoodKey '^\s*\Vdateformat.report='he=e-1
syn match taskrcGoodKey '^\s*\Vdebug='he=e-1
syn match taskrcGoodKey '^\s*\Vdebug.hooks='he=e-1
syn match taskrcGoodKey '^\s*\Vdebug.parser='he=e-1
syn match taskrcGoodKey '^\s*\Vdebug.tls='he=e-1
syn match taskrcGoodKey '^\s*\Vdefault.command='he=e-1
syn match taskrcGoodKey '^\s*\Vdefault.due='he=e-1
syn match taskrcGoodKey '^\s*\Vdefault.priority='he=e-1
@@ -126,14 +112,11 @@ syn match taskrcGoodKey '^\s*\Vdetection='he=e-1
syn match taskrcGoodKey '^\s*\Vdisplayweeknumber='he=e-1
syn match taskrcGoodKey '^\s*\Vdom='he=e-1
syn match taskrcGoodKey '^\s*\Vdue='he=e-1
syn match taskrcGoodKey '^\s*\Veditor='he=e-1
syn match taskrcGoodKey '^\s*\Vexit.on.missing.db='he=e-1
syn match taskrcGoodKey '^\s*\Vexpressions='he=e-1
syn match taskrcGoodKey '^\s*\Vextensions='he=e-1
syn match taskrcGoodKey '^\s*\Vfontunderline='he=e-1
syn match taskrcGoodKey '^\s*\Vgc='he=e-1
syn match taskrcGoodKey '^\s*\Vhooks='he=e-1
syn match taskrcGoodKey '^\s*\Vhooks.location='he=e-1
syn match taskrcGoodKey '^\s*\Vhyphenate='he=e-1
syn match taskrcGoodKey '^\s*\Vindent.annotation='he=e-1
syn match taskrcGoodKey '^\s*\Vindent.report='he=e-1
@@ -147,25 +130,18 @@ syn match taskrcGoodKey '^\s*\Vlist.all.tags='he=e-1
syn match taskrcGoodKey '^\s*\Vlocale='he=e-1
syn match taskrcGoodKey '^\s*\Vlocking='he=e-1
syn match taskrcGoodKey '^\s*\Vnag='he=e-1
syn match taskrcGoodKey '^\s*\Vnews.version='he=e-1
syn match taskrcGoodKey '^\s*\Vobfuscate='he=e-1
syn match taskrcGoodKey '^\s*\Vprint.empty.columns='he=e-1
syn match taskrcGoodKey '^\s*\Vrecurrence='he=e-1
syn match taskrcGoodKey '^\s*\Vrecurrence.confirmation='he=e-1
syn match taskrcGoodKey '^\s*\Vrecurrence.indicator='he=e-1
syn match taskrcGoodKey '^\s*\Vrecurrence.limit='he=e-1
syn match taskrcGoodKey '^\s*\Vregex='he=e-1
syn match taskrcGoodKey '^\s*\Vreport.\S\{-}.\(description\|columns\|context\|labels\|sort\|filter\|dateformat\|annotations\)='he=e-1
syn match taskrcGoodKey '^\s*\Vreport.\S\{-}.\(description\|columns\|labels\|sort\|filter\|dateformat\|annotations\)='he=e-1
syn match taskrcGoodKey '^\s*\Vreserved.lines='he=e-1
syn match taskrcGoodKey '^\s*\Vrow.padding='he=e-1
syn match taskrcGoodKey '^\s*\Vrule.color.merge='he=e-1
syn match taskrcGoodKey '^\s*\Vrule.precedence.color='he=e-1
syn match taskrcGoodKey '^\s*\Vsearch.case.sensitive='he=e-1
syn match taskrcGoodKey '^\s*\Vsummary.all.projects='he=e-1
syn match taskrcGoodKey '^\s*\Vsugar='he=e-1
syn match taskrcGoodKey '^\s*\Vsync.\(server.\(origin\|client_id\|encryption_secret\)\|local.server_dir\)='he=e-1
syn match taskrcGoodKey '^\s*\Vtag.indicator='he=e-1
syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\|indicator\)='he=e-1
syn match taskrcGoodKey '^\s*\Vtaskd.\(server\|credentials\|certificate\|key\|ca\|trust\|ciphers\)='he=e-1
syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\)='he=e-1
syn match taskrcGoodKey '^\s*\Vundo.style='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.active.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.age.coefficient='he=e-1
@@ -183,9 +159,6 @@ syn match taskrcGoodKey '^\s*\Vurgency.scheduled.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.tags.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.uda.\S\{-}.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.waiting.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.user.tag.\S\{-}.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.user.project.\S\{-}.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.user.keyword.\S\{-}.coefficient='he=e-1
syn match taskrcGoodKey '^\s*\Vurgency.inherit='he=e-1
syn match taskrcGoodKey '^\s*\Vverbose='he=e-1
syn match taskrcGoodKey '^\s*\Vweekstart='he=e-1

View File

@@ -1,11 +1,9 @@
cmake_minimum_required (VERSION 3.22)
cmake_minimum_required (VERSION 3.0)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
add_library (task STATIC CLI2.cpp CLI2.h
@@ -17,8 +15,8 @@ add_library (task STATIC CLI2.cpp CLI2.h
Lexer.cpp Lexer.h
TDB2.cpp TDB2.h
Task.cpp Task.h
TLSClient.cpp TLSClient.h
Variant.cpp Variant.h
Version.cpp Version.h
ViewTask.cpp ViewTask.h
dependency.cpp
feedback.cpp
@@ -52,16 +50,9 @@ add_executable (calc_executable calc.cpp)
add_executable (lex_executable lex.cpp)
# Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes.
# Similarly for `tc`.
target_link_libraries (task_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (calc_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES})
if (DARWIN)
# SystemConfiguration is required by Rust libraries like reqwest, to get proxy configuration.
target_link_libraries (task_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")
target_link_libraries (calc_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")
target_link_libraries (lex_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration")
endif (DARWIN)
target_link_libraries (task_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (calc_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
target_link_libraries (lex_executable task commands columns libshared task libshared ${TASK_LIBRARIES})
set_property (TARGET task_executable PROPERTY OUTPUT_NAME "task")

View File

@@ -58,7 +58,6 @@
////////////////////////////////////////////////////////////////////////////////
// This string is parsed and used as default values for configuration.
// Note: New configuration options should be added to the vim syntax file in scripts/vim/syntax/taskrc.vim
std::string configurationDefaults =
"# Taskwarrior program configuration file.\n"
"# For more documentation, see https://taskwarrior.org or try 'man task', 'man task-color',\n"
@@ -272,19 +271,21 @@ std::string configurationDefaults =
"list.all.tags=0 # Include old tag names in 'tags' command\n"
"print.empty.columns=0 # Print columns which have no data for any task\n"
"debug=0 # Display diagnostics\n"
"debug.tls=0 # Sync diagnostics\n"
"sugar=1 # Syntactic sugar\n"
"obfuscate=0 # Obfuscate data for error reporting\n"
"fontunderline=1 # Uses underlines rather than -------\n"
"\n"
"# WARNING: Please read the documentation (man task-sync) before setting up\n"
"# Taskwarrior for Taskserver synchronization.\n"
"\n"
"#sync.encryption_secret # Encryption secret for sync to a server\n"
"#sync.server.client_id # Client ID for sync to a server\n"
"#sync.server.origin # Origin of the sync server\n"
"#sync.local.server_dir # Directory for local sync\n"
"#sync.gcp.credential_path # Path to JSON file containing credentials to authenticate GCP Sync\n"
"#sync.gcp.bucket # Bucket for sync to GCP\n"
"#taskd.ca=<certificate file>\n"
"#taskd.certificate=<certificate file>\n"
"#taskd.credentials=<organization>/<name>/<password>\n"
"#taskd.server=<server>:<port>\n"
"taskd.trust=strict\n"
"#taskd.trust=ignore hostname\n"
"#taskd.trust=allow all\n"
"taskd.ciphers=NORMAL\n"
"\n"
"# Aliases - alternate names for commands\n"
"alias.rm=delete # Alias for the delete command\n"
@@ -558,11 +559,9 @@ int Context::initialize (int argc, const char** argv)
if (taskdata_overridden && verbose ("override"))
header (format ("TASKDATA override: {1}", data_dir._data));
tdb2.set_location (data_dir);
createDefaultConfig ();
bool create_if_missing = !config.getBoolean ("exit.on.missing.db");
tdb2.open_replica (data_dir, create_if_missing);
////////////////////////////////////////////////////////////////////////////
//
// [3] Instantiate Command objects and capture command entities.
@@ -735,6 +734,7 @@ int Context::run ()
{
hooks.onLaunch ();
rc = dispatch (output);
tdb2.commit (); // Harmless if called when nothing changed.
hooks.onExit (); // No chance to update data.
timer_total.stop ();
@@ -850,7 +850,8 @@ int Context::dispatch (std::string &out)
assert (c);
// The command know whether they need a GC.
if (c->needs_gc ())
if (c->needs_gc () &&
! tdb2.read_only ())
{
run_gc = config.getBoolean ("gc");
tdb2.gc ();
@@ -860,6 +861,13 @@ int Context::dispatch (std::string &out)
run_gc = false;
}
/*
// Only read-only commands can be run when TDB2 is read-only.
// TODO Implement TDB2::read_only
if (tdb2.read_only () && !c->read_only ())
throw std::string ("");
*/
// This is something that is only needed for write commands with no other
// filter processing.
if (c->accepts_modifications () &&
@@ -1253,6 +1261,23 @@ void Context::createDefaultConfig ()
if (! File::write (rc_file._data, contents.str ()))
throw format ("Could not write to '{1}'.", rc_file._data);
}
// Create data location, if necessary.
Directory d (data_dir);
if (! d.exists ())
{
if (config.getBoolean ("exit.on.missing.db"))
throw std::string ("Error: rc.data.location does not exist - exiting according to rc.exit.on.missing.db setting.");
d.create ();
if (config.has ("hooks.location"))
d = Directory (config.get ("hooks.location"));
else
d += "hooks";
d.create ();
}
}
////////////////////////////////////////////////////////////////////////////////
@@ -1352,13 +1377,16 @@ void Context::loadAliases ()
}
////////////////////////////////////////////////////////////////////////////////
// Using the general rc.debug setting automaticalls sets debug.hooks
// Using the general rc.debug setting automaticalls sets debug.tls, debug.hooks
// and debug.parser, unless they already have values, which by default they do
// not.
void Context::propagateDebug ()
{
if (config.getBoolean ("debug"))
{
if (! config.has ("debug.tls"))
config.set ("debug.tls", 2);
if (! config.has ("debug.hooks"))
config.set ("debug.hooks", 1);

View File

@@ -90,8 +90,13 @@ bool getDOM (const std::string& name, Variant& value)
if (name == "tw.syncneeded")
{
value = Variant (0);
if (Context::getContext ().tdb2.num_local_changes () > 0) {
value = Variant (1);
for (const auto& line : Context::getContext ().tdb2.backlog.get_lines ())
{
if (line[0] == '{')
{
value = Variant (1);
break;
}
}
return true;
@@ -417,7 +422,7 @@ bool getDOM (const std::string& name, const Task* task, Variant& value)
{
// annotation_1234567890
// 0 ^11
value = Variant ((time_t) strtoll (i.first.substr (11).c_str (), NULL, 10), Variant::type_date);
value = Variant ((time_t) strtol (i.first.substr (11).c_str (), NULL, 10), Variant::type_date);
return true;
}
else if (elements[2] == "description")

View File

@@ -98,7 +98,7 @@ void Filter::subset (std::vector <Task>& output)
if (precompiled.size ())
{
Timer timer_pending;
auto pending = Context::getContext ().tdb2.pending_tasks ();
auto pending = Context::getContext ().tdb2.pending.get_tasks ();
Context::getContext ().time_filter_us -= timer_pending.total_us ();
_startCount = (int) pending.size ();
@@ -126,7 +126,7 @@ void Filter::subset (std::vector <Task>& output)
if (! shortcut)
{
Timer timer_completed;
auto completed = Context::getContext ().tdb2.completed_tasks ();
auto completed = Context::getContext ().tdb2.completed.get_tasks ();
Context::getContext ().time_filter_us -= timer_completed.total_us ();
_startCount += (int) completed.size ();
@@ -149,7 +149,11 @@ void Filter::subset (std::vector <Task>& output)
safety ();
Timer pending_completed;
output = Context::getContext ().tdb2.all_tasks ();
for (auto& task : Context::getContext ().tdb2.pending.get_tasks ())
output.push_back (task);
for (auto& task : Context::getContext ().tdb2.completed.get_tasks ())
output.push_back (task);
Context::getContext ().time_filter_us -= pending_completed.total_us ();
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,12 +35,71 @@
#include <stdio.h>
#include <FS.h>
#include <Task.h>
#include <tc/WorkingSet.h>
#include <tc/Replica.h>
namespace tc {
class Server;
}
// TF2 Class represents a single file in the task database.
class TF2
{
public:
TF2 ();
~TF2 ();
void target (const std::string&);
const std::vector <Task>& get_tasks ();
const std::vector <std::string>& get_lines ();
bool get (int, Task&);
bool get (const std::string&, Task&);
bool has (const std::string&);
void add_task (Task&);
bool modify_task (const Task&);
bool purge_task (const Task&);
void add_line (const std::string&);
void clear_tasks ();
void clear_lines ();
void commit ();
Task load_task (const std::string&);
void load_gc (Task&);
void load_tasks (bool from_gc = false);
void load_lines ();
// ID <--> UUID mapping.
std::string uuid (int);
int id (const std::string&);
void has_ids ();
void auto_dep_scan ();
void clear ();
const std::string dump ();
void dependency_scan ();
bool _read_only;
bool _dirty;
bool _loaded_tasks;
bool _loaded_lines;
bool _has_ids;
bool _auto_dep_scan;
std::vector <Task> _tasks;
// _tasks_map was introduced mainly for speeding up "task import".
// Iterating over all _tasks for each imported task is slow, making use of
// appropriate data structures is fast.
std::unordered_map <std::string, Task> _tasks_map;
std::vector <Task> _added_tasks;
std::vector <Task> _modified_tasks;
std::unordered_set <std::string> _purged_tasks;
std::vector <std::string> _lines;
std::vector <std::string> _added_lines;
File _file;
private:
std::unordered_map <int, std::string> _I2U; // ID -> UUID map
std::unordered_map <std::string, int> _U2I; // UUID -> ID map
};
// TDB2 Class represents all the files in the task database.
class TDB2
@@ -50,18 +109,19 @@ public:
TDB2 ();
void open_replica (const std::string&, bool create_if_missing);
void add (Task&);
void modify (Task&);
void set_location (const std::string&);
void add (Task&, bool add_to_backlog = true);
void modify (Task&, bool add_to_backlog = true);
void purge (Task&);
void commit ();
void get_changes (std::vector <Task>&);
void revert ();
void gc ();
int next_id ();
int latest_id ();
// Generalized task accessors.
const std::vector <Task> all_tasks ();
const std::vector <Task> pending_tasks ();
const std::vector <Task> completed_tasks ();
bool get (int, Task&);
bool get (const std::string&, Task&);
bool has (const std::string&);
@@ -72,24 +132,32 @@ public:
std::string uuid (int);
int id (const std::string&);
int num_local_changes ();
int num_reverts_possible ();
// Read-only mode.
bool read_only ();
void clear ();
void dump ();
void sync (tc::Server server, bool avoid_snapshots);
bool confirm_revert(struct tc::ffi::TCReplicaOpList);
private:
void gather_changes ();
void update (Task&, const bool, const bool addition = false);
bool verifyUniqueUUID (const std::string&);
void show_diff (const std::string&, const std::string&, const std::string&);
void revert_undo (std::vector <std::string>&, std::string&, std::string&, std::string&, std::string&);
void revert_pending (std::vector <std::string>&, const std::string&, const std::string&);
void revert_completed (std::vector <std::string>&, std::vector <std::string>&, const std::string&, const std::string&);
void revert_backlog (std::vector <std::string>&, const std::string&, const std::string&, const std::string&);
public:
TF2 pending;
TF2 completed;
TF2 undo;
TF2 backlog;
private:
tc::Replica replica;
std::optional<tc::WorkingSet> _working_set;
// UUID -> Task containing all tasks modified in this invocation.
std::map<std::string, Task> changes;
const tc::WorkingSet &working_set ();
static std::string option_string (std::string input);
static void show_diff (const std::string&, const std::string&, const std::string&);
std::string _location;
int _id;
std::vector <Task> _changes;
};
#endif

576
src/TLSClient.cpp Normal file
View File

@@ -0,0 +1,576 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <cmake.h>
#ifdef HAVE_LIBGNUTLS
#include <TLSClient.h>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <gnutls/x509.h>
#include <shared.h>
#include <format.h>
#define HEADER_SIZE 4
#define MAX_BUF 16384
#if GNUTLS_VERSION_NUMBER < 0x030406
#if GNUTLS_VERSION_NUMBER >= 0x020a00
static int verify_certificate_callback (gnutls_session_t);
#endif
#endif
////////////////////////////////////////////////////////////////////////////////
static void gnutls_log_function (int level, const char* message)
{
std::cout << "c: " << level << ' ' << message;
}
////////////////////////////////////////////////////////////////////////////////
#if GNUTLS_VERSION_NUMBER < 0x030406
#if GNUTLS_VERSION_NUMBER >= 0x020a00
static int verify_certificate_callback (gnutls_session_t session)
{
const TLSClient* client = (TLSClient*) gnutls_session_get_ptr (session); // All
return client->verify_certificate ();
}
#endif
#endif
////////////////////////////////////////////////////////////////////////////////
TLSClient::~TLSClient ()
{
gnutls_deinit (_session); // All
gnutls_certificate_free_credentials (_credentials); // All
#if GNUTLS_VERSION_NUMBER < 0x030300
gnutls_global_deinit (); // All
#endif
if (_socket)
{
shutdown (_socket, SHUT_RDWR);
close (_socket);
}
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::limit (int max)
{
_limit = max;
}
////////////////////////////////////////////////////////////////////////////////
// Calling this method results in all subsequent socket traffic being sent to
// std::cout, labelled with 'c: ...'.
void TLSClient::debug (int level)
{
if (level)
_debug = true;
gnutls_global_set_log_function (gnutls_log_function); // All
gnutls_global_set_log_level (level); // All
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::trust (const enum trust_level value)
{
_trust = value;
if (_debug)
{
if (_trust == allow_all)
std::cout << "c: INFO Server certificate will be trusted automatically.\n";
else if (_trust == ignore_hostname)
std::cout << "c: INFO Server certificate will be verified but hostname ignored.\n";
else
std::cout << "c: INFO Server certificate will be verified.\n";
}
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::ciphers (const std::string& cipher_list)
{
_ciphers = cipher_list;
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::init (
const std::string& ca,
const std::string& cert,
const std::string& key)
{
_ca = ca;
_cert = cert;
_key = key;
int ret;
#if GNUTLS_VERSION_NUMBER < 0x030300
ret = gnutls_global_init (); // All
if (ret < 0)
throw format ("TLS init error. {1}", gnutls_strerror (ret)); // All
#endif
ret = gnutls_certificate_allocate_credentials (&_credentials); // All
if (ret < 0)
throw format ("TLS allocation error. {1}", gnutls_strerror (ret)); // All
#if GNUTLS_VERSION_NUMBER >= 0x030014
// Automatic loading of system installed CA certificates.
ret = gnutls_certificate_set_x509_system_trust (_credentials); // 3.0.20
if (ret < 0)
throw format ("Bad System Trust. {1}", gnutls_strerror (ret)); // All
#endif
if (_ca != "")
{
// The gnutls_certificate_set_x509_key_file call returns number of
// certificates parsed on success (including 0, when no certificate was
// found) and negative values on error
ret = gnutls_certificate_set_x509_trust_file (_credentials, _ca.c_str (), GNUTLS_X509_FMT_PEM); // All
if (ret == 0)
throw format ("CA file {1} contains no certificate.", _ca);
else if (ret < 0)
throw format ("Bad CA file: {1}", gnutls_strerror (ret)); // All
}
// TODO This may need 0x030111 protection.
if (_cert != "" &&
_key != "" &&
(ret = gnutls_certificate_set_x509_key_file (_credentials, _cert.c_str (), _key.c_str (), GNUTLS_X509_FMT_PEM)) < 0) // 3.1.11
throw format ("Bad client CERT/KEY file. {1}", gnutls_strerror (ret)); // All
#if GNUTLS_VERSION_NUMBER < 0x030406
#if GNUTLS_VERSION_NUMBER >= 0x020a00
// The automatic verification for the server certificate with
// gnutls_certificate_set_verify_function only works with gnutls
// >=2.9.10. So with older versions we should call the verify function
// manually after the gnutls handshake.
gnutls_certificate_set_verify_function (_credentials, verify_certificate_callback); // 2.10.0
#endif
#endif
ret = gnutls_init (&_session, GNUTLS_CLIENT); // All
if (ret < 0)
throw format ("TLS client init error. {1}", gnutls_strerror (ret)); // All
// Use default priorities unless overridden.
if (_ciphers == "")
_ciphers = "NORMAL";
const char *err;
ret = gnutls_priority_set_direct (_session, _ciphers.c_str (), &err); // All
if (ret < 0)
{
if (_debug && ret == GNUTLS_E_INVALID_REQUEST)
std::cout << "c: ERROR Priority error at: " << err << '\n';
throw format ("Error initializing TLS. {1}", gnutls_strerror (ret)); // All
}
// Apply the x509 credentials to the current session.
ret = gnutls_credentials_set (_session, GNUTLS_CRD_CERTIFICATE, _credentials); // All
if (ret < 0)
throw format ("TLS credentials error. {1}", gnutls_strerror (ret)); // All
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::connect (const std::string& host, const std::string& port)
{
_host = host;
_port = port;
int ret;
#if GNUTLS_VERSION_NUMBER >= 0x030406
// For _trust == TLSClient::allow_all we perform no action
if (_trust == TLSClient::ignore_hostname)
gnutls_session_set_verify_cert (_session, nullptr, 0); // 3.4.6
else if (_trust == TLSClient::strict)
gnutls_session_set_verify_cert (_session, _host.c_str (), 0); // 3.4.6
#endif
// SNI. Only permitted when _host is a DNS name, not an IPv4/6 address.
std::string dummyAddress;
int dummyPort;
if (! isIPv4Address (_host, dummyAddress, dummyPort) &&
! isIPv6Address (_host, dummyAddress, dummyPort))
{
ret = gnutls_server_name_set (_session, GNUTLS_NAME_DNS, _host.c_str (), _host.length ()); // All
if (ret < 0)
throw format ("TLS SNI error. {1}", gnutls_strerror (ret)); // All
}
// Store the TLSClient instance, so that the verification callback can access
// it during the handshake below and call the verification method.
gnutls_session_set_ptr (_session, (void*) this); // All
// use IPv4 or IPv6, does not matter.
struct addrinfo hints {};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
struct addrinfo* res;
ret = ::getaddrinfo (host.c_str (), port.c_str (), &hints, &res);
if (ret != 0)
throw std::string (::gai_strerror (ret));
// Try them all, stop on success.
struct addrinfo* p;
for (p = res; p != nullptr; p = p->ai_next)
{
if ((_socket = ::socket (p->ai_family, p->ai_socktype, p->ai_protocol)) == -1)
continue;
// When a socket is closed, it remains unavailable for a while (netstat -an).
// Setting SO_REUSEADDR allows this program to assume control of a closed,
// but unavailable socket.
int on = 1;
if (::setsockopt (_socket,
SOL_SOCKET,
SO_REUSEADDR,
(const void*) &on,
sizeof (on)) == -1)
throw std::string (::strerror (errno));
if (::connect (_socket, p->ai_addr, p->ai_addrlen) == -1)
continue;
break;
}
free (res);
if (p == nullptr)
throw format ("Could not connect to {1} {2}", host, port);
#if GNUTLS_VERSION_NUMBER >= 0x030100
gnutls_handshake_set_timeout (_session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); // 3.1.0
#endif
#if GNUTLS_VERSION_NUMBER >= 0x030109
gnutls_transport_set_int (_session, _socket); // 3.1.9
#else
gnutls_transport_set_ptr (_session, (gnutls_transport_ptr_t) (intptr_t) _socket); // All
#endif
// Perform the TLS handshake
do
{
ret = gnutls_handshake (_session); // All
}
while (ret < 0 && gnutls_error_is_fatal (ret) == 0); // All
if (ret < 0)
{
#if GNUTLS_VERSION_NUMBER >= 0x030406
if (ret == GNUTLS_E_CERTIFICATE_VERIFICATION_ERROR)
{
auto type = gnutls_certificate_type_get (_session); // All
auto status = gnutls_session_get_verify_cert_status (_session); // 3.4.6
gnutls_datum_t out;
gnutls_certificate_verification_status_print (status, type, &out, 0); // 3.1.4
std::string error {(const char*) out.data};
gnutls_free (out.data); // All
throw format ("Handshake failed. {1}", error); // All
}
#else
throw format ("Handshake failed. {1}", gnutls_strerror (ret)); // All
#endif
}
#if GNUTLS_VERSION_NUMBER < 0x020a00
// The automatic verification for the server certificate with
// gnutls_certificate_set_verify_function does only work with gnutls
// >=2.10.0. So with older versions we should call the verify function
// manually after the gnutls handshake.
ret = verify_certificate ();
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR Certificate verification failed.\n";
throw format ("Error initializing TLS. {1}", gnutls_strerror (ret)); // All
}
#endif
if (_debug)
{
#if GNUTLS_VERSION_NUMBER >= 0x03010a
char* desc = gnutls_session_get_desc (_session); // 3.1.10
std::cout << "c: INFO Handshake was completed: " << desc << '\n';
gnutls_free (desc);
#else
std::cout << "c: INFO Handshake was completed.\n";
#endif
}
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::bye ()
{
gnutls_bye (_session, GNUTLS_SHUT_RDWR); // All
}
////////////////////////////////////////////////////////////////////////////////
int TLSClient::verify_certificate () const
{
if (_trust == TLSClient::allow_all)
return 0;
if (_debug)
std::cout << "c: INFO Verifying certificate.\n";
// This verification function uses the trusted CAs in the credentials
// structure. So you must have installed one or more CA certificates.
unsigned int status = 0;
const char* hostname = _host.c_str();
#if GNUTLS_VERSION_NUMBER >= 0x030104
if (_trust == TLSClient::ignore_hostname)
hostname = nullptr;
int ret = gnutls_certificate_verify_peers3 (_session, hostname, &status); // 3.1.4
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR Certificate verification peers3 failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
// status 16450 == 0100000001000010
// GNUTLS_CERT_INVALID 1<<1
// GNUTLS_CERT_SIGNER_NOT_FOUND 1<<6
// GNUTLS_CERT_UNEXPECTED_OWNER 1<<14 Hostname does not match
if (_debug && status)
std::cout << "c: ERROR Certificate status=" << status << '\n';
#else
int ret = gnutls_certificate_verify_peers2 (_session, &status); // All
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR Certificate verification peers2 failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (_debug && status)
std::cout << "c: ERROR Certificate status=" << status << '\n';
if ((status == 0) && (_trust != TLSClient::ignore_hostname))
{
if (gnutls_certificate_type_get (_session) == GNUTLS_CRT_X509) // All
{
const gnutls_datum* cert_list;
unsigned int cert_list_size;
gnutls_x509_crt cert;
cert_list = gnutls_certificate_get_peers (_session, &cert_list_size); // All
if (cert_list_size == 0)
{
if (_debug)
std::cout << "c: ERROR Certificate get peers failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
ret = gnutls_x509_crt_init (&cert); // All
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR x509 init failed. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
ret = gnutls_x509_crt_import (cert, &cert_list[0], GNUTLS_X509_FMT_DER); // All
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR x509 cert import. " << gnutls_strerror (ret) << '\n'; // All
gnutls_x509_crt_deinit(cert); // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (gnutls_x509_crt_check_hostname (cert, hostname) == 0) // All
{
if (_debug)
std::cout << "c: ERROR x509 cert check hostname. " << gnutls_strerror (ret) << '\n'; // All
gnutls_x509_crt_deinit(cert);
return GNUTLS_E_CERTIFICATE_ERROR;
}
}
else
return GNUTLS_E_CERTIFICATE_ERROR;
}
#endif
#if GNUTLS_VERSION_NUMBER >= 0x030104
gnutls_certificate_type_t type = gnutls_certificate_type_get (_session); // All
gnutls_datum_t out;
ret = gnutls_certificate_verification_status_print (status, type, &out, 0); // 3.1.4
if (ret < 0)
{
if (_debug)
std::cout << "c: ERROR certificate verification status. " << gnutls_strerror (ret) << '\n'; // All
return GNUTLS_E_CERTIFICATE_ERROR;
}
if (_debug)
std::cout << "c: INFO " << out.data << '\n';
gnutls_free (out.data);
#endif
if (status != 0)
return GNUTLS_E_CERTIFICATE_ERROR;
// Continue handshake.
return 0;
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::send (const std::string& data)
{
std::string packet = "XXXX" + data;
// Encode the length.
unsigned long l = packet.length ();
packet[0] = l >>24;
packet[1] = l >>16;
packet[2] = l >>8;
packet[3] = l;
unsigned int total = 0;
int status;
do
{
status = gnutls_record_send (_session, packet.c_str () + total, packet.length () - total); // All
}
while ((status > 0 && (total += status) < packet.length ()) ||
status == GNUTLS_E_INTERRUPTED ||
status == GNUTLS_E_AGAIN);
if (_debug)
std::cout << "c: INFO Sending 'XXXX"
<< data.c_str ()
<< "' (" << total << " bytes)"
<< std::endl;
}
////////////////////////////////////////////////////////////////////////////////
void TLSClient::recv (std::string& data)
{
data = ""; // No appending of data.
int received = 0;
int total = 0;
// Get the encoded length.
unsigned char header[HEADER_SIZE] {};
do
{
received = gnutls_record_recv (_session, header + total, HEADER_SIZE - total); // All
}
while ((received > 0 && (total += received) < HEADER_SIZE) ||
received == GNUTLS_E_INTERRUPTED ||
received == GNUTLS_E_AGAIN);
if (total < HEADER_SIZE) {
throw std::string ("Failed to receive header: ") +
(received < 0 ? gnutls_strerror(received) : "connection lost?");
}
// Decode the length.
unsigned long expected = (header[0]<<24) |
(header[1]<<16) |
(header[2]<<8) |
header[3];
if (_debug)
std::cout << "c: INFO expecting " << expected << " bytes.\n";
if (_limit && expected >= (unsigned long) _limit) {
std::ostringstream err_str;
err_str << "Expected message size " << expected << " is larger than allowed limit " << _limit;
throw err_str.str ();
}
// Arbitrary buffer size.
char buffer[MAX_BUF];
// Keep reading until no more data. Concatenate chunks of data if a) the
// read was interrupted by a signal, and b) if there is more data than
// fits in the buffer.
do
{
int chunk_size = 0;
do
{
received = gnutls_record_recv (_session, buffer + chunk_size, MAX_BUF - chunk_size); // All
if (received > 0) {
total += received;
chunk_size += received;
}
}
while ((received > 0 && (unsigned long) total < expected && chunk_size < MAX_BUF) ||
received == GNUTLS_E_INTERRUPTED ||
received == GNUTLS_E_AGAIN);
// Other end closed the connection.
if (received == 0)
{
if (_debug)
std::cout << "c: INFO Peer has closed the TLS connection\n";
break;
}
// Something happened.
if (received < 0)
throw std::string (gnutls_strerror (received)); // All
data.append (buffer, chunk_size);
// Stop at defined limit.
if (_limit && total > _limit)
break;
}
while (received > 0 && total < (int) expected);
if (_debug)
std::cout << "c: INFO Receiving 'XXXX"
<< data.c_str ()
<< "' (" << total << " bytes)"
<< std::endl;
}
////////////////////////////////////////////////////////////////////////////////
#endif

View File

@@ -1,6 +1,6 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024, Dustin Mitchell.
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -23,50 +23,50 @@
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_TLSCLIENT
#define INCLUDED_TLSCLIENT
#ifndef INCLUDED_VERSION
#define INCLUDED_VERSION
#ifdef HAVE_LIBGNUTLS
#include <string>
#include <gnutls/gnutls.h>
// A utility class for handling Taskwarrior versions.
class Version {
class TLSClient
{
public:
// Parse a version from a string. This must be of the format
// digits.digits.digits.
explicit Version(std::string version);
enum trust_level { strict, ignore_hostname, allow_all };
// Create an invalid version.
Version() = default;
TLSClient () = default;
~TLSClient ();
void limit (int);
void debug (int);
void trust (const enum trust_level);
void ciphers (const std::string&);
void init (const std::string&, const std::string&, const std::string&);
void connect (const std::string&, const std::string&);
void bye ();
int verify_certificate() const;
Version(const Version &other) = default;
Version(Version &&other) = default;
Version &operator=(const Version &) = default;
Version &operator=(Version &&) = default;
// Return a version representing the release being built.
static Version Current();
bool is_valid() const;
// Compare versions.
bool operator<(const Version &) const;
bool operator<=(const Version &) const;
bool operator>(const Version &) const;
bool operator>=(const Version &) const;
bool operator==(const Version &) const;
bool operator!=(const Version &) const;
// Convert back to a string.
operator std::string() const;
friend std::ostream& operator<<(std::ostream& os, const Version& version);
void send (const std::string&);
void recv (std::string&);
private:
int major = -1;
int minor = -1;
int patch = -1;
std::string _ca {""};
std::string _cert {""};
std::string _key {""};
std::string _ciphers {""};
std::string _host {""};
std::string _port {""};
gnutls_certificate_credentials_t _credentials {};
gnutls_session_t _session {nullptr};
int _socket {0};
int _limit {0};
bool _debug {false};
enum trust_level _trust {strict};
};
#endif
#endif
////////////////////////////////////////////////////////////////////////////////

View File

@@ -144,19 +144,6 @@ Task::Task (const json::object* obj)
parseJSON (obj);
}
////////////////////////////////////////////////////////////////////////////////
Task::Task (tc::Task obj)
{
id = 0;
urgency_value = 0.0;
recalc_urgency = true;
is_blocked = false;
is_blocking = false;
annotation_count = 0;
parseTC (obj);
}
////////////////////////////////////////////////////////////////////////////////
Task::status Task::textToStatus (const std::string& input)
{
@@ -199,8 +186,8 @@ const std::string Task::identifier (bool shortened /* = false */) const
////////////////////////////////////////////////////////////////////////////////
void Task::setAsNow (const std::string& att)
{
char now[22];
snprintf (now, 22, "%lli", (long long int) time (nullptr));
char now[16];
snprintf (now, 16, "%u", (unsigned int) time (nullptr));
set (att, now);
recalc_urgency = true;
@@ -908,29 +895,6 @@ void Task::parseJSON (const json::object* root_obj)
}
}
////////////////////////////////////////////////////////////////////////////////
// Note that all fields undergo encode/decode.
void Task::parseTC (const tc::Task& task)
{
data = task.get_taskmap ();
// count annotations
annotation_count = 0;
for (auto i : data)
{
if (isAnnotationAttr (i.first))
{
++annotation_count;
}
}
data["uuid"] = task.get_uuid ();
id = Context::getContext ().tdb2.id (data["uuid"]);
is_blocking = task.is_blocking();
is_blocked = task.is_blocked();
}
////////////////////////////////////////////////////////////////////////////////
// No legacy formats are currently supported as of 2.4.0.
void Task::parseLegacy (const std::string& line)
@@ -1189,7 +1153,7 @@ void Task::addAnnotation (const std::string& description)
do
{
key = "annotation_" + format ((long long int) now);
key = "annotation_" + format ((int) now);
++now;
}
while (has (key));
@@ -1247,7 +1211,7 @@ void Task::setAnnotations (const std::map <std::string, std::string>& annotation
void Task::addDependency (int depid)
{
// Check that id is resolvable.
std::string uuid = Context::getContext ().tdb2.uuid (depid);
std::string uuid = Context::getContext ().tdb2.pending.uuid (depid);
if (uuid == "")
throw format ("Could not create a dependency on task {1} - not found.", depid);
@@ -1295,7 +1259,7 @@ void Task::addDependency (const std::string& uuid)
////////////////////////////////////////////////////////////////////////////////
void Task::removeDependency (int id)
{
std::string uuid = Context::getContext ().tdb2.uuid (id);
std::string uuid = Context::getContext ().tdb2.pending.uuid (id);
// The removeDependency(std::string&) method will check this too, but here we
// can give a more natural error message containing the id provided by the user
@@ -1332,7 +1296,7 @@ std::vector <int> Task::getDependencyIDs () const
if (!isDepAttr (attr))
continue;
auto dep = attr2Dep (attr);
ids.push_back (Context::getContext ().tdb2.id (dep));
ids.push_back (Context::getContext ().tdb2.pending.id (dep));
}
return ids;
@@ -1362,7 +1326,7 @@ std::vector <Task> Task::getDependencyTasks () const
// efficient.
std::vector <Task> blocking;
if (uuids.size() > 0)
for (auto& it : Context::getContext ().tdb2.pending_tasks ())
for (auto& it : Context::getContext ().tdb2.pending.get_tasks ())
if (it.getStatus () != Task::completed &&
it.getStatus () != Task::deleted &&
std::find (uuids.begin (), uuids.end (), it.get ("uuid")) != uuids.end ())
@@ -1377,7 +1341,7 @@ std::vector <Task> Task::getBlockedTasks () const
auto uuid = get ("uuid");
std::vector <Task> blocked;
for (auto& it : Context::getContext ().tdb2.pending_tasks ())
for (auto& it : Context::getContext ().tdb2.pending.get_tasks ())
if (it.getStatus () != Task::completed &&
it.getStatus () != Task::deleted &&
it.hasDependency (uuid))
@@ -1537,7 +1501,7 @@ void Task::removeTag (const std::string& tag)
////////////////////////////////////////////////////////////////////////////////
void Task::fixTagsAttribute ()
{
// Fix up the old `tags` attribute to match the `tag_..` attributes (or
// Fix up the old `tags` attribute to match the `tags_..` attributes (or
// remove it if there are no tags)
auto tags = getTags ();
if (tags.size () > 0) {
@@ -1550,14 +1514,14 @@ void Task::fixTagsAttribute ()
////////////////////////////////////////////////////////////////////////////////
bool Task::isTagAttr(const std::string& attr)
{
return attr.compare(0, 4, "tag_") == 0;
return attr.compare(0, 5, "tags_") == 0;
}
////////////////////////////////////////////////////////////////////////////////
const std::string Task::tag2Attr (const std::string& tag) const
{
std::stringstream tag_attr;
tag_attr << "tag_" << tag;
tag_attr << "tags_" << tag;
return tag_attr.str();
}
@@ -1565,7 +1529,7 @@ const std::string Task::tag2Attr (const std::string& tag) const
const std::string Task::attr2Tag (const std::string& attr) const
{
assert (isTagAttr (attr));
return attr.substr(4);
return attr.substr(5);
}
////////////////////////////////////////////////////////////////////////////////
@@ -1747,9 +1711,6 @@ void Task::substitute (
// 1) To provide missing attributes where possible
// 2) To provide suitable warnings about odd states
// 3) To generate errors when the inconsistencies are not fixable
// 4) To update status depending on other attributes
//
// Critically, note that despite the name this is not a read-only function.
//
void Task::validate (bool applyDefault /* = true */)
{
@@ -1826,7 +1787,7 @@ void Task::validate (bool applyDefault /* = true */)
if ((status == Task::pending) && (get ("end") != ""))
remove ("end");
// Provide a modified date unless user already specified one.
// Provide an entry date unless user already specified one.
if (! has ("modified") || get ("modified") == "")
setAsNow ("modified");
@@ -1977,31 +1938,6 @@ const std::string Task::decode (const std::string& value) const
return str_replace (modified, "&close;", "]");
}
////////////////////////////////////////////////////////////////////////////////
tc::Status Task::status2tc (const Task::status status)
{
switch (status) {
case Task::pending: return tc::Status::Pending;
case Task::completed: return tc::Status::Completed;
case Task::deleted: return tc::Status::Deleted;
case Task::waiting: return tc::Status::Pending; // waiting is no longer a status
case Task::recurring: return tc::Status::Recurring;
default: return tc::Status::Unknown;
}
}
////////////////////////////////////////////////////////////////////////////////
Task::status Task::tc2status (const tc::Status status)
{
switch (status) {
case tc::Status::Pending: return Task::pending;
case tc::Status::Completed: return Task::completed;
case tc::Status::Deleted: return Task::deleted;
case tc::Status::Recurring: return Task::recurring;
default: return Task::pending;
}
}
////////////////////////////////////////////////////////////////////////////////
int Task::determineVersion (const std::string& line)
{
@@ -2205,7 +2141,7 @@ float Task::urgency ()
////////////////////////////////////////////////////////////////////////////////
float Task::urgency_inherit () const
{
float v = -FLT_MAX;
float v = FLT_MIN;
#ifdef PRODUCT_TASKWARRIOR
// Calling getBlockedTasks is rather expensive.
// It is called recursively for each dependency in the chain here.

View File

@@ -35,7 +35,6 @@
#include <JSON.h>
#include <Table.h>
#include <Datetime.h>
#include <tc/Task.h>
class Task
{
@@ -66,7 +65,6 @@ public:
bool operator!= (const Task&);
Task (const std::string&);
Task (const json::object*);
Task (tc::Task);
void parse (const std::string&);
std::string composeF4 () const;
@@ -89,8 +87,6 @@ public:
// Series of helper functions.
static status textToStatus (const std::string&);
static std::string statusToText (status);
static tc::Status status2tc (const Task::status);
static Task::status tc2status (const tc::Status);
void setAsNow (const std::string&);
bool has (const std::string&) const;
@@ -182,12 +178,10 @@ public:
Table diffForUndoSide (const Task& after) const;
Table diffForUndoPatch (const Task& after, const Datetime& lastChange) const;
private:
int determineVersion (const std::string&);
void parseJSON (const std::string&);
void parseJSON (const json::object*);
void parseTC (const tc::Task&);
void parseLegacy (const std::string&);
void validate_before (const std::string&, const std::string&);
const std::string encode (const std::string&) const;

View File

@@ -1,118 +0,0 @@
////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024, Dustin Mitchell.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://www.opensource.org/licenses/mit-license.php
//
////////////////////////////////////////////////////////////////////////////////
#include <Version.h>
#include <cmake.h>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
////////////////////////////////////////////////////////////////////////////////
Version::Version(std::string version) {
std::vector<int> parts;
std::string part;
std::istringstream input(version);
while (std::getline(input, part, '.')) {
int value;
// Try converting string to integer
if (std::stringstream(part) >> value && value >= 0) {
parts.push_back(value);
} else {
return;
}
}
if (parts.size() != 3) {
return;
}
major = parts[0];
minor = parts[1];
patch = parts[2];
}
////////////////////////////////////////////////////////////////////////////////
Version Version::Current() { return Version(PACKAGE_VERSION); }
////////////////////////////////////////////////////////////////////////////////
bool Version::is_valid() const { return major >= 0; }
////////////////////////////////////////////////////////////////////////////////
bool Version::operator<(const Version &other) const {
return std::tie(major, minor, patch) <
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator<=(const Version &other) const {
return std::tie(major, minor, patch) <=
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator>(const Version &other) const {
return std::tie(major, minor, patch) >
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator>=(const Version &other) const {
return std::tie(major, minor, patch) >=
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator==(const Version &other) const {
return std::tie(major, minor, patch) ==
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
bool Version::operator!=(const Version &other) const {
std::cout << other;
return std::tie(major, minor, patch) !=
std::tie(other.major, other.minor, other.patch);
}
////////////////////////////////////////////////////////////////////////////////
Version::operator std::string() const {
std::ostringstream output;
if (is_valid()) {
output << major << '.' << minor << '.' << patch;
} else {
output << "(invalid version)";
}
return output.str();
}
////////////////////////////////////////////////////////////////////////////////
std::ostream &operator<<(std::ostream &os, const Version &version) {
os << std::string(version);
return os;
}

View File

@@ -1,11 +1,9 @@
cmake_minimum_required (VERSION 3.22)
cmake_minimum_required (VERSION 3.0)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
set (columns_SRCS Column.cpp Column.h

View File

@@ -171,7 +171,7 @@ void ColumnDescription::render (
{
for (const auto& i : task.getAnnotations ())
{
Datetime dt (strtoll (i.first.substr (11).c_str (), nullptr, 10));
Datetime dt (strtol (i.first.substr (11).c_str (), nullptr, 10));
description += '\n' + std::string (_indent, ' ') + dt.toString (_dateformat) + ' ' + i.second;
}
}
@@ -200,7 +200,7 @@ void ColumnDescription::render (
{
for (const auto& i : task.getAnnotations ())
{
Datetime dt (strtoll (i.first.substr (11).c_str (), nullptr, 10));
Datetime dt (strtol (i.first.substr (11).c_str (), nullptr, 10));
description += ' ' + dt.toString (_dateformat) + ' ' + i.second;
}
}

View File

@@ -89,7 +89,7 @@ void ColumnTypeDate::measure (Task& task, unsigned int& minimum, unsigned int& m
else if (_style == "countdown")
{
Datetime now;
minimum = maximum = Duration (date - now).formatVague (true).length ();
minimum = maximum = Duration (now - date).formatVague (true).length ();
}
else if (_style == "julian")
{

View File

@@ -241,7 +241,7 @@ void ColumnUDADate::measure (Task& task, unsigned int& minimum, unsigned int& ma
// rc.report.<report>.dateformat
// rc.dateformat.report
// rc.dateformat
Datetime date (strtoll (value.c_str (), nullptr, 10));
Datetime date ((time_t) strtol (value.c_str (), nullptr, 10));
auto format = Context::getContext ().config.get ("report." + _report + ".dateformat");
if (format == "")
format = Context::getContext ().config.get ("dateformat.report");
@@ -287,7 +287,7 @@ void ColumnUDADate::render (
format = Context::getContext ().config.get ("dateformat");
}
renderStringLeft (lines, width, color, Datetime (strtoll (value.c_str (), nullptr, 10)).toString (format));
renderStringLeft (lines, width, color, Datetime ((time_t) strtol (value.c_str (), nullptr, 10)).toString (format));
}
else if (_style == "indicator")
{

View File

@@ -1,11 +1,9 @@
cmake_minimum_required (VERSION 3.22)
cmake_minimum_required (VERSION 3.0)
include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/tc
${CMAKE_SOURCE_DIR}/src/commands
${CMAKE_SOURCE_DIR}/src/columns
${CMAKE_SOURCE_DIR}/src/libshared/src
${CMAKE_SOURCE_DIR}/taskchampion/lib
${TASK_INCLUDE_DIRS})
set (commands_SRCS Command.cpp Command.h

View File

@@ -81,7 +81,7 @@ int CmdCalendar::execute (std::string& output)
// Load the pending tasks.
handleUntil ();
handleRecurrence ();
auto tasks = Context::getContext ().tdb2.pending_tasks ();
auto tasks = Context::getContext ().tdb2.pending.get_tasks ();
Datetime today;
auto getPendingDate = false;
@@ -631,7 +631,7 @@ std::string CmdCalendar::renderMonths (
{
if(task.has("scheduled") && !coloredWithDue) {
std::string scheduled = task.get ("scheduled");
Datetime scheduleddmy (strtoll (scheduled.c_str(), nullptr, 10));
Datetime scheduleddmy (strtol (scheduled.c_str(), nullptr, 10));
if (scheduleddmy.sameDay (date))
{
@@ -640,7 +640,7 @@ std::string CmdCalendar::renderMonths (
}
if(task.has("due")) {
std::string due = task.get ("due");
Datetime duedmy (strtoll (due.c_str(), nullptr, 10));
Datetime duedmy (strtol (due.c_str(), nullptr, 10));
if (duedmy.sameDay (date))
{

View File

@@ -202,7 +202,7 @@ void CmdContext::defineContext (const std::vector <std::string>& words, std::str
// Check if the value is a proper filter by filtering current pending.data
Filter filter;
std::vector <Task> filtered;
auto pending = Context::getContext ().tdb2.pending_tasks ();
auto pending = Context::getContext ().tdb2.pending.get_tasks ();
try
{

View File

@@ -35,7 +35,6 @@
#include <Context.h>
#include <Filter.h>
#include <Lexer.h>
#include <Version.h>
#include <ViewTask.h>
#include <format.h>
#include <shared.h>
@@ -251,24 +250,24 @@ int CmdCustom::execute (std::string& output)
}
// Inform user about the new release highlights if not presented yet
Version news_version(Context::getContext ().config.get ("news.version"));
Version current_version = Version::Current();
if (news_version != current_version)
if (Context::getContext ().config.get ("news.version") != "2.6.0")
{
std::random_device device;
std::mt19937 random_generator(device());
std::uniform_int_distribution<std::mt19937::result_type> twentyfive_percent(1, 4);
std::string NEWS_NOTICE = (
"Recently upgraded to 2.6.0. "
"Please run 'task news' to read highlights about the new release."
);
// 1 in 10 chance to display the message.
if (twentyfive_percent(random_generator) == 4)
{
std::ostringstream notice;
notice << "Recently upgraded to " << current_version << ". "
"Please run 'task news' to read highlights about the new release.";
if (Context::getContext ().verbose ("footnote"))
Context::getContext ().footnote (notice.str());
Context::getContext ().footnote (NEWS_NOTICE);
else if (Context::getContext ().verbose ("header"))
Context::getContext ().header (notice.str());
Context::getContext ().header (NEWS_NOTICE);
}
}

View File

@@ -39,6 +39,10 @@
#include <commit.h>
#endif
#ifdef HAVE_LIBGNUTLS
#include <gnutls/gnutls.h>
#endif
////////////////////////////////////////////////////////////////////////////////
CmdDiagnostics::CmdDiagnostics ()
{
@@ -131,6 +135,18 @@ int CmdDiagnostics::execute (std::string& output)
#endif
<< '\n';
out << " libgnutls: "
#ifdef HAVE_LIBGNUTLS
#ifdef GNUTLS_VERSION
<< GNUTLS_VERSION
#elif defined LIBGNUTLS_VERSION
<< LIBGNUTLS_VERSION
#endif
#else
<< "n/a"
#endif
<< '\n';
out << " Build type: "
#ifdef CMAKE_BUILD_TYPE
<< CMAKE_BUILD_TYPE
@@ -198,6 +214,82 @@ int CmdDiagnostics::execute (std::string& output)
else if ((peditor = getenv ("EDITOR")) != nullptr)
out << " $EDITOR: " << peditor << '\n';
out << " Server: "
<< Context::getContext ().config.get ("taskd.server")
<< '\n';
auto ca_pem = Context::getContext ().config.get ("taskd.ca");
out << " CA: ";
if (ca_pem != "")
{
File file_ca (ca_pem);
if (file_ca.exists ())
out << ca_pem
<< (file_ca.readable () ? ", readable, " : ", not readable, ")
<< file_ca.size ()
<< " bytes\n";
else
out << "not found\n";
}
else
out << "-\n";
auto cert_pem = Context::getContext ().config.get ("taskd.certificate");
out << "Certificate: ";
if (cert_pem != "")
{
File file_cert (cert_pem);
if (file_cert.exists ())
out << cert_pem
<< (file_cert.readable () ? ", readable, " : ", not readable, ")
<< file_cert.size ()
<< " bytes\n";
else
out << "not found\n";
}
else
out << "-\n";
auto key_pem = Context::getContext ().config.get ("taskd.key");
out << " Key: ";
if (key_pem != "")
{
File file_key (key_pem);
if (file_key.exists ())
out << key_pem
<< (file_key.readable () ? ", readable, " : ", not readable, ")
<< file_key.size ()
<< " bytes\n";
else
out << "not found\n";
}
else
out << "-\n";
auto trust_value = Context::getContext ().config.get ("taskd.trust");
if (trust_value == "strict" ||
trust_value == "ignore hostname" ||
trust_value == "allow all")
out << " Trust: " << trust_value << '\n';
else
out << " Trust: Bad value - see 'man taskrc'\n";
out << " Ciphers: "
<< Context::getContext ().config.get ("taskd.ciphers")
<< '\n';
// Get credentials, but mask out the key.
auto credentials = Context::getContext ().config.get ("taskd.credentials");
auto last_slash = credentials.rfind ('/');
if (last_slash != std::string::npos)
credentials = credentials.substr (0, last_slash)
+ '/'
+ std::string (credentials.length () - last_slash - 1, '*');
out << " Creds: "
<< credentials
<< "\n\n";
// Display hook status.
Path hookLocation;
if (Context::getContext ().config.has ("hooks.location"))

View File

@@ -261,7 +261,7 @@ std::string CmdEdit::formatTask (Task task, const std::string& dateformat)
for (auto& anno : task.getAnnotations ())
{
Datetime dt (strtoll (anno.first.substr (11).c_str (), nullptr, 10));
Datetime dt (strtol (anno.first.substr (11).c_str (), nullptr, 10));
before << " Annotation: " << dt.toString (dateformat)
<< " -- " << str_replace (anno.second, "\n", ANNOTATION_EDIT_MARKER) << '\n';
}

View File

@@ -112,17 +112,15 @@ int CmdExport::execute (std::string& output)
}
else
{
// sort_tasks requires the order array initially be identity
// There is a sortOrder, so sorting will take place, which means the initial
// order of sequence is ascending.
for (unsigned int i = 0; i < filtered.size (); ++i)
sequence.push_back (i);
// if no sort order, sort by id
if (!sortOrder.size ()) {
reportSort = "id";
}
// Sort the tasks.
sort_tasks (filtered, sequence, reportSort);
if (sortOrder.size ()) {
sort_tasks (filtered, sequence, reportSort);
}
}
// Export == render.

View File

@@ -133,7 +133,7 @@ int CmdImport::import (const std::string& input)
}
// If an exception is caught, then it is because the free-form JSON
// objects/array above failed to parse. This may be an indication that the input
// objects/array above failed to parse. This is an indication that the input
// is an old-style line-by-line set of JSON objects, because both an array of
// objects, and a single object have failed to parse..
//
@@ -142,30 +142,19 @@ int CmdImport::import (const std::string& input)
// { ... }
catch (std::string& e)
{
// Make a very cursory check for the old-style format.
if (input[0] != '{') {
throw e;
}
// Read the entire file, so that errors do not result in a partial import.
std::vector<std::unique_ptr<json::object>> objects;
for (auto& line : split (input, '\n'))
{
if (line.length ())
{
json::value* root = json::parse (line);
if (root && root->type () == json::j_object)
objects.push_back (std::unique_ptr<json::object> ((json::object *)root));
else
throw format ("Invalid JSON: {1}", line);
if (root)
{
importSingleTask ((json::object*) root);
++count;
delete root;
}
}
}
// Import the tasks.
for (auto& root : objects) {
importSingleTask (root.get());
++count;
}
}
return count;

View File

@@ -37,7 +37,6 @@
#include <shared.h>
#include <format.h>
#include <util.h>
#include <Lexer.h>
////////////////////////////////////////////////////////////////////////////////
CmdInfo::CmdInfo ()
@@ -77,6 +76,11 @@ int CmdInfo::execute (std::string& output)
rc = 1;
}
// Get the undo data.
std::vector <std::string> undo;
if (Context::getContext ().config.getBoolean ("journal.info"))
undo = Context::getContext ().tdb2.undo.get_lines ();
// Determine the output date format, which uses a hierarchy of definitions.
// rc.dateformat.info
// rc.dateformat
@@ -240,7 +244,7 @@ int CmdInfo::execute (std::string& output)
auto created = task.get ("entry");
if (created.length ())
{
Datetime dt (strtoll (created.c_str (), nullptr, 10));
Datetime dt (strtol (created.c_str (), nullptr, 10));
age = Duration (now - dt).formatVague ();
}
@@ -369,7 +373,7 @@ int CmdInfo::execute (std::string& output)
// Task::urgency
row = view.addRow ();
view.set (row, 0, "Urgency");
view.set (row, 1, Lexer::trimLeft (format (task.urgency (), 4, 4)));
view.set (row, 1, format (task.urgency (), 4, 4));
// Show any UDAs
auto all = task.all ();
@@ -525,6 +529,54 @@ int CmdInfo::execute (std::string& output)
urgencyDetails.set (row, 5, rightJustify (format (task.urgency (), 4, 4), 6));
}
// Create a third table, containing undo log change details.
Table journal;
setHeaderUnderline (journal);
if (Context::getContext ().config.getBoolean ("obfuscate"))
journal.obfuscate ();
if (Context::getContext ().config.getBoolean ("color"))
journal.forceColor ();
journal.width (Context::getContext ().getWidth ());
journal.add ("Date");
journal.add ("Modification");
if (Context::getContext ().config.getBoolean ("journal.info") &&
undo.size () > 3)
{
// Scan the undo data for entries matching this task, without making
// copies.
unsigned int i = 0;
long last_timestamp = 0;
while (i < undo.size ())
{
int when = i++;
int previous = -1;
if (! undo[i].compare (0, 3, "old", 3))
previous = i++;
int current = i++;
i++; // Separator
if (undo[current].find ("uuid:\"" + uuid) != std::string::npos)
{
if (previous != -1)
{
int row = journal.addRow ();
Datetime timestamp (strtol (undo[when].substr (5).c_str (), nullptr, 10));
journal.set (row, 0, timestamp.toString (dateformat));
Task before (undo[previous].substr (4));
Task after (undo[current].substr (4));
journal.set (row, 1, before.diffForInfo (after, dateformat, last_timestamp, Datetime(after.get("modified")).toEpoch()));
}
}
}
}
out << optionalBlankLine ()
<< view.render ()
<< '\n';
@@ -532,6 +584,10 @@ int CmdInfo::execute (std::string& output)
if (urgencyDetails.rows () > 0)
out << urgencyDetails.render ()
<< '\n';
if (journal.rows () > 0)
out << journal.render ()
<< '\n';
}
output = out.str ();

View File

@@ -38,14 +38,6 @@
#include <util.h>
#include <main.h>
/* Adding a new version:
*
* - Add a new `versionX_Y_Z` method to `NewsItem`, and add news items for the new
* release.
* - Call the new method in `NewsItem.all()`. Calls should be in version order.
* - Test with `task news`.
*/
////////////////////////////////////////////////////////////////////////////////
CmdNews::CmdNews ()
{
@@ -99,7 +91,6 @@ void wait_for_enter ()
// Holds information about single improvement / bug.
//
NewsItem::NewsItem (
Version version,
bool major,
const std::string& title,
const std::string& bg_title,
@@ -109,7 +100,6 @@ NewsItem::NewsItem (
const std::string& reasoning,
const std::string& actions
) {
_version = version;
_major = major;
_title = title;
_bg_title = bg_title;
@@ -137,7 +127,7 @@ void NewsItem::render () {
// TODO: For some reason, bold cannot be blended in 256-color terminals
// Apply this workaround of colorizing twice.
std::cout << bold.colorize (header.colorize (format ("{1} ({2})\n", _title, _version)));
std::cout << bold.colorize (header.colorize (format ("{1}\n", _title)));
if (_background.size ()) {
if (_bg_title.empty ())
_bg_title = "Background";
@@ -148,7 +138,7 @@ void NewsItem::render () {
wait_for_enter ();
std::cout << " " << underline.colorize (format ("What changed in {1}?\n", _version));
std::cout << " " << underline.colorize ("What changed in 2.6.0?\n");
if (_punchline.size ())
std::cout << footnote.colorize (format ("{1}\n", _punchline));
@@ -170,13 +160,6 @@ void NewsItem::render () {
}
}
std::vector<NewsItem> NewsItem::all () {
std::vector<NewsItem> items;
version2_6_0(items);
version3_0_0(items);
return items;
}
////////////////////////////////////////////////////////////////////////////////
// Generate the highlights for the 2.6.0 version.
//
@@ -191,8 +174,7 @@ std::vector<NewsItem> NewsItem::all () {
// - The .by attribute modifier
// - Exporting a report
// - Multi-day holidays
void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
Version version("2.6.0");
void CmdNews::version2_6_0 (std::vector<NewsItem>& items) {
/////////////////////////////////////////////////////////////////////////////
// - Writeable context (major)
@@ -252,7 +234,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
" Read more about how to use contexts in CONTEXT section of 'man task'.";
NewsItem writeable_context (
version,
true,
"'Writeable' context",
"Background - what is context?",
@@ -276,7 +257,7 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
" The currently active context definition is now applied as default modifications\n"
" when creating new tasks using 'task add' and 'task log'.",
" \n"
" Consider following example, using context 'work' defined as 'project:Work' above:\n"
" Consider following example, using contex 'work' defined as 'project:Work' above:\n"
" \n"
" $ task context work\n"
" $ task add Talk to Jeff\n"
@@ -296,7 +277,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - 64-bit datetime support (major)
NewsItem uint64_support (
version,
false,
"Support for 64-bit timestamps and numeric values",
"",
@@ -314,7 +294,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Waiting is a virtual status
NewsItem waiting_status (
version,
true,
"Deprecation of the status:waiting",
"",
@@ -336,7 +315,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Support for environment variables in the taskrc
NewsItem env_vars (
version,
true,
"Environment variables in the taskrc",
"",
@@ -355,7 +333,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Reports outside of context
NewsItem contextless_reports (
version,
true,
"Context-less reports",
"",
@@ -377,7 +354,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Exporting a particular report
NewsItem exportable_reports (
version,
false,
"Exporting a particular report",
"",
@@ -401,7 +377,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Multi-day holidays
NewsItem multi_holidays (
version,
false,
"Multi-day holidays",
"",
@@ -410,7 +385,7 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
" obtained from our website, holidata.net.",
" Instead of single-day holiday entries only, Taskwarrior now supports holidays\n"
" that span a range of days (i.e. vacation).\n",
" Use a holiday.<name>.start and holiday.<name>.end to configure a multi-day holiday:\n"
" Use a holday.<name>.start and holiday.<name>.end to configure a multi-day holiday:\n"
" \n"
" holiday.sysadmin.name=System Administrator Appreciation Week\n"
" holiday.sysadmin.start=20100730\n"
@@ -424,7 +399,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Unicode 12
NewsItem unicode_12 (
version,
false,
"Extended Unicode support (Unicode 12)",
"",
@@ -432,7 +406,7 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
" The support for Unicode character set was improved to support Unicode 12.\n"
" This means better support for various language-specific characters - and emojis!",
"",
" Extended unicode support for language-specific characters helps non-English speakers.\n"
" Extended unicode support for language specific characters helps non-English users.\n"
" While most users don't enter emojis as task metadata, automated task creation tools,\n"
" such as bugwarrior, might create tasks with exotic Unicode data.",
" You can try it out - 'task add Prepare for an 👽 invasion!'"
@@ -443,7 +417,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - The .by attribute modifier
NewsItem by_modifier (
version,
false,
"The .by attribute modifier",
"",
@@ -462,7 +435,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Context-specific configuration overrides
NewsItem context_config (
version,
false,
"Context-specific configuration overrides",
"",
@@ -487,7 +459,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - XDG config home support
NewsItem xdg_support (
version,
true,
"Support for XDG Base Directory Specification",
"",
@@ -516,7 +487,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
// - Update holiday data
NewsItem holidata_2022 (
version,
false,
"Updated holiday data for 2022",
"",
@@ -530,28 +500,6 @@ void NewsItem::version2_6_0 (std::vector<NewsItem>& items) {
items.push_back(holidata_2022);
}
void NewsItem::version3_0_0 (std::vector<NewsItem>& items) {
Version version("3.0.0");
NewsItem sync {
version,
/*major=*/true,
/*title=*/"New data model and sync backend",
/*bg_title=*/"",
/*background=*/"",
/*punchline=*/
"The sync functionality for Taskwarrior has been rewritten entirely, and no longer\n"
"supports taskserver/taskd. The most robust solution is a cloud-storage backend,\n"
"although a less-mature taskchampion-sync-server is also available. See `task-sync(5)`\n"
"For details. As part of this change, the on-disk storage format has also changed.\n",
/*update=*/
"This is a breaking upgrade: you must export your task database from 2.x and re-import\n"
"it into 3.x. Hooks run during task import, so if you have any hooks defined,\n"
"temporarily disable them for this operation.\n\n"
"See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to Taskwarrior 3.0.",
};
items.push_back(sync);
}
////////////////////////////////////////////////////////////////////////////////
int CmdNews::execute (std::string& output)
{
@@ -561,13 +509,11 @@ int CmdNews::execute (std::string& output)
// Supress compiler warning about unused argument
output = "";
std::vector<NewsItem> items = NewsItem::all();
Version news_version(Context::getContext ().config.get ("news.version"));
Version current_version = Version::Current();
// 2.6.0 is the earliest version with news support.
if (!news_version.is_valid())
news_version = Version("2.6.0");
// TODO: 2.6.0 is the only version with explicit release notes, but in the
// future we need to only execute yet unread release notes
std::vector<NewsItem> items;
std::string version = "2.6.0";
version2_6_0 (items);
bool full_summary = false;
bool major_items = true;
@@ -592,12 +538,6 @@ int CmdNews::execute (std::string& output)
signal (SIGINT, signal_handler);
// Remove items that have already been shown
items.erase (
std::remove_if (items.begin (), items.end (), [&](const NewsItem& n){return n._version <= news_version;}),
items.end ()
);
// Remove non-major items if displaying a non-full (abbreviated) summary
int total_highlights = items.size ();
if (! full_summary)
@@ -606,25 +546,23 @@ int CmdNews::execute (std::string& output)
items.end ()
);
// Print release notes
Color bold = Color ("bold");
if (items.empty ()) {
std::cout << bold.colorize ("You are up to date!\n");
} else {
// Print release notes
std::cout << bold.colorize (format (
"\n"
"================================================\n"
"Taskwarrior {1} through {2} Release Highlights\n"
"================================================\n",
news_version,
current_version));
std::cout << bold.colorize (format (
"\n"
"==========================================\n"
"Taskwarrior {1} {2} Release highlights\n"
"==========================================\n",
version,
(full_summary ? "All" : (major_items ? "Major" : "Minor"))
));
for (unsigned short i=0; i < items.size (); i++) {
std::cout << format ("\n({1}/{2}) ", i+1, items.size ());
items[i].render ();
}
std::cout << "Thank you for catching up on the new features!\n";
for (unsigned short i=0; i < items.size (); i++) {
std::cout << format ("\n({1}/{2}) ", i+1, items.size ());
items[i].render ();
}
std::cout << "Thank you for catching up on the new features!\n";
wait_for_enter ();
// Display outro
@@ -650,9 +588,9 @@ int CmdNews::execute (std::string& output)
std::cout << outro.str ();
// Set a mark in the config to remember which version's release notes were displayed
if (full_summary && news_version != current_version)
if (config.get ("news.version") != "2.6.0")
{
CmdConfig::setConfigVariable ("news.version", std::string(current_version), false);
CmdConfig::setConfigVariable ("news.version", "2.6.0", false);
// Revert back to default signal handling after displaying the outro
signal (SIGINT, SIG_DFL);
@@ -689,15 +627,14 @@ int CmdNews::execute (std::string& output)
else
wait_for_enter (); // Do not display the outro and footnote at once
if (! items.empty() && ! full_summary && major_items) {
if (! full_summary && major_items)
Context::getContext ().footnote (format (
"Only major highlights were displayed ({1} out of {2} total).\n"
"If you're interested in more release highlights, run 'task news {3} minor'.",
items.size (),
total_highlights,
current_version
version
));
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More