Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,21 @@
*.yaml text eol=lf
*.plist text eol=lf

# Vendored C headers (database driver bridges, tree-sitter grammars)
# Vendored C headers (database driver bridges, SQLite, tree-sitter grammars)
Plugins/*/C*/include/** linguist-vendored
Packages/TableProCore/Sources/CSQLite/include/sqlite3*.h linguist-vendored
TablePro/Core/SSH/CLibSSH2/** linguist-vendored
Packages/TableProGrammars/Sources/TreeSitterGrammars/** linguist-vendored

# Third-party license texts, copied verbatim
Native/DamengBridge/LICENSES/** linguist-vendored
Packages/TableProEditor/LICENSE-*.md linguist-vendored
TablePro/Resources/ThirdPartyLicenses/texts/** linguist-vendored
# Joins the SQLite copyright page and the source blessing with a sentence of its own.
TablePro/Resources/ThirdPartyLicenses/texts/sqlite.txt -linguist-vendored

# Generated Linux files (Cargo, xgettext, cargo-about, license_metadata.py)
linux/Cargo.lock linguist-generated
linux/po/tablepro.pot linguist-generated
linux/data/licenses/** linguist-generated
linux/build-aux/debian/copyright linguist-generated
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,6 @@ Native/HanaBridge/bin
.docs/
/plans/reports

# Linux meson build directory
linux/_build/

# Translation exports from `xcodebuild -exportLocalizations`; the catalog is the source of truth.
# Anchored: unanchored, it also matched TableProTests/Localization/, so the string-catalog
# guard suite existed on disk and was invisible to git and to CI.
Expand Down
25 changes: 25 additions & 0 deletions linux/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# No root = true: the repository's .editorconfig sets the defaults, and this file overrides them.

[{meson.build,meson.options,meson_options.txt}]
indent_size = 2

[*.{ui,xml,xml.in,xml.in.in,svg}]
indent_size = 2

[*.css]
indent_size = 2

[*.hbs]
indent_size = 2

[build-aux/flatpak/*.json]
indent_size = 4

[*.rs]
max_line_length = 120

[build-aux/debian/rules]
indent_style = tab

[build-aux/debian/{control,copyright}]
indent_size = 1
7 changes: 3 additions & 4 deletions linux/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
target/
_build/
_packages/
*.swp
.DS_Store

docs/audit-*.md
_flatpak/
.flatpak-builder/
1 change: 1 addition & 0 deletions linux/build-aux/packaging/add-rust-toolchain-ppa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fi
# The key is committed, so no run trusts a key it fetched. apt checks
# InRelease against it, which makes plain HTTP as safe as the archive's
# own sources, and the base image has no CA bundle for HTTPS.
# shellcheck source=/dev/null
. /etc/os-release
mkdir -p /etc/apt/keyrings
cp "$here/rust-toolchain-staging.asc" /etc/apt/keyrings/rust-toolchain-staging.asc
Expand Down
1 change: 1 addition & 0 deletions linux/build-aux/packaging/build-deb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ build() {
# login that is not root, 1024 open files among them. Launchpad passes
# the builder's CPU count the same way as here.
status=0
# shellcheck disable=SC2016 # the inner sh expands $1
runuser -u builder -- env HOME=/home/builder DEB_BUILD_OPTIONS="parallel=$(nproc)" \
sh -euc 'cd /build/work && dpkg-source -x "$1" src && cd src && dpkg-buildpackage -b -us -uc' sh "$1" ||
status=$?
Expand Down
32 changes: 18 additions & 14 deletions scripts/check-banned-words.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
# A term matches case-insensitively at the start of a word, so a capitalized or suffixed form is a
# hit and a word that only ends in a term is not. A term that starts with punctuation, the em dash,
# matches anywhere. Each hit prints as file:line: term. Exits 1 on a hit, 0 when clean and 2 on a
# usage error. A diff skips linux/data/licenses/ and linux/build-aux/debian/copyright, which reproduce
# third-party license text verbatim.
# usage error. A diff skips a file .gitattributes marks linguist-generated or linguist-vendored,
# since a generator or an upstream project wrote its text.

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
LIST="$SCRIPT_DIR/banned-words.txt"
LIST_PATH_IN_REPO="scripts/banned-words.txt"
VERBATIM_IN_REPO="linux/data/licenses/ linux/build-aux/debian/copyright"

usage() {
sed -n "5,8s/^# //p" "${BASH_SOURCE[0]}"
Expand Down Expand Up @@ -72,23 +71,19 @@ function check(text, location, lowered, i) {
'

DIFF_READER='
BEGIN { verbatim_count = split(verbatim, verbatim_paths, " ") }

function is_verbatim(path, i) {
for (i = 1; i <= verbatim_count; i++) {
if (index(path, verbatim_paths[i]) == 1) {
return 1
}
BEGIN {
while ((getline path < exempt_list) > 0) {
exempt[path] = 1
}
return 0
close(exempt_list)
}

/^diff --git / { in_header = 1; file = ""; next }
in_header && /^\+\+\+ / {
file = substr($0, 5)
sub(/^b\//, "", file)
sub(/\t$/, "", file)
if (file == "/dev/null" || file == skip || is_verbatim(file)) {
if (file == "/dev/null" || file == skip || file in exempt) {
file = ""
}
next
Expand Down Expand Up @@ -117,13 +112,22 @@ END { exit(hits > 0 ? 1 : 0) }
'

check_diff() {
git rev-parse --show-toplevel > /dev/null 2>&1 || {
local top
top="$(git rev-parse --show-toplevel 2> /dev/null)" || {
echo "check-banned-words.sh: not inside a git repository" >&2
exit 2
}
EXEMPT_LIST="$(mktemp)" || exit 2
trap 'rm -f "$EXEMPT_LIST"' EXIT
# A renamed file still lists under its new path, and the diff below already prints any rename
# limit warning. The index holds the staged .gitattributes, and HEAD's when nothing is staged.
git -c core.quotePath=false diff --name-only --no-renames --diff-filter=ACMR "$@" |
git -C "$top" -c core.quotePath=false check-attr --cached --stdin \
linguist-generated linguist-vendored |
sed -n -E 's/^(.*): linguist-(generated|vendored): (set|true)$/\1/p' > "$EXEMPT_LIST" || exit 2
git -c core.quotePath=false diff -U0 --no-color --no-ext-diff --diff-filter=ACMR \
--src-prefix=a/ --dst-prefix=b/ "$@" |
LC_ALL=C awk -v skip="$LIST_PATH_IN_REPO" -v verbatim="$VERBATIM_IN_REPO" "$MATCHER$DIFF_READER"
LC_ALL=C awk -v skip="$LIST_PATH_IN_REPO" -v exempt_list="$EXEMPT_LIST" "$MATCHER$DIFF_READER"
}

check_files() {
Expand Down
92 changes: 92 additions & 0 deletions scripts/ci/test_check_banned_words.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Tests check-banned-words.sh against throwaway git repositories."""

import os
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path

SCRIPT = Path(__file__).resolve().parent.parent / "check-banned-words.sh"
TERM = "frobnicate"
SEED_TEXT = "".join(f"seed line {number}\n" for number in range(20))


class CheckBannedWordsTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.repo = Path(self.temp.name)
self.env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")}
self.git("-c", "init.defaultBranch=main", "init", "-q")
(self.repo / "scripts").mkdir()
shutil.copy(SCRIPT, self.repo / "scripts/check-banned-words.sh")
self.write("scripts/banned-words.txt", f"{TERM}\n")
self.write(".gitattributes", "gen/** linguist-generated\nvendor/** linguist-vendored\n")
self.write("README.md", SEED_TEXT)
self.write("gen/data.txt", SEED_TEXT)
self.git("add", "-A")
self.git("commit", "-q", "-m", "seed")
self.seed = self.git("rev-parse", "HEAD")

def tearDown(self):
self.temp.cleanup()

def git(self, *args):
result = subprocess.run(
["git", "-c", "user.name=test", "-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args],
cwd=self.repo, env=self.env, check=True, capture_output=True, text=True,
)
return result.stdout.strip()

def write(self, path, text):
file = self.repo / path
file.parent.mkdir(parents=True, exist_ok=True)
with open(file, "a", encoding="utf-8") as handle:
handle.write(text)

def stage_term(self, *paths):
for path in paths:
self.write(path, f"a {TERM} line\n")
self.git("add", "-A")

def check(self, *args, cwd=None):
result = subprocess.run(
["bash", str(self.repo / "scripts/check-banned-words.sh"), *args],
cwd=cwd or self.repo, env=self.env, capture_output=True, text=True,
)
return result.returncode, sorted(line.split(":")[0] for line in result.stdout.splitlines())

def commit_term(self, *paths):
self.stage_term(*paths)
self.git("commit", "-q", "-m", "change")
return self.check("--base", self.seed)

def test_a_generated_or_vendored_file_is_skipped(self):
self.assertEqual(self.commit_term("gen/notice.md", "vendor/lib.h", "src/app.txt"), (1, ["src/app.txt"]))

def test_a_diff_of_only_generated_and_vendored_files_passes(self):
self.assertEqual(self.commit_term("gen/notice.md", "vendor/lib.h"), (0, []))

def test_an_unset_or_false_attribute_is_checked(self):
self.write(".gitattributes", "vendor/own.txt -linguist-vendored\ngen/kept.txt linguist-generated=false\n")
self.assertEqual(self.commit_term("vendor/own.txt", "gen/kept.txt"), (1, ["gen/kept.txt", "vendor/own.txt"]))

def test_staged_reads_the_staged_gitattributes(self):
self.write(".gitattributes", "notes/** linguist-generated\n")
self.stage_term("notes/new.txt", "src/app.txt")
self.assertEqual(self.check("--staged"), (1, ["src/app.txt"]))

def test_a_renamed_file_follows_its_new_path(self):
(self.repo / "src").mkdir()
self.git("mv", "README.md", "gen/README.md")
self.git("mv", "gen/data.txt", "src/data.txt")
self.assertEqual(self.commit_term("gen/README.md", "src/data.txt"), (1, ["src/data.txt"]))

def test_a_subdirectory_still_resolves_attributes_from_the_top_level(self):
self.commit_term("gen/notice.md", "src/app.txt")
self.assertEqual(self.check("--base", self.seed, cwd=self.repo / "src"), (1, ["src/app.txt"]))


if __name__ == "__main__":
unittest.main()
Loading