mirror of
https://github.com/sharkdp/bat
synced 2026-08-03 18:51:44 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b739f7d1e8 |
@@ -442,7 +442,7 @@ jobs:
|
||||
echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Publish archives and packages
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
if: steps.is-release.outputs.IS_RELEASE
|
||||
with:
|
||||
files: |
|
||||
|
||||
@@ -281,6 +281,3 @@
|
||||
[submodule "assets/syntaxes/02_Extra/Kotlin"]
|
||||
path = assets/syntaxes/02_Extra/Kotlin
|
||||
url = https://github.com/guille/sublime-kotlin
|
||||
[submodule "assets/syntaxes/02_Extra/Caddy"]
|
||||
path = assets/syntaxes/02_Extra/Caddy
|
||||
url = https://github.com/caddyserver/sublimetext.git
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
## Features
|
||||
|
||||
- Map justfile, Justfile, .justfile, and *.justfile to Makefile syntax highlighting, see #3623 (@zachvalenta)
|
||||
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
|
||||
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
|
||||
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
|
||||
@@ -22,10 +21,6 @@
|
||||
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
|
||||
|
||||
## Bugfixes
|
||||
- Fix `--ignored-suffix` not falling back to first-line/shebang detection when the ignored suffix is also a registered extension (e.g. `--ignored-suffix .txt` on a shebang script), see #2745 and #3816 (@adnrivera)
|
||||
- Fix `capacity overflow` panic when printing a snip separator at `--terminal-width=1` with multiple line ranges. Closes #3803, see #3804 (@leeewee)
|
||||
- Pass `--no-paging` to `bat` invocations inside the bash / zsh / fish / PowerShell shell completion scripts so that shell-level pager wiring (e.g. `LESSOPEN='|-bat -f -pp %s'`) cannot inject ANSI escape sequences into the completion candidates. Closes #3760 (@mvanhorn)
|
||||
- Quote filenames before substituting them into `$LESSOPEN` / `$LESSCLOSE` templates, preventing shell injection when a filename contains shell metacharacters, see #3726 (@curious-rabbit)
|
||||
- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value, see #3700 (regression introduced in bc42149a). (@optimistiCli)
|
||||
- Fix inverted `$LESSCLOSE` warning so bat warns on nonzero exit, not on success. See #3654 (@cuiweixie)
|
||||
- Sanitize control characters in filenames before displaying them in the file header, error messages, and the terminal title, preventing ANSI escape injection via crafted filenames. Closes #3054, see #3691 (@curious-rabbit)
|
||||
@@ -46,8 +41,6 @@
|
||||
- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
|
||||
- Fixed syntax tests path, see #3610 (@foxfromworld)
|
||||
- Fix zsh tab completion word-splitting language names containing spaces (e.g. `HTML (Jinja2)`, `Apache Conf`), see #3693 (@YoshKoz)
|
||||
- Fix zsh tab completion offering invalid `-l` arguments (file globs, paths, hidden filenames) sourced from the second column of `--list-languages`. Closes #3735, see #3737 (@truffle-dev)
|
||||
- Fix `usize` underflow in `--list-languages` when `--terminal-width` is smaller than the longest language name, see #3812 (@greymoth-jp)
|
||||
|
||||
## Other
|
||||
- Use git version of cross. See #3533 (@OctopusET)
|
||||
@@ -55,7 +48,6 @@
|
||||
- Allow home and end keys to be used with builtin pager, see #3651 (@keith-hall)
|
||||
- Builtin syntax mapping: cleanup matcher glob parsing logic #3652 (@cyqsimon)
|
||||
- Statically link the CRT for MSVC builds via Cargo config to avoid runtime DLL dependencies. Closes #3634, see #3692 (@barry3406)
|
||||
- Replace `libgit2` with a pure Rust implementation of git called `gitoxide`, see PR #3703 (@blinxen)
|
||||
|
||||
## Syntaxes
|
||||
|
||||
@@ -67,11 +59,6 @@
|
||||
- Map several Google Cloud CLI config files to their appropriate syntax #3635 (@victor-gp)
|
||||
- Map all ignore dotfiles to Git Ignore syntax #3636 (@victor-gp)
|
||||
- Improved Kotlin syntax, see #3699 (@guille)
|
||||
- Include subdirectories in SSH Config syntax mapping, see #3758 (@injust)
|
||||
- Add Ghostty syntax mapping, see #3759 (@injust)
|
||||
- Add syntax highlighting for `Caddyfile` #3789 (@CosmicHorrorDev)
|
||||
- Include `.code-workspace` as a JSON extension #3809 (@dhruvkb)
|
||||
- Add syntax mapping for DNF repo configuration files, see #3814 (@injust)
|
||||
|
||||
## Themes
|
||||
|
||||
|
||||
Generated
+637
-1551
File diff suppressed because it is too large
Load Diff
+13
-13
@@ -31,7 +31,8 @@ minimal-application = [
|
||||
"regex-onig",
|
||||
"wild",
|
||||
]
|
||||
git = ["gix"] # Support indicating git modifications
|
||||
git = ["git2"] # Support indicating git modifications
|
||||
vendored-libgit2 = ["git2/vendored-libgit2"]
|
||||
paging = [ "shell-words", "grep-cli", "minus"] # Support applying a pager on the output
|
||||
lessopen = ["execute"] # Support $LESSOPEN preprocessor
|
||||
build-assets = ["syntect/yaml-load", "syntect/plist-load", "regex", "walkdir"]
|
||||
@@ -51,7 +52,7 @@ thiserror = "2.0"
|
||||
wild = { version = "2.2", optional = true }
|
||||
content_inspector = "0.2.4"
|
||||
shell-words = { version = "1.1.1", optional = true }
|
||||
minus = { version = "5.7", optional = true, features = [
|
||||
minus = { version = "5.6", optional = true, features = [
|
||||
"dynamic_output",
|
||||
"search",
|
||||
] }
|
||||
@@ -63,10 +64,10 @@ serde_yaml = "0.9.28"
|
||||
semver = "1.0"
|
||||
path_abs = { version = "0.5", default-features = false }
|
||||
clircle = { version = "0.6.1", default-features = false }
|
||||
bugreport = { version = "0.6.0", optional = true }
|
||||
bugreport = { version = "0.5.0", optional = true }
|
||||
etcetera = { version = "0.11.0", optional = true }
|
||||
grep-cli = { version = "0.1.12", optional = true }
|
||||
regex = { version = "1.12.3", optional = true }
|
||||
regex = { version = "1.12.2", optional = true }
|
||||
walkdir = { version = "2.5", optional = true }
|
||||
bytesize = { version = "2.3.1" }
|
||||
encoding_rs = "0.8.35"
|
||||
@@ -75,11 +76,10 @@ terminal-colorsaurus = "1.0"
|
||||
unicode-segmentation = "1.13.2"
|
||||
itertools = "0.14.0"
|
||||
|
||||
[dependencies.gix]
|
||||
version = "0.85"
|
||||
[dependencies.git2]
|
||||
version = "0.20"
|
||||
optional = true
|
||||
default-features = false
|
||||
features = ["sha1", "blob-diff"]
|
||||
|
||||
[dependencies.syntect]
|
||||
version = "5.3.0"
|
||||
@@ -87,18 +87,18 @@ default-features = false
|
||||
features = ["parsing"]
|
||||
|
||||
[dependencies.clap]
|
||||
version = "4.6.1"
|
||||
version = "4.5.60"
|
||||
optional = true
|
||||
features = ["wrap_help", "cargo"]
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
plist = "1.9.0"
|
||||
plist = "1.7.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.16"
|
||||
expect-test = "1.5.0"
|
||||
serial_test = { version = "2.0.0", default-features = false }
|
||||
predicates = "3.1.4"
|
||||
predicates = "3.1.3"
|
||||
wait-timeout = "0.2.1"
|
||||
tempfile = "3.27.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -108,13 +108,13 @@ nix = { version = "0.31", default-features = false, features = ["term"] }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.97"
|
||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||
indexmap = { version = "2.13.0", features = ["serde"] }
|
||||
itertools = "0.14.0"
|
||||
once_cell = "1.20"
|
||||
prettyplease = "0.2.37"
|
||||
proc-macro2 = "1.0.106"
|
||||
quote = "1.0.45"
|
||||
regex = "1.12.3"
|
||||
regex = "1.12.2"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_with = { version = "3.17.0", default-features = false, features = ["macros"] }
|
||||
@@ -123,7 +123,7 @@ toml = { version = "1.1.1", features = ["preserve_order"] }
|
||||
walkdir = "2.5"
|
||||
|
||||
[build-dependencies.clap]
|
||||
version = "4.6.1"
|
||||
version = "4.5.60"
|
||||
optional = true
|
||||
features = ["wrap_help", "cargo"]
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ pacman -S bat
|
||||
|
||||
### On Fedora
|
||||
|
||||
You can install [the `bat` package](https://packages.fedoraproject.org/pkgs/rust-bat/bat/) from the official sources:
|
||||
You can install [the `bat` package](https://koji.fedoraproject.org/koji/packageinfo?packageID=27506) from the official [Fedora Modular](https://docs.fedoraproject.org/en-US/modularity/using-modules/) repository.
|
||||
|
||||
```bash
|
||||
dnf install bat
|
||||
|
||||
Vendored
+2
-2
@@ -14,12 +14,12 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
$ArrayPrint = @('unicode', 'caret')
|
||||
|
||||
function Get-MyThemes(){
|
||||
$themes = {{PROJECT_EXECUTABLE}} --no-paging --list-themes | ForEach-Object {$_ -replace "^(.*)$", '''$1'''} | select-object
|
||||
$themes = {{PROJECT_EXECUTABLE}} --list-themes | ForEach-Object {$_ -replace "^(.*)$", '''$1'''} | select-object
|
||||
return $themes
|
||||
}
|
||||
|
||||
function Get-MyLanguages(){
|
||||
$themes = {{PROJECT_EXECUTABLE}} --no-paging --list-languages | ForEach-Object{[pscustomobject]@{MyParameter=$_.Substring(0,$_.IndexOf(":")).Trim();MyDescription=$_.Substring($_.IndexOf(":")+1)}} | select-object
|
||||
$themes = {{PROJECT_EXECUTABLE}} --list-languages | ForEach-Object{[pscustomobject]@{MyParameter=$_.Substring(0,$_.IndexOf(":")).Trim();MyDescription=$_.Substring($_.IndexOf(":")+1)}} | select-object
|
||||
return $themes
|
||||
}
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -80,7 +80,7 @@ _bat() {
|
||||
-l | --language)
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(compgen -W "$(
|
||||
"$1" --no-paging --list-languages | while IFS=: read -r lang _; do
|
||||
"$1" --list-languages | while IFS=: read -r lang _; do
|
||||
printf "%s\n" "$lang"
|
||||
done
|
||||
)" -- "$cur"))
|
||||
@@ -150,14 +150,14 @@ _bat() {
|
||||
;;
|
||||
--theme)
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(compgen -W "auto${IFS}auto:always${IFS}auto:system${IFS}dark${IFS}light${IFS}$("$1" --no-paging --list-themes)" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "auto${IFS}auto:always${IFS}auto:system${IFS}dark${IFS}light${IFS}$("$1" --list-themes)" -- "$cur"))
|
||||
__bat_escape_completions
|
||||
return 0
|
||||
;;
|
||||
--theme-dark | \
|
||||
--theme-light)
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(compgen -W "$("$1" --no-paging --list-themes)" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "$("$1" --list-themes)" -- "$cur"))
|
||||
__bat_escape_completions
|
||||
return 0
|
||||
;;
|
||||
|
||||
Vendored
+5
-5
@@ -15,11 +15,11 @@ function __bat_complete_files -a token
|
||||
end
|
||||
|
||||
function __bat_complete_one_language -a comp
|
||||
command $bat --no-paging --list-languages | string split -f1 : | string match -e "$comp"
|
||||
command $bat --list-languages | string split -f1 : | string match -e "$comp"
|
||||
end
|
||||
|
||||
function __bat_complete_list_languages
|
||||
for spec in (command $bat --no-paging --list-languages)
|
||||
for spec in (command $bat --list-languages)
|
||||
set -l name (string split -f1 : $spec)
|
||||
for ext in (string split -f2 : $spec | string split ,)
|
||||
test -n "$ext"; or continue
|
||||
@@ -234,11 +234,11 @@ complete -c $bat -l tabs -x -a "$tabs_opts" -d "Set tab width" -n __bat_no_excl_
|
||||
|
||||
complete -c $bat -l terminal-width -x -d "Set terminal <width>, +<offset>, or -<offset>" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l theme -x -a "$special_themes(command $bat --no-paging --list-themes | command cat)" -d "Set the syntax highlighting theme" -n __bat_no_excl_args
|
||||
complete -c $bat -l theme -x -a "$special_themes(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l theme-dark -x -a "(command $bat --no-paging --list-themes | command cat)" -d "Set the syntax highlighting theme for dark backgrounds" -n __bat_no_excl_args
|
||||
complete -c $bat -l theme-dark -x -a "(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme for dark backgrounds" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l theme-light -x -a "(command $bat --no-paging --list-themes | command cat)" -d "Set the syntax highlighting theme for light backgrounds" -n __bat_no_excl_args
|
||||
complete -c $bat -l theme-light -x -a "(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme for light backgrounds" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -s u -l unbuffered -d "Enable unbuffered input reading for streaming use cases" -n __bat_no_excl_args
|
||||
|
||||
|
||||
Vendored
+3
-8
@@ -90,25 +90,20 @@ _{{PROJECT_EXECUTABLE}}_main() {
|
||||
languages)
|
||||
local IFS=$'\n'
|
||||
local -a languages
|
||||
# `--list-languages` emits one `name:matchers` line per language,
|
||||
# which `_describe` parses as `value:description`. Only the
|
||||
# language name is offered as the completion value; the matchers
|
||||
# show up as the menu description. See
|
||||
# https://github.com/sharkdp/bat/issues/3735.
|
||||
languages=( ${(f)"$({{PROJECT_EXECUTABLE}} --no-paging --color=never --decorations=never --list-languages)"} )
|
||||
languages=( ${(f)"$({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }')"} )
|
||||
|
||||
_describe 'language' languages && ret=0
|
||||
;;
|
||||
|
||||
themes)
|
||||
local -a themes expl
|
||||
themes=(${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --no-paging --color=never --decorations=never --list-themes)"} )
|
||||
themes=(${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
|
||||
|
||||
_wanted themes expl 'theme' compadd -a themes && ret=0
|
||||
;;
|
||||
theme_preferences)
|
||||
local -a themes expl
|
||||
themes=(auto dark light auto:always auto:system ${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --no-paging --color=never --decorations=never --list-themes)"} )
|
||||
themes=(auto dark light auto:always auto:system ${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
|
||||
|
||||
_wanted themes expl 'theme' compadd -a themes && ret=0
|
||||
;;
|
||||
|
||||
Vendored
-1
Submodule assets/syntaxes/02_Extra/Caddy deleted from cd7d132da3
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/LESS updated: d076a4dd41...836b47ec61
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/PureScript updated: 1773f4fddb...5acebc1850
+1
-1
Submodule assets/syntaxes/02_Extra/sublime-odin updated: 633e545c51...5d6a0ed41e
+1
-1
Submodule assets/syntaxes/02_Extra/typst-syntax-highlight updated: 5f71d12fa1...363f0e767c
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/vscode-wgsl updated: a285c38f74...acf26718d7
+1
-1
@@ -97,7 +97,7 @@ The following files have been manually modified after converting from a `.tmLang
|
||||
as it is not kept in a standalone repository. The file is generated from
|
||||
https://github.com/open-policy-agent/opa/blob/master/misc/syntax/textmate/Rego.tmLanguage
|
||||
* `SML.sublime_syntax` has been added manually from
|
||||
https://github.com/seanjames777/SML-Language-Definition as it is not
|
||||
https://github.com/seanjames777/SML-Language-Definitiona as it is not
|
||||
kept in a standalone repository. The file generated is from
|
||||
https://github.com/seanjames777/SML-Language-Definition/blob/master/sml.tmLanguage
|
||||
* `Cabal.sublime_syntax` has been added manually from
|
||||
|
||||
+8
-50
@@ -347,15 +347,15 @@ impl HighlightingAssets {
|
||||
file_name: &OsStr,
|
||||
ignored_suffixes: &IgnoredSuffixes,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let stripped =
|
||||
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
|
||||
self.get_syntax_for_file_extension(stripped_file_name, ignored_suffixes)
|
||||
.map(Some)
|
||||
})?;
|
||||
match stripped {
|
||||
Some(syntax) => Ok(syntax),
|
||||
None => self.find_syntax_by_extension(Path::new(file_name).extension()),
|
||||
let mut syntax = self.find_syntax_by_extension(Path::new(file_name).extension())?;
|
||||
if syntax.is_none() {
|
||||
syntax =
|
||||
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
|
||||
// Note: recursion
|
||||
self.get_syntax_for_file_extension(stripped_file_name, ignored_suffixes)
|
||||
})?;
|
||||
}
|
||||
Ok(syntax)
|
||||
}
|
||||
|
||||
fn get_first_line_syntax(
|
||||
@@ -686,48 +686,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_detection_ignored_suffix_falls_back_to_first_line() {
|
||||
let mut test = SyntaxDetectionTest::new();
|
||||
|
||||
// By default a `.txt` file uses Plain Text, even with a shebang: the
|
||||
// `.txt` extension wins and first-line detection is not reached.
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("test.txt", "#!/usr/bin/env bash"),
|
||||
"Plain Text"
|
||||
);
|
||||
|
||||
// Once `.txt` is an ignored suffix it is stripped before detection. The
|
||||
// stripped name (`test`) has no extension, so detection falls back to the
|
||||
// first line -- even though `.txt` is itself a registered extension that
|
||||
// would otherwise match as Plain Text. See #2745.
|
||||
test.syntax_mapping.insert_ignored_suffix(".txt");
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("test.txt", "#!/usr/bin/env bash"),
|
||||
"Bourne Again Shell (bash)"
|
||||
);
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("test.txt", "<?php"),
|
||||
"PHP"
|
||||
);
|
||||
|
||||
// A `.txt` file without a recognizable first line keeps no syntax (the
|
||||
// caller renders it as plain text); we must not resurrect the shadowed
|
||||
// `.txt` -> Plain Text match here.
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("notes.txt", "just some prose"),
|
||||
"!no syntax!"
|
||||
);
|
||||
|
||||
// Stripping that exposes a real extension still works: `.dev` is ignored,
|
||||
// and the remaining `.json` extension is detected as usual.
|
||||
test.syntax_mapping.insert_ignored_suffix(".dev");
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("config.json.dev", ""),
|
||||
"JSON"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_detection_is_case_insensitive() {
|
||||
let mut test = SyntaxDetectionTest::new();
|
||||
|
||||
+1
-8
@@ -152,14 +152,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
|
||||
let comma_separator = ", ";
|
||||
let separator = " ";
|
||||
// Line-wrapping for the possible file extension overflow.
|
||||
// Clamp instead of subtracting: a tiny `--terminal-width` (smaller than the
|
||||
// longest language name) would otherwise underflow `usize` and wrap to a huge
|
||||
// value, silently disabling wrapping (and panicking in debug/overflow-checked
|
||||
// builds). Mirrors the `saturating_sub` fix applied to `print_snip` in #3804.
|
||||
let desired_width = config
|
||||
.term_width
|
||||
.saturating_sub(longest)
|
||||
.saturating_sub(separator.len());
|
||||
let desired_width = config.term_width - longest - separator.len();
|
||||
|
||||
let style = if config.colored_output {
|
||||
Green.normal()
|
||||
|
||||
+64
-178
@@ -1,13 +1,12 @@
|
||||
#![cfg(feature = "git")]
|
||||
|
||||
use gix::diff::blob::pipeline::{Mode, WorktreeRoots};
|
||||
use gix::diff::blob::{Algorithm, HunkIter, ResourceKind};
|
||||
use gix::object::tree::EntryKind;
|
||||
use path_abs::PathInfo;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
use git2::{DiffOptions, IntoCString, Repository};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum LineChange {
|
||||
Added,
|
||||
RemovedAbove,
|
||||
@@ -17,181 +16,68 @@ pub enum LineChange {
|
||||
|
||||
pub type LineChanges = HashMap<u32, LineChange>;
|
||||
|
||||
fn collect_changes_from_hunks(hunks: HunkIter) -> Option<LineChanges> {
|
||||
let mut changes: LineChanges = HashMap::new();
|
||||
for hunk in hunks {
|
||||
if hunk.before.is_empty() && !hunk.after.is_empty() {
|
||||
for line in hunk.after {
|
||||
changes.insert(line + 1, LineChange::Added);
|
||||
}
|
||||
} else if hunk.after.is_empty() && !hunk.before.is_empty() {
|
||||
if hunk.after.start == 0 {
|
||||
changes.insert(1, LineChange::RemovedAbove);
|
||||
} else {
|
||||
changes.insert(hunk.after.start, LineChange::RemovedBelow);
|
||||
}
|
||||
} else {
|
||||
for line in hunk.after {
|
||||
changes.insert(line + 1, LineChange::Modified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(changes)
|
||||
}
|
||||
|
||||
pub fn get_git_diff(filename: &Path) -> Option<LineChanges> {
|
||||
let filepath_absolute = filename.canonicalize().ok()?;
|
||||
let repository = gix::discover(filepath_absolute.parent().ok()?).ok()?;
|
||||
let repo_path_absolute = repository.workdir()?.canonicalize().ok()?;
|
||||
let filepath_relative_to_repo = gix::path::to_unix_separators_on_windows(gix::path::into_bstr(
|
||||
filepath_absolute.strip_prefix(&repo_path_absolute).ok()?,
|
||||
));
|
||||
let index = repository.index_or_load_from_head_or_empty().ok()?;
|
||||
let index_entry = index.entry_by_path(filepath_relative_to_repo.as_ref())?;
|
||||
let mut cache = repository
|
||||
.diff_resource_cache(
|
||||
Mode::ToGit,
|
||||
WorktreeRoots {
|
||||
old_root: None,
|
||||
new_root: repository.workdir().map(Path::to_path_buf),
|
||||
},
|
||||
)
|
||||
let repo = Repository::discover(filename).ok()?;
|
||||
|
||||
let repo_path_absolute = fs::canonicalize(repo.workdir()?).ok()?;
|
||||
|
||||
let filepath_absolute = fs::canonicalize(filename).ok()?;
|
||||
let filepath_relative_to_repo = filepath_absolute.strip_prefix(&repo_path_absolute).ok()?;
|
||||
|
||||
let mut diff_options = DiffOptions::new();
|
||||
let pathspec = filepath_relative_to_repo.into_c_string().ok()?;
|
||||
diff_options.pathspec(pathspec);
|
||||
diff_options.context_lines(0);
|
||||
|
||||
let diff = repo
|
||||
.diff_index_to_workdir(None, Some(&mut diff_options))
|
||||
.ok()?;
|
||||
cache
|
||||
.set_resource(
|
||||
index_entry.id,
|
||||
index_entry.mode.to_tree_entry_mode()?.kind(),
|
||||
filepath_relative_to_repo.as_ref(),
|
||||
ResourceKind::OldOrSource,
|
||||
&repository,
|
||||
)
|
||||
.ok()?;
|
||||
cache
|
||||
.set_resource(
|
||||
repository.object_hash().null(),
|
||||
EntryKind::Blob,
|
||||
filepath_relative_to_repo.as_ref(),
|
||||
ResourceKind::NewOrDestination,
|
||||
&repository,
|
||||
)
|
||||
.ok()?;
|
||||
let diff = gix::diff::blob::diff_with_slider_heuristics(
|
||||
Algorithm::Histogram,
|
||||
&cache.prepare_diff().ok()?.interned_input(),
|
||||
|
||||
let mut line_changes: LineChanges = HashMap::new();
|
||||
|
||||
let mark_section =
|
||||
|line_changes: &mut LineChanges, start: u32, end: i32, change: LineChange| {
|
||||
for line in start..=end as u32 {
|
||||
line_changes.insert(line, change);
|
||||
}
|
||||
};
|
||||
|
||||
let _ = diff.foreach(
|
||||
&mut |_, _| true,
|
||||
None,
|
||||
Some(&mut |delta, hunk| {
|
||||
let path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
|
||||
|
||||
if filepath_relative_to_repo != path {
|
||||
return false;
|
||||
}
|
||||
|
||||
let old_lines = hunk.old_lines();
|
||||
let new_start = hunk.new_start();
|
||||
let new_lines = hunk.new_lines();
|
||||
let new_end = (new_start + new_lines) as i32 - 1;
|
||||
|
||||
if old_lines == 0 && new_lines > 0 {
|
||||
mark_section(&mut line_changes, new_start, new_end, LineChange::Added);
|
||||
} else if new_lines == 0 && old_lines > 0 {
|
||||
if new_start == 0 {
|
||||
mark_section(&mut line_changes, 1, 1, LineChange::RemovedAbove);
|
||||
} else {
|
||||
mark_section(
|
||||
&mut line_changes,
|
||||
new_start,
|
||||
new_start as i32,
|
||||
LineChange::RemovedBelow,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
mark_section(&mut line_changes, new_start, new_end, LineChange::Modified);
|
||||
}
|
||||
|
||||
true
|
||||
}),
|
||||
None,
|
||||
);
|
||||
|
||||
collect_changes_from_hunks(diff.hunks())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{get_git_diff, LineChange};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn git(repo: &Path, args: &[&str]) {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.expect("git command can run");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"git {args:?} failed\nstdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_repo() -> tempfile::TempDir {
|
||||
let dir = tempfile::tempdir().expect("can create temporary directory");
|
||||
let repo = dir.path();
|
||||
git(repo, &["init"]);
|
||||
git(repo, &["config", "user.email", "test@test.test"]);
|
||||
git(repo, &["config", "user.name", "Test"]);
|
||||
dir
|
||||
}
|
||||
|
||||
fn create_and_track_file(repo: &tempfile::TempDir, filename: &str) -> PathBuf {
|
||||
let filepath = repo.path().join(filename);
|
||||
std::fs::write(&filepath, "file\n").expect("can write file");
|
||||
git(repo.path(), &["add", filename]);
|
||||
git(repo.path(), &["commit", "-m", "initial"]);
|
||||
|
||||
filepath.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_a_git_repository() {
|
||||
let dir = tempfile::tempdir().expect("can create temporary directory");
|
||||
let file = dir.path().join("file.txt");
|
||||
std::fs::write(&file, "line 1\n").expect("can write file");
|
||||
|
||||
assert_eq!(get_git_diff(&file), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_is_calculated_against_index() {
|
||||
let repo = setup_repo();
|
||||
let file = create_and_track_file(&repo, "file.txt");
|
||||
|
||||
std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file");
|
||||
// Add modified file to index -> should find 0 changes but not none
|
||||
git(repo.path(), &["add", "file.txt"]);
|
||||
|
||||
assert_eq!(get_git_diff(&file).expect("empty map").len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_is_calculated_against_index_2() {
|
||||
let repo = setup_repo();
|
||||
let file = create_and_track_file(&repo, "file.txt");
|
||||
|
||||
std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file");
|
||||
git(repo.path(), &["add", "file.txt"]);
|
||||
// modify the second line again which should show a single change
|
||||
std::fs::write(&file, "line 1\nline 2 modified again\n").expect("can write file");
|
||||
|
||||
assert_eq!(get_git_diff(&file).expect("one change").len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_is_calculated_correctly() {
|
||||
let repo = setup_repo();
|
||||
create_and_track_file(&repo, "committed.txt");
|
||||
let filename = "committed2.txt";
|
||||
let file = create_and_track_file(&repo, filename);
|
||||
|
||||
std::fs::write(&file, "file\nline 2 added\n").expect("can write file");
|
||||
let mut changes = get_git_diff(&file).expect("multiple changes");
|
||||
assert_eq!(changes.get(&2), Some(&LineChange::Added));
|
||||
git(repo.path(), &["add", filename]);
|
||||
|
||||
std::fs::write(&file, "file\nline 2 modified\n").expect("can write file");
|
||||
changes = get_git_diff(&file).expect("multiple changes");
|
||||
assert_eq!(changes.get(&2), Some(&LineChange::Modified));
|
||||
git(repo.path(), &["add", filename]);
|
||||
|
||||
std::fs::write(&file, "line 2 modified\nline 3 added").expect("can write file");
|
||||
changes = get_git_diff(&file).expect("multiple changes");
|
||||
assert_eq!(changes.get(&1), Some(&LineChange::RemovedAbove));
|
||||
assert_eq!(changes.get(&2), Some(&LineChange::Added));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn faulty_git_version_does_not_panic() {
|
||||
let repo = setup_repo();
|
||||
let file = create_and_track_file(&repo, "file.txt");
|
||||
std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file");
|
||||
// changes are detected
|
||||
assert_eq!(get_git_diff(&file).expect("one change").len(), 2);
|
||||
// write invalid repositoryformatversion
|
||||
git(
|
||||
repo.path(),
|
||||
&["config", "core.repositoryformatversion", "one"],
|
||||
);
|
||||
// changes are no longer detected
|
||||
assert_eq!(get_git_diff(&file), None);
|
||||
}
|
||||
Some(line_changes)
|
||||
}
|
||||
|
||||
+4
-112
@@ -14,53 +14,6 @@ use crate::{
|
||||
input::{Input, InputKind, InputReader, OpenedInput, OpenedInputKind},
|
||||
};
|
||||
|
||||
/// Wrap `s` in POSIX single quotes. An embedded `'` is encoded as `'\''`
|
||||
/// (close, escaped quote, reopen) since `'...'` has no escape character.
|
||||
fn shell_quote(s: &str) -> String {
|
||||
if !s.contains('\'') {
|
||||
let mut quoted = String::with_capacity(s.len() + 2);
|
||||
quoted.push('\'');
|
||||
quoted.push_str(s);
|
||||
quoted.push('\'');
|
||||
return quoted;
|
||||
}
|
||||
let mut quoted = String::with_capacity(s.len() + 2);
|
||||
quoted.push('\'');
|
||||
for c in s.chars() {
|
||||
if c == '\'' {
|
||||
quoted.push_str("'\\''");
|
||||
} else {
|
||||
quoted.push(c);
|
||||
}
|
||||
}
|
||||
quoted.push('\'');
|
||||
quoted
|
||||
}
|
||||
|
||||
/// Substitute the first `%s` in a $LESSOPEN/$LESSCLOSE template with a quoted value.
|
||||
fn shell_substitute(template: &str, replacement: &str) -> String {
|
||||
template.replacen("%s", &shell_quote(replacement), 1)
|
||||
}
|
||||
|
||||
/// Substitute the first two `%s` occurrences positionally in one pass; chaining
|
||||
/// `shell_substitute` twice would mis-target if `first` itself contains `%s`.
|
||||
fn shell_substitute_two(template: &str, first: &str, second: &str) -> String {
|
||||
let mut out = String::with_capacity(template.len() + first.len() + second.len() + 4);
|
||||
let mut remaining = template;
|
||||
let mut to_substitute = [Some(first), Some(second)];
|
||||
for slot in &mut to_substitute {
|
||||
if let Some(idx) = remaining.find("%s") {
|
||||
out.push_str(&remaining[..idx]);
|
||||
out.push_str(&shell_quote(slot.take().unwrap()));
|
||||
remaining = &remaining[idx + 2..];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.push_str(remaining);
|
||||
out
|
||||
}
|
||||
|
||||
/// Preprocess files and/or stdin using $LESSOPEN and $LESSCLOSE
|
||||
pub(crate) struct LessOpenPreprocessor {
|
||||
lessopen: String,
|
||||
@@ -134,7 +87,7 @@ impl LessOpenPreprocessor {
|
||||
None => return input.open(stdin, stdout_identifier),
|
||||
};
|
||||
|
||||
let mut lessopen_command = shell(shell_substitute(&self.lessopen, path_str));
|
||||
let mut lessopen_command = shell(self.lessopen.replacen("%s", path_str, 1));
|
||||
lessopen_command.stdout(Stdio::piped());
|
||||
|
||||
let lessopen_output = match lessopen_command.execute_output() {
|
||||
@@ -169,7 +122,7 @@ impl LessOpenPreprocessor {
|
||||
let mut stdin_buffer = Vec::new();
|
||||
stdin.read_to_end(&mut stdin_buffer)?;
|
||||
|
||||
let mut lessopen_command = shell(shell_substitute(&self.lessopen, "-"));
|
||||
let mut lessopen_command = shell(self.lessopen.replacen("%s", "-", 1));
|
||||
lessopen_command.stdout(Stdio::piped());
|
||||
|
||||
let lessopen_output = match lessopen_command.execute_input_output(&stdin_buffer)
|
||||
@@ -228,7 +181,7 @@ impl LessOpenPreprocessor {
|
||||
lessclose: self
|
||||
.lessclose
|
||||
.as_ref()
|
||||
.map(|s| shell_substitute_two(s, &path_str, &stdout)),
|
||||
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", &stdout, 1)),
|
||||
}
|
||||
} else {
|
||||
Preprocessed {
|
||||
@@ -236,7 +189,7 @@ impl LessOpenPreprocessor {
|
||||
lessclose: self
|
||||
.lessclose
|
||||
.as_ref()
|
||||
.map(|s| shell_substitute_two(s, &path_str, "-")),
|
||||
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", "-", 1)),
|
||||
}
|
||||
},
|
||||
))?,
|
||||
@@ -431,65 +384,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_quote_plain_filename() {
|
||||
assert_eq!(super::shell_quote("file.txt"), "'file.txt'");
|
||||
assert_eq!(super::shell_quote("with space.txt"), "'with space.txt'");
|
||||
assert_eq!(super::shell_quote(""), "''");
|
||||
assert_eq!(super::shell_quote("-"), "'-'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_quote_metacharacters_neutralised() {
|
||||
assert_eq!(super::shell_quote("; rm -rf ~ ;"), "'; rm -rf ~ ;'");
|
||||
assert_eq!(super::shell_quote("$(payload)"), "'$(payload)'");
|
||||
assert_eq!(super::shell_quote("`payload`"), "'`payload`'");
|
||||
assert_eq!(super::shell_quote("a|b&c;d"), "'a|b&c;d'");
|
||||
assert_eq!(super::shell_quote("..>/dev/null"), "'..>/dev/null'");
|
||||
assert_eq!(super::shell_quote("\n\t"), "'\n\t'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_quote_embedded_single_quote() {
|
||||
assert_eq!(super::shell_quote("it's"), "'it'\\''s'");
|
||||
assert_eq!(super::shell_quote("'"), "''\\'''");
|
||||
assert_eq!(super::shell_quote("a'b'c"), "'a'\\''b'\\''c'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_only_replaces_first_percent_s() {
|
||||
assert_eq!(super::shell_substitute("echo %s", "x"), "echo 'x'");
|
||||
assert_eq!(super::shell_substitute("echo %s %s", "x"), "echo 'x' %s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_protects_against_filename_injection() {
|
||||
let template = "|preproc %s";
|
||||
let attacker_filename = "; rm -rf ~ ;";
|
||||
let result = super::shell_substitute(template, attacker_filename);
|
||||
assert_eq!(result, "|preproc '; rm -rf ~ ;'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_two_replaces_first_two_placeholders() {
|
||||
assert_eq!(
|
||||
super::shell_substitute_two("echo %s and %s done", "a", "b"),
|
||||
"echo 'a' and 'b' done"
|
||||
);
|
||||
// A third %s is left alone.
|
||||
assert_eq!(
|
||||
super::shell_substitute_two("echo %s %s %s", "a", "b"),
|
||||
"echo 'a' 'b' %s"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_two_handles_percent_s_in_first_argument() {
|
||||
// The chained-substitute approach would replace the injected `%s` here.
|
||||
assert_eq!(
|
||||
super::shell_substitute_two("a %s b %s c", "%s", "second"),
|
||||
"a '%s' b 'second' c"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-15
@@ -603,23 +603,11 @@ impl Printer for InteractivePrinter<'_> {
|
||||
let title = "8<";
|
||||
let title_count = title.chars().count();
|
||||
|
||||
let snip_left = "─ ".repeat(
|
||||
self.config
|
||||
.term_width
|
||||
.saturating_sub(panel_count)
|
||||
.saturating_sub(title_count / 2)
|
||||
/ 4,
|
||||
);
|
||||
let snip_left = "─ ".repeat((self.config.term_width - panel_count - (title_count / 2)) / 4);
|
||||
let snip_left_count = snip_left.chars().count(); // Can't use .len() with Unicode.
|
||||
|
||||
let snip_right = " ─".repeat(
|
||||
self.config
|
||||
.term_width
|
||||
.saturating_sub(panel_count)
|
||||
.saturating_sub(snip_left_count)
|
||||
.saturating_sub(title_count)
|
||||
/ 2,
|
||||
);
|
||||
let snip_right =
|
||||
" ─".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2);
|
||||
|
||||
writeln!(
|
||||
handle,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# JSON Lines is a simple variation of JSON #2535
|
||||
[mappings]
|
||||
"JSON" = ["*.jsonl", "*.jsonc", "*.jsonld", "*.geojson", "*.ndjson", "*.code-workspace"]
|
||||
"JSON" = ["*.jsonl", "*.jsonc", "*.jsonld", "*.geojson", "*.ndjson"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[mappings]
|
||||
"Makefile" = ["justfile", "Justfile", ".justfile", "*.justfile"]
|
||||
@@ -1,2 +1,2 @@
|
||||
[mappings]
|
||||
"SSH Config" = ["**/.ssh/**/config"]
|
||||
"SSH Config" = ["**/.ssh/config"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[mappings]
|
||||
"INI" = ["/etc/dnf/dnf.conf", "/etc/yum.repos.d/*.repo"]
|
||||
@@ -1,2 +0,0 @@
|
||||
[mappings]
|
||||
"INI" = ["**/ghostty/**/*.ghostty", "**/ghostty/themes/*"]
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
NAME="${0##*/}"
|
||||
for i in {1..3}; do
|
||||
echo "hello $NAME $i"
|
||||
done
|
||||
@@ -305,19 +305,6 @@ fn line_range_multiple() {
|
||||
.stdout("line 1\nline 2\nline 4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snip_at_terminal_width_one_does_not_panic() {
|
||||
bat()
|
||||
.arg("multiline.txt")
|
||||
.arg("--style=snip")
|
||||
.arg("--color=always")
|
||||
.arg("--terminal-width=1")
|
||||
.arg("--line-range=1:2")
|
||||
.arg("--line-range=4:4")
|
||||
.assert()
|
||||
.success();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_range_multiple_with_context() {
|
||||
bat()
|
||||
@@ -4248,37 +4235,3 @@ fn tcl_shebang_detection_expect() {
|
||||
.assert()
|
||||
.success();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignored_suffix_enables_first_line_detection() {
|
||||
// A shebang shell script saved with a `.txt` extension is Plain Text by
|
||||
// default (the extension wins). With `--ignored-suffix .txt` the suffix is
|
||||
// stripped before detection, so it falls back to the first line and is
|
||||
// highlighted exactly as if its language were forced to bash. See #2745.
|
||||
let fixture = "regression_tests/issue_2745.txt";
|
||||
let common = ["--color=always", "--decorations=never", "--style=plain"];
|
||||
|
||||
let stdout = |args: &[&str]| -> Vec<u8> {
|
||||
let assert = bat()
|
||||
.args(common)
|
||||
.args(args)
|
||||
.arg(fixture)
|
||||
.assert()
|
||||
.success();
|
||||
assert.get_output().stdout.clone()
|
||||
};
|
||||
|
||||
let forced_bash = stdout(&["--language", "bash"]);
|
||||
let with_ignored_suffix = stdout(&["--ignored-suffix", ".txt"]);
|
||||
let default = stdout(&[]);
|
||||
|
||||
// The fixture really is being highlighted (forcing bash is not a no-op).
|
||||
assert!(
|
||||
forced_bash.windows(2).any(|w| w == b"\x1b["),
|
||||
"forced-bash output should contain ANSI color codes"
|
||||
);
|
||||
// With the ignored suffix, detection matches forced bash highlighting...
|
||||
assert_eq!(with_ignored_suffix, forced_bash);
|
||||
// ...while the default (extension wins) stays plain and differs.
|
||||
assert_ne!(default, forced_bash);
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
[38;2;248;248;242m([0m[38;2;255;255;255mlogging[0m[38;2;248;248;242m)[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mlog[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moutput[0m[38;2;248;248;242m file /var/log/caddy.log[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;117;113;94m# a comment[0m
|
||||
[38;2;255;255;255mlocalhost:8080[0m[38;2;249;38;114m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mexample.com[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mroot[0m[38;2;248;248;242m * /var/www/site[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mfile_server[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mreverse_proxy[0m[38;2;248;248;242m /.well-known/matrix/* localhost:8008[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mheader_up[0m[38;2;248;248;242m Host [0m[38;2;190;132;255m{upstream_hostport}[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mimport[0m[38;2;248;248;242m logging[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;255;255;255mwww.example.com[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mredir[0m[38;2;248;248;242m https://example.com[0m[38;2;190;132;255m{uri}[0m[38;2;248;248;242m permanent[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mimport[0m[38;2;248;248;242m logging[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;255;255;255mstatus.example.com[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mreverse_proxy[0m[38;2;248;248;242m localhost:3002[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mimport[0m[38;2;248;248;242m logging[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
(logging) {
|
||||
log {
|
||||
output file /var/log/caddy.log
|
||||
}
|
||||
}
|
||||
|
||||
# a comment
|
||||
localhost:8080, example.com {
|
||||
root * /var/www/site
|
||||
file_server
|
||||
reverse_proxy /.well-known/matrix/* localhost:8008 {
|
||||
header_up Host {upstream_hostport}
|
||||
}
|
||||
import logging
|
||||
}
|
||||
|
||||
www.example.com {
|
||||
redir https://example.com{uri} permanent
|
||||
import logging
|
||||
}
|
||||
|
||||
status.example.com {
|
||||
reverse_proxy localhost:3002
|
||||
import logging
|
||||
}
|
||||
+28
-37
@@ -1,17 +1,15 @@
|
||||
use gix::actor::SignatureRef;
|
||||
use gix::bstr::BString;
|
||||
use gix::bstr::ByteSlice;
|
||||
use gix::date::time::Format;
|
||||
use gix::date::Time;
|
||||
use gix::objs::tree;
|
||||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use tempfile::TempDir;
|
||||
|
||||
use git2::build::CheckoutBuilder;
|
||||
use git2::Repository;
|
||||
use git2::Signature;
|
||||
|
||||
pub struct BatTester {
|
||||
/// Temporary working directory
|
||||
temp_dir: TempDir,
|
||||
@@ -61,42 +59,35 @@ impl Default for BatTester {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_sample_directory() -> Result<TempDir, Box<dyn std::error::Error>> {
|
||||
fn create_sample_directory() -> Result<TempDir, git2::Error> {
|
||||
// Create temp directory and initialize repository
|
||||
let temp_dir = TempDir::new().expect("Temp directory");
|
||||
let repo = gix::init(&temp_dir)?;
|
||||
let mut tree = gix::objs::Tree::empty();
|
||||
let repo = Repository::init(&temp_dir)?;
|
||||
|
||||
// Create sample.rs from snapshot file
|
||||
let blob_id = repo.write_blob_stream(File::open("tests/snapshots/sample.rs")?)?;
|
||||
let entry = tree::Entry {
|
||||
mode: tree::EntryMode::from(tree::EntryKind::Blob),
|
||||
oid: blob_id.object()?.id,
|
||||
filename: BString::from("sample.rs"),
|
||||
};
|
||||
tree.entries.push(entry);
|
||||
let tree_id = repo.write_object(tree)?;
|
||||
// Copy over `sample.rs`
|
||||
let sample_path = temp_dir.path().join("sample.rs");
|
||||
println!("{sample_path:?}");
|
||||
fs::copy("tests/snapshots/sample.rs", &sample_path).expect("successful copy");
|
||||
|
||||
let author = SignatureRef {
|
||||
name: "test".as_bytes().as_bstr(),
|
||||
email: "test@test.test".as_bytes().as_bstr(),
|
||||
time: &Time::now_local_or_utc().format_or_unix(Format::Raw),
|
||||
};
|
||||
let commit_id = repo.commit_as(
|
||||
author,
|
||||
author,
|
||||
"HEAD",
|
||||
// Commit
|
||||
let mut index = repo.index()?;
|
||||
index.add_path(Path::new("sample.rs"))?;
|
||||
|
||||
let oid = index.write_tree()?;
|
||||
let signature = Signature::now("bat test runner", "bat@test.runner")?;
|
||||
let tree = repo.find_tree(oid)?;
|
||||
let _ = repo.commit(
|
||||
Some("HEAD"), // point HEAD to our new commit
|
||||
&signature, // author
|
||||
&signature, // committer
|
||||
"initial commit",
|
||||
tree_id,
|
||||
gix::commit::NO_PARENT_IDS,
|
||||
)?;
|
||||
assert_eq!(commit_id, repo.head_id()?);
|
||||
&tree,
|
||||
&[],
|
||||
);
|
||||
let mut opts = CheckoutBuilder::new();
|
||||
repo.checkout_head(Some(opts.force()))?;
|
||||
|
||||
fs::copy(
|
||||
"tests/snapshots/sample.modified.rs",
|
||||
temp_dir.path().join("sample.rs"),
|
||||
)
|
||||
.expect("successful copy");
|
||||
fs::copy("tests/snapshots/sample.modified.rs", &sample_path).expect("successful copy");
|
||||
|
||||
Ok(temp_dir)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user