From 09aff628f0079342b5d5f8636c0c9b5e39f8f4f400845d8dd94a3889c15aacfb Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Wed, 10 Jan 2024 16:52:56 +0300 Subject: [PATCH] Initial commit --- README | 101 +++++++++++++++++++++++++++++++++++++++++++++ add | 17 ++++++++ cd | 7 ++++ comment-list | 16 +++++++ dep-add | 20 +++++++++ lib.zsh.rc | 30 ++++++++++++++ list | 11 +++++ recfile-export | 18 ++++++++ recfile-export-all | 11 +++++ show | 22 ++++++++++ tag-add | 17 ++++++++ tag-list | 9 ++++ 12 files changed, 279 insertions(+) create mode 100644 README create mode 100755 add create mode 100755 cd create mode 100755 comment-list create mode 100755 dep-add create mode 100644 lib.zsh.rc create mode 100755 list create mode 100755 recfile-export create mode 100755 recfile-export-all create mode 100755 show create mode 100755 tag-add create mode 100755 tag-list diff --git a/README b/README new file mode 100644 index 0000000..102fcbd --- /dev/null +++ b/README @@ -0,0 +1,101 @@ +schwabrak -- simple flat file based issue tracker + +I want relatively simple issue/ticket tracker. Something like Trac, +Redmine or Fossil. But they require heavy dependencies (Python, Ruby), +database and they are centralised. You can not work with them offline +and it is relatively complicated to replicate data to locally running +copy of any of those trackers (well, except for Fossil I believe). And +does web-interface necessary at all? + +What is a ticket/issue? Just some plain text descriptions, that have +attached enumerations (statuses, assignments, priorities, projects, +subsystems, severities, resolutions, etc) and a pile of append-only +comments, possibly with another file attachments. Can all of that +live in a directory with several plain text files? Can it be linked +with other issues just by making a symbolic links in deps/ subdirectory? +Are not Git commits provide supplementary metainformation about when and +by whom any of the change is made with that directory? Definitely yes! +And because of DVCS you get ability to keep the whole distributed copy +of the tracker on each developer's machine. You can send changes to it +asynchronously as a patch or bundle. + +Basically schwabrak is mainly about a convention how to keep issues in +files, loosely similar to https://github.com/driusan/PoormanIssueTracker, +where I borrowed idea of replacing spaces with dashes in issue names. + +Issues are kept in issues/ directory. Each issue can be a part of +projects hierarchy: main-project/sub-proj/issue-name. Directory's name +is the issue's brief name. Dashes should be interpreted as spaces and n +> 1 dashes should be interpreted as n-1 dashes when converting directory +name to a human readable issue title, as PoormanIssueTracker suggests. +"about" file contains the description of the issue. "result" (initially +empty) contains the closed issue resolution information. + +"created" issue's file contains the datetime it was created. It is used +to help sort issues by date. Unfortunately Git does not keep and restore +mtimes, that probably can eliminate the need of separate files with the +timestamps. + +Each issue can have attached tags. For keeping their set in consistent +well-defined state, tags/ directory above the issues/ contains available +tags for you projects. + $ for tag in status:open status:done assigned:alice assigned:bob + doc db test:integration severity:high severity:low ... ; do + touch tags/$tag + done +It is your choice how to name and deal with them. Want to find all +issues in done state? for i (issues/**/tags/status:done) print $i:h:h. +All of that kind of information are just enumerations. + +deps/ subdirectory in each issue can contain symbolic link to another +issue, referencing it. Create another kind of links between them as you +wish. + +Want to search among the issues? Just use git grep, or ordinary grep! +Want to search through attached PDFs or other kind of documents? You are +free to index issues/ directory with something like recoll. Want to see +the whole history of changes related to specific issues? Just run +git log issues/issue-name! You can add a tag by simply touching +issues/issues-name/tags/tag, but "tag-add" included in schwabrak creates +symbolic links to tags/tag and checks if the tags is known beforehand, +to keep tags set consistent. Want to remove tag? (git) rm +issues/issues-name/tags/tag! + +"comment" issue's file is intended to keep the last comment related to +the issue. By committing it you automatically accompany it with your +(commit's author) name and the time it was added. + $ cat > issues/issues-name/comment < issues/$name/created +touch issues/$name/about issues/$name/result issues/$name/comment +$EDITOR issues/$name/about +git add issues/$name diff --git a/cd b/cd new file mode 100755 index 0000000..b419c6f --- /dev/null +++ b/cd @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +$root/list | fzf -d "\t" --tac --preview="$root/show {2}" | cut -f2 | read d +print issues/$d diff --git a/comment-list b/comment-list new file mode 100755 index 0000000..284cd72 --- /dev/null +++ b/comment-list @@ -0,0 +1,16 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +issue=${1#issues/} +hashes=(`git log --format=format:%H issues/$issue/comment`) +for i ({${#hashes}..1}) { + header=`git show --no-patch --format=format:"When: %ai%nAuthor: %an <%ae>" ${hashes[$i]}` + comment=`git cat-file blob ${hashes[$i]}:issues/$issue/comment | sed "s/^/+ /"` + [[ -n $comment ]] || continue + print $header + print Comment: + print $comment + [[ $i -eq 1 ]] || print +} diff --git a/dep-add b/dep-add new file mode 100755 index 0000000..8079ef9 --- /dev/null +++ b/dep-add @@ -0,0 +1,20 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +usage() { + die Usage: $0 ISSUE-DST ISSUE-SRC +} + +[[ -n $1 ]] || usage +[[ -n $2 ]] || usage +dst=issues/${1#issues/} +src=issues/${2#issues/} +[[ -d $dst ]] || die Unexistent dst +[[ -d $src ]] || die Unexistent src +dst=$dst:a +src=$src:a +mkdir -p $dst/deps +cd $dst/deps +ln -f -s `relpath $src .` diff --git a/lib.zsh.rc b/lib.zsh.rc new file mode 100644 index 0000000..159dbb2 --- /dev/null +++ b/lib.zsh.rc @@ -0,0 +1,30 @@ +set -e +setopt EXTENDED_GLOB GLOB_STAR_SHORT + +PERL=${PERL:-perl} +EDITOR=${EDITOR:-vi} + +die() { + print $@ >&2 + exit 1 +} + +[[ -d issues ]] || die You must run that command in directory with issues/ + +relpath() { + $PERL -mFile::Spec -le "print File::Spec->abs2rel(@ARGV)" $1:a $2:a +} + +endash() { + $PERL -npe 's/(-+)/$1-/g ; s/ /-/g' +} + +dedash() { + $PERL -npe 's/([^-])-([^-])/$1 $2/g ; s/-(-+)/$1/g' +} + +delim() { + local i + for i ({1..40}) print -n -- - + print +} diff --git a/list b/list new file mode 100755 index 0000000..3495d6b --- /dev/null +++ b/list @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +for issue (issues/**/created) { + issue=$issue:h + issue=${issue#issues/} + print -n `cat issues/$issue/created`\\t$issue\\t + $root/tag-list $issue +} | sort -r diff --git a/recfile-export b/recfile-export new file mode 100755 index 0000000..95efcf5 --- /dev/null +++ b/recfile-export @@ -0,0 +1,18 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +issue=${1#issues/} +print Created: `cat issues/$issue/created` +print Issue: $issue +print Project: $issue:h +print Name: `print $issue:t | dedash` +for tag (`$root/tag-list $issue`) print Tag: $tag +for dep (issues/$issue/deps/*(N)) print Depends: $(relpath $(realpath $dep) issues) +print About: +sed "s/^/+ /" < issues/$issue/about +print Result: +sed "s/^/+ /" < issues/$issue/result +print Comments: +$root/comment-list $issue | sed "s/^/+ /" diff --git a/recfile-export-all b/recfile-export-all new file mode 100755 index 0000000..fd068b1 --- /dev/null +++ b/recfile-export-all @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +$root/list | while read line ; do + line=(${=line}) + issue=${line[3]} + $root/recfile-export $issue + print +done diff --git a/show b/show new file mode 100755 index 0000000..0792bc8 --- /dev/null +++ b/show @@ -0,0 +1,22 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +issue=${1#issues/} +print `cat issues/$issue/created` \| $issue:h \| `print $issue:t | dedash` +$root/tag-list $issue +deps=(issues/$issue/deps/*(N)) +[[ ${#deps} -eq 0 ]] || { + delim + print Depends on: + for dep ($deps) print "\t"$dep:t +} +delim +cat issues/$issue/about +[[ -s issues/$issue/result ]] && { + delim + cat issues/$issue/result +} +delim +$root/comment-list $issue diff --git a/tag-add b/tag-add new file mode 100755 index 0000000..a7e43cc --- /dev/null +++ b/tag-add @@ -0,0 +1,17 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +usage() { + die Usage: $0 ISSUE TAG +} + +[[ -n $1 ]] || usage +[[ -n $2 ]] || usage +issue=${1#issues/} +tag=tags/$2:t +tag=$tag:a +[[ -r $tag ]] || die Unknown tag +mkdir -p issues/$issue/tags +ln -f -s `relpath $tag issues/$issue/tags` issues/$issue/tags/$tag:t diff --git a/tag-list b/tag-list new file mode 100755 index 0000000..011225d --- /dev/null +++ b/tag-list @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +root=$0:h:a +. $root/lib.zsh.rc + +issue=${1#issues/} +tags=() +for t (issues/$issue/tags/*(NOn)) tags=($t:t $tags) +[[ ${#tags} -eq 0 ]] || print $tags -- 2.44.0