git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: Migration of git-scm.com to a static web site: ready for review/testing

From
Johannes Schindelin <johannes.schindelin@gmx.de>
Date
Sep 11, 2024, 22:20 UTC
Message-ID
<19f553dc-a778-3ba7-b037-0a08da7e9388@gmx.de>
In-Reply-To
<155bc141-a16a-9fd2-920b-7a8c398d5e36@gmx.de>
Once again, me,
On Thu, 12 Sep 2024, Johannes Schindelin wrote:
> I will try to reply to this mail with a range-diff that is slightly edited
> to leave out the changes made by GitHub workflows (i.e. book and manual
> pages).
Here goes:
  1:  3420701f6 <   -:  --------- Remove a bogus end tag
  2:  861766083 <   -:  --------- docs: fix a bogus <a/>
  3:  5af3129c1 <   -:  --------- Remove obsolete banner
  4:  82173f4f1 <   -:  --------- Revert "book2.rake: drop progit2-ms translation"
  5:  32795076e =   1:  987e733c3 Drop the Travis CI configuration
  6:  0c6f2b28b =   2:  247a0615a Add a Hugo-aware .gitignore file
  7:  5c522f814 !   3:  593aa9af8 Adjust the Gemfile in preparation for generating a static site
    @@ Gemfile
      source "https://rubygems.org"
     -ruby "3.1.3"
    -
    --gem "rails", "~> 6.0"
    +-
    +-gem "rails", "~> 6.1"
     -
     -# hacks for rails6 + ruby 3.1
     -gem 'net-imap', require: false
    @@ Gemfile
     -gem 'net-smtp', require: false
     -
     -gem "asciidoctor", "~> 2.0.0"
    --gem "elasticsearch", "2.0.2"
    +-gem "elasticsearch", "7.13"
     -gem "iso8601"
     -gem "octokit"
     -gem "puma"
  8:  46233d1bb !   4:  5cddbb9a3 Add a Hugo configuration
    @@ Commit message
      ## hugo.yml (new) ##
     @@
     +---
    -+baseURL: https://git-scm.com/
     +languageCode: en
     +title: Git
     +disablePathToLower: true
    @@ hugo.yml (new)
     +    renderer:
     +      unsafe: true
     +params:
    -+  latest_version: 2.43.0
    -+  latest_relnote_url: https://raw.github.com/git/git/master/Documentation/RelNotes/2.43.0.txt
    -+  latest_release_date: '2023-11-20'
    ++  latest_version: 2.46.0
    ++  latest_relnote_url: https://raw.github.com/git/git/master/Documentation/RelNotes/2.46.0.txt
    ++  latest_release_date: '2024-07-29'
  9:  ed4e92165 !   5:  8de703d8c Drop the Ruby app
    @@ Gemfile.lock (deleted)
     -GEM
     -  remote: https://rubygems.org/
     -  specs:
    --    actioncable (6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    actioncable (6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      nio4r (~> 2.0)
     -      websocket-driver (>= 0.6.1)
    --    actionmailbox (6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      activejob (= 6.1.7.3)
    --      activerecord (= 6.1.7.3)
    --      activestorage (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    actionmailbox (6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      activejob (= 6.1.7.7)
    +-      activerecord (= 6.1.7.7)
    +-      activestorage (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      mail (>= 2.7.1)
    --    actionmailer (6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      actionview (= 6.1.7.3)
    --      activejob (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    actionmailer (6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      actionview (= 6.1.7.7)
    +-      activejob (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      mail (~> 2.5, >= 2.5.4)
     -      rails-dom-testing (~> 2.0)
    --    actionpack (6.1.7.3)
    --      actionview (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    actionpack (6.1.7.7)
    +-      actionview (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      rack (~> 2.0, >= 2.0.9)
     -      rack-test (>= 0.6.3)
     -      rails-dom-testing (~> 2.0)
     -      rails-html-sanitizer (~> 1.0, >= 1.2.0)
    --    actiontext (6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      activerecord (= 6.1.7.3)
    --      activestorage (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    actiontext (6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      activerecord (= 6.1.7.7)
    +-      activestorage (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      nokogiri (>= 1.8.5)
    --    actionview (6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    actionview (6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      builder (~> 3.1)
     -      erubi (~> 1.4)
     -      rails-dom-testing (~> 2.0)
     -      rails-html-sanitizer (~> 1.1, >= 1.2.0)
    --    activejob (6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    activejob (6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      globalid (>= 0.3.6)
    --    activemodel (6.1.7.3)
    --      activesupport (= 6.1.7.3)
    --    activerecord (6.1.7.3)
    --      activemodel (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    --    activestorage (6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      activejob (= 6.1.7.3)
    --      activerecord (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    activemodel (6.1.7.7)
    +-      activesupport (= 6.1.7.7)
    +-    activerecord (6.1.7.7)
    +-      activemodel (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
    +-    activestorage (6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      activejob (= 6.1.7.7)
    +-      activerecord (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      marcel (~> 1.0)
     -      mini_mime (>= 1.1.0)
    --    activesupport (6.1.7.3)
    +-    activesupport (6.1.7.7)
     -      concurrent-ruby (~> 1.0, >= 1.0.2)
     -      i18n (>= 1.6, < 2)
     -      minitest (>= 5.1)
    @@ Gemfile.lock (deleted)
     -    builder (3.2.4)
     -    byebug (11.1.3)
     -    coderay (1.1.3)
    --    concurrent-ruby (1.2.2)
    +-    concurrent-ruby (1.2.3)
     -    crack (0.4.5)
     -      rexml
     -    crass (1.0.6)
    @@ Gemfile.lock (deleted)
     -    dotenv-rails (2.7.6)
     -      dotenv (= 2.7.6)
     -      railties (>= 3.2)
    --    elasticsearch (2.0.2)
    --      elasticsearch-api (= 2.0.2)
    --      elasticsearch-transport (= 2.0.2)
    --    elasticsearch-api (2.0.2)
    +-    elasticsearch (7.13.0)
    +-      elasticsearch-api (= 7.13.0)
    +-      elasticsearch-transport (= 7.13.0)
    +-    elasticsearch-api (7.13.0)
     -      multi_json
    --    elasticsearch-transport (2.0.2)
    --      faraday
    +-    elasticsearch-transport (7.13.0)
    +-      faraday (~> 1)
     -      multi_json
     -    erubi (1.12.0)
     -    execjs (2.8.1)
    @@ Gemfile.lock (deleted)
     -    factory_bot_rails (6.2.0)
     -      factory_bot (~> 6.2.0)
     -      railties (>= 5.0.0)
    --    faraday (1.8.0)
    +-    faraday (1.10.3)
     -      faraday-em_http (~> 1.0)
     -      faraday-em_synchrony (~> 1.0)
     -      faraday-excon (~> 1.1)
    --      faraday-httpclient (~> 1.0.1)
    +-      faraday-httpclient (~> 1.0)
    +-      faraday-multipart (~> 1.0)
     -      faraday-net_http (~> 1.0)
    --      faraday-net_http_persistent (~> 1.1)
    +-      faraday-net_http_persistent (~> 1.0)
     -      faraday-patron (~> 1.0)
     -      faraday-rack (~> 1.0)
    --      multipart-post (>= 1.2, < 3)
    +-      faraday-retry (~> 1.0)
     -      ruby2_keywords (>= 0.0.4)
     -    faraday-em_http (1.0.0)
     -    faraday-em_synchrony (1.0.0)
     -    faraday-excon (1.1.0)
     -    faraday-httpclient (1.0.1)
    --    faraday-net_http (1.0.1)
    +-    faraday-multipart (1.0.4)
    +-      multipart-post (~> 2)
    +-    faraday-net_http (1.0.2)
     -    faraday-net_http_persistent (1.2.0)
     -    faraday-patron (1.0.0)
     -    faraday-rack (1.0.0)
    +-    faraday-retry (1.0.3)
     -    ffi (1.15.4)
     -    foreman (0.87.2)
    --    globalid (1.1.0)
    --      activesupport (>= 5.0)
    +-    globalid (1.2.1)
    +-      activesupport (>= 6.1)
     -    hashdiff (1.0.1)
    --    i18n (1.12.0)
    +-    i18n (1.14.4)
     -      concurrent-ruby (~> 1.0)
     -    iso8601 (0.13.0)
     -    json (2.6.2)
     -    listen (3.7.0)
     -      rb-fsevent (~> 0.10, >= 0.10.3)
     -      rb-inotify (~> 0.9, >= 0.9.10)
    --    loofah (2.19.1)
    +-    loofah (2.22.0)
     -      crass (~> 1.0.2)
    --      nokogiri (>= 1.5.9)
    +-      nokogiri (>= 1.12.0)
     -    mail (2.8.1)
     -      mini_mime (>= 0.1.1)
     -      net-imap
     -      net-pop
     -      net-smtp
    --    marcel (1.0.2)
    +-    marcel (1.0.4)
     -    method_source (1.0.0)
    --    mini_mime (1.1.2)
    --    mini_portile2 (2.8.1)
    --    minitest (5.18.0)
    +-    mini_mime (1.1.5)
    +-    mini_portile2 (2.8.6)
    +-    minitest (5.22.3)
     -    multi_json (1.15.0)
    --    multipart-post (2.1.1)
    +-    multipart-post (2.4.1)
     -    net-imap (0.3.1)
     -      net-protocol
     -    net-pop (0.1.2)
     -      net-protocol
    --    net-protocol (0.1.3)
    +-    net-protocol (0.2.2)
     -      timeout
     -    net-smtp (0.3.2)
     -      net-protocol
    --    nio4r (2.5.9)
    --    nokogiri (1.14.3)
    --      mini_portile2 (~> 2.8.0)
    +-    nio4r (2.7.3)
    +-    nokogiri (1.16.5)
    +-      mini_portile2 (~> 2.8.2)
     -      racc (~> 1.4)
     -    octokit (4.21.0)
     -      faraday (>= 0.9)
    @@ Gemfile.lock (deleted)
     -      byebug (~> 11.0)
     -      pry (~> 0.13.0)
     -    public_suffix (4.0.6)
    --    puma (5.6.7)
    +-    puma (5.6.8)
     -      nio4r (~> 2.0)
    --    racc (1.6.2)
    --    rack (2.2.6.4)
    +-    racc (1.7.3)
    +-    rack (2.2.8.1)
     -    rack-test (2.1.0)
     -      rack (>= 1.3)
     -    rack-timeout (0.6.0)
    --    rails (6.1.7.3)
    --      actioncable (= 6.1.7.3)
    --      actionmailbox (= 6.1.7.3)
    --      actionmailer (= 6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      actiontext (= 6.1.7.3)
    --      actionview (= 6.1.7.3)
    --      activejob (= 6.1.7.3)
    --      activemodel (= 6.1.7.3)
    --      activerecord (= 6.1.7.3)
    --      activestorage (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    rails (6.1.7.7)
    +-      actioncable (= 6.1.7.7)
    +-      actionmailbox (= 6.1.7.7)
    +-      actionmailer (= 6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      actiontext (= 6.1.7.7)
    +-      actionview (= 6.1.7.7)
    +-      activejob (= 6.1.7.7)
    +-      activemodel (= 6.1.7.7)
    +-      activerecord (= 6.1.7.7)
    +-      activestorage (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      bundler (>= 1.15.0)
    --      railties (= 6.1.7.3)
    +-      railties (= 6.1.7.7)
     -      sprockets-rails (>= 2.0.0)
     -    rails-controller-testing (1.0.5)
     -      actionpack (>= 5.0.1.rc1)
     -      actionview (>= 5.0.1.rc1)
     -      activesupport (>= 5.0.1.rc1)
    --    rails-dom-testing (2.0.3)
    --      activesupport (>= 4.2.0)
    +-    rails-dom-testing (2.2.0)
    +-      activesupport (>= 5.0.0)
    +-      minitest
     -      nokogiri (>= 1.6)
    --    rails-html-sanitizer (1.5.0)
    --      loofah (~> 2.19, >= 2.19.1)
    +-    rails-html-sanitizer (1.6.0)
    +-      loofah (~> 2.21)
    +-      nokogiri (~> 1.14)
     -    rails_12factor (0.0.3)
     -      rails_serve_static_assets
     -      rails_stdout_logging
     -    rails_serve_static_assets (0.0.5)
     -    rails_stdout_logging (0.0.5)
    --    railties (6.1.7.3)
    --      actionpack (= 6.1.7.3)
    --      activesupport (= 6.1.7.3)
    +-    railties (6.1.7.7)
    +-      actionpack (= 6.1.7.7)
    +-      activesupport (= 6.1.7.7)
     -      method_source
     -      rake (>= 12.2)
     -      thor (~> 1.0)
     -    rainbow (3.0.0)
    --    rake (13.0.6)
    +-    rake (13.1.0)
     -    rb-fsevent (0.11.0)
     -    rb-inotify (0.10.1)
     -      ffi (~> 1.0)
    @@ Gemfile.lock (deleted)
     -    redis-store (1.9.0)
     -      redis (>= 4, < 5)
     -    regexp_parser (2.1.1)
    --    rexml (3.2.5)
    +-    rexml (3.2.8)
    +-      strscan (>= 3.0.9)
     -    rspec-core (3.10.1)
     -      rspec-support (~> 3.10.0)
     -    rspec-expectations (3.10.1)
    @@ Gemfile.lock (deleted)
     -    shoulda-context (2.0.0)
     -    shoulda-matchers (4.5.1)
     -      activesupport (>= 4.2.0)
    --    sprockets (4.2.0)
    +-    sprockets (4.2.1)
     -      concurrent-ruby (~> 1.0)
     -      rack (>= 2.2.4, < 4)
     -    sprockets-rails (3.4.2)
    @@ Gemfile.lock (deleted)
     -      activesupport (>= 5.2)
     -      sprockets (>= 3.0.0)
     -    sqlite3 (1.4.2)
    --    thor (1.2.1)
    +-    strscan (3.1.0)
    +-    thor (1.3.1)
     -    tilt (2.0.10)
    --    timeout (0.3.0)
    +-    timeout (0.4.1)
     -    tzinfo (2.0.6)
     -      concurrent-ruby (~> 1.0)
     -    uglifier (4.2.0)
    @@ Gemfile.lock (deleted)
     -      addressable (>= 2.8.0)
     -      crack (>= 0.3.2)
     -      hashdiff (>= 0.4.0, < 2.0.0)
    --    websocket-driver (0.7.5)
    +-    websocket-driver (0.7.6)
     -      websocket-extensions (>= 0.1.0)
     -    websocket-extensions (0.1.5)
    --    zeitwerk (2.6.7)
    +-    zeitwerk (2.6.13)
     -
     -PLATFORMS
     -  ruby
    @@ Gemfile.lock (deleted)
     -  database_cleaner
     -  diffy
     -  dotenv-rails
    --  elasticsearch (= 2.0.2)
    +-  elasticsearch (= 7.13)
     -  fabrication
     -  factory_bot_rails
     -  foreman
    @@ Gemfile.lock (deleted)
     -  pry-byebug
     -  puma
     -  rack-timeout
    --  rails (~> 6.0)
    +-  rails (~> 6.1)
     -  rails-controller-testing
     -  rails_12factor
     -  redis-rails
    @@ Rakefile (deleted)
      ## app.json (deleted) ##
     @@
     -{
    +-  "stack": "heroku-24",
     -  "scripts": {
     -  },
     -  "env": {
    @@ lib/searchable.rb (deleted)
     -        "query" => {
     -          "bool" => {
     -            "should" => [],
    --            "minimum_number_should_match" => 1
    +-            "minimum_should_match" => 1
     -          },
     -        },
     -        "highlight" => {
 10:  7865f2051 =   6:  24844c72e In preparation for using Hugo, move the "blog has moved" page
 11:  4effd55ad =   7:  5830b2c27 Hugo-ify the "blog has moved" page
 12:  6a09add78 =   8:  34580a126 Move the favicon to the `static/` directory
 13:  a79b9c844 =   9:  9f96eea1f Drop the robots.txt file
 14:  32a4eb168 =  10:  47cbca3c8 Hugo'ify the 404 page
 15:  906bb0235 =  11:  7a1f5bdac Drop the custom pages for HTML codes 422 and 500
 16:  bff32d315 !  12:  340558bd1 ci: switch to building with Hugo
    @@ .github/workflows/ci.yml
      name: CI
     -on: pull_request
     +on: [pull_request]
    -+
    -+env:
    -+  HUGO_VERSION: 0.120.3
      jobs:
        build:
          runs-on: ubuntu-latest
          steps:
     -    - uses: actions/checkout@v2
    -+    - uses: actions/checkout@v3
    ++    - uses: actions/checkout@v4
     -    - name: ruby setup
     -      uses: ruby/setup-ruby@v1
     -      with:
     -        bundler-cache: true
    ++    - name: configure Hugo version
    ++      run: |
    ++        set -x &&
    ++        echo "HUGO_VERSION=$(sed -n 's/^ *hugo_version: *//p' <hugo.yml)" >>$GITHUB_ENV
    ++
     +    - name: install Hugo ${{ env.HUGO_VERSION }}
     +      run: |
     +        set -x &&
    @@ .github/workflows/ci.yml
     -    - name: rubocop
     -      run: bundle exec rubocop -P
    -+    - name: run Pagefind to build the search index
    -+      run: npx -y pagefind --site public
    ++    - name: build tar archive
    ++      run: cd public && tar czvf ../pages.tar.gz *
     -    - name: rspec
     -      run: bundle exec rspec
    -+    - name: build tar archive
    -+      run: cd public && tar czvf ../pages.tar.gz *
    -+
     +    - name: Upload build artifact
    -+      uses: actions/upload-artifact@v3
    ++      uses: actions/upload-artifact@v4
     +      with:
     +        name: pages
     +        path: pages.tar.gz
    +
    + ## hugo.yml ##
    +@@ hugo.yml: markup:
    +     renderer:
    +       unsafe: true
    + params:
    ++  hugo_version: 0.134.1
    +   latest_version: 2.46.0
    +   latest_relnote_url: https://raw.github.com/git/git/master/Documentation/RelNotes/2.46.0.txt
    +   latest_release_date: '2024-07-29'
 17:  f685a732d !  13:  2b1735089 README: reflect that this is now a Hugo site
    @@ README.md
     +## Local development setup
     -You'll need a Ruby environment to run Rails.  First do:
    -+You'll need the extended version of [Hugo](https://gohugo.io/). On Windows, we recommend using the Windows Subsystem for Linux (WSL). You can serve the site locally via
    ++It is highly recommended to clone this repository using [`scalar`](https://git-scm.com/docs/scalar); This allows to work only on the parts of the repository relevant to your interests. You can select which directories are checked out using the [`git sparse-checkout add <directory>...`](https://git-scm.com/docs/git-sparse-checkout) command. The relevant directories are:
     -    $ rvm use .
     -    $ bundle install
    -+    $ hugo serve -w
    ++- If you want to test any page rendering using Hugo:
    ++  - layouts/
    ++  - content/
    ++  - static/
    ++  - assets/
     -Then you need to create the database structure:
    -+The site should be running on http://127.0.0.1:1313. Note that it may be advisable to do this in a sparse checkout that excludes large parts of `content/`, to speed up the rendering time.
    ++- To add new GUIs:
    ++  - data/
     -    $ rake db:migrate
    -+## Update manual pages
    ++- To work on pre-rendering pages that originate from other repositories (such as the ProGit book):
    ++  - script/
     -Alternatively you can run the script at `script/bootstrap` which will set up Ruby dependencies and the local SQLite database.
    --
    ++- To work on the GitHub workflows that perform the automated, scheduled pre-rendering:
    ++  - .github/
    +
     -Now you'll want to populate the man pages.  You can do so from a local Git
     -source clone like this:
    ++- The pre-rendered pages (ProGit book, its translated versions, the manual pages, their translated versions):
    ++  - external/book/
    ++  - external/docs/
    ++  You will want to avoid editing these directly, as they contain pages that are pre-rendered via GitHub workflows, sourcing content from other repositories.
    ++
    ++To render the site locally, you'll need the extended version of [Hugo](https://gohugo.io/). On Windows, we recommend using the Windows Subsystem for Linux (WSL). You can serve the site locally via
    ++
    ++    $ hugo serve -w
    ++
    ++The site should be running on http://127.0.0.1:1313. Note that it may be advisable to do this in a sparse checkout that excludes large parts of `content/`, to speed up the rendering time.
    ++
    ++## Update manual pages
    ++
     +(TODO!)
     +You can do so using a local Git source clone like this:
    @@ README.md: Alternatively, you can get the book content from a repository on your
     -
      ## Contributing
    - If you wish to contribute to this website, please [fork it on GitHub](https://github.com/git/git-scm.com), push your
    -@@ README.md: be accepted. If it involves code, please also write tests for it.
    +-If you wish to contribute to this website, please [fork it on GitHub](https://github.com/git/git-scm.com), push your
    +-change to a named branch, then send a pull request. If it is a big feature,
    +-you might want to [start an issue](https://github.com/git/git-scm.com/issues/new) first to make sure it's something that will
    +-be accepted. If it involves code, please also write tests for it.
    +-
    +-## Adding new GUI
    ++If you wish to contribute to this website, please [fork it on GitHub](https://github.com/git/git-scm.com).
    - The [list of GUI clients](https://git-scm.com/downloads/guis) has been constructed by the community for a long time. If you want to add another tool you'll need to follow a few steps:
    +-The [list of GUI clients](https://git-scm.com/downloads/guis) has been constructed by the community for a long time. If you want to add another tool you'll need to follow a few steps:
    ++Then, clone it using [`scalar`](https://git-scm.com/docs/scalar) (this avoids long clone times) and then use [`git sparse-checkout add <directory>`](https://git-scm.com/docs/git-sparse-checkout) to check out the files relevant to your work.
     -1. Add the GUI client details at the YAML file: https://github.com/git/git-scm.com/blob/main/resources/guis.yml
     -    1. The fields `name`, `url`, `price`, `license` should be very straightforward to fill.
    @@ README.md: be accepted. If it involves code, please also write tests for it.
     -    3. `platforms` is a list of at least 1 platform in which the tool is supported. The possibilities are: `Windows`, `Mac`, `Linux`, `Android`, and `iOS`
     -    4. `order` can be filled with the biggest number already existing, plus 1 (Adding to the bottom - this will be covered in the following steps)
     -    5. `trend_name` is an optional field that can be used for helping sorting the clients (also covered in the next steps)
    --
    ++After making the changes, commit and push to a named branch in your fork, then open a pull request. If it is a big feature, you might want to [start an issue](https://github.com/git/git-scm.com/issues/new) first to make sure it's something that will be accepted.
    +
     -2. Add the image to `public/images/guis/<GUI_CLIENT_NAME>@2x.png` and `public/images/guis/<GUI_CLIENT_NAME>.png` making sure the aspect ratio matches a 588:332 image.
    --
    ++## Adding a new GUI
    +
     -3. Sort the tools
     -    1. From the root of the repository, run: `$ ./script/sort-gui`
     -    2. A list of google trends url's will be displayed at the bottom if everything went well.
    @@ README.md: be accepted. If it involves code, please also write tests for it.
     -    7. The script makes some basic verifications. If there was some problem, it should be easily visible in the output
     -      1. If you have more than 1 tool with the same name, a warning will appear: `======= WARNING: THERE ARE DUPLICATED GUIS =======`
     -      2. If you are using the same `order` value for more than 1 tool, a warning will appear: `======= WARNING: THERE ARE DUPLICATED ORDERS (value: <VALUE>) =======`
    --
    ++The [list of GUI clients](https://git-scm.com/downloads/guis) has been constructed by the community for a long time. If you want to add another tool you'll need to follow a few steps:
    +
     -## FAQ
    --
    --While setting the repo if you find any error, check if it's a known issue and the corresponding solution bellow.
    -+1. Add a new `.md` file with the GUI client details: https://github.com/git/git-scm.com/tree/main/content/data/guis
    ++1. Add a new `.md` file with the GUI client details: data/guis
     +    1. The fields need to be enclosed within `---` lines
     +    2. The fields `name`, `project_url`, `price`, `license` should be very straightforward to fill.
     +    3. The field `image_tag` corresponds to the path of the image of the tool (should start with `images/guis/`).
     +    4. `platforms` is a list of at least 1 platform in which the tool is supported. The possibilities are: `Windows`, `Mac`, `Linux`, `Android`, and `iOS`
    -+    5. `order` can be filled with the biggest number already existing, plus 1 (Adding to the bottom - this will be covered in the following steps). This is the only field whose value should _not_ be enclosed in double-quote characters.
    -+    6. `trend_name` is an optional field that can be used for helping sorting the clients (also covered in the next steps)
    ++    5. `order` can be filled with the biggest number already existing, plus 1 (this number determines the order in which the GUIs are rendered). This is the only field whose value should _not_ be enclosed in double-quote characters.
    ++    6. `trend_name` is an optional field that can be used for helping sorting the clients.
    --### An error occurred while installing pg (1.2.3), and Bundler cannot continue.
    +-While setting the repo if you find any error, check if it's a known issue and the corresponding solution bellow.
     +2. Add the image to `static/images/guis/<GUI_CLIENT_NAME>@2x.png` and `static/images/guis/<GUI_CLIENT_NAME>.png` making sure the aspect ratio matches a 588:332 image.
    +-### An error occurred while installing pg (1.2.3), and Bundler cannot continue.
    ++## Useful links
    +
     -If you got this error when running `bundle install`, then you need to install postgresql on your OS. Check [this stackoverflow topic](https://stackoverflow.com/questions/52339221/rails-gem-error-while-installing-pg-1-1-3-and-bundler-cannot-continue) for more details.
    -+## Useful link regarding working with Hugo
    ++### Hugo (static site generator)
     +* https://gohugo.io/
     +* https://gohugo.io/content-management/shortcodes/
 18:  ae9a0d82e =  14:  e53e639b5 Move stylesheets to `assets/sass/`
 19:  faab827f4 !  15:  11ab6559e Move the images to `static/images/`
    @@ public/images/guis/git-glint@2x.png => static/images/guis/git-glint@2x.png
      ## public/images/guis/git-kraken.png => static/images/guis/git-kraken.png ##
    - ## public/images/guis/git-kraken@2x.png => static/images/guis/git-kraken@2x.png ##
    -
      ## public/images/guis/git2go.png => static/images/guis/git2go.png ##
      ## public/images/guis/git2go@2x.png => static/images/guis/git2go@2x.png ##
    @@ public/images/guis/gitklient.png => static/images/guis/gitklient.png
      ## public/images/guis/gitklient@2x.png => static/images/guis/gitklient@2x.png ##
    + ## public/images/guis/gitkraken-2024@2x.png => static/images/guis/gitkraken-2024@2x.png ##
    +
      ## public/images/guis/gitonic.png => static/images/guis/gitonic.png ##
      ## public/images/guis/gitonic@2x.png => static/images/guis/gitonic@2x.png ##
    @@ public/images/guis/gitx.png => static/images/guis/gitx.png
      ## public/images/guis/gitx@2x.png => static/images/guis/gitx@2x.png ##
    + ## public/images/guis/gk-cli-keifs@2x.png => static/images/guis/gk-cli-keifs@2x.png ##
    +
      ## public/images/guis/gmaster.png => static/images/guis/gmaster.png ##
      ## public/images/guis/gmaster@2x.png => static/images/guis/gmaster@2x.png ##
    @@ public/images/guis/pragmagit.png => static/images/guis/pragmagit.png
      ## public/images/guis/pragmagit@2x.png => static/images/guis/pragmagit@2x.png ##
    + ## public/images/guis/relagit.png => static/images/guis/relagit.png ##
    +
    + ## public/images/guis/relagit@2x.png => static/images/guis/relagit@2x.png ##
    +
      ## public/images/guis/repoz.png => static/images/guis/repoz.png ##
      ## public/images/guis/repoz@2x.png => static/images/guis/repoz@2x.png ##
    @@ public/images/guis/snailgit.png => static/images/guis/snailgit.png
      ## public/images/guis/snailgit@2x.png => static/images/guis/snailgit@2x.png ##
    + ## public/images/guis/sourcegit.png => static/images/guis/sourcegit.png ##
    +
    + ## public/images/guis/sourcegit@2x.png => static/images/guis/sourcegit@2x.png ##
    +
      ## public/images/guis/sourcetree.png => static/images/guis/sourcetree.png ##
      ## public/images/guis/sourcetree@2x.png => static/images/guis/sourcetree@2x.png ##
 20:  34a9aafae =  16:  b379241b1 Move Javascripts to the `static/` directory
 21:  1e7d57b39 =  17:  add38c971 Remove the layout for the error pages
 22:  3775916fa =  18:  0929494dc Migrate the primary layout to the Hugo world
 23:  03da9c6ea !  19:  817c49726 Adjust the layouts
    @@ layouts/_default/baseof.html
     -  <%= stylesheet_link_tag "application" %>
     -  <%= javascript_include_tag "modernize" %>
    -+  {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | resources.ToCSS | resources.Minify }}
    ++  {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | css.Sass | resources.Minify }}
     +  <link rel="stylesheet" href="{{ $style.RelPermalink }}">
     +  <script src="/js/modernizr.js"></script>
     +  <script src="/js/modernize.js"></script>
    @@ layouts/_default/baseof.html
     +        {{ partial "sidebar.html" . }}
              <div id="content">
     -          <%= yield %>
    -+          {{ .Content }}
    ++          {{ if (eq .Page.Path "/docs") }}
    ++            {{ partial "ref/index.html" . }}
    ++          {{ else }}
    ++            {{ .Content }}
    ++          {{ end }}
              </div>
            </div>
     -      <%= render partial: "shared/footer" %>
 24:  b721d0a20 =  20:  f2546f28f Remove some includable bits that won't be needed in the future
 25:  25b0a2330 =  21:  e465a5ae6 Move some includable bits to `layouts/`
 26:  c9fd7e391 =  22:  b4a98bee7 Make Javascript imports explicit
 27:  3b33bf0de =  23:  68e705c49 Inline the taglines on the top
 28:  79ffa61fc =  24:  3558e3570 Move the main index.html to the `content/` directory
 29:  1991470d8 =  25:  d23fd0543 Migrate the top-level page of the site to Hugo
 30:  e308db418 =  26:  4d5b58b58 CSS: quote URLs consistently
 31:  f55cae009 =  27:  37497edb9 Explicitly include 'normalize' in application CSS
 32:  61a203caf =  28:  4ac41f786 Use the base URL in partial .scss files
 33:  c3730b407 !  29:  6b2d46049 Add a script to update the latest Git version
    @@ Commit message
      ## Gemfile ##
     @@
    + # frozen_string_literal: true
      source "https://rubygems.org"
    -
    ++
     +gem "octokit"
      ## script/update-git-version.rb (new) ##
 34:  79e745184 =  30:  02abe2c42 update-git-version: skip -rc versions
 35:  0e8a57fab =  31:  115ebbebf Allow redirecting from `list` pages
 36:  ac13d76df !  32:  1118b8eaf Define and use sections in a flexible way
    @@ layouts/_default/baseof.html
     +{{ $section := "" }}
     +{{ if isset .Params "section" }}
     +{{ $section = .Params.section }}
    -+{{ else if isset .Page "Section" }}
    ++{{ else if (eq .Page.Path "/docs") }}
    ++{{ $section = "documentation" }}
    ++{{ else if (isset .Page "Section") }}
     +{{ $section = .Page.Section }}
    -+{{ else if or (eq .Page.Type "doc") (eq .Page.Type "docs") (eq .Page.Type "video") }}
    ++{{ else if (or (eq .Page.Type "doc") (eq .Page.Type "docs") (eq .Page.Type "video")) }}
     +{{ $section = "documentation" }}
    -+{{ else if isset .Page "Type" }}
    ++{{ else if (isset .Page "Type") }}
     +{{ $section = .Page.Type }}
     +{{ else }}
    -+{{ warnf "No section found in %s" (.File | jsonify) }}
    ++{{ warnf "No section found in %s" (.Page.Path | jsonify) }}
     +{{ end }}
     +{{ .Scratch.Set "section" $section }}
     +
 37:  894f2cef3 =  33:  fffd7a142 Move the "About" pages to `content/about/`
 38:  1f7ef75b7 !  34:  3f3dd9823 Convert 'About' pages to Hugo
    @@ content/about/trademark.html
      ## layouts/_default/baseof.html ##
     @@
    -       <div id="content-wrapper">
    -         {{ partial "sidebar.html" . }}
              <div id="content">
    --          {{ .Content }}
    -+          {{ if eq $section "about" }}
    -+	    <div id="main">
    +           {{ if (eq .Page.Path "/docs") }}
    +             {{ partial "ref/index.html" . }}
    ++          {{ else if eq $section "about" }}
    ++            <div id="main">
     +              <h1>About</h1>
     +
     +              <ol id="about-nav">
    @@ layouts/_default/baseof.html
     +              </ol>
     +              {{ .Content }}
     +            </div>
    -+          {{ else }}
    -+            {{ .Content }}
    -+          {{ end }}
    -         </div>
    -       </div>
    -       {{ partial "footer.html" . }}
    +           {{ else }}
    +             {{ .Content }}
    +           {{ end }}
      ## layouts/partials/sidebar.html ##
     @@
 39:  c139f5c1f =  35:  e743fb5cc about/trademark: add back old redirect
 40:  ded625e52 =  36:  b68954145 Install redirects for two `About` pages
 41:  14843ae22 =  37:  a5f2b8be5 Remove dynamic About navigation code
 42:  20e420d65 =  38:  100808034 Migrate the community page from the web app to the static site
 43:  dd4f0b599 !  39:  7a4f64d25 Adjust the community page
    @@ content/community/_index.html
      <div id="main">
        <h1> Community</h1>
    -   <h2> Mailing List</h2>
    -
    -   <p>
    --    General questions or comments for the Git community can be sent to the mailing list by using the email address <a href="mailto:git@vger.kernel.org">git@vger.kernel.org</a>.
    -+    General questions or comments for the Git community can be sent to the mailing list by using the email address <a href="mailto:git@vger.kernel.org">git@vger.kernel.org</a>.
    -   </p>
    --
    -+
    -   <p>
    -     <strong>If you wish to report any possible bug for Git, please use this mailing list as well.</strong>
    -   </p>
     @@
        <p>
          The <a href="https://git.github.io/">Git Developer Pages</a> have a <a href="https://git.github.io/Hacking-Git/">Hacking Git page</a> which lists useful development resources. They also have <a href="https://git.github.io/General-Application-Information/">information</a> for people applying to work on Git as part of programs like <a href="https://www.outreachy.org/">Outreachy</a> or the <a href="https://summerofcode.withgoogle.com/">Google Summer of Code</a>.
 44:  6625d81dd =  40:  6767a083e Adjust the GUI clients link in the sidebar
 45:  293e57ba7 =  41:  4a2f13d4b Move the GUI Clients page into the location expected by Hugo
 46:  45c7d59a6 !  42:  df1b23221 Adapt the GUI Clients page to Hugo
    @@ Commit message
         the JSON containing the list of GUI Clients into individual files inside
         `data/`.
    +    The files in `data/guis/` were written via:
    +
    +      git show HEAD:resources/guis.yml |
    +      ruby -e '
    +        # cannot use YAML.load because that would lose comments
    +        $filename = nil
    +        $content = ""
    +        def write
    +          return if $filename.nil?
    +          File.open($filename, "w") { |f| f.write("---\n#{$content}---\n") } unless $filename.nil?
    +          $filename = nil
    +          $content = ""
    +        end
    +        ARGF.each do |line|
    +          if line.start_with?("-") then
    +            write
    +            line[0] = " "
    +          end
    +          if line.start_with?("  ") then
    +            $filename = "data/guis/#{line[8..].chomp.gsub(/ *\(.*\)$/, "").gsub(/ /, "-").downcase}.yml" if line.start_with?("  name: ")
    +            $content += line[2..]
    +              .gsub(/^image_tag: /, "\\0images/")
    +              .gsub(/^url:/, "project_url:")
    +              .gsub(/([-:] )([^"]*[^"0-9][^"]*)\n$/, "\\1\"\\2\"\n")
    +              .gsub(/^-/, "  -")
    +            end
    +        end
    +        write
    +      '
    +
         Signed-off-by: Victoria Dye <vdye@github.com>
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    @@ data/guis/anchorpoint.yml (new)
      ## data/guis/aurees.yml (new) ##
     @@
     +---
    -+name: "Aurees"
    ++name: "Aurees (no longer under active development)"
     +project_url: "https://aurees.com/"
     +image_tag: "images/guis/aurees@2x.png"
     +platforms:
    @@ data/guis/gitahead.yml (new)
     +  - "Linux"
     +price: "Free"
     +license: "MIT"
    -+order: 13
    ++order: 17
     +---
      ## data/guis/gitatomic.yml (new) ##
    @@ data/guis/gitbreeze.yml (new)
     +  - "Linux"
     +price: "Free"
     +license: "Proprietary"
    -+trend_name: "git breeze"
     +order: 53
     +---
    @@ data/guis/gitdrive.yml (new)
     +image_tag: "images/guis/gitdrive@2x.png"
     +platforms:
     +  - "iOS"
    -+# No details what the $6.99 in-app purchase does, all it says is 'Unlimited Version'
    ++# No details what the $6.99 in-app purchase does, all it says is "Unlimited Version"
     +price: "Free / $6.99"
     +license: "Proprietary"
     +order: 37
    @@ data/guis/github-desktop.yml (new)
     +order: 1
     +---
    - ## data/guis/gitkraken.yml (new) ##
    + ## data/guis/gitkraken-cli.yml (new) ##
     @@
     +---
    -+name: "GitKraken"
    -+project_url: "https://www.gitkraken.com/"
    -+image_tag: "images/guis/git-kraken@2x.png"
    ++name: "GitKraken CLI"
    ++project_url: "https://www.gitkraken.com/cli"
    ++image_tag: "images/guis/gk-cli-keifs@2x.png"
    ++platforms:
    ++  - "Windows"
    ++  - "Mac"
    ++  - "Linux"
    ++price: "Free / $48.00+/user annually"
    ++license: "Proprietary"
    ++order: 55
    ++---
    +
    + ## data/guis/gitkraken-desktop.yml (new) ##
    +@@
    ++---
    ++name: "GitKraken Desktop"
    ++project_url: "https://www.gitkraken.com/git-client"
    ++image_tag: "images/guis/gitkraken-2024@2x.png"
     +platforms:
     +  - "Windows"
     +  - "Mac"
     +  - "Linux"
     +# Free tier only works with local and public repositories
    -+price: "Free / $59+/user annually"
    ++price: "Free / $48+/user annually"
     +license: "Proprietary"
     +order: 5
     +---
    @@ data/guis/gitnuro.yml (new)
     @@
     +---
     +name: "Gitnuro"
    -+project_url: "https://gitnuro.jetpackduba.com/"
    ++project_url: "https://gitnuro.com/"
     +image_tag: "images/guis/Gitnuro@2x.png"
     +platforms:
     +  - "Windows"
    @@ data/guis/gitui.yml (new)
     @@
     +---
     +name: "GitUI"
    -+project_url: "https://extrawurst.itch.io/gitui"
    ++project_url: "http://gitui.org"
     +image_tag: "images/guis/gitui@2x.png"
     +platforms:
     +  - "Windows"
    @@ data/guis/glint.yml (new)
     +  - "Windows"
     +  - "Mac"
     +  - "Linux"
    -+price: "Free (can only open up to two repositories at once)/ $35/user annually"
    ++# Free tier can only open up to two repositories at once
    ++price: "Free / $35/user annually"
     +license: "Proprietary"
     +trend_name: "git glint"
    -+order: 52
    ++order: 13
     +---
      ## data/guis/guitar.yml (new) ##
    @@ data/guis/pragma-git.yml (new)
     +license: "MIT"
     +trend_name: "pragma github"
     +order: 54
    ++---
    +
    + ## data/guis/relagit.yml (new) ##
    +@@
    ++---
    ++name: "RelaGit"
    ++project_url: "https://rela.dev"
    ++image_tag: "images/guis/relagit@2x.png"
    ++platforms:
    ++  - "Windows"
    ++  - "Mac"
    ++  - "Linux"
    ++price: "Free"
    ++license: "LGPL-3.0-or-later"
    ++order: 57
     +---
      ## data/guis/repoz.yml (new) ##
    @@ data/guis/snailgit.yml (new)
     +price: "Free (limited) / $9.99"
     +license: "Proprietary"
     +order: 32
    ++---
    +
    + ## data/guis/sourcegit.yml (new) ##
    +@@
    ++---
    ++name: "SourceGit"
    ++project_url: "https://sourcegit-scm.github.io"
    ++image_tag: "images/guis/sourcegit@2x.png"
    ++platforms:
    ++  - "Windows"
    ++  - "Mac"
    ++  - "Linux"
    ++price: "Free"
    ++license: "MIT"
    ++trend_name: "SourceGit"
    ++order: 56
     +---
      ## data/guis/sourcetree.yml (new) ##
    @@ resources/guis.yml (deleted)
     -  price: Free
     -  license: GNU GPL
     -  order: 4
    --- name: GitKraken
    --  url: https://www.gitkraken.com/
    --  image_tag: guis/git-kraken@2x.png
    +-- name: GitKraken Desktop
    +-  url: https://www.gitkraken.com/git-client
    +-  image_tag: guis/gitkraken-2024@2x.png
     -  platforms:
     -  - Windows
     -  - Mac
     -  - Linux
     -  # Free tier only works with local and public repositories
    --  price: Free / $59+/user annually
    +-  price: Free / $48+/user annually
     -  license: Proprietary
     -  order: 5
     -- name: Magit
    @@ resources/guis.yml (deleted)
     -  license: GNU GPL
     -  trend_name: Git giggle
     -  order: 27
    --- name: Aurees
    +-- name: Aurees (no longer under active development)
     -  url: https://aurees.com/
     -  image_tag: guis/aurees@2x.png
     -  platforms:
    @@ resources/guis.yml (deleted)
     -  - Linux
     -  price: Free
     -  license: MIT
    --  order: 13
    +-  order: 17
     -- name: Gittyup
     -  url: https://murmele.github.io/Gittyup/
     -  image_tag: guis/gittyup@2x.png
    @@ resources/guis.yml (deleted)
     -  license: Proprietary
     -  order: 42
     -- name: GitUI
    --  url: https://extrawurst.itch.io/gitui
    +-  url: http://gitui.org
     -  image_tag: guis/gitui@2x.png
     -  platforms:
     -  - Windows
    @@ resources/guis.yml (deleted)
     -  trend_name: gitonic
     -  order: 50
     -- name: Gitnuro
    --  url: https://gitnuro.jetpackduba.com/
    +-  url: https://gitnuro.com/
     -  image_tag: guis/Gitnuro@2x.png
     -  platforms:
     -    - Windows
    @@ resources/guis.yml (deleted)
     -  price: Free / $35/user annually
     -  license: Proprietary
     -  trend_name: git glint
    --  order: 52
    +-  order: 13
     -- name: GitBreeze
     -  url: https://gitbreeze.dev/
     -  image_tag: guis/gitbreeze@2x.png
    @@ resources/guis.yml (deleted)
     -  license: MIT
     -  trend_name: pragma github
     -  order: 54
    --
    +-- name: GitKraken CLI
    +-  url: https://www.gitkraken.com/cli
    +-  image_tag: guis/gk-cli-keifs@2x.png
    +-  platforms:
    +-  - Windows
    +-  - Mac
    +-  - Linux
    +-  price: Free / $48.00+/user annually
    +-  license: Proprietary
    +-  order: 55
    +-- name: SourceGit
    +-  url: https://sourcegit-scm.github.io
    +-  image_tag: guis/sourcegit@2x.png
    +-  platforms:
    +-    - Windows
    +-    - Mac
    +-    - Linux
    +-  price: Free
    +-  license: MIT
    +-  trend_name: SourceGit
    +-  order: 56
    +-- name: RelaGit
    +-  url: https://rela.dev
    +-  image_tag: guis/relagit@2x.png
    +-  platforms:
    +-    - Windows
    +-    - Mac
    +-    - Linux
    +-  price: Free
    +-  license: LGPL-3.0-or-later
    +-  order: 57
 47:  5b1e9a6de =  43:  ad17fe7f0 downloads/guis: adjust the Javascript for filtering
 48:  579cf46a3 =  44:  e6208d5ed Move logos page to the new location
 49:  99766e6fd =  45:  ea70f19f1 Migrate logos page
 50:  1a42a0157 =  46:  bc0d9409a Create base Downloads page
 51:  07c392bd8 =  47:  3d22dc37c Move the OS-specific Downloads pages into the location needed for Hugo
 52:  6d315ee76 =  48:  17c567019 Implement the `site-param` shortcode
 53:  c2cbcaf82 =  49:  1812362c7 Hugo-ify Linux & Mac download pages
 54:  d55dfbd55 !  50:  58a155e45 Add a script to store download data in `hugo.yml`
    @@ README.md: Or you can do it from GitHub (much slower) like this:
      ## hugo.yml ##
     @@ hugo.yml: params:
    -   latest_version: 2.43.0
    -   latest_relnote_url: https://raw.github.com/git/git/master/Documentation/RelNotes/2.43.0.txt
    -   latest_release_date: '2023-11-20'
    +   latest_version: 2.46.0
    +   latest_relnote_url: https://raw.github.com/git/git/master/Documentation/RelNotes/2.46.0.txt
    +   latest_release_date: '2024-07-29'
     +  macos_installer:
     +    url: https://sourceforge.net/projects/git-osx-installer/files/git-2.33.0-intel-universal-mavericks.dmg/download?use_mirror=autoselect
     +    version: 2.33.0
    @@ hugo.yml: params:
     +    filename: git-2.33.0-intel-universal-mavericks.dmg
     +  windows_installer:
     +    portable32:
    -+      filename: PortableGit-2.43.0-32-bit.7z.exe
    -+      release_date: '2023-11-20'
    -+      version: 2.43.0
    -+      url: https://github.com/git-for-windows/git/releases/download/v2.43.0.windows.1/PortableGit-2.43.0-32-bit.7z.exe
    ++      filename: PortableGit-2.45.2-32-bit.7z.exe
    ++      release_date: '2024-06-03'
    ++      version: 2.45.2
    ++      url: https://github.com/git-for-windows/git/releases/download/v2.45.2.windows.1/PortableGit-2.45.2-32-bit.7z.exe
     +    portable64:
    -+      filename: PortableGit-2.43.0-64-bit.7z.exe
    -+      release_date: '2023-11-20'
    -+      version: 2.43.0
    -+      url: https://github.com/git-for-windows/git/releases/download/v2.43.0.windows.1/PortableGit-2.43.0-64-bit.7z.exe
    ++      filename: PortableGit-2.45.2-64-bit.7z.exe
    ++      release_date: '2024-06-03'
    ++      version: 2.45.2
    ++      url: https://github.com/git-for-windows/git/releases/download/v2.45.2.windows.1/PortableGit-2.45.2-64-bit.7z.exe
     +    installer32:
    -+      filename: Git-2.43.0-32-bit.exe
    -+      release_date: '2023-11-20'
    -+      version: 2.43.0
    -+      url: https://github.com/git-for-windows/git/releases/download/v2.43.0.windows.1/Git-2.43.0-32-bit.exe
    ++      filename: Git-2.45.2-32-bit.exe
    ++      release_date: '2024-06-03'
    ++      version: 2.45.2
    ++      url: https://github.com/git-for-windows/git/releases/download/v2.45.2.windows.1/Git-2.45.2-32-bit.exe
     +    installer64:
    -+      filename: Git-2.43.0-64-bit.exe
    -+      release_date: '2023-11-20'
    -+      version: 2.43.0
    -+      url: https://github.com/git-for-windows/git/releases/download/v2.43.0.windows.1/Git-2.43.0-64-bit.exe
    ++      filename: Git-2.45.2-64-bit.exe
    ++      release_date: '2024-06-03'
    ++      version: 2.45.2
    ++      url: https://github.com/git-for-windows/git/releases/download/v2.45.2.windows.1/Git-2.45.2-64-bit.exe
      ## app/services/download_service.rb => script/update-download-data.rb ##
     @@
 55:  d58f20885 =  51:  cce79c1a0 Downloads (MacOS): show relative date
 56:  85d109ff6 =  52:  c0340b223 Convert the Windows downloads page to Hugo
 57:  5599b85a8 =  53:  5ae935edb download(win): white-space fix
 58:  6d1b7c7f3 =  54:  0b69e8c0c Add 'Downloads' page aliases
 59:  2f572e985 =  55:  8a9b19d16 Move the documentation landing page in place for Hugo
 60:  1a96900f1 =  56:  fc28e03d2 Migrate the 'Documentation' landing page to Hugo
 61:  2f53db947 <   -:  --------- Move the "references" landing page to `content/`
  -:  --------- >  57:  49631dde3 Move the "references" landing page to `layouts/partials/`, in preparation for using it as a Hugo partial template.
 62:  dd874d4c7 =  58:  59972c75f Move the files defining the documentation categories
 63:  60f601394 !  59:  5ecc96a9a Adjust the "references" page to Hugo
    @@ Commit message
         Signed-off-by: Victoria Dye <vdye@github.com>
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    - ## content/docs/_index.html ##
    -@@
    --<%- @section    = 'documentation' %>
    --<%- @subsection = 'reference' %>
    --<%- @page_title = "Git - Reference" %>
    -+---
    -+section: "documentation"
    -+subsection: "reference"
    -+title: "Git - Reference"
    -+url: /docs.html
    -+aliases:
    -+- /docs/index.html
    -+---
    -
    - <div id='main'>
    -   <h1>Reference</h1>
    -   <div class='callout quickref'>
    -     <p>
    -       Quick reference guides:
    --      <%= link_to "GitHub Cheat Sheet", "https://github.github.com/training-kit/" %>
    -+      <a href="https://github.github.com/training-kit/">GitHub Cheat Sheet</a>
    -       |
    --      <%= link_to "Visual Git Cheat Sheet", "https://ndpsoftware.com/git-cheatsheet.html" %>
    -+      <a href="https://ndpsoftware.com/git-cheatsheet.html">Visual Git Cheat Sheet</a>
    -     </p>
    -   </div>
    -   <div class="callout all-commands">
    -@@
    -   <div class='reference-menu'>
    -     <div class='two-column'>
    -       <div class='column-left'>
    --        <%= render 'shared/ref/setup' %>
    --        <%= render 'shared/ref/creating' %>
    --        <%= render 'shared/ref/snapshot' %>
    --        <%= render 'shared/ref/branching' %>
    --        <%= render 'shared/ref/sharing' %>
    --        <%= render 'shared/ref/inspection' %>
    --        <%= render 'shared/ref/patching' %>
    --        <%= render 'shared/ref/debugging' %>
    -+        {{< category "setup" >}}
    -+        {{< category "projects" >}}
    -+        {{< category "snapshotting" >}}
    -+        {{< category "branching" >}}
    -+        {{< category "sharing" >}}
    -+        {{< category "inspection" >}}
    -+        {{< category "patching" >}}
    -+        {{< category "debugging" >}}
    -       </div>
    -       <div class='column-right'>
    --        <%= render 'shared/ref/guides' %>
    --        <%= render 'shared/ref/email' %>
    --        <%= render 'shared/ref/external' %>
    --        <%= render 'shared/ref/admin' %>
    --        <%= render 'shared/ref/server' %>
    --        <%= render 'shared/ref/plumbing' %>
    -+        {{< category "guides" >}}
    -+        {{< category "email" >}}
    -+        {{< category "external" >}}
    -+        {{< category "admin" >}}
    -+        {{< category "server-admin" >}}
    -+        {{< category "plumbing" >}}
    -       </div>
    -     </div>
    -   </div>
    -
      ## data/doc_categories/admin.yml ##
     @@
     -<h3 class='admin'>Administration</h3>
    @@ data/doc_categories/setup.yml
     -  <li><%= man('git-config') %></li>
     -  <li><%= man('git-help') %></li>
     -  <li><%= man('git-bugreport') %></li>
    --  <%= link_to "Credential helpers", "/doc/credential-helpers", sidebar_link_options("credential-helpers") %>
    +-  <li><%= link_to "Credential helpers", "/doc/credential-helpers", sidebar_link_options("credential-helpers") %></li>
     -</ul>
     +---
     +category_id: "setup"
    @@ layouts/partials/ref/category.html (new)
     +<ul class='unstyled'>
     +  {{ range $doc := $category.commands }}
     +  <li>
    -+    <a href="{{ print "docs/" $doc.doc (cond (and (ne .Params.lang nil) (ne $doc.no_append_lang true)) (print "/" .Params.lang) "") }}">
    ++    <a href="{{ print "/docs/" $doc.doc (cond (and (ne $.Page.Params.lang nil) (ne $doc.no_append_lang true) (isset (index $.Site.Data.docs.pages $doc.doc) "languages") (isset (index $.Site.Data.docs.pages $doc.doc "languages") $.Page.Params.lang)) (print "/" $.Page.Params.lang) "") }}">
     +      {{ if (eq $doc.title nil) }}
     +        {{ replace $doc.doc "git-" "" }}
     +      {{ else }}
    @@ layouts/partials/ref/category.html (new)
     +  {{ end }}
     +</ul>
    - ## layouts/shortcodes/category.html (new) ##
    + ## layouts/partials/ref/index.html ##
     @@
    -+{{ .Scratch.Set "category_id" (.Get 0) }}
    -+{{ partial "ref/category.html" . }}
    +-<%- @section    = 'documentation' %>
    +-<%- @subsection = 'reference' %>
    +-<%- @page_title = "Git - Reference" %>
    +-
    ++{{ $context := . }}
    + <div id='main'>
    +   <h1>Reference</h1>
    +   <div class='callout quickref'>
    +     <p>
    +       Quick reference guides:
    +-      <%= link_to "GitHub Cheat Sheet", "https://github.github.com/training-kit/" %>
    ++      <a href="https://github.github.com/training-kit/">GitHub Cheat Sheet</a>
    +       |
    +-      <%= link_to "Visual Git Cheat Sheet", "https://ndpsoftware.com/git-cheatsheet.html" %>
    ++      <a href="https://ndpsoftware.com/git-cheatsheet.html">Visual Git Cheat Sheet</a>
    +     </p>
    +   </div>
    +   <div class="callout all-commands">
    +@@
    +   <div class='reference-menu'>
    +     <div class='two-column'>
    +       <div class='column-left'>
    +-        <%= render 'shared/ref/setup' %>
    +-        <%= render 'shared/ref/creating' %>
    +-        <%= render 'shared/ref/snapshot' %>
    +-        <%= render 'shared/ref/branching' %>
    +-        <%= render 'shared/ref/sharing' %>
    +-        <%= render 'shared/ref/inspection' %>
    +-        <%= render 'shared/ref/patching' %>
    +-        <%= render 'shared/ref/debugging' %>
    ++        {{ range slice "setup" "projects" "snapshotting" "branching" "sharing" "inspection" "patching" "debugging" }}
    ++        {{ $context.Scratch.Set "category_id" . }}
    ++        {{ partial "ref/category.html" $context }}
    ++        {{ end }}
    +       </div>
    +       <div class='column-right'>
    +-        <%= render 'shared/ref/guides' %>
    +-        <%= render 'shared/ref/email' %>
    +-        <%= render 'shared/ref/external' %>
    +-        <%= render 'shared/ref/admin' %>
    +-        <%= render 'shared/ref/server' %>
    +-        <%= render 'shared/ref/plumbing' %>
    ++        {{ range slice "guides" "email" "external" "admin" "server-admin" "plumbing" }}
    ++        {{ $context.Scratch.Set "category_id" . }}
    ++        {{ partial "ref/category.html" $context }}
    ++        {{ end }}
    +       </div>
    +     </div>
    +   </div>
 64:  a3b5464d2 =  60:  10c090f35 Migrate 'Videos' page
 65:  ebbf2dc93 !  61:  9bc68f7dd Migrate individual video pages
    @@ app/views/doc/watch.html.erb (deleted)
      ## layouts/_default/baseof.html ##
     @@
    -       <div id="content-wrapper">
    -         {{ partial "sidebar.html" . }}
              <div id="content">
    --          {{ if eq $section "about" }}
    -+          {{ if isset .Params "video_title" }}
    -+	    <div id="main">
    +           {{ if (eq .Page.Path "/docs") }}
    +             {{ partial "ref/index.html" . }}
    ++          {{ else if isset .Params "video_title" }}
    ++            <div id="main">
     +              <h1>{{ .Params.category }} Episode {{ .Params.episode }}</h1>
     +              <h2>{{ .Params.video_title }}</h2>
     +
    @@ layouts/_default/baseof.html
     +                <iframe src="https://player.vimeo.com/video/{{ .Params.ext_id }}?title=0&amp;byline=0&amp;portrait=0&amp;color=f14e32" width="635" height="360" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
     +              </div>
     +            </div>
    -+          {{ else if eq $section "about" }}
    - 	    <div id="main">
    +           {{ else if eq $section "about" }}
    +             <div id="main">
                    <h1>About</h1>
    -
 66:  d97d6afea !  62:  f0e2dad52 Migrate external links page to Hugo
    @@ app/views/doc/_ext_tutorials.erb => layouts/shortcodes/external/tutorials.html
              </p>
     -      </li>
     +      </li>
    -     </ul>
    -   </div>
    -   <div class='column-right'>
    +       <li>
    +         <h4><a href="https://www.python4data.science/en/latest/productive/git/index.html">Git for Data Science</a></h4>
    +         <p class='description'>
     @@
              <p class='description'>
                Learn the basics of Git and Version Control through detailed and easy to follow steps.
 67:  9549255ef =  63:  b4755928a Migrate the 'About this site' page to Hugo
 68:  4114ef8f4 =  64:  593d99e18 Migrate the Software Freedom Conservancy page
 69:  d05bb21d4 !  65:  976c2b5c1 Hugo'ify the "credential helpers" page
    @@ layouts/partials/ref/category.html
      <ul class='unstyled'>
        {{ range $doc := $category.commands }}
        <li>
    --    <a href="{{ print "docs/" $doc.doc (cond (and (ne .Params.lang nil) (ne $doc.no_append_lang true)) (print "/" .Params.lang) "") }}">
    +-    <a href="{{ print "/docs/" $doc.doc (cond (and (ne $.Page.Params.lang nil) (ne $doc.no_append_lang true) (isset (index $.Site.Data.docs.pages $doc.doc) "languages") (isset (index $.Site.Data.docs.pages $doc.doc "languages") $.Page.Params.lang)) (print "/" $.Page.Params.lang) "") }}">
     +    {{ if (eq $doc.doc "credential-helpers") }}
     +      <a href="doc/credential-helpers">
     +    {{ else }}
    -+      <a href="{{ print "docs/" $doc.doc (cond (and (ne .Params.lang nil) (ne $doc.no_append_lang true)) (print "/" .Params.lang) "") }}">
    ++      <a href="{{ print "/docs/" $doc.doc (cond (and (ne $.Page.Params.lang nil) (ne $doc.no_append_lang true) (isset (index $.Site.Data.docs.pages $doc.doc) "languages") (isset (index $.Site.Data.docs.pages $doc.doc "languages") $.Page.Params.lang)) (print "/" $.Page.Params.lang) "") }}">
     +    {{ end }}
            {{ if (eq $doc.title nil) }}
              {{ replace $doc.doc "git-" "" }}
 70:  00a42808e =  66:  ccecdf517 Move the search results page
 71:  5a183e236 !  67:  00d2b544f Implement client-side search using Pagefind
    @@ Commit message
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    + ## .github/workflows/ci.yml ##
    +@@ .github/workflows/ci.yml: jobs:
    +     steps:
    +     - uses: actions/checkout@v4
    +
    +-    - name: configure Hugo version
    ++    - name: configure Hugo and Pagefind version
    +       run: |
    +         set -x &&
    +         echo "HUGO_VERSION=$(sed -n 's/^ *hugo_version: *//p' <hugo.yml)" >>$GITHUB_ENV
    ++        echo "PAGEFIND_VERSION=$(sed -n 's/^ *pagefind_version: *//p' <hugo.yml)" >>$GITHUB_ENV
    +
    +     - name: install Hugo ${{ env.HUGO_VERSION }}
    +       run: |
    +@@ .github/workflows/ci.yml: jobs:
    +     - name: run Hugo to build the pages
    +       run: hugo
    +
    ++    - name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
    ++      run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public
    ++
    +     - name: build tar archive
    +       run: cd public && tar czvf ../pages.tar.gz *
    +
    +
      ## README.md ##
    -@@ README.md: You'll need the extended version of [Hugo](https://gohugo.io/). On Windows, we r
    +@@ README.md: To render the site locally, you'll need the extended version of [Hugo](https://g
      The site should be running on http://127.0.0.1:1313. Note that it may be advisable to do this in a sparse checkout that excludes large parts of `content/`, to speed up the rendering time.
    @@ README.md: You'll need the extended version of [Hugo](https://gohugo.io/). On Wi
      (TODO!)
     @@ README.md: The [list of GUI clients](https://git-scm.com/downloads/guis) has been construct
    -
    - 2. Add the image to `static/images/guis/<GUI_CLIENT_NAME>@2x.png` and `static/images/guis/<GUI_CLIENT_NAME>.png` making sure the aspect ratio matches a 588:332 image.
    -
    --## Useful link regarding working with Hugo
    -+## Useful links regarding working with Hugo and Pagefind
    -
      * https://gohugo.io/
      * https://gohugo.io/content-management/shortcodes/
    -+* https://pagefind.app/
    ++### Pagefind (client-side search)
    ++
    ++* https://pagefind.app/
    ++
      ## License
    + The source code for the site is licensed under the MIT license, which you can find in
      ## assets/sass/search.scss ##
     @@
    @@ content/search/results.html
     +  <ol class="full-search-results"></ol>
      </div>
    + ## hugo.yml ##
    +@@ hugo.yml: markup:
    +       unsafe: true
    + params:
    +   hugo_version: 0.134.1
    ++  pagefind_version: 1.1.1
    +   latest_version: 2.46.0
    +   latest_relnote_url: https://raw.github.com/git/git/master/Documentation/RelNotes/2.46.0.txt
    +   latest_release_date: '2024-07-29'
    +
      ## static/js/application.js ##
     @@ static/js/application.js: var Search = {
        selectedIndex: 0,
 72:  1c55e8995 !  68:  fd440ba66 Implement "live search" using Pagefind
    @@ static/js/application.js: var Search = {
     -    })
     +    (async () => {
     +      Search.pagefind = await import(`${baseURLPrefix}pagefind/pagefind.js`);
    -+      Search.pagefind.init();
    ++      await Search.pagefind.init();
     +      await callback();
     +    })().catch(console.log);
        },
 73:  99046a762 =  69:  03d63b90d Mark pages with the correct language
 74:  3d70c728b !  70:  a12108c7f Direct Pagefind what parts of the pages to index
    @@ layouts/_default/baseof.html
              {{ partial "sidebar.html" . }}
     -        <div id="content">
     +        <div id="content" data-pagefind-body>
    -           {{ if isset .Params "video_title" }}
    - 	    <div id="main">
    -               <h1>{{ .Params.category }} Episode {{ .Params.episode }}</h1>
    +           {{ if (eq .Page.Path "/docs") }}
    +             {{ partial "ref/index.html" . }}
    +           {{ else if isset .Params "video_title" }}
 75:  1b6bb3c10 =  71:  23433ad6d Live search: only load 10 results at a time
 76:  cd28ebbe1 !  72:  a688de393 Respect the language in non-live search
    @@ static/js/application.js: var Search = {
          }
          (async () => {
            Search.pagefind = await import(`${baseURLPrefix}pagefind/pagefind.js`);
    ++      const options = {}
     +      const language = this.getQueryValue('language');
    -+      if (language) Search.pagefind.options({language});
    -       Search.pagefind.init();
    ++      if (language) options.language = language;
    ++      await Search.pagefind.options(options);
    +       await Search.pagefind.init();
            await callback();
          })().catch(console.log);
 77:  338a2e6c0 !  73:  2a357df65 Use Pagefind's UI for the full search
    @@ layouts/_default/baseof.html
        <link href='/favicon.ico' rel='shortcut icon' type='image/x-icon'>
     +  {{ if eq $section "search" }}<link href="{{ relURL "pagefind/pagefind-ui.css" }}" rel="stylesheet">{{ end }}
    -   {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | resources.ToCSS | resources.Minify }}
    +   {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | css.Sass | resources.Minify }}
        <link rel="stylesheet" href="{{ $style.RelPermalink }}">
        <script src="/js/modernizr.js"></script>
    @@ layouts/partials/header.html
     +
     +  {{ if ne (.Scratch.Get "section") "search" }}
        <form id="search" action="/search/results">
    -     <input id="search-text" name="search" placeholder="Search entire site..." autocomplete="off" type="text" />
    +     <input id="search-text" name="search" placeholder="Type / to search entire site…" autocomplete="off" type="text" />
        </form>
        <div id="search-results"></div>
     +  {{ end }}
    @@ static/js/application.js: var Search = {
     -    this.initializeSearchIndex(async () => {
     -      const results = await Search.pagefind.search(searchTerm);
     -      if (!results || !results.results || !results.results.length) return;
    -+    new PagefindUI({ element: "#search-div", showSubResults: true, language });
    -
    +-
     -      const list = (await Promise.all(results.results.map(async e => {
     -        const result = await e.data();
     -        const href = result.url;
    @@ static/js/application.js: var Search = {
     -          <a class="url" href="${href}">${href}</a>
     -          <p>${result.excerpt}</p></li>`;
     -      }))).join('');
    --
    ++    new PagefindUI({
    ++      element: "#search-div",
    ++      showSubResults: true,
    ++      language
    ++    });
    +
     -      searchResultsElements[0].innerHTML = list || '<li>No results found</li>';
     -    })
     +    const searchTerm = this.getQueryValue('search');
  -:  --------- >  74:  c7ff8dad0 Add a helper script to run Pagefind
  -:  --------- >  75:  c32fdf3dc ci: verify the order of the search results
 78:  8147144aa =  76:  26ff8cb61 Implement the `relurl` shortcode
 79:  61d1610e4 !  77:  c8a20dffc Use relative URLs
    @@ content/community/_index.html: aliases:
        <h2> Contributing to Git </h2>
        <p>
    --    The <a href="https://github.com/git/git/tree/master/Documentation">Documentation directory</a> in the Git source code has several files of interest to developers who are looking to help contribute. After reading the <a href="https://github.com/git/git/blob/master/Documentation/CodingGuidelines">coding guidelines</a>, you can learn <a href="/docs/SubmittingPatches">how to submit patches</a>. If you are just starting out, you can read the <a href="/docs/MyFirstContribution">My First Contribution tutorial</a>. For those looking to get more deeply involved, there is a <a href="https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt">howto for Git maintainers</a>.
    -+    The <a href="https://github.com/git/git/tree/master/Documentation">Documentation directory</a> in the Git source code has several files of interest to developers who are looking to help contribute. After reading the <a href="https://github.com/git/git/blob/master/Documentation/CodingGuidelines">coding guidelines</a>, you can learn <a href="{{< relurl "docs/SubmittingPatches" >}}">how to submit patches</a>. If you are just starting out, you can read the <a href="{{< relurl "docs/MyFirstContribution" >}}">My First Contribution tutorial</a>. For those looking to get more deeply involved, there is a <a href="https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt">howto for Git maintainers</a>.
    +-    The <a href="https://github.com/git/git/tree/master/Documentation">Documentation directory</a> in the Git source code has several files of interest to developers who are looking to help contribute. After reading the <a href="https://github.com/git/git/blob/master/Documentation/CodingGuidelines">coding guidelines</a> and <a href="https://github.com/git/git/blob/master/CODE_OF_CONDUCT.md">code of conduct</a>, you can learn <a href="/docs/SubmittingPatches">how to submit patches</a>. If you are just starting out, you can read the <a href="/docs/MyFirstContribution">My First Contribution tutorial</a>. For those looking to get more deeply involved, there is a <a href="https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt">howto for Git maintainers</a>.
    ++    The <a href="https://github.com/git/git/tree/master/Documentation">Documentation directory</a> in the Git source code has several files of interest to developers who are looking to help contribute. After reading the <a href="https://github.com/git/git/blob/master/Documentation/CodingGuidelines">coding guidelines</a> and <a href="https://github.com/git/git/blob/master/CODE_OF_CONDUCT.md">code of conduct</a>, you can learn <a href="{{< relurl "docs/SubmittingPatches" >}}">how to submit patches</a>. If you are just starting out, you can read the <a href="{{< relurl "docs/MyFirstContribution" >}}">My First Contribution tutorial</a>. For those looking to get more deeply involved, there is a <a href="https://github.com/git/git/blob/master/Documentation/howto/maintain-git.txt">howto for Git maintainers</a>.
        </p>
        <p>
    @@ content/doc/_index.html: aliases:
        </div>
    - ## content/docs/_index.html ##
    -@@ content/docs/_index.html: aliases:
    -     </p>
    -   </div>
    -   <div class="callout all-commands">
    --    <a href="/docs/git#_git_commands">Complete list of all commands</a>
    -+    <a href="{{< relurl "docs/git#_git_commands" >}}">Complete list of all commands</a>
    -   </div>
    -   <div class='reference-menu'>
    -     <div class='two-column'>
    -
      ## content/downloads/_index.html ##
     @@ content/downloads/_index.html: aliases:
              <table class="binaries">
    @@ layouts/_default/baseof.html
     +  <link href="{{ relURL "favicon.ico" }}" rel='shortcut icon' type='image/x-icon'>
        {{ if eq $section "search" }}<link href="{{ relURL "pagefind/pagefind-ui.css" }}" rel="stylesheet">{{ end }}
    -   {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | resources.ToCSS | resources.Minify }}
    +   {{ $style := resources.Get "sass/application.scss" | resources.ExecuteAsTemplate "application.scss" . | css.Sass | resources.Minify }}
        <link rel="stylesheet" href="{{ $style.RelPermalink }}">
     -  <script src="/js/modernizr.js"></script>
     -  <script src="/js/modernize.js"></script>
    @@ layouts/partials/header.html
        {{ if ne (.Scratch.Get "section") "search" }}
     -  <form id="search" action="/search/results">
     +  <form id="search" action="{{ relURL "search/results" }}">
    -     <input id="search-text" name="search" placeholder="Search entire site..." autocomplete="off" type="text" />
    +     <input id="search-text" name="search" placeholder="Type / to search entire site…" autocomplete="off" type="text" />
        </form>
        <div id="search-results"></div>
    @@ layouts/partials/ref/category.html
     -      <a href="doc/credential-helpers">
     +      <a href="{{ relURL "doc/credential-helpers" }}">
          {{ else }}
    --      <a href="{{ print "docs/" $doc.doc (cond (and (ne .Params.lang nil) (ne $doc.no_append_lang true)) (print "/" .Params.lang) "") }}">
    -+      <a href="{{ relURL (print "docs/" $doc.doc (cond (and (ne .Params.lang nil) (ne $doc.no_append_lang true)) (print "/" .Params.lang) "")) }}">
    +-      <a href="{{ print "/docs/" $doc.doc (cond (and (ne $.Page.Params.lang nil) (ne $doc.no_append_lang true) (isset (index $.Site.Data.docs.pages $doc.doc) "languages") (isset (index $.Site.Data.docs.pages $doc.doc "languages") $.Page.Params.lang)) (print "/" $.Page.Params.lang) "") }}">
    ++      <a href="{{ relURL (print "docs/" $doc.doc (cond (and (ne $.Page.Params.lang nil) (ne $doc.no_append_lang true) (isset (index $.Site.Data.docs.pages $doc.doc) "languages") (isset (index $.Site.Data.docs.pages $doc.doc "languages") $.Page.Params.lang)) (print "/" $.Page.Params.lang) "")) }}">
          {{ end }}
            {{ if (eq $doc.title nil) }}
              {{ replace $doc.doc "git-" "" }}
    + ## layouts/partials/ref/index.html ##
    +@@
    +     </p>
    +   </div>
    +   <div class="callout all-commands">
    +-    <a href="/docs/git#_git_commands">Complete list of all commands</a>
    ++    <a href="{{ relURL "docs/git#_git_commands" }}">Complete list of all commands</a>
    +   </div>
    +   <div class='reference-menu'>
    +     <div class='two-column'>
    +
      ## layouts/partials/sidebar.html ##
     @@
        <nav>
 80:  199ad72ad =  78:  943375d44 Accommodate for base URLs other than `/`
 81:  905f3d1b1 !  79:  312d96605 ci: ensure that there are only relative URLs
    @@ .github/workflows/ci.yml: jobs:
     +          exit 1
     +        fi
     +
    -     - name: run Pagefind to build the search index
    -       run: npx -y pagefind --site public
    +     - name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
    +       run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public
 82:  efccaef38 =  80:  f54c9f6af Drop the Rails version of the ProGit book
 83:  046ae69cf =  81:  05158a866 Move the ProGit book related Ruby code to a new location
 84:  1b6c1ef2c !  82:  34b4c74b9 Transmogrify the Rake script to generate the Pro Git book sections
    @@ README.md: Now you need to get the latest downloads for the downloads pages:
     +If you have 2FA enabled, you'll need to create a [Personal Access Token](https://help.github.com/articles/creating-an-access-token-for-command-line-use/).
     -Alternatively, you can get the book content from a repository on your computer by specifying the path in the `GENPATH` environment variable to the `local_genbook2` target:
    -+If you want to build the book for all available languages, just skip the language code:
    ++If you want to build the book for all available languages, just omit the language code parameter:
     -    $ GENLANG=fr GENPATH=../progit2-fr rake local_genbook2
     +    $ ruby ./script/update-book2.rb
    @@ README.md: Now you need to get the latest downloads for the downloads pages:
      ## Contributing
    + ## hugo.yml ##
    +@@ hugo.yml: markup:
    +   goldmark:
    +     renderer:
    +       unsafe: true
    ++module:
    ++  mounts:
    ++  - source: content
    ++    target: content
    ++  - source: static
    ++    target: static
    ++  - source: data
    ++    target: data
    ++  - source: external/book/data/book
    ++    target: data/book
    ++  - source: external/book/content/book
    ++    target: content/book
    ++  - source: external/book/static/book
    ++    target: static/book
    + params:
    +   hugo_version: 0.134.1
    +   pagefind_version: 1.1.1
    +
      ## layouts/_default/baseof.html ##
     @@
            </div> <!-- .inner -->
    @@ script/book.rb: class Book < ApplicationRecord
     +    }
     +  end
     +
    ++  def content_note
    ++    "### DO NOT EDIT! Generated by script/update-book2.rb"
    ++  end
    ++
    ++  def wrap_front_matter(front_matter)
    ++    "#{front_matter.to_yaml.sub("---\n", "---\n#{self.content_note}\n")}---\n"
    ++  end
    ++
     +  def absolute_path(path)
    -+    File.absolute_path(File.join(File.dirname(__FILE__), "..", "content", "book", @language_code, "v#{@edition}", path))
    ++    File.absolute_path(File.join(File.dirname(__FILE__), "..", "external", "book", "content", "book", @language_code, "v#{@edition}", path))
     +  end
     +
     +  def removeAllFiles
    @@ script/book.rb: class Book < ApplicationRecord
     +    return front_matter
     +  end
     +
    ++  def wrap_front_matter(front_matter)
    ++    @book.wrap_front_matter(front_matter)
    ++  end
    ++
     +  attr_accessor :title
     +  attr_accessor :chapter_type
     +  attr_accessor :chapter_number
    @@ script/book.rb: class Book < ApplicationRecord
     +  def save
     +    return if self.slug.nil?
     +
    -+    front_matter = "#{self.front_matter.to_yaml}\n---\n"
    -+
     +    path = self.absolute_path(self.slug)
     +    FileUtils.mkdir_p(File.dirname(path))
     +    File.open("#{path}.html", 'w') do |file|
    -+      file.write(front_matter)
    ++      file.write(@chapter.wrap_front_matter(self.front_matter))
     +      file.write(self.html.strip)
     +    end
        end
 85:  cdb7f4377 !  83:  803869e33 update-book2: adjust navigation
    @@ layouts/shortcodes/previous-section.html (new)
      ## script/book.rb ##
     @@ script/book.rb: class Book
    -     File.absolute_path(File.join(File.dirname(__FILE__), "..", "content", "book", @language_code, "v#{@edition}", path))
    +     File.absolute_path(File.join(File.dirname(__FILE__), "..", "external", "book", "content", "book", @language_code, "v#{@edition}", path))
        end
     +  def relative_url(path)
 86:  44c163eaa !  84:  f6734888d update-book2: also include the images
    @@ Commit message
      ## script/book.rb ##
     @@ script/book.rb: class Book
    -     }
    +     "#{front_matter.to_yaml.sub("---\n", "---\n#{self.content_note}\n")}---\n"
        end
     -  def absolute_path(path)
    --    File.absolute_path(File.join(File.dirname(__FILE__), "..", "content", "book", @language_code, "v#{@edition}", path))
    +-    File.absolute_path(File.join(File.dirname(__FILE__), "..", "external", "book", "content", "book", @language_code, "v#{@edition}", path))
     +  def absolute_path(path, top_level = "content")
    -+    File.absolute_path(File.join(File.dirname(__FILE__), "..", top_level, "book", @language_code, "v#{@edition}", path))
    ++    File.absolute_path(File.join(File.dirname(__FILE__), "..", "external", "book", top_level, "book", @language_code, "v#{@edition}", path))
        end
        def relative_url(path)
 87:  7d0edee4b =  85:  dcff0ad8c update-book2: show the chapter/section numbers in the titles
 88:  eb4e84dfd !  86:  91531feee Generate the front page of the Pro Git book
    @@ script/book.rb: class Book
     +      front_matter["book"]["ebook_mobi"] = self.ebook_mobi
     +    end
     +
    -+    front_matter = "#{front_matter.to_yaml}\n---"
    -+
     +    path = self.absolute_path("_index.html")
     +    FileUtils.mkdir_p(File.dirname(path))
     +    File.open(path, 'w') do |file|
    -+      file.write(front_matter)
    ++      file.write(self.wrap_front_matter(front_matter))
     +    end
     +
    ++    front_matter = { "redirect_to" => "book/#{@language_code}/v#{@edition}" }
     +    File.open(self.absolute_path("../_index.html"), 'w') do |file|
    -+      file.write("---\nredirect_to: \"book/#{@language_code}/v#{@edition}\"\n---\n")
    ++      file.write(self.wrap_front_matter(front_matter))
     +    end
    ++
     +    if @language_code == "en"
     +      File.open(self.absolute_path("../../_index.html"), 'w') do |file|
    -+        file.write("---\nredirect_to: \"book/#{@language_code}/v#{@edition}\"\n---\n")
    ++        file.write(self.wrap_front_matter(front_matter))
     +      end
     +    end
     +  end
 89:  714b23ec6 !  87:  419be615f book: also generate the URLs for the downloadable formats of the book
    @@ Commit message
         ... but do this only for the English version, as the other versions are
         not available in downloadable formats.
    +    As the idea is to run this script in a GitHub workflow on a shallow
    +    clone, let's be prepared for the situation where the tip commit of the
    +    default branch is _not_ (yet?) tagged and therefore no tag is present in
    +    the clone. In this situation, let's just go ahead and fetch all tags,
    +    then move along and update the URLs accordingly.
    +
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      ## script/update-book2.rb ##
    @@ script/update-book2.rb: def local_genbook2(language_code, worktree_path)
          end
     +    if language_code == 'en'
     +      latest_tag = `git -C "#{worktree_path}" for-each-ref --format '%(refname:short)' --sort=-committerdate --count=1 refs/tags/`.chomp
    ++      if latest_tag.empty?
    ++        puts "No tag found in #{worktree_path}, trying to fetch tags"
    ++        latest_tag = `git -C "#{worktree_path}" fetch --tags origin && git -C "#{worktree_path}" for-each-ref --format '%(refname:short)' --sort=-committerdate --count=1 refs/tags/`.chomp
    ++	raise "Still no tags in #{worktree_path}?" if latest_tag.empty?
    ++      end
     +      book.ebook_pdf = "https://github.com/progit/progit2/releases/download/#{latest_tag}/progit.pdf"
     +      book.ebook_epub = "https://github.com/progit/progit2/releases/download/#{latest_tag}/progit.epub"
     +      book.ebook_mobi = "https://github.com/progit/progit2/releases/download/#{latest_tag}/progit.mobi"
 90:  68e96e524 <   -:  --------- Record SHA of the Pro Git book even when generating it locally
  -:  --------- >  88:  406254667 update-book2.rb: gracefully warn about missing files, but continue
  -:  --------- >  89:  9f7d416f5 Record SHA of the Pro Git book even when generating it locally
 91:  915329bc2 !  90:  e6e83c127 Pro Git book: populate the `Chapters` drop-down
    @@ layouts/_default/baseof.html
            {{ partial "footer.html" . }}
          </div> <!-- #content-wrapper -->
        {{ else if (isset .Params "book") }}
    -+    {{ .Scratch.Set "book" (index .Site.Data (print "book-" .Params.book.language_code)) }}
    ++    {{ .Scratch.Set "book" (index .Site.Data.book .Params.book.language_code) }}
          <div class="inner">
            <div id="content-wrapper">
              {{ partial "sidebar.html" . }}
    @@ script/book.rb: class Book
     +      "language_code" => @language_code,
     +      "chapters" => chapters
     +    }
    -+    path = File.join(File.dirname(__FILE__), "..", "data", "book-#{@language_code}.yml")
    ++    path = File.join(File.dirname(__FILE__), "..", "external", "book", "data", "book", "#{@language_code}.yml")
     +    FileUtils.mkdir_p(File.dirname(path))
     +    File.open(path, 'w') do |file|
    ++      file.write("#{self.content_note}\n")
     +      file.write(data.to_yaml.strip)
     +    end
     +
 92:  6156ecfdd =  91:  1a51fcc94 book: keep colons and other special characters in URLs
 93:  2dd98a59e =  92:  e492c9c63 book: fix translated labels
 94:  289ee87b7 !  93:  7f3b191a3 book: generate cross-references correctly
    @@ script/book.rb: class Book
        def front_matter
     @@ script/book.rb: class Book
    -         file.write("---\nredirect_to: \"book/#{@language_code}/v#{@edition}\"\n---\n")
    +         file.write(self.wrap_front_matter(front_matter))
            end
          end
     +
    @@ script/book.rb: class Book
     +    @xrefs.each do |id_xref, section|
     +      path = self.absolute_path("ch00/#{id_xref}.html")
     +      relurl = "#{section.relative_url(nil)}##{id_xref}"
    ++      front_matter = { "redirect_to" => relurl }
     +      File.open(path, 'w') do |file|
    -+        file.write("---\nredirect_to: \"#{relurl}\"\n---\n")
    ++        file.write(self.wrap_front_matter(front_matter))
     +      end
     +    end
        end
 95:  ded726a6c =  94:  4a31646bc book-tl: work around an incorrect image reference
 96:  4078053ef =  95:  01b1dd71f book: work around lagging translations' image paths
 97:  8f4d3d8a3 !  96:  731b6b686 book(xrefs): maintain question marks that are part of the URL path
    @@ Commit message
         sections' titles; These need to be (URL-)encoded in the URL and not be
         mistaken for the separator between path and GET parameter(s).
    +    While at it, also install redirects for URLs containing a question mark
    +    so that incorrect URLs that contain a literal question mark (which is
    +    interpreted as separator between the URL path and the `GET`
    +    parameter(s)) redirect to the correct ones.
    +
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      ## script/book.rb ##
    @@ script/book.rb: class Section
          }
          if @slug =~ /:|[^-A-Za-z0-9_]/
     -      front_matter["url"] = self.relative_url(@slug)
    -+      front_matter["url"] = self.relative_url(@slug).gsub(/%3F/, '?')
    ++      relative_url = self.relative_url(@slug)
    ++      front_matter["url"] = "/#{relative_url.gsub(/%3F/, '?')}.html"
    ++      if relative_url =~ /%3F/
    ++        front_matter["aliases"] = [
    ++	  "/#{relative_url.gsub(/%3F.*/, '')}.html"
    ++	]
    ++      end
          end
          return front_matter
        end
 98:  b99c5d1d5 !  97:  0343bf0ab book: redirect missing xrefs to the English book
    @@ script/book.rb: class Book
          @xrefs.each do |id_xref, section|
            path = self.absolute_path("ch00/#{id_xref}.html")
     -      relurl = "#{section.relative_url(nil)}##{id_xref}"
    +-      front_matter = { "redirect_to" => relurl }
     +      if section == 'redirect-to-en'
     +        url = "book/en/v2/ch00/#{id_xref}"
     +      else
     +        url = "#{section.relative_url(nil)}##{id_xref}"
     +      end
    ++      front_matter = { "redirect_to" => url }
            File.open(path, 'w') do |file|
    --        file.write("---\nredirect_to: \"#{relurl}\"\n---\n")
    -+        file.write("---\nredirect_to: \"#{url}\"\n---\n")
    +         file.write(self.wrap_front_matter(front_matter))
            end
    -     end
    -   end
      ## script/update-book2.rb ##
     @@ script/update-book2.rb: def genbook(language_code, &get_content)
 99:  c3f723764 =  98:  8b30c3748 book: handle footnotes gracefully
100:  58dcc1d03 =  99:  bedb26d29 book: fix a couple of broken redirects
101:  be1163d71 = 100:  f890fa149 docs: prepare for `index.rake` to become a proper Ruby script
102:  73280317a ! 101:  990ac016c Turn what used to be the `index.rake` file into a proper Ruby script
    @@ README.md: Note that this will take about 7 times as long, and the site will not
      Similarly, you can also populate the localized man pages. From a local clone of https://github.com/jnavila/git-html-l10n :
    + ## hugo.yml ##
    +@@ hugo.yml: module:
    +     target: content/book
    +   - source: external/book/static/book
    +     target: static/book
    ++  - source: external/docs/data
    ++    target: data
    ++  - source: external/docs/content
    ++    target: content
    + params:
    +   hugo_version: 0.134.1
    +   pagefind_version: 1.1.1
    +
      ## layouts/_default/baseof.html ##
     @@
            </div>
    @@ script/update-docs.rb
     +require_relative "version"
     +
     +SITE_ROOT = File.join(File.expand_path(File.dirname(__FILE__)), '../')
    -+DATA_FILE = "#{SITE_ROOT}data/docs.yml"
    ++DOCS_INDEX_FILE = "#{SITE_ROOT}external/docs/content/docs/_index.html"
    ++DATA_FILE = "#{SITE_ROOT}external/docs/data/docs.yml"
     +
     +def read_data
     +  if File.exists?(DATA_FILE)
    @@ script/update-docs.rb
      def make_asciidoc(content)
        Asciidoctor::Document.new(content,
    +@@ script/update-docs.rb: def make_asciidoc(content)
    +                             doctype: "book")
    + end
    +
    ++def content_note
    ++  "### DO NOT EDIT! Generated by script/update-docs.rb\n"
    ++end
    ++
    ++def wrap_front_matter(front_matter)
    ++  "#{front_matter.to_yaml.sub("---\n", "---\n#{content_note}\n")}---\n"
    ++end
    ++
    + def expand_l10n(path, content, get_f_content, categories)
    +   content.gsub!(/include::(\S+)\.txt/) do |line|
    +     line.gsub!("include::", "")
     @@ script/update-docs.rb: def expand_l10n(path, content, get_f_content, categories)
      end
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
              next if doc_limit && path !~ /#{doc_limit}/
     -        file = DocFile.where(name: docname).first_or_create
    -+        doc_path = "#{SITE_ROOT}content/docs/#{docname}"
    ++        doc_path = "#{SITE_ROOT}external/docs/content/docs/#{docname}"
              puts "   build: #{docname}"
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +
     +        FileUtils.mkdir_p(doc_path)
     +        File.open("#{doc_path}/#{tagname}.html", "w") do |out|
    -+          out.write("#{front_matter.to_yaml}\n---\n", out)
    ++          out.write(wrap_front_matter(front_matter))
     +          out.write(html, out)
     +        end
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +          FileUtils.mkdir_p(File.dirname(doc_path))
     +          File.open("#{doc_path}.html", "w") do |out|
     +            front_matter["aliases"] = ["/docs/#{docname}/index.html"]
    -+            out.write("#{front_matter.to_yaml}\n---\n")
    ++            out.write(wrap_front_matter(front_matter))
     +            out.write(html)
     +          end
     +        end
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     -    Rails.cache.write("latest-version", Version.latest_version.name)
     +    data["latest-version"] = tagname
     +  end
    ++
    ++  front_matter = {
    ++    "section" => "documentation",
    ++    "subsection" => "reference",
    ++    "title" => "Git - Reference",
    ++    "url" => "/docs.html",
    ++    "aliases" => ["/docs/index.html"]
    ++  }
    ++
    ++  File.open(DOCS_INDEX_FILE, "w") do |out|
    ++    out.write(wrap_front_matter(front_matter))
    ++  end
    ++
     +  File.open(DATA_FILE, "w") do |out|
     +    YAML.dump(data, out)
        end
103:  e34a4915b = 102:  c7eb81c73 Highlight the `Reference` nav item for manual pages
104:  42a90c22a ! 103:  5d5bfe56a update-docs: install redirects for the various versions
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
              FileUtils.mkdir_p(doc_path)
     -        File.open("#{doc_path}/#{tagname}.html", "w") do |out|
    --          out.write("#{front_matter.to_yaml}\n---\n", out)
    +-          out.write(wrap_front_matter(front_matter))
     -          out.write(html, out)
     +        front_matter_with_redirects = front_matter.clone
     +        front_matter_with_redirects["aliases"] =
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +            ["/docs/#{docname}/#{v}/index.html"]
     +          end
     +        File.open("#{doc_path}/#{doc_versions[changed_in]}.html", "w") do |out|
    -+          out.write("#{front_matter_with_redirects.to_yaml}\n---\n")
    ++          out.write(wrap_front_matter(front_matter_with_redirects))
     +          out.write(html)
              end
105:  6a68ca809 ! 104:  1fc1b2d4b Show the different versions of the manual pages
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     -            anchor += "-1" while ids.include?(anchor)
     -            ids.add(anchor)
     -            "<dt class=\"hdlist1\" id=\"#{anchor}\"> <a class=\"anchor\" href=\"##{anchor}\"></a>#{$1} </dt>"
    -+        if !File.exists?("#{SITE_ROOT}_generated-asciidoc/#{asciidoc_sha}")
    -+          FileUtils.mkdir_p("#{SITE_ROOT}_generated-asciidoc")
    -+          File.open("#{SITE_ROOT}_generated-asciidoc/#{asciidoc_sha}", "w") do |out|
    ++        if !File.exists?("#{SITE_ROOT}external/docs/asciidoc/#{asciidoc_sha}")
    ++          FileUtils.mkdir_p("#{SITE_ROOT}external/docs/asciidoc")
    ++          File.open("#{SITE_ROOT}external/docs/asciidoc/#{asciidoc_sha}", "w") do |out|
     +            out.write(content)
                end
              end
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +            page_data["diff-cache"] = {} if !page_data["diff-cache"]
     +            cached_diff = page_data["diff-cache"]["#{pre_sha}..#{post_sha}"]
     +            if !cached_diff
    -+              pre_content = File.read("#{SITE_ROOT}_generated-asciidoc/#{pre_sha}")
    -+              post_content = File.read("#{SITE_ROOT}_generated-asciidoc/#{post_sha}")
    ++              pre_content = File.read("#{SITE_ROOT}external/docs/asciidoc/#{pre_sha}")
    ++              post_content = File.read("#{SITE_ROOT}external/docs/asciidoc/#{post_sha}")
     +              cached_diff = page_data["diff-cache"]["#{pre_sha}..#{post_sha}"] = diff(pre_content, post_content)
     +            end
     +            page_versions.unshift({
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     -    data["latest-version"] = tagname
     +    data["latest-version"] = version if !data["latest-version"] || Version.version_to_num(data["latest-version"]) < Version.version_to_num(version)
        end
    -   File.open(DATA_FILE, "w") do |out|
    -     YAML.dump(data, out)
    +
    +   front_matter = {
106:  1ab0c772b ! 105:  9953afacd search: exclude older manual page versions
    @@ layouts/_default/baseof.html
     -          <div id="main" data-pagefind-body>
     +          <!-- older manual page versions are less interesting and need to be excluded from the search -->
    -+          {{ $include_in_search := (or (not (isset .Params "docname")) (isset .Params "latest-changes")) }}
    ++          {{ $include_in_search := (or (not (isset .Params "docname")) (isset .Params "latest-changes") (isset .Params "lang")) }}
     +          <div id="main"{{ if $include_in_search }} data-pagefind-body{{ end }}>
                  {{ .Content }}
                </div>
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
                File.open("#{doc_path}.html", "w") do |out|
     +            front_matter["latest-changes"] = version
                  front_matter["aliases"] = ["/docs/#{docname}/index.html"]
    -             out.write("#{front_matter.to_yaml}\n---\n")
    +             out.write(wrap_front_matter(front_matter))
                  out.write(html)
107:  c1948441b ! 106:  ec44e3010 search: offer the section as a filter
    @@ layouts/_default/baseof.html
     @@
                <!-- older manual page versions are less interesting and need to be excluded from the search -->
    -           {{ $include_in_search := (or (not (isset .Params "docname")) (isset .Params "latest-changes")) }}
    +           {{ $include_in_search := (or (not (isset .Params "docname")) (isset .Params "latest-changes") (isset .Params "lang")) }}
     -          <div id="main"{{ if $include_in_search }} data-pagefind-body{{ end }}>
     +          <div id="main"{{ if $include_in_search }} data-pagefind-filter="category:{{ $section }}" data-pagefind-body{{ end }}>
                  {{ .Content }}
    @@ layouts/_default/baseof.html
              {{ partial "sidebar.html" . }}
     -        <div id="content" data-pagefind-body>
     +        <div id="content" data-pagefind-filter="category:{{ $section }}" data-pagefind-body>
    -           {{ if isset .Params "video_title" }}
    - 	    <div id="main">
    -               <h1>{{ .Params.category }} Episode {{ .Params.episode }}</h1>
    +           {{ if (eq .Page.Path "/docs") }}
    +             {{ partial "ref/index.html" . }}
    +           {{ else if isset .Params "video_title" }}
108:  ad3768be5 <   -:  --------- search: give the manual pages' titles maximal weight
  -:  --------- > 107:  5e6f328ce search: give the manual pages' titles maximal weight
109:  05a15df77 ! 108:  25906cd41 search: reintroduce the categories in the live search
    @@ Commit message
         We do this by specifically adding metadata indicating the category of
         manual pages as well as for the sections of the book (for details, see
         https://pagefind.app/docs/metadata/). Then, we use that information to
    -    sort the results as they arrive into the correct table row (and
    -    displaying it when necessary, as the empty rows are initially hidden).
    +    sort the results into the correct table row. Due to the `async` nature
    +    of the search results coming in, we need to be
    +    careful to process them in order when doing that.
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    @@ layouts/_default/baseof.html
                    2nd Edition
                  </span>
                </div>
    --          <div id="main" data-pagefind-filter="category:{{ $section }}" data-pagefind-body class="book edition2">
    -+          <div id="main" data-pagefind-filter="category:{{ $section }}" data-pagefind-meta="category:Book" data-pagefind-body class="book edition2">
    +-          <div id="main" data-pagefind-filter="category:{{ $section }}" data-pagefind-weight="0.05" data-pagefind-body class="book edition2">
    ++          <div id="main" data-pagefind-filter="category:{{ $section }}" data-pagefind-meta="category:Book" data-pagefind-weight="0.05" data-pagefind-body class="book edition2">
                  <h1>{{ .Params.book.section.cs_number }} {{ .Params.book.chapter.title }} - {{ .Params.book.section.title }}</h1>
                  <div>
                    {{ .Content }}
     @@
                <!-- older manual page versions are less interesting and need to be excluded from the search -->
    -           {{ $include_in_search := (or (not (isset .Params "docname")) (isset .Params "latest-changes")) }}
    --          <div id="main"{{ if $include_in_search }} data-pagefind-filter="category:{{ $section }}" data-pagefind-body{{ end }}>
    -+          <div id="main"{{ if $include_in_search }} data-pagefind-filter="category:{{ $section }}" data-pagefind-meta="category:Reference" data-pagefind-body{{ end }}>
    -             {{ safe.HTML (replaceRE "(?s:>NAME</h2>.*?<p)" "$0 data-pagefind-weight=\"7\"" .Content) }}
    -           </div>
    -         </div>
    +           {{ $include_in_search := (or (not (isset .Params "docname")) (isset .Params "latest-changes") (isset .Params "lang")) }}
    +-          <div id="main"{{ if $include_in_search }} data-pagefind-filter="category:{{ $section }}" data-pagefind-weight="0.05" data-pagefind-body{{ end }}>
    ++          <div id="main"{{ if $include_in_search }} data-pagefind-filter="category:{{ $section }}" data-pagefind-meta="category:Reference" data-pagefind-weight="0.05" data-pagefind-body{{ end }}>
    +             {{ .Content }}
    +             {{ $match := findRESubmatch "(?s)>NAME</h2>.*?<p[^>]*>(git-)?([^ ]+)" .Content 1 }}
    +             {{ if (eq ($match | len) 1) }}
      ## static/js/application.js ##
     @@ static/js/application.js: var Search = {
    @@ static/js/application.js: var Search = {
     +
                const chunkLength = 10;
                let displayCount = 0;
    ++
    ++          const categorizeResult = (i) => {
    ++            while (i < displayCount && typeof results.results[i].data === 'object') {
    ++              const result = results.results[i++];
    ++              if (result.data.meta.category === 'Reference') {
    ++                if (ulReference.children().length === 0) ulReference.parent().parent().css("display", "table-row")
    ++                ulReference.append(result.li)
    ++              } else if (result.data.meta.category === 'Book') {
    ++                if (ulBook.children().length === 0) ulBook.parent().parent().css("display", "table-row")
    ++                ulBook.append(result.li)
    ++              }
    ++            }
    ++          };
    ++
                const loadResultsChunk = () => {
    -@@ static/js/application.js: var Search = {
    +             if (loadButton.loading || displayCount >= results.results.length) return;
    +
    +             loadButton.loading = true;
    +             const n = displayCount + chunkLength;
    +             while (displayCount < n) {
    +-              const li = $("<li><a>&hellip;</a></li>");
    +-              li.insertBefore(loadButton);
    ++              const result = results.results[displayCount]
    ++              result.li = $("<li><a>&hellip;</a></li>");
    ++              result.li.insertBefore(loadButton);
    +
                    // load the result lazily
    -               (async () => {
    -                 const result = await results.results[displayCount].data();
    -+                if (result.meta.category === 'Reference') {
    -+                  if (ulReference.children().length === 0) ulReference.parent().parent().css("display", "table-row")
    -+                  ulReference.append(li)
    -+                } else if (result.meta.category === 'Book') {
    -+                  if (ulBook.children().length === 0) ulBook.parent().parent().css("display", "table-row")
    -+                  ulBook.append(li)
    -+                }
    -                 li.html(`<a href = "${result.url}">${result.meta.title}</a>`);
    -               })().catch(console.log);
    +-              (async () => {
    +-                const result = await results.results[displayCount].data();
    +-                li.html(`<a href = "${result.url}">${result.meta.title}</a>`);
    +-              })().catch(console.log);
    ++              (async (i) => {
    ++                result.data = await results.results[displayCount].data();
    ++                if (!i || typeof results.results[i - 1].data === 'object') categorizeResult(i);
    ++                result.li.html(`<a href = "${result.data.url}">${result.data.meta.title}</a>`);
    ++              })(displayCount).catch((err) => {
    ++                console.log(err);
    ++                result.li.html(`<i>Error loading result</i>`);
    ++              });
    +               if (++displayCount >= results.results.length) {
    +                 loadButton.remove();
110:  49f58f182 <   -:  --------- search: show manual pages' short title in the live search
  -:  --------- > 109:  4f239bc08 search: show manual pages' short title in the live search
  -:  --------- > 110:  347830d09 search: link to "pretty" URLs
111:  6fc0f8e9e = 111:  cb68f3611 docs: handle multiple `linkgit:` in the same line
112:  1f9dc5079 = 112:  687183039 docs: correctly handle `{litdd}` in `linkgit` values
113:  44dca56fe = 113:  2b061bf40 docs: handle `gitlink:` gracfully
114:  702ce1711 = 114:  149872c37 docs: handle double colon in `linkgit::` gracefully
115:  26a84723f = 115:  25d0d5463 docs: handle `linkgit:curl[1]` gracefully
116:  9cb3ff53a = 116:  187843aba Allow `redirect_to` with fully-qualified URLs
117:  412391840 ! 117:  f20b1c76d docs: add fall-back redirects for unrendered pages
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +      # that are not populated. Let's redirect to the source files in the
     +      # git/git repository.
     +      check_paths.each do |path|
    -+        doc_path = "#{SITE_ROOT}content/#{path}.html"
    ++        doc_path = "#{SITE_ROOT}external/docs/content/#{path}.html"
     +        if !File.exists?(doc_path)
     +          type = 'blob'
     +          target = path.sub(/^docs\//, '')
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +          front_matter = { "redirect_to" => "https://github.com/git/git/#{type}/HEAD/Documentation/#{target}" } # ltrim `docs/`
     +          FileUtils.mkdir_p(File.dirname(doc_path))
     +          File.open(doc_path, "w") do |out|
    -+            out.write("#{front_matter.to_yaml}\n---\n")
    ++            out.write(wrap_front_matter(front_matter))
     +          end
     +        end
     +      end
118:  55c7b5f48 ! 118:  fcbfb1f1d docs: work around a `link:` bug in older versions
    @@ Metadata
     Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      ## Commit message ##
    -    docs: work around a `link:` bug in older versions
    +    docs: work around `link:`/`linkgit:` bugs in older versions
         Since we want to build older Git versions' manual pages, too, we have to
         work around bugs since fixed.
    @@ script/update-docs.rb: def index_doc(filter_tags, doc_list, get_content)
     +        # Handle erroneous `link:api-trace2.txt`, see 4945f046c7f5 (api docs:
     +        # link to html version of api-trace2, 2022-09-16)
     +        content.gsub!(/link:api-trace2.txt/, 'link:api-trace2.html')
    ++	# Handle `linkgit:git-config.txt` mistake, fixed in ad52148a7d0
    ++	# (Documentation: fix broken linkgit to git-config, 2016-03-21)
    ++        content.gsub!(/linkgit:git-config.txt/, 'linkgit:git-config')
              content.gsub!(/link:(?:technical\/)?(\S*?)\.html(\#\S*?)?\[(.*?)\]/m, "link:/docs/\\1\\2[\\3]")
              asciidoc = make_asciidoc(content)
119:  a7344a9d2 ! 119:  5b1d692eb Migrate the translated manual pages to the Hugo world
    @@ layouts/_default/baseof.html
              <div id="content">
                <div id='reference-version'>
     +            {{ partial "ref/languages.html" . }}
    -+            {{ partialCached "ref/topics.html" . }}
    ++            {{ partial "ref/topics.html" . }}
                  {{ partial "ref/versions.html" . }}
                </div>
    @@ script/update-docs.rb: def index_l10n_doc(filter_tags, doc_list, get_content)
            path = File.basename(full_path, ".txt")
     -      file = DocFile.where(name: path).first_or_create
    -+      doc_path = "#{SITE_ROOT}content/docs/#{path}"
    ++      doc_path = "#{SITE_ROOT}external/docs/content/docs/#{path}"
            puts "   build: #{path} for #{lang}"
    @@ script/update-docs.rb: def index_l10n_doc(filter_tags, doc_list, get_content)
     -        html.gsub!(/linkgit:(\S+?)\[(\d+)\]/) do |line|
     -          x = /^linkgit:(\S+?)\[(\d+)\]/.match(line)
     -          "<a href='/docs/#{x[1].gsub(/&#x2d;/, '-')}/#{lang}'>#{x[1]}[#{x[2]}]</a>"
    -+      if !File.exists?("#{SITE_ROOT}_generated-asciidoc/#{asciidoc_sha}")
    -+        FileUtils.mkdir_p("#{SITE_ROOT}_generated-asciidoc")
    -+        File.open("#{SITE_ROOT}_generated-asciidoc/#{asciidoc_sha}", "w") do |out|
    ++      if !File.exists?("#{SITE_ROOT}external/docs/asciidoc/#{asciidoc_sha}")
    ++        FileUtils.mkdir_p("#{SITE_ROOT}external/docs/asciidoc")
    ++        File.open("#{SITE_ROOT}external/docs/asciidoc/#{asciidoc_sha}", "w") do |out|
     +          out.write(content)
              end
     -        # HTML anchor on hdlist1 (i.e. command options)
    @@ script/update-docs.rb: def index_l10n_doc(filter_tags, doc_list, get_content)
     +
     +      FileUtils.mkdir_p(doc_path)
     +      File.open("#{doc_path}/#{lang}.html", "w") do |out|
    -+        out.write("#{front_matter.to_yaml}\n---\n")
    ++        out.write(wrap_front_matter(front_matter))
     +        out.write(html)
     +      end
     +
120:  dd2d5016c = 120:  b88d2bef6 docs(zh_HANS-CN): a trailing "full stop" character is not part of a URL
121:  9c93bfdec <   -:  --------- redirect_to: keep the anchor, if any was specified
  -:  --------- > 121:  968e6e25b redirect_to/aliases: keep the anchor, if any was specified
122:  6ef6dbe2f ! 122:  f2e7a5ffa docs(translated): add redirects for missing files
    @@ script/update-docs.rb: def index_l10n_doc(filter_tags, doc_list, get_content)
     +      end
            asciidoc = make_asciidoc(content)
            asciidoc_sha = Digest::SHA1.hexdigest(asciidoc.source)
    -       if !File.exists?("#{SITE_ROOT}_generated-asciidoc/#{asciidoc_sha}")
    +       if !File.exists?("#{SITE_ROOT}external/docs/asciidoc/#{asciidoc_sha}")
     @@ script/update-docs.rb: def index_l10n_doc(filter_tags, doc_list, get_content)
            html.gsub!(/linkgit:(\S+?)\[(\d+)\]/) do |line|
              x = /^linkgit:(\S+?)\[(\d+)\]/.match(line)
    @@ script/update-docs.rb: def index_l10n_doc(filter_tags, doc_list, get_content)
     +    # translated manual pages may point to other translated manual pages that do
     +    # not exist. In these cases, redirect to the English version.
     +    check_paths.each do |path|
    -+      doc_path = "#{SITE_ROOT}content/#{path}.html"
    ++      doc_path = "#{SITE_ROOT}external/docs/content/#{path}.html"
     +      if !File.exists?(doc_path)
     +        front_matter = { "redirect_to" => "#{path.sub(/\/[^\/]*$/, '')}" } # rtrim `/<lang>`
     +        FileUtils.mkdir_p(File.dirname(doc_path))
     +        File.open(doc_path, "w") do |out|
    -+          out.write("#{front_matter.to_yaml}\n---\n")
    ++          out.write(wrap_front_matter(front_matter))
     +        end
     +      end
     +    end
123:  e7e038a32 ! 123:  19d453f94 ci: update the books via a GitHub workflow
    @@ Commit message
         This information is then used by a scheduled workflow to determine what
         needs to be updated (if anything) and then performing that task.
    +    When GitHub workflows push new changes, they cannot trigger other GitHub
    +    workflows (to avoid infinite loops). Therefore, this new GitHub workflow
    +    not only synchronizes the books, but also builds the site and deploys
    +    it.
    +
    +    Note: The code to build the site and to deploy it is provided in a
    +    custom Action, to make it reusable. It will come in handy over the next
    +    commits, where other GitHub workflows are added that likewise need
    +    to synchronize changes that desire a site rebuild & deployment.
    +
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
      ## .github/actions/deploy-to-github-pages/action.yml (new) ##
    @@ .github/actions/deploy-to-github-pages/action.yml (new)
     +
     +    - name: setup GitHub Pages
     +      id: pages
    -+      uses: actions/configure-pages@v3
    ++      uses: actions/configure-pages@v5
     +
    -+    - name: install Hugo
    -+      env:
    -+        HUGO_VERSION: 0.120.3
    ++    - name: configure Hugo and Pagefind version
    ++      shell: bash
    ++      run: |
    ++        set -x &&
    ++        echo "HUGO_VERSION=$(sed -n 's/^ *hugo_version: *//p' <hugo.yml)" >>$GITHUB_ENV
    ++        echo "PAGEFIND_VERSION=$(sed -n 's/^ *pagefind_version: *//p' <hugo.yml)" >>$GITHUB_ENV
    ++
    ++    - name: install Hugo ${{ env.HUGO_VERSION }}
     +      shell: bash
     +      run: |
     +        set -x &&
    @@ .github/actions/deploy-to-github-pages/action.yml (new)
     +      env:
     +        HUGO_RELATIVEURLS: false
     +      shell: bash
    -+      run: hugo --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
    ++      run: hugo config && hugo --minify --baseURL "${{ steps.pages.outputs.base_url }}/"
     +
    -+    - name: run Pagefind to build the search index
    ++    - name: run Pagefind ${{ env.PAGEFIND_VERSION }} to build the search index
     +      shell: bash
    -+      run: npx -y pagefind --site public
    ++      run: npx -y pagefind@${{ env.PAGEFIND_VERSION }} --site public
     +
     +    - name: upload GitHub Pages artifact
    -+      uses: actions/upload-pages-artifact@v2
    ++      uses: actions/upload-pages-artifact@v3
     +      with:
     +        path: ./public
     +
     +    - name: deploy
     +      id: deploy
    -+      uses: actions/deploy-pages@v2
    ++      uses: actions/deploy-pages@v4
      ## .github/workflows/update-book.yml (new) ##
     @@
    @@ .github/workflows/update-book.yml (new)
     +  check-for-updates:
     +    runs-on: ubuntu-latest
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +        with:
     +          sparse-checkout: |
    -+            _sync_state
    ++            external/book/sync
     +            script
    -+      - uses: actions/github-script@v6
    ++      - uses: actions/github-script@v7
     +        id: get-pending
     +        with:
     +          script: |
    @@ .github/workflows/update-book.yml (new)
     +        language: ${{ fromJson(needs.check-for-updates.outputs.matrix) }}
     +      fail-fast: false
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +        with:
     +          sparse-checkout: |
    -+            _sync_state
     +            script
    -+            data
    -+            content/book/${{ matrix.language.lang }}
    -+            static/book/${{ matrix.language.lang }}
    ++            external/book/sync
    ++            external/book/data
    ++            external/book/content/book/${{ matrix.language.lang }}
    ++            external/book/static/book/${{ matrix.language.lang }}
     +      - name: clone ${{ matrix.language.repository }}
     +        run: |
     +          printf '%s\n' /progit-clone/ /vendor >>.git/info/exclude &&
    @@ .github/workflows/update-book.yml (new)
     +      - name: commit changes
     +        run: |
     +          # record the commit hash
    -+          mkdir -p _sync_state &&
    -+          git -C progit-clone rev-parse HEAD >_sync_state/book-${{ matrix.language.lang }}.sha &&
    ++          mkdir -p external/book/sync &&
    ++          git -C progit-clone rev-parse HEAD >external/book/sync/book-${{ matrix.language.lang }}.sha &&
     +
     +          # commit it all
    -+          git add -A \
    -+            _sync_state \
    -+            data/book-${{ matrix.language.lang }}.yml \
    -+            content/book &&
    -+          # there might be images
    -+          if test -d static/book
    -+          then
    -+            git add -A static/book
    -+          fi &&
    ++          git add -A external/book &&
     +          git -c user.name=${{ github.actor }} \
     +            -c user.email=${{ github.actor }}@noreply.github.com \
     +            commit -m 'book: update ${{ matrix.language.lang }}' \
    @@ .github/workflows/update-book.yml (new)
     +        run: |
     +          git branch -m book-${{ matrix.language.lang }}
     +          git bundle create ${{ matrix.language.lang }}.bundle refs/remotes/origin/${{ github.ref_name }}..book-${{ matrix.language.lang }}
    -+      - uses: actions/upload-artifact@v3
    ++      - uses: actions/upload-artifact@v4
     +        with:
     +          name: bundle-${{ matrix.language.lang }}
     +          path: ${{ matrix.language.lang }}.bundle
    @@ .github/workflows/update-book.yml (new)
     +      url: ${{ steps.deploy.outputs.url }}
     +    runs-on: ubuntu-latest
     +    steps:
    -+      - uses: actions/checkout@v3
    -+      - uses: actions/download-artifact@v3
    ++      - uses: actions/checkout@v4
    ++      - uses: actions/download-artifact@v4
     +      - name: apply updates
     +        id: apply
     +        run: |
    @@ .github/workflows/update-book.yml (new)
     +        id: deploy
     +        uses: ./.github/actions/deploy-to-github-pages
    - ## _sync_state/.gitignore (new) ##
    -
      ## script/ci-helper.js (new) ##
     @@
     +const fs = require('fs')
    @@ script/ci-helper.js (new)
     +  const result = []
     +  for (const lang of Object.keys(books)) {
     +    try {
    -+      const localSha = await getFileContents(`_sync_state/book-${lang}.sha`)
    ++      const localSha = await getFileContents(`external/book/sync/book-${lang}.sha`)
     +
     +      const [owner, repo] = books[lang].split('/')
     +      const { data: { default_branch: remoteDefaultBranch } } =
124:  c7c4d347d ! 124:  1ace765fc Update the download data via a GitHub workflow
    @@ .github/workflows/update-download-data.yml (new)
     +      name: github-pages
     +      url: ${{ steps.deploy.outputs.url }}
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +        with:
     +          sparse-checkout: |
     +            .github/actions
    -+            _sync_state
     +            script
     +      - name: ruby setup
     +        uses: ruby/setup-ruby@v1
    @@ .github/workflows/update-download-data.yml (new)
     +          git \
     +            -c user.name=${{ github.actor }} \
     +            -c user.email=${{ github.actor }}@noreply.github.com \
    -+            commit -m "$commit_message" -- hugo.yml
    ++            commit -m "$commit_message" \
    ++              -m 'Updated via the `update-download-data.yml` GitHub workflow.' \
    ++              -- hugo.yml
     +      - name: verify that there are no uncommitted changes
     +        run: |
     +          git update-index --refresh &&
125:  3bd0e53a3 ! 125:  775c1d220 update-book: allow force-rebuilding
    @@ .github/workflows/update-book.yml: jobs:
                  const { getPendingBookUpdates } = require('./script/ci-helper.js')
     -            const pending = await getPendingBookUpdates(github)
    -+            const pending = await getPendingBookUpdates(github, ${{ inputs.force-rebuild }})
    ++            const pending = await getPendingBookUpdates(github, ${{ inputs.force-rebuild == true }})
                  // an empty matrix is invalid and makes the workflow run fail, unfortunately
                  return pending.length ? pending : ['']
            - name: ruby setup
    @@ script/ci-helper.js: const getAllBooks = async () => {
        const result = []
        for (const lang of Object.keys(books)) {
     -    try {
    --      const localSha = await getFileContents(`_sync_state/book-${lang}.sha`)
    +-      const localSha = await getFileContents(`external/book/sync/book-${lang}.sha`)
     +    if (!forceRebuild) {
     +      try {
    -+        const localSha = await getFileContents(`_sync_state/book-${lang}.sha`)
    ++        const localSha = await getFileContents(`external/book/sync/book-${lang}.sha`)
     -      const [owner, repo] = books[lang].split('/')
     -      const { data: { default_branch: remoteDefaultBranch } } =
126:  835105745 ! 126:  2193295a0 Add a workflow to update the Git version and manual pages
    @@ .github/workflows/update-git-version-and-manual-pages.yml (new)
     +      name: github-pages
     +      url: ${{ steps.deploy.outputs.url }}
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +        with:
     +          sparse-checkout: |
     +            .github/actions
    -+            _sync_state
     +            script
     +      - name: ruby setup
     +        uses: ruby/setup-ruby@v1
    @@ .github/workflows/update-git-version-and-manual-pages.yml (new)
     +          git \
     +            -c user.name=${{ github.actor }} \
     +            -c user.email=${{ github.actor }}@noreply.github.com \
    -+            commit -m "Update git-version ($version)" -- hugo.yml
    ++            commit -m "Update git-version ($version)" \
    ++              -m 'Updated via the `update-git-version-and-manual-pages.yml` GitHub workflow.' \
    ++              -- hugo.yml
     +      - name: prepare worktree
     +        if: steps.commit.outputs.result != ''
     +        run: git sparse-checkout disable
    @@ .github/workflows/update-git-version-and-manual-pages.yml (new)
     +      - name: commit manual pages
     +        if: steps.commit.outputs.result != ''
     +        run: |
    -+          git add -A _generated-asciidoc data/docs.yml content/docs &&
    ++          git add -A external/docs &&
     +          git \
     +            -c user.name=${{ github.actor }} \
     +            -c user.email=${{ github.actor }}@noreply.github.com \
    -+            commit -m "Update manual pages (${{ steps.commit.outputs.result }})"
    ++            commit -m "Update manual pages (${{ steps.commit.outputs.result }})" \
    ++              -m 'Updated via the `update-git-version-and-manual-pages.yml` GitHub workflow.'
     +      - name: verify that there are no uncommitted changes
     +        run: |
     +          git update-index --refresh &&
127:  15a7705ad ! 127:  93af22a96 update-manual-pages: optionally force a complete rebuild
    @@ .github/workflows/update-git-version-and-manual-pages.yml: name: Synchronize wit
          # check daily for updates
          - cron: '37 17 * * *'
     @@ .github/workflows/update-git-version-and-manual-pages.yml: jobs:
    -             -c user.email=${{ github.actor }}@noreply.github.com \
    -             commit -m "Update git-version ($version)" -- hugo.yml
    +               -m 'Updated via the `update-git-version-and-manual-pages.yml` GitHub workflow.' \
    +               -- hugo.yml
            - name: prepare worktree
     -        if: steps.commit.outputs.result != ''
    -+        if: steps.commit.outputs.result != '' || inputs.force-rebuild
    ++        if: steps.commit.outputs.result != '' || inputs.force-rebuild == true
              run: git sparse-checkout disable
            - name: clone git.git
     -        if: steps.commit.outputs.result != ''
    -+        if: steps.commit.outputs.result != '' || inputs.force-rebuild
    ++        if: steps.commit.outputs.result != '' || inputs.force-rebuild == true
              run: git clone --bare https://github.com/git/git '${{ runner.temp }}/git'
            - name: update manual pages
     -        if: steps.commit.outputs.result != ''
     -        run: bundle exec ruby script/update-docs.rb '${{ runner.temp }}/git' en
    -+        if: steps.commit.outputs.result != '' || inputs.force-rebuild
    ++        if: steps.commit.outputs.result != '' || inputs.force-rebuild == true
     +        run: |
    -+          test false = '${{ inputs.force-rebuild }}' || export RERUN=true
    ++          if test true = '${{ inputs.force-rebuild }}'
    ++          then
    ++            export RERUN=true
    ++          fi
     +          bundle exec ruby script/update-docs.rb '${{ runner.temp }}/git' en
            - name: commit manual pages
     -        if: steps.commit.outputs.result != ''
     +        id: manual-pages
    -+        if: steps.commit.outputs.result != '' || inputs.force-rebuild
    ++        if: steps.commit.outputs.result != '' || inputs.force-rebuild == true
              run: |
    -           git add -A _generated-asciidoc data/docs.yml content/docs &&
    -+          if test false != '${{ inputs.force-rebuild }}' && git diff-index --cached --quiet HEAD --
    +           git add -A external/docs &&
    ++          if test true = '${{ inputs.force-rebuild }}' && git diff-index --cached --quiet HEAD --
     +          then
     +            echo '::notice::A manual pages rebuild was requested but resulted in no changes' >&2
     +            exit 0
    @@ .github/workflows/update-git-version-and-manual-pages.yml: jobs:
                git \
                  -c user.name=${{ github.actor }} \
                  -c user.email=${{ github.actor }}@noreply.github.com \
    --            commit -m "Update manual pages (${{ steps.commit.outputs.result }})"
    -+            commit -m "Update manual pages (${version:-manually forced rebuild})" &&
    +-            commit -m "Update manual pages (${{ steps.commit.outputs.result }})" \
    +-              -m 'Updated via the `update-git-version-and-manual-pages.yml` GitHub workflow.'
    ++            commit -m "Update manual pages (${version:-manually forced rebuild})" \
    ++              -m 'Updated via the `update-git-version-and-manual-pages.yml` GitHub workflow.' &&
     +          echo "result=modified" >>$GITHUB_OUTPUT
            - name: verify that there are no uncommitted changes
              run: |
128:  21f13a6ef ! 128:  9ccb08bb2 Add a GitHub workflow to deploy `gh-pages` to GitHub Pages
    @@ .github/workflows/deploy.yml (new)
     +      name: github-pages
     +      url: ${{ steps.deploy.outputs.url }}
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +      - name: deploy to GitHub Pages
     +        id: deploy
     +        uses: ./.github/actions/deploy-to-github-pages
129:  6a53852f0 ! 129:  ac51259a9 Add a GitHub workflow to update the translated manual pages regularly
    @@ .github/workflows/update-translated-manual-pages.yml (new)
     +  check-for-updates:
     +    runs-on: ubuntu-latest
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +        with:
     +          sparse-checkout: |
    -+            _sync_state
    ++            external/docs/sync
     +            script
    -+      - uses: actions/github-script@v6
    ++      - uses: actions/github-script@v7
     +        id: get-pending
     +        with:
     +          script: |
    @@ .github/workflows/update-translated-manual-pages.yml (new)
     +      up-to-date: ${{ steps.get-pending.outputs.result }}
     +  update-translated-manual-pages:
     +    needs: [check-for-updates]
    -+    if: inputs.force-rebuild || needs.check-for-updates.outputs.up-to-date == 'false'
    ++    if: inputs.force-rebuild == true || needs.check-for-updates.outputs.up-to-date == 'false'
     +    runs-on: ubuntu-latest
     +    permissions:
     +      contents: write # to push changes (if any)
    @@ .github/workflows/update-translated-manual-pages.yml (new)
     +      name: github-pages
     +      url: ${{ steps.deploy.outputs.url }}
     +    steps:
    -+      - uses: actions/checkout@v3
    ++      - uses: actions/checkout@v4
     +      - name: ruby setup
     +        uses: ruby/setup-ruby@v1
     +        with:
    @@ .github/workflows/update-translated-manual-pages.yml (new)
     +        run: git clone --bare https://github.com/jnavila/git-html-l10n '${{ runner.temp }}/git-html-l10n'
     +      - name: update translated manual pages
     +        run: |
    -+          test false = '${{ inputs.force-rebuild }}' || export RERUN=true
    ++          if test true = '${{ inputs.force-rebuild }}'
    ++          then
    ++            export RERUN=true
    ++          fi
     +          bundle exec ruby script/update-docs.rb '${{ runner.temp }}/git-html-l10n' l10n
     +      - name: commit translated manual pages
     +        id: manual-pages
     +        run: |
    -+          git -C '${{ runner.temp }}/git-html-l10n' rev-parse HEAD >_sync_state/git-html-l10n.sha &&
    -+          git add _sync_state/git-html-l10n.sha &&
    ++          mkdir -p external/docs/sync &&
    ++          git -C '${{ runner.temp }}/git-html-l10n' rev-parse HEAD >external/docs/sync/git-html-l10n.sha &&
    ++          git add external/docs/sync/git-html-l10n.sha &&
     +
    -+          git add -A _generated-asciidoc/ data/docs.yml content/docs &&
    -+          if test false != '${{ inputs.force-rebuild }}' && git diff-index --cached --quiet HEAD --
    ++          git add -A external/docs &&
    ++          if test true = '${{ inputs.force-rebuild }}' && git diff-index --cached --quiet HEAD --
     +          then
     +            echo '::notice::Rebuild of the translated manual pages was requested but resulted in no changes' >&2
     +            exit 0
    @@ .github/workflows/update-translated-manual-pages.yml (new)
     +          git \
     +            -c user.name=${{ github.actor }} \
     +            -c user.email=${{ github.actor }}@noreply.github.com \
    -+            commit -m "Update translated manual pages" &&
    ++            commit -m "Update translated manual pages" \
    ++              -m 'Updated via the `update-translated-manual-pages.yml` GitHub workflow.' &&
     +          echo "result=modified" >>$GITHUB_OUTPUT
     +      - name: verify that there are no uncommitted changes
     +        run: |
    @@ script/ci-helper.js: const getPendingBookUpdates = async (octokit, forceRebuild)
     +const areTranslatedManualPagesUpToDate = async (octokit) => {
     +  try {
    -+    const localSha = await getFileContents(`_sync_state/git-html-l10n.sha`)
    ++    const localSha = await getFileContents(`external/docs/sync/git-html-l10n.sha`)
     +
     +    const [owner, repo] = 'jnavila/git-html-l10n'.split('/')
     +    const { data: { default_branch: remoteDefaultBranch } } =
  -:  --------- > 130:  28b147af1 deploy: check for broken links
  -:  --------- > 131:  c9dae6afc deploy(linkcheck): deal with stray "secondary rate limits"
130:  bff0ab61c ! 132:  2b63bbb71 Adjust `ARCHITECTURE.md` to the Hugo reality
    @@ Commit message
         https://github.com/git/git-scm.com/issues/942.
         However, I aborted that migration when it turned out that Jekyll
    -    required 20 minutes to process the files while Hugo spent less than half
    -    a minute on them.
    +    required 20 minutes to process the files while Hugo spent less than
    +    half a minute on them.
         Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
    @@ ARCHITECTURE.md
     +  - original content from this repository
        - community book content brought in from https://github.com/progit;
    -     see the `lib/tasks/book2.rake` file.
    -@@ ARCHITECTURE.md: The content is a mix of:
    -   - manpages from releases of the git project, imported and formatted
    -     via asciidoctor; see the `lib/tasks/index.rake` task.
    +-    see the `lib/tasks/book2.rake` file.
    ++    see the `script/update-book2.rb` and `script/book.rb` files.
    -+To deploy to GitHub Pages, it is necessary to turn off the default setting to
    -+"publish from a branch" and instead change the setting to "publish with a
    -+custom GitHub Actions workflow":
    -+https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow
    +-  - manpages from releases of the git project, imported and formatted
    +-    via asciidoctor; see the `lib/tasks/index.rake` task.
    ++    The content is pre-rendered and tracked in the `external/book/` directory
    ++    tree.
    +
    ++  - manual pages from releases of the git project, imported and formatted via
    ++    AsciiDoctor, and translated versions of the manual pages from
    ++    https://github.com/jnavila/git-manpages-l10n/ (which itself contains
    ++    pre-rendered pages from https://github.com/jnavila/git-manpages-l10n/); see
    ++    the `script/update-docs.rb` file.
     -## Heroku
    -+## Non-static parts
    ++    The pre-rendered pages are tracked in the `external/docs/` directory tree.
     -The app itself is served by Heroku. The app name is `git-scm` (so you
     -can visit it directly as https://git-scm.herokuapp.com). The site is
     -owned by the git-scm.com team. If you want to be involved in managing
     -uptime/deploys/etc, you'll need a Heroku account and request to be added
     -to that team.
    ++To deploy to GitHub Pages, it is necessary to turn off the default setting to
    ++"publish from a branch" and instead change the setting to "publish with a
    ++custom GitHub Actions workflow":
    ++https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow
    ++With this change, the site can be tested in the fork by pushing to the
    ++`gh-pages` branch (which will trigger the `deploy.yml` workflow) and then
    ++navigating to https://git-scm.<user>.github.io/.
    +
    +-We use a few Heroku add-ons:
    ++## Non-static parts
    +
    +-  - Bonsai elasticsearch (see below)
     +While the site consists mostly of static content, there are a couple of
     +parts that are sort of dynamic.
    --We use a few Heroku add-ons:
    +-  - Heroku Postgres as the database
     +The search is implemented client-side, via [Pagefind](https://pagefind.app/).
    --  - Bonsai elasticsearch (see below)
    +-  - Heroku Redis for rails caching
     +A few scheduled GitHub workflows keep the content up to date:
    --  - Heroku Postgres as the database
    +-  - Heroku scheduler for cron jobs
     +  - `update-git-version-and-manual-pages` and `update-download-data` (pick
     +    up newly released git versions)
    --  - Heroku Redis for rails caching
    +-The nightly scheduled jobs are:
     +  - `update-translated-manual-pages` (fetch and format translated manual
     +    pages from the jnavila/git-html-l10n repository)
    --  - Heroku scheduler for cron jobs
    --
    --The nightly scheduled jobs are:
    --
     -  - `rake downloads` (pick up newly released git versions)
     -
     -  - `rake preindex` (pull in and format manpages for released git
    @@ ARCHITECTURE.md: The content is a mix of:
     -dyno. So we have Cloudflare sitting in front of it, aggressively caching
     -everything. That also should make the site faster to serve to regions
     -far away from Heroku's servers.
    +-
    +-The Cloudflare setup is mostly pretty simple:
     +These workflows are also marked as `workflow_dispatch`, i.e. they can be run
     +manually (e.g. to update the download links just after Git for Windows
     +published a new release).
    --The Cloudflare setup is mostly pretty simple:
    --
     - - they serve DNS for the whole domain (that's where they insert the CDN
     -   magic)
     -
    @@ ARCHITECTURE.md: The content is a mix of:
     +`deploy` GitHub workflow.
     +Note that some of the formatting of manual pages and book content happens
    -+when they are imported by the GitHub workflows. Therefore, after fixing some
    -+formatting, these workflows may need the force-rebuild flag to be toggled (see
    -+the individual workflows for details).
    ++when they are imported by the GitHub workflows. Therefore, whenever there are
    ++changes to the scripts/workflows/automation that affect formatting, these
    ++workflows may need to be triggered using the force-rebuild flag to be toggled
    ++(see the individual workflows for details).
      ## DNS
    @@ ARCHITECTURE.md: The content is a mix of:
      Note that we own both git-scm.com and git-scm.org; the latter redirects
      to the former.
    -@@ ARCHITECTURE.md: The site mostly just runs without intervention:
    -   - code merged to `main` is auto-deployed
    +-
    + ## Manual Intervention
    +
    + The site mostly just runs without intervention:
    +
    +-  - code merged to `main` is auto-deployed
    ++  - code merged to `gh-pages` is auto-deployed
     -  - new git versions are detected daily and manpages and download links
     +  - new git versions are detected daily and manual pages and download links
    @@ ARCHITECTURE.md: The site mostly just runs without intervention:
     -    `lib/tasks/book2.rake`
     +    `script/book.rb`
    -   - forced re-imports of content (e.g., a formatting fix to imported
    +-  - forced re-imports of content (e.g., a formatting fix to imported
     -    manpages) must be triggered manually
    -+    manual pages) must be triggered manually with `force-rebuild` toggled
    ++  - forced re-imports of content (e.g., when fixing formatting in the
    ++    imported manual pages) must be triggered manually with `force-rebuild`
    ++    toggled
  -:  --------- > 133:  e91c406fa Add Playwright-based UI tests
  -:  --------- > 134:  a43cdff2c playwright: add a few platform-specific tests
  -:  --------- > 135:  f6b7ccd21 playwright: let the UI tests pass with the Rails app
  -:  --------- > 136:  e7db6d766 playwright: add a regression test for the `git remote renom` issue
  -:  --------- > 137:  87609ac77 playwright: add a GitHub workflow to run the tests
  -:  --------- > 138:  5facfdaee playwright: auto-start a web server for the special URL localhost:5000
  -:  --------- > 139:  b54046816 playwright: verify that URLs with question marks work
  -:  --------- > 140:  4210b9bba ci: run the Playwright tests in every Pull Request
  -:  --------- > 141:  ffb957b5f ci: run the Playwright tests on every deployment

Ciao, Johannes

Previous: Johannes SchindelinNext: Toon Claes
Message 13 of 15 in “Migration of git-scm.com to a static web site: ready for review/testing”
  1. Johannes SchindelinNov 17, 2023
  2. Todd ZullingerNov 17, 2023
  3. Johannes SchindelinNov 18, 2023
  4. Todd ZullingerNov 18, 2023
  5. Johannes SchindelinNov 21, 2023
  6. Todd ZullingerNov 28, 2023
  7. Johannes SchindelinSep 11, 2024
  8. Johannes SixtNov 18, 2023
  9. Johannes SchindelinNov 18, 2023
  10. Kaartic SivaraamNov 23, 2023
  11. Johannes SchindelinSep 11, 2024
  12. Johannes SchindelinSep 11, 2024
  13. Johannes SchindelinSep 11, 2024
  14. Toon ClaesSep 12, 2024
  15. Johannes SchindelinSep 14, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.