Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e5177aa7c | ||
|
|
ae3651fd3f | ||
|
|
ddeec3512a | ||
|
|
630585d7b4 | ||
|
|
9105985c7c | ||
|
|
3bf0200602 | ||
|
|
1b9353dccc | ||
|
|
1ee69ea214 | ||
|
|
dcbe916286 | ||
|
|
cc505e4881 | ||
|
|
758ac8f850 | ||
|
|
ff325bc19e | ||
|
|
ddae5c4ba9 | ||
|
|
4add839548 | ||
|
|
3ea726f2bb | ||
|
|
ce70a182c1 | ||
|
|
8de7ff52e7 | ||
|
|
dfc36aefcf | ||
|
|
c2cb7f36a7 | ||
|
|
e5ab1bc7a5 | ||
|
|
4797c4e17e | ||
|
|
0b286460b6 | ||
|
|
a99b6084e8 | ||
|
|
5664182f5e | ||
|
|
68c63372c1 | ||
|
|
ed3667c19e | ||
|
|
caae3fa37b | ||
|
|
066bb3e331 | ||
|
|
98204b17a6 | ||
|
|
096f94d3d1 | ||
|
|
01ced3238e | ||
|
|
8cc4c461d6 | ||
|
|
3e8bda6a23 |
18
.github/workflows/checks.yml
vendored
18
.github/workflows/checks.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
# If this version is old enough to cause errors, or older than the
|
# If this version is old enough to cause errors, or older than the
|
||||||
# TaskChampion MSRV, bump it to the MSRV of the currently-required
|
# TaskChampion MSRV, bump it to the MSRV of the currently-required
|
||||||
# TaskChampion package; if necessary, bump that version as well.
|
# TaskChampion package; if necessary, bump that version as well.
|
||||||
toolchain: "1.73.0" # MSRV
|
toolchain: "1.81.0" # MSRV
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- uses: actions-rs/cargo@v1.0.3
|
- uses: actions-rs/cargo@v1.0.3
|
||||||
@@ -64,3 +64,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
|
cargo-metadata:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: "Cargo Metadata"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
components: rustfmt
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: "Check metadata"
|
||||||
|
run: ".github/workflows/metadata-check.sh"
|
||||||
|
|||||||
12
.github/workflows/docker-image.yaml
vendored
12
.github/workflows/docker-image.yaml
vendored
@@ -23,6 +23,10 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Create lowercase repository name
|
||||||
|
run: |
|
||||||
|
GHCR_REPOSITORY="${{ github.repository_owner }}"
|
||||||
|
echo "REPOSITORY=${GHCR_REPOSITORY,,}" >> ${GITHUB_ENV}
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -35,19 +39,19 @@ jobs:
|
|||||||
uses: docker/login-action@v3.3.0
|
uses: docker/login-action@v3.3.0
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Taskwarrior Docker image
|
- name: Build and push Taskwarrior Docker image
|
||||||
id: build-and-push
|
id: build-and-push
|
||||||
uses: docker/build-push-action@v6.9.0
|
uses: docker/build-push-action@v6.10.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: "./docker/task.dockerfile"
|
file: "./docker/task.dockerfile"
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ github.actor }}/task:${{ github.ref_name }}
|
tags: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/task:${{ github.ref_name }}
|
||||||
|
|
||||||
- name: Sign the published Docker image
|
- name: Sign the published Docker image
|
||||||
env:
|
env:
|
||||||
COSIGN_EXPERIMENTAL: "true"
|
COSIGN_EXPERIMENTAL: "true"
|
||||||
run: cosign sign ${{ env.REGISTRY }}/${{ github.actor }}/task@${{ steps.build-and-push.outputs.digest }}
|
run: cosign sign ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/task@${{ steps.build-and-push.outputs.digest }}
|
||||||
|
|||||||
32
.github/workflows/metadata-check.sh
vendored
Executable file
32
.github/workflows/metadata-check.sh
vendored
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# Check the 'cargo metadata' for various requirements
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
META=$(mktemp)
|
||||||
|
trap 'rm -rf -- "${META}"' EXIT
|
||||||
|
|
||||||
|
cargo metadata --locked --format-version 1 > "${META}"
|
||||||
|
|
||||||
|
get_msrv() {
|
||||||
|
local package="${1}"
|
||||||
|
jq -r '.packages[] | select(.name == "'"${package}"'") | .rust_version' "${META}"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_msrv() {
|
||||||
|
local taskchampion_msrv=$(get_msrv taskchampion)
|
||||||
|
local taskchampion_lib_msrv=$(get_msrv taskchampion-lib)
|
||||||
|
|
||||||
|
echo "Found taskchampion MSRV ${taskchampion_msrv}"
|
||||||
|
echo "Found taskchampion-lib MSRV ${taskchampion_lib_msrv}"
|
||||||
|
|
||||||
|
if [ "${taskchampion_msrv}" != "${taskchampion_lib_msrv}" ]; then
|
||||||
|
echo "Those MSRVs should be the same (or taskchampion-lib should be greater, in which case adjust this script)"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "✓ MSRVs are at the same version."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_msrv
|
||||||
7
.github/workflows/tests.yaml
vendored
7
.github/workflows/tests.yaml
vendored
@@ -118,7 +118,8 @@ jobs:
|
|||||||
# If this version is old enough to cause errors, or older than the
|
# If this version is old enough to cause errors, or older than the
|
||||||
# TaskChampion MSRV, bump it to the MSRV of the currently-required
|
# TaskChampion MSRV, bump it to the MSRV of the currently-required
|
||||||
# TaskChampion package; if necessary, bump that version as well.
|
# TaskChampion package; if necessary, bump that version as well.
|
||||||
toolchain: "1.73.0" # MSRV
|
# This should match the MSRV in `src/taskchampion-cpp/Cargo.toml`.
|
||||||
|
toolchain: "1.81.0" # MSRV
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- uses: actions-rs/cargo@v1.0.3
|
- uses: actions-rs/cargo@v1.0.3
|
||||||
@@ -134,9 +135,9 @@ jobs:
|
|||||||
- name: "Fedora 40"
|
- name: "Fedora 40"
|
||||||
runner: ubuntu-latest
|
runner: ubuntu-latest
|
||||||
dockerfile: fedora40
|
dockerfile: fedora40
|
||||||
- name: "Fedora 39"
|
- name: "Fedora 41"
|
||||||
runner: ubuntu-latest
|
runner: ubuntu-latest
|
||||||
dockerfile: fedora39
|
dockerfile: fedora41
|
||||||
- name: "Debian Testing"
|
- name: "Debian Testing"
|
||||||
runner: ubuntu-latest
|
runner: ubuntu-latest
|
||||||
dockerfile: debiantesting
|
dockerfile: debiantesting
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ repos:
|
|||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v19.1.3
|
rev: v19.1.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types_or: [c++, c]
|
types_or: [c++, c]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ enable_testing()
|
|||||||
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set (CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
project (task
|
project (task
|
||||||
VERSION 3.2.0
|
VERSION 3.3.0
|
||||||
DESCRIPTION "Taskwarrior - a command-line TODO list manager"
|
DESCRIPTION "Taskwarrior - a command-line TODO list manager"
|
||||||
HOMEPAGE_URL https://taskwarrior.org/)
|
HOMEPAGE_URL https://taskwarrior.org/)
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DI
|
|||||||
message ("-- Looking for SHA1 references")
|
message ("-- Looking for SHA1 references")
|
||||||
if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
|
if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index)
|
||||||
set (HAVE_COMMIT true)
|
set (HAVE_COMMIT true)
|
||||||
execute_process (COMMAND git log -1 --pretty=format:%h
|
execute_process (COMMAND git log -1 --pretty=format:%h --no-show-signature
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
OUTPUT_VARIABLE COMMIT)
|
OUTPUT_VARIABLE COMMIT)
|
||||||
configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in
|
configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in
|
||||||
|
|||||||
1609
Cargo.lock
generated
1609
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
ChangeLog
24
ChangeLog
@@ -1,11 +1,31 @@
|
|||||||
------ current release ---------------------------
|
------ current release ---------------------------
|
||||||
|
|
||||||
|
- Sync now supports AWS S3 as a backend.
|
||||||
|
- A new `task import-v2` command allows importing Taskwarrior-2.x
|
||||||
|
data files directly.
|
||||||
|
|
||||||
|
3.3.0 -
|
||||||
|
|
||||||
|
Thanks to the following people for contributions to this release:
|
||||||
|
|
||||||
|
- Chongyun Lee
|
||||||
|
- David Tolnay
|
||||||
|
- Dustin J. Mitchell
|
||||||
|
- Felix Schurk
|
||||||
|
- geoffpaulsen
|
||||||
|
- Kalle Kietäväinen
|
||||||
|
- Kursat Aktas
|
||||||
|
- Scott Mcdermott
|
||||||
|
- Thomas Lauf
|
||||||
|
|
||||||
|
------ old releases ------------------------------
|
||||||
|
|
||||||
3.2.0 -
|
3.2.0 -
|
||||||
|
|
||||||
- Support for the journal in `task info` has been restored (#3671) and the
|
- Support for the journal in `task info` has been restored (#3671) and the
|
||||||
task info output no longer contains `tag_` values (#3619).
|
task info output no longer contains `tag_` values (#3619).
|
||||||
- The `rc.weekstart` value now affects calculation of week numbers in
|
- The `rc.weekstart` value now affects calculation of week numbers in
|
||||||
expressions like `2013-W49` (#2654).
|
expressions like `2013-W49` (#3654).
|
||||||
- Build-time flag `ENABLE_TLS_NATIVE_ROOTS` will cause `task sync` to use the
|
- Build-time flag `ENABLE_TLS_NATIVE_ROOTS` will cause `task sync` to use the
|
||||||
system TLS roots instead of its built-in roots to authenticate the server (#3660).
|
system TLS roots instead of its built-in roots to authenticate the server (#3660).
|
||||||
- The output from `task undo` is now more human-readable. The `undo.style`
|
- The output from `task undo` is now more human-readable. The `undo.style`
|
||||||
@@ -23,8 +43,6 @@ Thanks to the following people for contributions to this release:
|
|||||||
- Thomas Lauf
|
- Thomas Lauf
|
||||||
- Tobias Predel
|
- Tobias Predel
|
||||||
|
|
||||||
------ old releases ------------------------------
|
|
||||||
|
|
||||||
3.1.0 -
|
3.1.0 -
|
||||||
|
|
||||||
- Support for `task purge` has been restored, and new support added for automatically
|
- Support for `task purge` has been restored, and new support added for automatically
|
||||||
|
|||||||
44
INSTALL
44
INSTALL
@@ -22,7 +22,7 @@ You will need the following libraries:
|
|||||||
- libuuid (not needed for OSX)
|
- libuuid (not needed for OSX)
|
||||||
|
|
||||||
You will need a Rust toolchain of the Minimum Supported Rust Version (MSRV):
|
You will need a Rust toolchain of the Minimum Supported Rust Version (MSRV):
|
||||||
- rust 1.73.0
|
- rust 1.81.0
|
||||||
|
|
||||||
Basic Installation
|
Basic Installation
|
||||||
------------------
|
------------------
|
||||||
@@ -89,6 +89,11 @@ get absolute installation directories:
|
|||||||
CMAKE_INSTALL_PREFIX/TASK_MAN1DIR /usr/local/share/man/man1
|
CMAKE_INSTALL_PREFIX/TASK_MAN1DIR /usr/local/share/man/man1
|
||||||
CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5
|
CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5
|
||||||
|
|
||||||
|
The following variables control aspects of the build process:
|
||||||
|
|
||||||
|
SYSTEM_CORROSION - Use system provided corrosion instead of vendored version
|
||||||
|
ENABLE_TLS_NATIVE_ROOTS - Use the system's TLS root certificates
|
||||||
|
|
||||||
|
|
||||||
Uninstallation
|
Uninstallation
|
||||||
--------------
|
--------------
|
||||||
@@ -110,6 +115,43 @@ If Taskwarrior will not build on your system, first take a look at the Operating
|
|||||||
System notes below. If this doesn't help, then go to the Troubleshooting
|
System notes below. If this doesn't help, then go to the Troubleshooting
|
||||||
section, which includes instructions on how to contact us for help.
|
section, which includes instructions on how to contact us for help.
|
||||||
|
|
||||||
|
Offline Build Notes
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
It is common for packaging systems (e.g. NixOS, FreeBSD Ports Collection, pkgsrc, etc)
|
||||||
|
to disable networking during builds. This restriction requires all distribution files
|
||||||
|
to be prepositioned after checksum verification as a prerequisite for the build. The
|
||||||
|
following steps have been successful in allowing Taskwarrior to be built in this
|
||||||
|
environment:
|
||||||
|
|
||||||
|
1. Extract all crates in a known location, e.g. ${WRKDIR}/cargo-crates
|
||||||
|
This includes crates needed for corrosion (search for Cargo.lock files)
|
||||||
|
|
||||||
|
2. Create .cargo-checksum.json for each crate
|
||||||
|
For example:
|
||||||
|
printf '{"package":"%s","files":{}}' $(sha256 -q ${DISTDIR}/rayon-core-1.12.1.tar.gz) \
|
||||||
|
> ${WRKDIR}/cargo-crates/rayon-core-1.12.1/.cargo-checksum.json
|
||||||
|
|
||||||
|
3. Create a custom config.toml file
|
||||||
|
For example, ${WRKDIR}/.cargo/config.toml
|
||||||
|
[source.cargo]
|
||||||
|
directory = '${WRKDIR}/cargo-crates'
|
||||||
|
[source.crates-io]
|
||||||
|
replace-with = 'cargo'
|
||||||
|
|
||||||
|
4. After running cmake, configure cargo
|
||||||
|
For example:
|
||||||
|
cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} ${CARGO_ENV} \
|
||||||
|
/usr/local/bin/cargo update \
|
||||||
|
--manifest-path ${WRKDIR}/.cargo/config.toml \
|
||||||
|
--verbose
|
||||||
|
|
||||||
|
5. Set CARGO_HOME in environment
|
||||||
|
For example
|
||||||
|
CARGO_HOME=${WRKDIR}/.cargo
|
||||||
|
|
||||||
|
The build and installation steps should be the same as a standard build
|
||||||
|
at this point.
|
||||||
|
|
||||||
Operating System Notes
|
Operating System Notes
|
||||||
----------------------
|
----------------------
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
||||||
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
||||||
[](https://github.com/sponsors/GothenburgBitFactory/)
|
[](https://github.com/sponsors/GothenburgBitFactory/)
|
||||||
|
[](https://gurubase.io/g/taskwarrior)
|
||||||
</br>
|
</br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Security
|
# Security
|
||||||
|
|
||||||
To report a vulnerability, please contact [dustin@cs.uchicago.edu](mailto:dustin@cs.uchicago.edu), you may use GPG public-key D8097934A92E4B4210368102FF8B7AC6154E3226 which is available [here](https://keybase.io/djmitche/pgp_keys.asc?fingerprint=d8097934a92e4b4210368102ff8b7ac6154e3226).
|
To report a vulnerability, please contact Dustin via signal, [`djmitche.78`](https://signal.me/#eu/2T98jpkMAzvFL2wg3OkZnNrfhk1DFfu6eqkMEPqcAuCsLZPVk39A67rp4khmrMNF).
|
||||||
Initial response is expected within ~48h.
|
Initial response is expected within ~48h.
|
||||||
|
|
||||||
We kindly ask to follow the responsible disclosure model and refrain from sharing information until:
|
We kindly ask to follow the responsible disclosure model and refrain from sharing information until:
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ include (CheckCXXCompilerFlag)
|
|||||||
CHECK_CXX_COMPILER_FLAG("-std=c++17" _HAS_CXX17)
|
CHECK_CXX_COMPILER_FLAG("-std=c++17" _HAS_CXX17)
|
||||||
|
|
||||||
if (_HAS_CXX17)
|
if (_HAS_CXX17)
|
||||||
set (_CXX14_FLAGS "-std=c++17")
|
set (CMAKE_CXX_STANDARD 17)
|
||||||
|
set (CMAKE_CXX_EXTENSIONS OFF)
|
||||||
else (_HAS_CXX17)
|
else (_HAS_CXX17)
|
||||||
message (FATAL_ERROR "C++17 support missing. Try upgrading your C++ compiler. If you have a good reason for using an outdated compiler, please let us know at support@gothenburgbitfactory.org.")
|
message (FATAL_ERROR "C++17 support missing. Try upgrading your C++ compiler. If you have a good reason for using an outdated compiler, please let us know at support@gothenburgbitfactory.org.")
|
||||||
endif (_HAS_CXX17)
|
endif (_HAS_CXX17)
|
||||||
@@ -32,7 +33,7 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "GNU")
|
|||||||
set (GNUHURD true)
|
set (GNUHURD true)
|
||||||
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN")
|
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN")
|
||||||
set (CYGWIN true)
|
set (CYGWIN true)
|
||||||
set (_CXX14_FLAGS "-std=gnu++17")
|
set (CMAKE_CXX_EXTENSIONS ON)
|
||||||
else (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
else (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||||
set (UNKNOWN true)
|
set (UNKNOWN true)
|
||||||
endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||||
|
|||||||
@@ -94,3 +94,13 @@ They can be found in the [ctest](https://cmake.org/cmake/help/latest/manual/ctes
|
|||||||
Note that any development should be performed using a git clone, and the current development branch.
|
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.
|
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.
|
Follow the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) for creating a pull request.
|
||||||
|
|
||||||
|
## Using a Custom Version of TaskChampion
|
||||||
|
|
||||||
|
To build against a different version of Taskchampion, modify the requirement in `src/taskchampion-cpp/Cargo.toml`.
|
||||||
|
|
||||||
|
To build from a local checkout, replace the version with a [path dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies), giving the path to the directory containing TaskChampion's `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
taskchampion = { path = "path/to/taskchampion" }
|
||||||
|
```
|
||||||
|
|||||||
@@ -139,6 +139,83 @@ Then configure Taskwarrior with:
|
|||||||
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
|
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
|
||||||
.fi
|
.fi
|
||||||
|
|
||||||
|
.SS Amazon Web Services
|
||||||
|
|
||||||
|
To synchronize your tasks to AWS, select a region near you and use the AWS
|
||||||
|
console to create a new S3 bucket. The default settings for the bucket are
|
||||||
|
adequate.
|
||||||
|
|
||||||
|
You will also need an AWS IAM user with the following policy, where BUCKETNAME
|
||||||
|
is the name of the bucket. The same user can be configured for multiple
|
||||||
|
Taskwarrior clients.
|
||||||
|
|
||||||
|
.nf
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "TaskChampion",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:GetObject",
|
||||||
|
"s3:ListBucket",
|
||||||
|
"s3:DeleteObject"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::BUCKETNAME",
|
||||||
|
"arn:aws:s3:::BUCKETNAME/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
|
||||||
|
To create such a user, create a new policy in the IAM console, select the JSON
|
||||||
|
option in the policy editor, and paste the policy. Click "Next" and give the
|
||||||
|
policy a name such as "TaskwarriorSync". Next, create a new user, with a name
|
||||||
|
of your choosing, select "Attach Policies Directly", and then choose the
|
||||||
|
newly-created policy.
|
||||||
|
|
||||||
|
You will need access keys configured for the new user. Find the user in the
|
||||||
|
user list, open the "Security Credentials" tab, then click "Create access key"
|
||||||
|
and follow the steps.
|
||||||
|
|
||||||
|
At this point, you can choose how to provide those credentials to Taskwarrior.
|
||||||
|
The simplest is to include them in the Taskwarrior configuration:
|
||||||
|
|
||||||
|
.nf
|
||||||
|
$ task config sync.aws.region <region>
|
||||||
|
$ task config sync.aws.bucket <bucket-name>
|
||||||
|
$ task config sync.aws.access_key_id <access-key-id>
|
||||||
|
$ task config sync.aws.secret_access_key <secret-access-key>
|
||||||
|
.fi
|
||||||
|
|
||||||
|
Alternatively, you can set up an AWS CLI profile, using a profile name of your
|
||||||
|
choosing such as "taskwarrior-creds":
|
||||||
|
|
||||||
|
.nf
|
||||||
|
$ aws configure --profile taskwarrior-creds
|
||||||
|
.fi
|
||||||
|
|
||||||
|
Enter the access key ID and secret access key. The default region and format
|
||||||
|
are not important. Then configure Taskwarrior with:
|
||||||
|
|
||||||
|
.nf
|
||||||
|
$ task config sync.aws.region <region>
|
||||||
|
$ task config sync.aws.bucket <bucket-name>
|
||||||
|
$ task config sync.aws.profile taskwarrior-creds
|
||||||
|
.fi
|
||||||
|
|
||||||
|
To use AWS's default credential sources, such as environment variables, the
|
||||||
|
default profile, or an instance profile, set
|
||||||
|
|
||||||
|
.nf
|
||||||
|
$ task config sync.aws.region <region>
|
||||||
|
$ task config sync.aws.bucket <bucket-name>
|
||||||
|
$ task config sync.aws.default_credentials true
|
||||||
|
.fi
|
||||||
|
|
||||||
.SS Local Synchronization
|
.SS Local Synchronization
|
||||||
|
|
||||||
In order to take advantage of synchronization's side effect of saving disk
|
In order to take advantage of synchronization's side effect of saving disk
|
||||||
|
|||||||
@@ -414,6 +414,11 @@ few example scripts, such as:
|
|||||||
import-yaml.pl
|
import-yaml.pl
|
||||||
.fi
|
.fi
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B task import-v2
|
||||||
|
Imports tasks from the Taskwarrior v2.x format. This is used when upgrading from
|
||||||
|
version 2.x to version 3.x.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B task log <mods>
|
.B task log <mods>
|
||||||
Adds a new task that is already completed, to the task list. It is affected by
|
Adds a new task that is already completed, to the task list. It is affected by
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ services:
|
|||||||
security_opt:
|
security_opt:
|
||||||
- label=type:container_runtime_t
|
- label=type:container_runtime_t
|
||||||
tty: true
|
tty: true
|
||||||
test-fedora39:
|
test-fedora41:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: test/docker/fedora39
|
dockerfile: test/docker/fedora41
|
||||||
network_mode: "host"
|
network_mode: "host"
|
||||||
security_opt:
|
security_opt:
|
||||||
- label=type:container_runtime_t
|
- label=type:container_runtime_t
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ add_library (task STATIC CLI2.cpp CLI2.h
|
|||||||
Hooks.cpp Hooks.h
|
Hooks.cpp Hooks.h
|
||||||
Lexer.cpp Lexer.h
|
Lexer.cpp Lexer.h
|
||||||
Operation.cpp Operation.h
|
Operation.cpp Operation.h
|
||||||
|
TF2.cpp TF2.h
|
||||||
TDB2.cpp TDB2.h
|
TDB2.cpp TDB2.h
|
||||||
Task.cpp Task.h
|
Task.cpp Task.h
|
||||||
Variant.cpp Variant.h
|
Variant.cpp Variant.h
|
||||||
|
|||||||
@@ -318,6 +318,12 @@ std::string configurationDefaults =
|
|||||||
"#sync.server.client_id # Client ID for sync to a server\n"
|
"#sync.server.client_id # Client ID for sync to a server\n"
|
||||||
"#sync.server.url # URL of the sync server\n"
|
"#sync.server.url # URL of the sync server\n"
|
||||||
"#sync.local.server_dir # Directory for local sync\n"
|
"#sync.local.server_dir # Directory for local sync\n"
|
||||||
|
"#sync.aws.region # region for AWS sync\n"
|
||||||
|
"#sync.aws.bucket # bucket for AWS sync\n"
|
||||||
|
"#sync.aws.access_key_id # access_key_id for AWS sync\n"
|
||||||
|
"#sync.aws.secret_access_key # secret_access_key for AWS sync\n"
|
||||||
|
"#sync.aws.profile # profile name for AWS sync\n"
|
||||||
|
"#sync.aws.default_credentials # use default credentials for AWS sync\n"
|
||||||
"#sync.gcp.credential_path # Path to JSON file containing credentials to "
|
"#sync.gcp.credential_path # Path to JSON file containing credentials to "
|
||||||
"authenticate GCP Sync\n"
|
"authenticate GCP Sync\n"
|
||||||
"#sync.gcp.bucket # Bucket for sync to GCP\n"
|
"#sync.gcp.bucket # Bucket for sync to GCP\n"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Operation& Operation::operator=(const Operation& other) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
bool Operation::operator<(Operation& other) const {
|
bool Operation::operator<(const Operation& other) const {
|
||||||
if (is_create()) {
|
if (is_create()) {
|
||||||
return !other.is_create();
|
return !other.is_create();
|
||||||
} else if (is_update()) {
|
} else if (is_update()) {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class Operation {
|
|||||||
// Define a partial order on Operations:
|
// Define a partial order on Operations:
|
||||||
// - Create < Update < Delete < UndoPoint
|
// - Create < Update < Delete < UndoPoint
|
||||||
// - Given two updates, sort by timestamp
|
// - Given two updates, sort by timestamp
|
||||||
bool operator<(Operation &other) const;
|
bool operator<(const Operation &other) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const tc::Operation *op;
|
const tc::Operation *op;
|
||||||
|
|||||||
184
src/TF2.cpp
Normal file
184
src/TF2.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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 <Color.h>
|
||||||
|
#include <Context.h>
|
||||||
|
#include <Datetime.h>
|
||||||
|
#include <TF2.h>
|
||||||
|
#include <Table.h>
|
||||||
|
#include <cmake.h>
|
||||||
|
#include <format.h>
|
||||||
|
#include <main.h>
|
||||||
|
#include <shared.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <util.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <list>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#define STRING_TDB2_REVERTED "Modified task reverted."
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
TF2::TF2() : _loaded_tasks(false), _loaded_lines(false) {}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
TF2::~TF2() {}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void TF2::target(const std::string& f) { _file = File(f); }
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
const std::vector<std::map<std::string, std::string>>& TF2::get_tasks() {
|
||||||
|
if (!_loaded_tasks) load_tasks();
|
||||||
|
|
||||||
|
return _tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Attempt an FF4 parse.
|
||||||
|
//
|
||||||
|
// Note that FF1, FF2, FF3, and JSON are no longer supported.
|
||||||
|
//
|
||||||
|
// start --> [ --> Att --> ] --> end
|
||||||
|
// ^ |
|
||||||
|
// +-------+
|
||||||
|
//
|
||||||
|
std::map<std::string, std::string> TF2::load_task(const std::string& input) {
|
||||||
|
std::map<std::string, std::string> data;
|
||||||
|
|
||||||
|
// File format version 4, from 2009-5-16 - now, v1.7.1+
|
||||||
|
// This is the parse format tried first, because it is most used.
|
||||||
|
data.clear();
|
||||||
|
|
||||||
|
if (input[0] == '[') {
|
||||||
|
// Not using Pig to parse here (which would be idiomatic), because we
|
||||||
|
// don't need to differentiate betwen utf-8 and normal characters.
|
||||||
|
// Pig's scanning the string can be expensive.
|
||||||
|
auto ending_bracket = input.find_last_of(']');
|
||||||
|
if (ending_bracket != std::string::npos) {
|
||||||
|
std::string line = input.substr(1, ending_bracket);
|
||||||
|
|
||||||
|
if (line.length() == 0) throw std::string("Empty record in input.");
|
||||||
|
|
||||||
|
Pig attLine(line);
|
||||||
|
std::string name;
|
||||||
|
std::string value;
|
||||||
|
while (!attLine.eos()) {
|
||||||
|
if (attLine.getUntilAscii(':', name) && attLine.skip(':') &&
|
||||||
|
attLine.getQuoted('"', value)) {
|
||||||
|
#ifdef PRODUCT_TASKWARRIOR
|
||||||
|
legacyAttributeMap(name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
data[name] = decode(json::decode(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
attLine.skip(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string remainder;
|
||||||
|
attLine.getRemainder(remainder);
|
||||||
|
if (remainder.length()) throw std::string("Unrecognized characters at end of line.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::string("Record not recognized as format 4.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// for compatibility, include all tags in `tags` as `tag_..` attributes
|
||||||
|
if (data.find("tags") != data.end()) {
|
||||||
|
for (auto& tag : split(data["tags"], ',')) {
|
||||||
|
data[Task::tag2Attr(tag)] = "x";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// same for `depends` / `dep_..`
|
||||||
|
if (data.find("depends") != data.end()) {
|
||||||
|
for (auto& dep : split(data["depends"], ',')) {
|
||||||
|
data[Task::dep2Attr(dep)] = "x";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Decode values after parse.
|
||||||
|
// [ <- &open;
|
||||||
|
// ] <- &close;
|
||||||
|
const std::string TF2::decode(const std::string& value) const {
|
||||||
|
if (value.find('&') == std::string::npos) return value;
|
||||||
|
|
||||||
|
auto modified = str_replace(value, "&open;", "[");
|
||||||
|
return str_replace(modified, "&close;", "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void TF2::load_tasks() {
|
||||||
|
Timer timer;
|
||||||
|
|
||||||
|
if (!_loaded_lines) {
|
||||||
|
load_lines();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce unnecessary allocations/copies.
|
||||||
|
// Calling it on _tasks is the right thing to do even when from_gc is set.
|
||||||
|
_tasks.reserve(_lines.size());
|
||||||
|
|
||||||
|
int line_number = 0; // Used for error message in catch block.
|
||||||
|
try {
|
||||||
|
for (auto& line : _lines) {
|
||||||
|
++line_number;
|
||||||
|
auto task = load_task(line);
|
||||||
|
_tasks.push_back(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loaded_tasks = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (const std::string& e) {
|
||||||
|
throw e + format(" in {1} at line {2}", _file._data, line_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
Context::getContext().time_load_us += timer.total_us();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void TF2::load_lines() {
|
||||||
|
if (_file.open()) {
|
||||||
|
if (Context::getContext().config.getBoolean("locking")) _file.lock();
|
||||||
|
|
||||||
|
_file.read(_lines);
|
||||||
|
_file.close();
|
||||||
|
_loaded_lines = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// vim: ts=2 et sw=2
|
||||||
66
src/TF2.h
Normal file
66
src/TF2.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright 2006 - 2024, 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
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDED_TF2
|
||||||
|
#define INCLUDED_TF2
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
#include <Task.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// TF2 Class represents a single 2.x-style file in the task database.
|
||||||
|
//
|
||||||
|
// This is only used for importing tasks from 2.x. It only reads format 4, based
|
||||||
|
// on a stripped-down version of the TF2 class from v2.6.2.
|
||||||
|
class TF2 {
|
||||||
|
public:
|
||||||
|
TF2();
|
||||||
|
~TF2();
|
||||||
|
|
||||||
|
void target(const std::string&);
|
||||||
|
|
||||||
|
const std::vector<std::map<std::string, std::string>>& get_tasks();
|
||||||
|
|
||||||
|
std::map<std::string, std::string> load_task(const std::string&);
|
||||||
|
void load_tasks();
|
||||||
|
void load_lines();
|
||||||
|
const std::string decode(const std::string& value) const;
|
||||||
|
|
||||||
|
bool _loaded_tasks;
|
||||||
|
bool _loaded_lines;
|
||||||
|
std::vector<std::map<std::string, std::string>> _tasks;
|
||||||
|
std::vector<std::string> _lines;
|
||||||
|
File _file;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -35,6 +35,7 @@ set (commands_SRCS Command.cpp Command.h
|
|||||||
CmdHistory.cpp CmdHistory.h
|
CmdHistory.cpp CmdHistory.h
|
||||||
CmdIDs.cpp CmdIDs.h
|
CmdIDs.cpp CmdIDs.h
|
||||||
CmdImport.cpp CmdImport.h
|
CmdImport.cpp CmdImport.h
|
||||||
|
CmdImportV2.cpp CmdImportV2.h
|
||||||
CmdInfo.cpp CmdInfo.h
|
CmdInfo.cpp CmdInfo.h
|
||||||
CmdLog.cpp CmdLog.h
|
CmdLog.cpp CmdLog.h
|
||||||
CmdLogo.cpp CmdLogo.h
|
CmdLogo.cpp CmdLogo.h
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
// cmake.h include header must come first
|
// cmake.h include header must come first
|
||||||
|
|
||||||
#include <CmdCustom.h>
|
#include <CmdCustom.h>
|
||||||
|
#include <CmdNews.h>
|
||||||
#include <Context.h>
|
#include <Context.h>
|
||||||
#include <Filter.h>
|
#include <Filter.h>
|
||||||
#include <Lexer.h>
|
#include <Lexer.h>
|
||||||
@@ -222,11 +223,9 @@ int CmdCustom::execute(std::string& output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inform user about the new release highlights if not presented yet
|
// Inform user about the new release highlights if not presented yet
|
||||||
Version news_version(Context::getContext().config.get("news.version"));
|
if (CmdNews::should_nag()) {
|
||||||
Version current_version = Version::Current();
|
|
||||||
auto should_nag = news_version != current_version && Context::getContext().verbose("news");
|
|
||||||
if (should_nag) {
|
|
||||||
std::ostringstream notice;
|
std::ostringstream notice;
|
||||||
|
Version current_version = Version::Current();
|
||||||
notice << "Recently upgraded to " << current_version
|
notice << "Recently upgraded to " << current_version
|
||||||
<< ". "
|
<< ". "
|
||||||
"Please run 'task news' to read highlights about the new release.";
|
"Please run 'task news' to read highlights about the new release.";
|
||||||
@@ -242,7 +241,8 @@ int CmdCustom::execute(std::string& output) {
|
|||||||
Color warning = Color(Context::getContext().config.get("color.warning"));
|
Color warning = Color(Context::getContext().config.get("color.warning"));
|
||||||
std::cerr << warning.colorize(format("Found existing '*.data' files in {1}", location)) << "\n";
|
std::cerr << warning.colorize(format("Found existing '*.data' files in {1}", location)) << "\n";
|
||||||
std::cerr << " Taskwarrior's storage format changed in 3.0, requiring a manual migration.\n";
|
std::cerr << " Taskwarrior's storage format changed in 3.0, requiring a manual migration.\n";
|
||||||
std::cerr << " See https://github.com/GothenburgBitFactory/taskwarrior/releases.\n";
|
std::cerr << " See https://taskwarrior.org/docs/upgrade-3/. Run `task import-v2` to import\n";
|
||||||
|
std::cerr << " the tasks into the Taskwarrior-3.x format\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
feedback_backlog();
|
feedback_backlog();
|
||||||
|
|||||||
135
src/commands/CmdImportV2.cpp
Normal file
135
src/commands/CmdImportV2.cpp
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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>
|
||||||
|
// cmake.h include header must come first
|
||||||
|
|
||||||
|
#include <CmdImportV2.h>
|
||||||
|
#include <CmdModify.h>
|
||||||
|
#include <Context.h>
|
||||||
|
#include <TF2.h>
|
||||||
|
#include <format.h>
|
||||||
|
#include <shared.h>
|
||||||
|
#include <util.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
CmdImportV2::CmdImportV2() {
|
||||||
|
_keyword = "import-v2";
|
||||||
|
_usage = "task import-v2";
|
||||||
|
_description = "Imports Taskwarrior v2.x files";
|
||||||
|
_read_only = false;
|
||||||
|
_displays_id = false;
|
||||||
|
_needs_gc = false;
|
||||||
|
_uses_context = false;
|
||||||
|
_accepts_filter = false;
|
||||||
|
_accepts_modifications = false;
|
||||||
|
_accepts_miscellaneous = true;
|
||||||
|
_category = Command::Category::migration;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
int CmdImportV2::execute(std::string&) {
|
||||||
|
std::vector<std::map<std::string, std::string>> task_data;
|
||||||
|
|
||||||
|
std::string location = (Context::getContext().data_dir);
|
||||||
|
File pending_file = File(location + "/pending.data");
|
||||||
|
if (pending_file.exists()) {
|
||||||
|
TF2 pending_tf;
|
||||||
|
pending_tf.target(pending_file);
|
||||||
|
auto& pending_tasks = pending_tf.get_tasks();
|
||||||
|
task_data.insert(task_data.end(), pending_tasks.begin(), pending_tasks.end());
|
||||||
|
}
|
||||||
|
File completed_file = File(location + "/completed.data");
|
||||||
|
if (completed_file.exists()) {
|
||||||
|
TF2 completed_tf;
|
||||||
|
completed_tf.target(completed_file);
|
||||||
|
auto& completed_tasks = completed_tf.get_tasks();
|
||||||
|
task_data.insert(task_data.end(), completed_tasks.begin(), completed_tasks.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto count = import(task_data);
|
||||||
|
|
||||||
|
Context::getContext().footnote(
|
||||||
|
format("Imported {1} tasks from `*.data` files. You may now delete these files.", count));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
int CmdImportV2::import(const std::vector<std::map<std::string, std::string>>& task_data) {
|
||||||
|
auto count = 0;
|
||||||
|
const std::string uuid_key = "uuid";
|
||||||
|
const std::string id_key = "id";
|
||||||
|
const std::string descr_key = "description";
|
||||||
|
auto& replica = Context::getContext().tdb2.replica();
|
||||||
|
rust::Vec<tc::Operation> ops;
|
||||||
|
tc::add_undo_point(ops);
|
||||||
|
|
||||||
|
for (auto& task : task_data) {
|
||||||
|
auto uuid_iter = task.find(uuid_key);
|
||||||
|
if (uuid_iter == task.end()) {
|
||||||
|
std::cout << " err - Task with no UUID\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto uuid_str = uuid_iter->second;
|
||||||
|
auto uuid = tc::uuid_from_string(uuid_str);
|
||||||
|
|
||||||
|
bool added_task = false;
|
||||||
|
auto maybe_task_data = replica->get_task_data(uuid);
|
||||||
|
auto task_data = maybe_task_data.is_some() ? maybe_task_data.take() : [&]() {
|
||||||
|
added_task = true;
|
||||||
|
return tc::create_task(uuid, ops);
|
||||||
|
}();
|
||||||
|
|
||||||
|
for (auto& attr : task) {
|
||||||
|
if (attr.first == uuid_key || attr.first == id_key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
task_data->update(attr.first, attr.second, ops);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (added_task) {
|
||||||
|
std::cout << " add ";
|
||||||
|
} else {
|
||||||
|
std::cout << " mod ";
|
||||||
|
}
|
||||||
|
std::cout << uuid_str << ' ';
|
||||||
|
if (auto descr_iter = task.find(descr_key); descr_iter != task.end()) {
|
||||||
|
std::cout << descr_iter->second;
|
||||||
|
} else {
|
||||||
|
std::cout << "(no description)";
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
replica->commit_operations(std::move(ops));
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
46
src/commands/CmdImportV2.h
Normal file
46
src/commands/CmdImportV2.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#ifndef INCLUDED_CMDIMPORTV2
|
||||||
|
#define INCLUDED_CMDIMPORTV2
|
||||||
|
|
||||||
|
#include <Command.h>
|
||||||
|
#include <JSON.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
class CmdImportV2 : public Command {
|
||||||
|
public:
|
||||||
|
CmdImportV2();
|
||||||
|
int execute(std::string &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int import(const std::vector<std::map<std::string, std::string>> &task_data);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -159,6 +159,7 @@ std::vector<NewsItem> NewsItem::all() {
|
|||||||
version3_0_0(items);
|
version3_0_0(items);
|
||||||
version3_1_0(items);
|
version3_1_0(items);
|
||||||
version3_2_0(items);
|
version3_2_0(items);
|
||||||
|
version3_3_0(items);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,6 +516,20 @@ void NewsItem::version3_2_0(std::vector<NewsItem>& items) {
|
|||||||
items.push_back(info);
|
items.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NewsItem::version3_3_0(std::vector<NewsItem>& items) {
|
||||||
|
Version version("3.3.0");
|
||||||
|
NewsItem info{
|
||||||
|
version,
|
||||||
|
/*title=*/"AWS S3 Sync",
|
||||||
|
/*bg_title=*/"",
|
||||||
|
/*background=*/"",
|
||||||
|
/*punchline=*/"Use an AWS S3 bucket to sync Taskwarrior",
|
||||||
|
/*update=*/
|
||||||
|
"Taskwarrior now supports AWS as a backend for sync, in addition to existing support\n"
|
||||||
|
"for GCP and taskchampion-sync-server. See `man task-sync` for details.\n\n"};
|
||||||
|
items.push_back(info);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int CmdNews::execute(std::string& output) {
|
int CmdNews::execute(std::string& output) {
|
||||||
auto words = Context::getContext().cli2.getWords();
|
auto words = Context::getContext().cli2.getWords();
|
||||||
@@ -578,7 +593,7 @@ int CmdNews::execute(std::string& output) {
|
|||||||
std::cout << outro.str();
|
std::cout << outro.str();
|
||||||
|
|
||||||
// Set a mark in the config to remember which version's release notes were displayed
|
// Set a mark in the config to remember which version's release notes were displayed
|
||||||
if (news_version != current_version) {
|
if (news_version < current_version) {
|
||||||
CmdConfig::setConfigVariable("news.version", std::string(current_version), false);
|
CmdConfig::setConfigVariable("news.version", std::string(current_version), false);
|
||||||
|
|
||||||
// Revert back to default signal handling after displaying the outro
|
// Revert back to default signal handling after displaying the outro
|
||||||
@@ -615,3 +630,28 @@ int CmdNews::execute(std::string& output) {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CmdNews::should_nag() {
|
||||||
|
if (!Context::getContext().verbose("news")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Version news_version(Context::getContext().config.get("news.version"));
|
||||||
|
if (!news_version.is_valid()) news_version = Version("2.6.0");
|
||||||
|
|
||||||
|
Version current_version = Version::Current();
|
||||||
|
|
||||||
|
if (news_version >= current_version) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are actually any interesting news items to show.
|
||||||
|
std::vector<NewsItem> items = NewsItem::all();
|
||||||
|
for (auto& item : items) {
|
||||||
|
if (item._version > news_version) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class NewsItem {
|
|||||||
static void version3_0_0(std::vector<NewsItem>&);
|
static void version3_0_0(std::vector<NewsItem>&);
|
||||||
static void version3_1_0(std::vector<NewsItem>&);
|
static void version3_1_0(std::vector<NewsItem>&);
|
||||||
static void version3_2_0(std::vector<NewsItem>&);
|
static void version3_2_0(std::vector<NewsItem>&);
|
||||||
|
static void version3_3_0(std::vector<NewsItem>&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NewsItem(Version, const std::string&, const std::string& = "", const std::string& = "",
|
NewsItem(Version, const std::string&, const std::string& = "", const std::string& = "",
|
||||||
@@ -63,6 +64,8 @@ class CmdNews : public Command {
|
|||||||
public:
|
public:
|
||||||
CmdNews();
|
CmdNews();
|
||||||
int execute(std::string&);
|
int execute(std::string&);
|
||||||
|
|
||||||
|
static bool should_nag();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -194,9 +194,15 @@ int CmdShow::execute(std::string& output) {
|
|||||||
" search.case.sensitive"
|
" search.case.sensitive"
|
||||||
" sugar"
|
" sugar"
|
||||||
" summary.all.projects"
|
" summary.all.projects"
|
||||||
" sync.local.server_dir"
|
" sync.aws.access_key_id"
|
||||||
|
" sync.aws.bucket"
|
||||||
|
" sync.aws.default_credentials"
|
||||||
|
" sync.aws.profile"
|
||||||
|
" sync.aws.region"
|
||||||
|
" sync.aws.secret_access_key"
|
||||||
" sync.gcp.credential_path"
|
" sync.gcp.credential_path"
|
||||||
" sync.gcp.bucket"
|
" sync.gcp.bucket"
|
||||||
|
" sync.local.server_dir"
|
||||||
" sync.server.client_id"
|
" sync.server.client_id"
|
||||||
" sync.encryption_secret"
|
" sync.encryption_secret"
|
||||||
" sync.server.url"
|
" sync.server.url"
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include <taskchampion-cpp/lib.h>
|
#include <taskchampion-cpp/lib.h>
|
||||||
#include <util.h>
|
#include <util.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -65,12 +66,11 @@ int CmdSync::execute(std::string& output) {
|
|||||||
bool avoid_snapshots = false;
|
bool avoid_snapshots = false;
|
||||||
bool verbose = Context::getContext().verbose("sync");
|
bool verbose = Context::getContext().verbose("sync");
|
||||||
|
|
||||||
// If no server is set up, quit.
|
|
||||||
std::string origin = Context::getContext().config.get("sync.server.origin");
|
std::string origin = Context::getContext().config.get("sync.server.origin");
|
||||||
std::string url = Context::getContext().config.get("sync.server.url");
|
std::string url = Context::getContext().config.get("sync.server.url");
|
||||||
std::string server_dir = Context::getContext().config.get("sync.local.server_dir");
|
std::string server_dir = Context::getContext().config.get("sync.local.server_dir");
|
||||||
std::string client_id = Context::getContext().config.get("sync.server.client_id");
|
std::string client_id = Context::getContext().config.get("sync.server.client_id");
|
||||||
std::string gcp_credential_path = Context::getContext().config.get("sync.gcp.credential_path");
|
std::string aws_bucket = Context::getContext().config.get("sync.aws.bucket");
|
||||||
std::string gcp_bucket = Context::getContext().config.get("sync.gcp.bucket");
|
std::string gcp_bucket = Context::getContext().config.get("sync.gcp.bucket");
|
||||||
std::string encryption_secret = Context::getContext().config.get("sync.encryption_secret");
|
std::string encryption_secret = Context::getContext().config.get("sync.encryption_secret");
|
||||||
|
|
||||||
@@ -85,7 +85,55 @@ int CmdSync::execute(std::string& output) {
|
|||||||
out << format("Syncing with {1}", server_dir) << '\n';
|
out << format("Syncing with {1}", server_dir) << '\n';
|
||||||
}
|
}
|
||||||
replica->sync_to_local(server_dir, avoid_snapshots);
|
replica->sync_to_local(server_dir, avoid_snapshots);
|
||||||
|
} else if (aws_bucket != "") {
|
||||||
|
std::string aws_region = Context::getContext().config.get("sync.aws.region");
|
||||||
|
std::string aws_profile = Context::getContext().config.get("sync.aws.profile");
|
||||||
|
std::string aws_access_key_id = Context::getContext().config.get("sync.aws.access_key_id");
|
||||||
|
std::string aws_secret_access_key =
|
||||||
|
Context::getContext().config.get("sync.aws.secret_access_key");
|
||||||
|
std::string aws_default_credentials =
|
||||||
|
Context::getContext().config.get("sync.aws.default_credentials");
|
||||||
|
if (aws_region == "") {
|
||||||
|
throw std::string("sync.aws.region is required");
|
||||||
|
}
|
||||||
|
if (encryption_secret == "") {
|
||||||
|
throw std::string("sync.encryption_secret is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool using_profile = false;
|
||||||
|
bool using_creds = false;
|
||||||
|
bool using_default = false;
|
||||||
|
if (aws_profile != "") {
|
||||||
|
using_profile = true;
|
||||||
|
}
|
||||||
|
if (aws_access_key_id != "" || aws_secret_access_key != "") {
|
||||||
|
using_creds = true;
|
||||||
|
}
|
||||||
|
if (aws_default_credentials != "") {
|
||||||
|
using_default = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (using_profile + using_creds + using_default != 1) {
|
||||||
|
throw std::string("exactly one method of specifying AWS credentials is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
out << format("Syncing with AWS bucket {1}", aws_bucket) << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (using_profile) {
|
||||||
|
replica->sync_to_aws_with_profile(aws_region, aws_bucket, aws_profile, encryption_secret,
|
||||||
|
avoid_snapshots);
|
||||||
|
} else if (using_creds) {
|
||||||
|
replica->sync_to_aws_with_access_key(aws_region, aws_bucket, aws_access_key_id,
|
||||||
|
aws_secret_access_key, encryption_secret,
|
||||||
|
avoid_snapshots);
|
||||||
|
} else {
|
||||||
|
replica->sync_to_aws_with_default_creds(aws_region, aws_bucket, encryption_secret,
|
||||||
|
avoid_snapshots);
|
||||||
|
}
|
||||||
} else if (gcp_bucket != "") {
|
} else if (gcp_bucket != "") {
|
||||||
|
std::string gcp_credential_path = Context::getContext().config.get("sync.gcp.credential_path");
|
||||||
if (encryption_secret == "") {
|
if (encryption_secret == "") {
|
||||||
throw std::string("sync.encryption_secret is required");
|
throw std::string("sync.encryption_secret is required");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@
|
|||||||
#include <CmdHistory.h>
|
#include <CmdHistory.h>
|
||||||
#include <CmdIDs.h>
|
#include <CmdIDs.h>
|
||||||
#include <CmdImport.h>
|
#include <CmdImport.h>
|
||||||
|
#include <CmdImportV2.h>
|
||||||
#include <CmdInfo.h>
|
#include <CmdInfo.h>
|
||||||
#include <CmdLog.h>
|
#include <CmdLog.h>
|
||||||
#include <CmdLogo.h>
|
#include <CmdLogo.h>
|
||||||
@@ -188,6 +189,8 @@ void Command::factory(std::map<std::string, Command*>& all) {
|
|||||||
all[c->keyword()] = c;
|
all[c->keyword()] = c;
|
||||||
c = new CmdImport();
|
c = new CmdImport();
|
||||||
all[c->keyword()] = c;
|
all[c->keyword()] = c;
|
||||||
|
c = new CmdImportV2();
|
||||||
|
all[c->keyword()] = c;
|
||||||
c = new CmdInfo();
|
c = new CmdInfo();
|
||||||
all[c->keyword()] = c;
|
all[c->keyword()] = c;
|
||||||
c = new CmdLog();
|
c = new CmdLog();
|
||||||
|
|||||||
Submodule src/libshared updated: 47a750c385...1a06cb4cae
@@ -35,13 +35,15 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// recur.cpp
|
// recur.cpp
|
||||||
void handleRecurrence();
|
void handleRecurrence();
|
||||||
void handleUntil();
|
void handleUntil();
|
||||||
Datetime getNextRecurrence(Datetime&, std::string&);
|
std::optional<Datetime> checked_add_datetime(Datetime& base, time_t delta);
|
||||||
|
std::optional<Datetime> getNextRecurrence(Datetime&, std::string&);
|
||||||
bool generateDueDates(Task&, std::vector<Datetime>&);
|
bool generateDueDates(Task&, std::vector<Datetime>&);
|
||||||
void updateRecurrenceMask(Task&);
|
void updateRecurrenceMask(Task&);
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,24 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
// Add a `time_t` delta to a Datetime, checking for and returning nullopt on integer overflow.
|
||||||
|
std::optional<Datetime> checked_add_datetime(Datetime& base, time_t delta) {
|
||||||
|
// Datetime::operator+ takes an integer delta, so check that range
|
||||||
|
if (static_cast<time_t>(std::numeric_limits<int>::max()) < delta) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for time_t overflow in the Datetime.
|
||||||
|
if (std::numeric_limits<time_t>::max() - base.toEpoch() < delta) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return base + delta;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Scans all tasks, and for any recurring tasks, determines whether any new
|
// Scans all tasks, and for any recurring tasks, determines whether any new
|
||||||
// child tasks need to be generated to fill gaps.
|
// child tasks need to be generated to fill gaps.
|
||||||
@@ -95,7 +111,12 @@ void handleRecurrence() {
|
|||||||
Datetime old_wait(t.get_date("wait"));
|
Datetime old_wait(t.get_date("wait"));
|
||||||
Datetime old_due(t.get_date("due"));
|
Datetime old_due(t.get_date("due"));
|
||||||
Datetime due(d);
|
Datetime due(d);
|
||||||
rec.set("wait", format((due + (old_wait - old_due)).toEpoch()));
|
auto wait = checked_add_datetime(due, old_wait - old_due);
|
||||||
|
if (wait) {
|
||||||
|
rec.set("wait", format(wait->toEpoch()));
|
||||||
|
} else {
|
||||||
|
rec.remove("wait");
|
||||||
|
}
|
||||||
rec.setStatus(Task::waiting);
|
rec.setStatus(Task::waiting);
|
||||||
mask += 'W';
|
mask += 'W';
|
||||||
} else {
|
} else {
|
||||||
@@ -148,7 +169,8 @@ bool generateDueDates(Task& parent, std::vector<Datetime>& allDue) {
|
|||||||
auto recurrence_limit = Context::getContext().config.getInteger("recurrence.limit");
|
auto recurrence_limit = Context::getContext().config.getInteger("recurrence.limit");
|
||||||
int recurrence_counter = 0;
|
int recurrence_counter = 0;
|
||||||
Datetime now;
|
Datetime now;
|
||||||
for (Datetime i = due;; i = getNextRecurrence(i, recur)) {
|
Datetime i = due;
|
||||||
|
while (1) {
|
||||||
allDue.push_back(i);
|
allDue.push_back(i);
|
||||||
|
|
||||||
if (specificEnd && i > until) {
|
if (specificEnd && i > until) {
|
||||||
@@ -164,13 +186,23 @@ bool generateDueDates(Task& parent, std::vector<Datetime>& allDue) {
|
|||||||
if (i > now) ++recurrence_counter;
|
if (i > now) ++recurrence_counter;
|
||||||
|
|
||||||
if (recurrence_counter >= recurrence_limit) return true;
|
if (recurrence_counter >= recurrence_limit) return true;
|
||||||
|
auto next = getNextRecurrence(i, recur);
|
||||||
|
if (next) {
|
||||||
|
i = *next;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
Datetime getNextRecurrence(Datetime& current, std::string& period) {
|
/// Determine the next recurrence of the given period.
|
||||||
|
///
|
||||||
|
/// If no such date can be calculated, such as with a very large period, returns
|
||||||
|
/// nullopt.
|
||||||
|
std::optional<Datetime> getNextRecurrence(Datetime& current, std::string& period) {
|
||||||
auto m = current.month();
|
auto m = current.month();
|
||||||
auto d = current.day();
|
auto d = current.day();
|
||||||
auto y = current.year();
|
auto y = current.year();
|
||||||
@@ -201,7 +233,7 @@ Datetime getNextRecurrence(Datetime& current, std::string& period) {
|
|||||||
else
|
else
|
||||||
days = 1;
|
days = 1;
|
||||||
|
|
||||||
return current + (days * 86400);
|
return checked_add_datetime(current, days * 86400);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (unicodeLatinDigit(period[0]) && period[period.length() - 1] == 'm') {
|
else if (unicodeLatinDigit(period[0]) && period[period.length() - 1] == 'm') {
|
||||||
@@ -317,7 +349,7 @@ Datetime getNextRecurrence(Datetime& current, std::string& period) {
|
|||||||
if (!p.parse(period, idx))
|
if (!p.parse(period, idx))
|
||||||
throw std::string(format("The recurrence value '{1}' is not valid.", period));
|
throw std::string(format("The recurrence value '{1}' is not valid.", period));
|
||||||
|
|
||||||
return current + p.toTime_t();
|
return checked_add_datetime(current, p.toTime_t());
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ name = "taskchampion-lib"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
rust-version = "1.81.0" # MSRV
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
taskchampion = "0.9.0"
|
taskchampion = "=2.0.2"
|
||||||
cxx = "1.0.124"
|
cxx = "1.0.133"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# use native CA roots, instead of bundled
|
# use native CA roots, instead of bundled
|
||||||
tls-native-roots = ["taskchampion/tls-native-roots"]
|
tls-native-roots = ["taskchampion/tls-native-roots"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cxx-build = "1.0"
|
cxx-build = "1.0.133"
|
||||||
|
|||||||
@@ -164,6 +164,36 @@ mod ffi {
|
|||||||
avoid_snapshots: bool,
|
avoid_snapshots: bool,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::Profile`.
|
||||||
|
fn sync_to_aws_with_profile(
|
||||||
|
&mut self,
|
||||||
|
region: String,
|
||||||
|
bucket: String,
|
||||||
|
profile_name: String,
|
||||||
|
encryption_secret: &CxxString,
|
||||||
|
avoid_snapshots: bool,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::AccessKey`.
|
||||||
|
fn sync_to_aws_with_access_key(
|
||||||
|
&mut self,
|
||||||
|
region: String,
|
||||||
|
bucket: String,
|
||||||
|
access_key_id: String,
|
||||||
|
secret_access_key: String,
|
||||||
|
encryption_secret: &CxxString,
|
||||||
|
avoid_snapshots: bool,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
/// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::Default`.
|
||||||
|
fn sync_to_aws_with_default_creds(
|
||||||
|
&mut self,
|
||||||
|
region: String,
|
||||||
|
bucket: String,
|
||||||
|
encryption_secret: &CxxString,
|
||||||
|
avoid_snapshots: bool,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
/// Sync with a server created from `ServerConfig::Gcp`.
|
/// Sync with a server created from `ServerConfig::Gcp`.
|
||||||
///
|
///
|
||||||
/// An empty value for `credential_path` is converted to `Option::None`.
|
/// An empty value for `credential_path` is converted to `Option::None`.
|
||||||
@@ -464,6 +494,7 @@ fn new_replica_on_disk(
|
|||||||
let storage = tc::StorageConfig::OnDisk {
|
let storage = tc::StorageConfig::OnDisk {
|
||||||
taskdb_dir: PathBuf::from(taskdb_dir),
|
taskdb_dir: PathBuf::from(taskdb_dir),
|
||||||
create_if_missing,
|
create_if_missing,
|
||||||
|
access_mode: tc::storage::AccessMode::ReadWrite,
|
||||||
}
|
}
|
||||||
.into_storage()?;
|
.into_storage()?;
|
||||||
Ok(Box::new(tc::Replica::new(storage).into()))
|
Ok(Box::new(tc::Replica::new(storage).into()))
|
||||||
@@ -580,6 +611,63 @@ impl Replica {
|
|||||||
Ok(self.0.sync(&mut server, avoid_snapshots)?)
|
Ok(self.0.sync(&mut server, avoid_snapshots)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_to_aws_with_profile(
|
||||||
|
&mut self,
|
||||||
|
region: String,
|
||||||
|
bucket: String,
|
||||||
|
profile_name: String,
|
||||||
|
encryption_secret: &CxxString,
|
||||||
|
avoid_snapshots: bool,
|
||||||
|
) -> Result<(), CppError> {
|
||||||
|
let mut server = tc::server::ServerConfig::Aws {
|
||||||
|
region,
|
||||||
|
bucket,
|
||||||
|
credentials: tc::server::AwsCredentials::Profile { profile_name },
|
||||||
|
encryption_secret: encryption_secret.as_bytes().to_vec(),
|
||||||
|
}
|
||||||
|
.into_server()?;
|
||||||
|
Ok(self.0.sync(&mut server, avoid_snapshots)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_to_aws_with_access_key(
|
||||||
|
&mut self,
|
||||||
|
region: String,
|
||||||
|
bucket: String,
|
||||||
|
access_key_id: String,
|
||||||
|
secret_access_key: String,
|
||||||
|
encryption_secret: &CxxString,
|
||||||
|
avoid_snapshots: bool,
|
||||||
|
) -> Result<(), CppError> {
|
||||||
|
let mut server = tc::server::ServerConfig::Aws {
|
||||||
|
region,
|
||||||
|
bucket,
|
||||||
|
credentials: tc::server::AwsCredentials::AccessKey {
|
||||||
|
access_key_id,
|
||||||
|
secret_access_key,
|
||||||
|
},
|
||||||
|
encryption_secret: encryption_secret.as_bytes().to_vec(),
|
||||||
|
}
|
||||||
|
.into_server()?;
|
||||||
|
Ok(self.0.sync(&mut server, avoid_snapshots)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_to_aws_with_default_creds(
|
||||||
|
&mut self,
|
||||||
|
region: String,
|
||||||
|
bucket: String,
|
||||||
|
encryption_secret: &CxxString,
|
||||||
|
avoid_snapshots: bool,
|
||||||
|
) -> Result<(), CppError> {
|
||||||
|
let mut server = tc::server::ServerConfig::Aws {
|
||||||
|
region,
|
||||||
|
bucket,
|
||||||
|
credentials: tc::server::AwsCredentials::Default,
|
||||||
|
encryption_secret: encryption_secret.as_bytes().to_vec(),
|
||||||
|
}
|
||||||
|
.into_server()?;
|
||||||
|
Ok(self.0.sync(&mut server, avoid_snapshots)?)
|
||||||
|
}
|
||||||
|
|
||||||
fn sync_to_gcp(
|
fn sync_to_gcp(
|
||||||
&mut self,
|
&mut self,
|
||||||
bucket: String,
|
bucket: String,
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ set (pythonTests
|
|||||||
hyphenate.test.py
|
hyphenate.test.py
|
||||||
ids.test.py
|
ids.test.py
|
||||||
import.test.py
|
import.test.py
|
||||||
|
import-v2.test.py
|
||||||
info.test.py
|
info.test.py
|
||||||
limit.test.py
|
limit.test.py
|
||||||
list.all.projects.test.py
|
list.all.projects.test.py
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM fedora:39
|
FROM fedora:41
|
||||||
|
|
||||||
RUN dnf update -y
|
RUN dnf update -y
|
||||||
RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y
|
RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y
|
||||||
81
test/import-v2.test.py
Executable file
81
test/import-v2.test.py
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
###############################################################################
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Ensure python finds the local simpletap module
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from basetest import Task, TestCase
|
||||||
|
from basetest.utils import mkstemp
|
||||||
|
|
||||||
|
|
||||||
|
class TestImport(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.t = Task()
|
||||||
|
self.t.config("dateformat", "m/d/Y")
|
||||||
|
|
||||||
|
# Multiple tasks.
|
||||||
|
self.pending = """\
|
||||||
|
[description:"bing" due:"1734480000" entry:"1734397061" modified:"1734397061" status:"pending" uuid:"ad7f7585-bff3-4b57-a116-abfc9f71ee4a"]
|
||||||
|
[description:"baz" entry:"1734397063" modified:"1734397063" status:"pending" uuid:"591ccfee-dd8d-44e9-908a-40618257cf54"]\
|
||||||
|
"""
|
||||||
|
self.completed = """\
|
||||||
|
[description:"foo" end:"1734397073" entry:"1734397054" modified:"1734397074" status:"deleted" uuid:"6849568f-55d7-4152-8db0-00356e39f0bb"]
|
||||||
|
[description:"bar" end:"1734397065" entry:"1734397056" modified:"1734397065" status:"completed" uuid:"51921813-7abb-412d-8ada-7c1417d01209"]\
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_import_v2(self):
|
||||||
|
with open(os.path.join(self.t.datadir, "pending.data"), "w") as f:
|
||||||
|
f.write(self.pending)
|
||||||
|
with open(os.path.join(self.t.datadir, "completed.data"), "w") as f:
|
||||||
|
f.write(self.completed)
|
||||||
|
code, out, err = self.t("import-v2")
|
||||||
|
self.assertIn("Imported 4 tasks", err)
|
||||||
|
|
||||||
|
code, out, err = self.t("list")
|
||||||
|
self.assertIn("bing", out)
|
||||||
|
self.assertIn("baz", out)
|
||||||
|
self.assertNotIn("foo", out)
|
||||||
|
self.assertNotIn("bar", out)
|
||||||
|
|
||||||
|
code, out, err = self.t("completed")
|
||||||
|
self.assertNotIn("bing", out)
|
||||||
|
self.assertNotIn("baz", out)
|
||||||
|
self.assertNotIn("foo", out) # deleted, not in the completed report
|
||||||
|
self.assertIn("bar", out)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from simpletap import TAPTestRunner
|
||||||
|
|
||||||
|
unittest.main(testRunner=TAPTestRunner())
|
||||||
|
|
||||||
|
# vim: ai sts=4 et sw=4 ft=python
|
||||||
0
test/test_hooks/on-add-accept
Normal file → Executable file
0
test/test_hooks/on-add-accept
Normal file → Executable file
0
test/test_hooks/on-add-misbehave1
Normal file → Executable file
0
test/test_hooks/on-add-misbehave1
Normal file → Executable file
0
test/test_hooks/on-add-misbehave2
Normal file → Executable file
0
test/test_hooks/on-add-misbehave2
Normal file → Executable file
0
test/test_hooks/on-add-misbehave3
Normal file → Executable file
0
test/test_hooks/on-add-misbehave3
Normal file → Executable file
0
test/test_hooks/on-add-misbehave4
Normal file → Executable file
0
test/test_hooks/on-add-misbehave4
Normal file → Executable file
0
test/test_hooks/on-add-misbehave5
Normal file → Executable file
0
test/test_hooks/on-add-misbehave5
Normal file → Executable file
0
test/test_hooks/on-add-misbehave6
Normal file → Executable file
0
test/test_hooks/on-add-misbehave6
Normal file → Executable file
0
test/test_hooks/on-add-modify
Normal file → Executable file
0
test/test_hooks/on-add-modify
Normal file → Executable file
0
test/test_hooks/on-add-reject
Normal file → Executable file
0
test/test_hooks/on-add-reject
Normal file → Executable file
0
test/test_hooks/on-add.dummy
Normal file → Executable file
0
test/test_hooks/on-add.dummy
Normal file → Executable file
0
test/test_hooks/on-exit-bad
Normal file → Executable file
0
test/test_hooks/on-exit-bad
Normal file → Executable file
0
test/test_hooks/on-exit-good
Normal file → Executable file
0
test/test_hooks/on-exit-good
Normal file → Executable file
0
test/test_hooks/on-exit-misbehave1
Normal file → Executable file
0
test/test_hooks/on-exit-misbehave1
Normal file → Executable file
0
test/test_hooks/on-exit-misbehave2
Normal file → Executable file
0
test/test_hooks/on-exit-misbehave2
Normal file → Executable file
0
test/test_hooks/on-exit.dummy
Normal file → Executable file
0
test/test_hooks/on-exit.dummy
Normal file → Executable file
0
test/test_hooks/on-launch-bad
Normal file → Executable file
0
test/test_hooks/on-launch-bad
Normal file → Executable file
0
test/test_hooks/on-launch-good
Normal file → Executable file
0
test/test_hooks/on-launch-good
Normal file → Executable file
0
test/test_hooks/on-launch-good-env
Normal file → Executable file
0
test/test_hooks/on-launch-good-env
Normal file → Executable file
0
test/test_hooks/on-launch-misbehave1
Normal file → Executable file
0
test/test_hooks/on-launch-misbehave1
Normal file → Executable file
0
test/test_hooks/on-launch-misbehave2
Normal file → Executable file
0
test/test_hooks/on-launch-misbehave2
Normal file → Executable file
0
test/test_hooks/on-launch.dummy
Normal file → Executable file
0
test/test_hooks/on-launch.dummy
Normal file → Executable file
0
test/test_hooks/on-modify-accept
Normal file → Executable file
0
test/test_hooks/on-modify-accept
Normal file → Executable file
0
test/test_hooks/on-modify-for-template-badexit.py
Normal file → Executable file
0
test/test_hooks/on-modify-for-template-badexit.py
Normal file → Executable file
0
test/test_hooks/on-modify-for-template.py
Normal file → Executable file
0
test/test_hooks/on-modify-for-template.py
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave2
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave2
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave3
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave3
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave4
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave4
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave5
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave5
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave6
Normal file → Executable file
0
test/test_hooks/on-modify-misbehave6
Normal file → Executable file
0
test/test_hooks/on-modify-reject
Normal file → Executable file
0
test/test_hooks/on-modify-reject
Normal file → Executable file
0
test/test_hooks/on-modify-revert
Normal file → Executable file
0
test/test_hooks/on-modify-revert
Normal file → Executable file
0
test/test_hooks/on-modify.dummy
Normal file → Executable file
0
test/test_hooks/on-modify.dummy
Normal file → Executable file
0
test/test_hooks/wrapper.sh
Normal file → Executable file
0
test/test_hooks/wrapper.sh
Normal file → Executable file
@@ -33,6 +33,7 @@
|
|||||||
#include <util.h>
|
#include <util.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
int TEST_NAME(int, char**) {
|
int TEST_NAME(int, char**) {
|
||||||
@@ -91,6 +92,12 @@ int TEST_NAME(int, char**) {
|
|||||||
t.ok(nontrivial(" \t\ta"), "nontrivial ' \\t\\ta' -> true");
|
t.ok(nontrivial(" \t\ta"), "nontrivial ' \\t\\ta' -> true");
|
||||||
t.ok(nontrivial("a\t\t "), "nontrivial 'a\\t\\t ' -> true");
|
t.ok(nontrivial("a\t\t "), "nontrivial 'a\\t\\t ' -> true");
|
||||||
|
|
||||||
|
Datetime dt(1234526400);
|
||||||
|
Datetime max(std::numeric_limits<time_t>::max());
|
||||||
|
t.ok(checked_add_datetime(dt, 10).has_value(), "small delta");
|
||||||
|
t.ok(!checked_add_datetime(dt, 0x100000000).has_value(), "delta > 32bit");
|
||||||
|
t.ok(!checked_add_datetime(max, 1).has_value(), "huge base time");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user