Categories
DailyOps

How to compare files in a human-friendly way

Compare files in a human-friendly way using an experimental difftastic utility.

This utility is built using Rust, so I will use rustup snap.

$ snap info rustup
name:      rustup
summary:   "EXPERIMENTAL: The Rust Language installer"
publisher: Daniel Silverstone (dsilvers)
store-url: https://snapcraft.io/rustup
contact:   dsilvers@digital-scurf.org
license:   Apache-2.0 OR MIT
description: |
  **NOTA BENE:** _This is an experimental, unofficial, snap and should not be relied on._
  
  Rustup is the Rust Language's installer and primary front-end.  You probably
  want this if you want to develop anything written in Rust.
  
  Please note, this snap is experimental and may be yanked without notice
  if this experiment proves to be a bad idea.
snap-id: tO4cnls38mXDXJiA0U72G7eCyrCAMweL
channels:
  latest/stable:    1.24.3 2021-06-22 (1027) 4MB classic
  latest/candidate: ^                            
  latest/beta:      1.24.3 2021-05-31 (1027) 4MB classic
  latest/edge:      1.24.3 2022-04-01 (1350) 5MB classic

Install the Rust toolchain installer.

$ sudo snap install --classic rustup
rustup 1.24.3 from Daniel Silverstone (dsilvers) installed

Inspect available commands.

$ rustup
rustup 1.24.3 (ce5817a94 2021-05-31)
The Rust toolchain installer

USAGE:
    rustup [FLAGS] [+toolchain] 

FLAGS:
    -v, --verbose    Enable verbose output
    -q, --quiet      Disable progress output
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <+toolchain>    release channel (e.g. +stable) or custom toolchain to set override

SUBCOMMANDS:
    show           Show the active and installed toolchains or profiles
    update         Update Rust toolchains and rustup
    check          Check for updates to Rust toolchains and rustup
    default        Set the default toolchain
    toolchain      Modify or query the installed toolchains
    target         Modify a toolchain's supported targets
    component      Modify a toolchain's installed components
    override       Modify directory toolchain overrides
    run            Run a command with an environment configured for a given toolchain
    which          Display which binary will be run for a given command
    doc            Open the documentation for the current toolchain
    man            View the man page for a given command
    self           Modify the rustup installation
    set            Alter rustup settings
    completions    Generate tab-completion scripts for your shell
    help           Prints this message or the help of the given subcommand(s)

DISCUSSION:
    Rustup installs The Rust Programming Language from the official
    release channels, enabling you to easily switch between stable,
    beta, and nightly compilers and keep them updated. It makes
    cross-compiling simpler with binary builds of the standard library
    for common platforms.

    If you are new to Rust consider running `rustup doc --book` to
    learn Rust.

By default there will be no active toolchain.

$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/milosz/snap/rustup/common/rustup

no active toolchain

Install Rust toolchain using stable channel.

$ rustup toolchain install stable
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2022-02-24, rust version 1.59.0 (9d1b2106e 2022-02-23)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
info: installing component 'rust-std'
info: installing component 'rustc'
info: installing component 'rustfmt'

Display current toolchain.

$ rustup show
Default host: x86_64-unknown-linux-gnu
rustup home:  /home/milosz/snap/rustup/common/rustup

stable-x86_64-unknown-linux-gnu (default)
rustc 1.59.0 (9d1b2106e 2022-02-23)

Install difftastic.

$ cargo install difftastic
    Updating crates.io index
  Downloaded difftastic v0.25.0
  Downloaded 1 crate (6.2 MB) in 1.49s
  Installing difftastic v0.25.0
  Downloaded archery v0.4.0
  Downloaded env_logger v0.7.1
  Downloaded const_format v0.2.22
  Downloaded mimalloc v0.1.28
  Downloaded const_format_proc_macros v0.2.22
  Downloaded owo-colors v3.3.0
  Downloaded pretty_env_logger v0.4.0
  Downloaded term_size v0.3.2
  Downloaded typed-arena v2.0.1
  Downloaded radix-heap v0.4.2
  Downloaded wu-diff v0.1.2
  Downloaded tree-sitter v0.20.6
  Downloaded rpds v0.10.0
  Downloaded libmimalloc-sys v0.1.24
  Downloaded num_cpus v1.13.1
  Downloaded os_str_bytes v6.0.0
  Downloaded quote v1.0.17
  Downloaded quick-error v1.2.3
  Downloaded rustc-hash v1.1.0
  Downloaded indexmap v1.8.1
  Downloaded crossbeam-utils v0.8.8
  Downloaded memchr v2.4.1
  Downloaded crossbeam-epoch v0.9.8
  Downloaded aho-corasick v0.7.18
  Downloaded lazy_static v1.4.0
  Downloaded regex v1.5.5
  Downloaded crossbeam-channel v0.5.4
  Downloaded clap v3.1.8
  Downloaded scopeguard v1.1.0
  Downloaded same-file v1.0.6
  Downloaded strsim v0.10.0
  Downloaded walkdir v2.3.2
  Downloaded proc-macro2 v1.0.36
  Downloaded textwrap v0.15.0
  Downloaded termcolor v1.1.3
  Downloaded either v1.6.1
  Downloaded rayon-core v1.9.1
  Downloaded crossbeam-deque v0.8.1
  Downloaded cfg-if v1.0.0
  Downloaded atty v0.2.14
  Downloaded regex-syntax v0.6.25
  Downloaded memoffset v0.6.5
  Downloaded unicode-xid v0.2.2
  Downloaded humantime v1.3.0
  Downloaded hashbrown v0.11.2
  Downloaded itertools v0.10.3
  Downloaded static_assertions v1.1.0
  Downloaded log v0.4.16
  Downloaded cc v1.0.73
  Downloaded autocfg v1.1.0
  Downloaded bitflags v1.3.2
  Downloaded rayon v1.5.1
  Downloaded libc v0.2.121
  Downloaded 53 crates (4.2 MB) in 0.87s (largest was `libmimalloc-sys` at 1.1 MB)
   Compiling autocfg v1.1.0
   Compiling libc v0.2.121
   Compiling crossbeam-utils v0.8.8
   Compiling cc v1.0.73
   Compiling memchr v2.4.1
   Compiling lazy_static v1.4.0
   Compiling cfg-if v1.0.0
   Compiling rayon-core v1.9.1
   Compiling scopeguard v1.1.0
   Compiling proc-macro2 v1.0.36
   Compiling log v0.4.16
   Compiling regex-syntax v0.6.25
   Compiling unicode-xid v0.2.2
   Compiling either v1.6.1
   Compiling quick-error v1.2.3
   Compiling termcolor v1.1.3
   Compiling hashbrown v0.11.2
   Compiling static_assertions v1.1.0
   Compiling strsim v0.10.0
   Compiling textwrap v0.15.0
   Compiling bitflags v1.3.2
   Compiling same-file v1.0.6
   Compiling wu-diff v0.1.2
   Compiling typed-arena v2.0.1
   Compiling owo-colors v3.3.0
   Compiling rustc-hash v1.1.0
   Compiling radix-heap v0.4.2
   Compiling memoffset v0.6.5
   Compiling crossbeam-epoch v0.9.8
   Compiling rayon v1.5.1
   Compiling indexmap v1.8.1
   Compiling humantime v1.3.0
   Compiling archery v0.4.0
   Compiling itertools v0.10.3
   Compiling walkdir v2.3.2
   Compiling libmimalloc-sys v0.1.24
   Compiling tree-sitter v0.20.6
   Compiling rpds v0.10.0
   Compiling crossbeam-channel v0.5.4
   Compiling aho-corasick v0.7.18
   Compiling os_str_bytes v6.0.0
   Compiling num_cpus v1.13.1
   Compiling atty v0.2.14
   Compiling term_size v0.3.2
   Compiling quote v1.0.17
   Compiling regex v1.5.5
   Compiling crossbeam-deque v0.8.1
   Compiling clap v3.1.8
   Compiling const_format_proc_macros v0.2.22
   Compiling mimalloc v0.1.28
   Compiling env_logger v0.7.1
   Compiling pretty_env_logger v0.4.0
   Compiling const_format v0.2.22
   Compiling difftastic v0.25.0
    Finished release [optimized] target(s) in 1m 00s
  Installing /home/milosz/.cargo/bin/difft
   Installed package `difftastic v0.25.0` (executable `difft`)

Extend PATH variable and update ~/.bashrc file accordingly.

$ export PATH="$PATH:/home/milosz/.cargo/bin/"

Display usage information.

$ difft 
Difftastic 0.25.0
Wilfred Hughes <me@wilfred.me.uk>
A diff that understands syntax

USAGE:
    difft [OPTIONS] OLD-PATH NEW-PATH

OPTIONS:
        --background <BACKGROUND>    Set the background brightness. Overrides $DFT_BACKGROUND if
                                     present. Difftastic will prefer brighter colours on dark
                                     backgrounds. [possible values: dark, light]
        --byte-limit <LIMIT>         Use a text diff if either input file exceeds this size.
                                     Overrides $DFT_BYTE_LIMIT if present. [default: 1000000]
        --color <WHEN>               When to use color output. [possible values: always, auto,
                                     never]
    -h, --help                       Print help information
        --missing-as-empty           Treat paths that don't exist as equivalent to an empty file.
        --node-limit <LIMIT>         Use a text diff if the number of syntax nodes exceeds this
                                     number. Overrides $DFT_NODE_LIMIT if present. [default: 30000]
        --skip-unchanged             Don't display anything if a file is unchanged.
    -V, --version                    Print version information
        --width <COLUMNS>            Use this many columns when calculating line wrapping. Overrides
                                     $DFT_WIDTH if present. If not specified, difftastic will detect
                                     the terminal width.

DEBUG OPTIONS:
        --dump-syntax <PATH>    Parse a single file with tree-sitter and display the difftastic
                                syntax tree.
        --dump-ts <PATH>        Parse a single file with tree-sitter and display the tree-sitter
                                parse tree.

Define difftastic as an external diff utility for git.

$ git config --global diff.external difft

Sample usage.

greasemonkey-gitlab-scoped-labels on main via v12.22.5 
 git log -p -n 3 --ext-diff          
commit 8bf864710ac48d5530937ff16bb3f91932f12f0f (HEAD -> main, tag: version_11, origin/main, origin/HEAD)
Author: Milosz Galazka <milosz@sleeplessbeastie.eu>
Date:   Mon Mar 14 19:56:08 2022 +0100

    Version update

gitlab-scoped-labels.js --- JavaScript
1 // ==UserScript==                                    1 // ==UserScript==                                    
2 // @name     GitLab scoped labels (view only)        2 // @name     GitLab scoped labels (view only)        
3 // @version  10                                      3 // @version  11                                      
4 // @grant    none                                    4 // @grant    none                                    
5 // @include  https://git.octocat.lab/*               5 // @include  https://git.octocat.lab/*               
6 // @license  GPL-3.0 License; https://www.gnu.org/li 6 // @license  GPL-3.0 License; https://www.gnu.org/lic
  censes/gpl-3.0.txt                                   enses/gpl-3.0.txt                                    


commit 5ff891177ed3bfaaf20c6d6376036d028e7f48a0
Merge: 11c1570 4397cfe
Author: Milosz Galazka <milosz@sleeplessbeastie.eu>
Date:   Mon Mar 14 19:54:39 2022 +0100

    Merge pull request #5 from baubie/main
    
    Add scoped label styling to merge request pages.

commit 4397cfe5b2747e10f3e58c0d83530ffe1e7e99e3
Author: Brandon Aubie <brandon@aubie.ca>
Date:   Mon Mar 14 12:17:16 2022 -0400

    Add scoped label styling to merge request pages.

gitlab-scoped-labels.js --- JavaScript
72  72       label.innerHTML = label.innerText.replace(/([^:]*)::([^:]*)/, "<span style='background-color: " + color + "' class='gl-label-text gl-label-text-light'>$1</span> <span class='gl-label-text-scoped'>$2</span>");
73  73     }
74  74   }
75  75 } else if(window.location.pathname.match(/.*\/merge_requests$/)) { // merge request list
    76   const labels = document.querySelectorAll("span.gl-label");
    77   for(const label of labels) {
    78     if(label.innerText.includes("::")) {      
    79       label.classList.add("gl-label-scoped")
    80       const color = label.innerHTML.replace(/<.*style="background-color: (#\w*)".*/, "$1")
    81       label.setAttribute("style", label.getAttribute("style") + "--label-background-color: " + color + "; --label-inset-border: inset 0 0 0 2px " + color + "; color " + color + ";")
    82       label.innerHTML = label.innerText.replace(/([^:]*)::([^:]*)/, "<span style='background-color: " + color + "' class='gl-label-text gl-label-text-light'>$1</span> <span class='gl-label-text-scoped'>$2</span>");
    83     }
    84   }
    85 } else if(window.location.pathname.match(/.*\/merge_requests\/.*/)) { // merge request
    86   const labels = document.querySelectorAll("span.gl-label");
    87   for(const label of labels) {
    88     if(label.innerText.includes("::")) {      
    89       label.classList.add("gl-label-scoped")
    90       const color = label.innerHTML.replace(/<.*style="background-color: (#\w*)".*/, "$1")
    91       label.setAttribute("style", label.getAttribute("style") + "--label-background-color: " + color + "; --label-inset-border: inset 0 0 0 2px " + color + "; color " + color + ";")
    92       label.innerHTML = label.innerText.replace(/([^:]*)::([^:]*)/, "<span style='background-color: " + color + "' class='gl-label-text gl-label-text-light'>$1</span> <span class='gl-label-text-scoped'>$2</span>");
    93     }
    94   }
    95   const callback = function(mutations, observer) {
    96     for(const mutation of mutations) {
    97       if(mutation.target.classList.contains("notes")) {
    98         for(const timeline of mutation.addedNodes) {      
    99           const labels = timeline.querySelectorAll("span.gl-label")
   100           for(const label of labels) {
   101             if(label.innerText.includes("::")) { 
   102               label.classList.add("gl-label-scoped")
   103               label.firstChild.innerHTML.replace(/<.* style="background-color: (#\w*)".*/, "--label-background-color: $1; --label-inset-border: inset 0 0 0 1px $1;")
   104               label.setAttribute("style", label.getAttribute("style") + label.firstChild.innerHTML.replace(/<.* style="background-color: (#\w*)".*/, "--label-background-color: $1; --label-inset-border: inset 0 0 0 1px $1;"))
   105               label.firstChild.innerHTML = label.firstChild.innerHTML.replace(/<(.*)>([^:]*)::([^:]*)<\/span>/, "<$1>$2</span> <span class='gl-label-text-scoped'>$3</span>")
   106             }
   107           }
   108         }
   109       }
   110     }
   111   }    
   112   const observer = new MutationObserver(callback)
   113   observer.observe(document, { childList: true, subtree: true })
   114 }
greasemonkey-gitlab-scoped-labels on main via v12.22.5 
 git diff HEAD~3 HEAD~4
gitlab-scoped-labels.js --- JavaScript
17 17             label.classList.add("gl-label-scoped")
18 18             label.innerHTML = label.innerText.replace(/([^:]*)::([^:]*)/, "<a href='#' class='gl-link gl-label-link'><span class='gl-label-text'>$1</span> <span class='gl-label-text-scoped'>$2</span></a>");
19 19           } else if(label.parentNode.classList.contains("board-title-text")) {
.. 20             console.log(label)
20 21             label.classList.add("gl-label-scoped")
21 22           }
22 23         }
crio-top on main via v1.17 
 git diff HEAD~2 HEAD~4
Makefile --- Text
 5     go run src/crio-top/main.go --configuration exa  5     go run src/crio-top/main.go --configuration exam
 . mples/configuration.yaml                             . ples/configuration.yaml                             
 6                                                      6                                                     
 7 test:                                                7 test:                                               
 8     go test ./src/background ./src/configuration ./  8     go test ./src/...                               
 . src/terminal                                         . 
 9                                                      9                                                     
10 coverage:                                           10 coverage:                                           
11     go test -cover ./src/background ./src/configura 11     go test -cover ./src/...                        
.. tion ./src/terminal                                 .. 
12                                                     12                                                     
13 all: build                                          13 all: build                                          

readme.md --- Text
31 31   header:
32 32     width:
33 33       server: 20
.. 34     status: true
34 35   refresh:
35 36     window: 2
36 37     data: 2

Inspect Difftastic manual for more details.