Full Code of pcal43/fastback for AI

main 43f7f506b19e cached
135 files
460.8 KB
111.2k tokens
624 symbols
1 requests
Download .txt
Showing preview only (515K chars total). Download the full file or copy to clipboard to get everything.
Repository: pcal43/fastback
Branch: main
Commit: 43f7f506b19e
Files: 135
Total size: 460.8 KB

Directory structure:
gitextract_2zf8f3wn/

├── .github/
│   └── workflows/
│       └── build-pull-request.yml
├── .gitignore
├── Justfile
├── LICENSE
├── README.md
├── build.gradle
├── common/
│   ├── build.gradle
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   │   └── net/
│       │   │       └── pcal/
│       │   │           └── fastback/
│       │   │               └── common/
│       │   │                   ├── commands/
│       │   │                   │   ├── Command.java
│       │   │                   │   ├── Commands.java
│       │   │                   │   ├── CreateFileRemoteCommand.java
│       │   │                   │   ├── DeleteCommand.java
│       │   │                   │   ├── FullCommand.java
│       │   │                   │   ├── GcCommand.java
│       │   │                   │   ├── HelpCommand.java
│       │   │                   │   ├── InfoCommand.java
│       │   │                   │   ├── InitCommand.java
│       │   │                   │   ├── ListCommand.java
│       │   │                   │   ├── LocalCommand.java
│       │   │                   │   ├── PermissionsFactory.java
│       │   │                   │   ├── PruneCommand.java
│       │   │                   │   ├── PushCommand.java
│       │   │                   │   ├── RemoteDeleteCommand.java
│       │   │                   │   ├── RemoteListCommand.java
│       │   │                   │   ├── RemotePruneCommand.java
│       │   │                   │   ├── RemoteRestoreCommand.java
│       │   │                   │   ├── RestoreCommand.java
│       │   │                   │   ├── SchedulableAction.java
│       │   │                   │   ├── SetCommand.java
│       │   │                   │   └── SnapshotNameSuggestions.java
│       │   │                   ├── config/
│       │   │                   │   ├── FastbackConfigKey.java
│       │   │                   │   ├── GitConfig.java
│       │   │                   │   ├── GitConfigImpl.java
│       │   │                   │   ├── GitConfigKey.java
│       │   │                   │   └── OtherConfigKey.java
│       │   │                   ├── logging/
│       │   │                   │   ├── AutosaveLogger.java
│       │   │                   │   ├── CommandLogger.java
│       │   │                   │   ├── Log4jLogger.java
│       │   │                   │   ├── ShutdownLogger.java
│       │   │                   │   ├── SystemLogger.java
│       │   │                   │   ├── UserLogger.java
│       │   │                   │   └── UserMessage.java
│       │   │                   ├── mixins/
│       │   │                   │   ├── FileFixerUpperMixin.java
│       │   │                   │   ├── MessageScreenMixin.java
│       │   │                   │   ├── MinecraftServerMixin.java
│       │   │                   │   ├── ScreenAccessors.java
│       │   │                   │   ├── ServerAccessors.java
│       │   │                   │   └── SessionAccessors.java
│       │   │                   ├── mod/
│       │   │                   │   ├── AutosaveListener.java
│       │   │                   │   ├── ClientHelper.java
│       │   │                   │   ├── LoaderHelper.java
│       │   │                   │   ├── Mod.java
│       │   │                   │   ├── ModImpl.java
│       │   │                   │   └── UserMessageUtil.java
│       │   │                   ├── repo/
│       │   │                   │   ├── BranchUtils.java
│       │   │                   │   ├── CommitUtils.java
│       │   │                   │   ├── JGitConsumer.java
│       │   │                   │   ├── JGitFunction.java
│       │   │                   │   ├── JGitIncrementalProgressMonitor.java
│       │   │                   │   ├── JGitPercentageProgressMonitor.java
│       │   │                   │   ├── JGitSupplier.java
│       │   │                   │   ├── PreflightUtils.java
│       │   │                   │   ├── PruneUtils.java
│       │   │                   │   ├── PushUtils.java
│       │   │                   │   ├── ReclamationUtils.java
│       │   │                   │   ├── Repo.java
│       │   │                   │   ├── RepoFactory.java
│       │   │                   │   ├── RepoFactoryImpl.java
│       │   │                   │   ├── RepoImpl.java
│       │   │                   │   ├── RestoreUtils.java
│       │   │                   │   ├── SnapshotId.java
│       │   │                   │   ├── SnapshotIdUtils.java
│       │   │                   │   ├── WorldId.java
│       │   │                   │   └── WorldIdUtils.java
│       │   │                   ├── retention/
│       │   │                   │   ├── AllRetentionPolicy.java
│       │   │                   │   ├── DailyRetentionPolicy.java
│       │   │                   │   ├── FixedCountRetentionPolicy.java
│       │   │                   │   ├── GFSRetentionPolicy.java
│       │   │                   │   ├── RetentionPolicy.java
│       │   │                   │   ├── RetentionPolicyCodec.java
│       │   │                   │   └── RetentionPolicyType.java
│       │   │                   └── utils/
│       │   │                       ├── EnvironmentUtils.java
│       │   │                       ├── Executor.java
│       │   │                       ├── ExecutorImpl.java
│       │   │                       ├── FileUtils.java
│       │   │                       ├── ProcessException.java
│       │   │                       └── ProcessUtils.java
│       │   └── resources/
│       │       ├── assets/
│       │       │   └── fastback/
│       │       │       └── lang/
│       │       │           ├── de_de.json
│       │       │           ├── en_us.json
│       │       │           ├── es_es.json
│       │       │           ├── ja_jp.json
│       │       │           ├── ru_ru.json
│       │       │           └── zh_cn.json
│       │       ├── fastback.mixins.json
│       │       └── world/
│       │           ├── gitattributes-jgit
│       │           ├── gitattributes-native
│       │           └── gitignore
│       └── test/
│           └── java/
│               └── net/
│                   └── pcal/
│                       └── fastback/
│                           └── common/
│                               ├── repo/
│                               │   ├── V1SnapshotIdTest.java
│                               │   └── V2SnapshotIdTest.java
│                               └── retention/
│                                   ├── DailyRetentionPolicyTest.java
│                                   ├── GFSRetentionPolicyTest.java
│                                   └── RetentionPolicyCodecTest.java
├── docs/
│   ├── _config.yml
│   ├── actions-list.md
│   ├── advanced.md
│   ├── commands-list.md
│   ├── commands.md
│   ├── diskspace.md
│   ├── faq.md
│   ├── index.md
│   ├── native-git.md
│   ├── permissions-list.md
│   ├── permissions.md
│   ├── remote.md
│   ├── retention-list.md
│   ├── scheduling.md
│   └── usage.md
├── etc/
│   ├── blurb.md
│   └── docgen.sh
├── fabric/
│   ├── build.gradle
│   └── src/
│       └── main/
│           ├── java/
│           │   └── net/
│           │       └── pcal/
│           │           └── fastback/
│           │               └── fabric/
│           │                   ├── FabricClientInitializer.java
│           │                   ├── FabricLoaderHelper.java
│           │                   └── FabricServerInitializer.java
│           └── resources/
│               └── fabric.mod.json
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── neoforge/
│   ├── build.gradle
│   └── src/
│       └── main/
│           ├── java/
│           │   └── net/
│           │       └── pcal/
│           │           └── fastback/
│           │               └── neoforge/
│           │                   ├── NeoForgeClientInitializer.java
│           │                   ├── NeoForgeLoaderHelper.java
│           │                   └── NeoForgeModInitializer.java
│           └── resources/
│               ├── META-INF/
│               │   └── neoforge.mods.toml
│               └── pack.mcmeta
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/build-pull-request.yml
================================================
name: build-pull-request

on: [ pull_request ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      # this is consistently timing out on github, not sure why
      # - uses: gradle/actions/wrapper-validation@v3
      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: 25

      - name: Set up Gradle
        uses: gradle/actions/setup-gradle@v4
        with:
          # Releases are not published with github actions, so I'm less concerned about cache poisoning
          cache-read-only: false

      # Aggressively cache Gradle home and build dirs
      - name: Cache Gradle dependencies
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Cache build outputs
        uses: actions/cache@v4
        with:
          path: |
            **/build
          key: ${{ runner.os }}-build-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-build-

      - name: Execute Gradle build
        run: ./gradlew build


================================================
FILE: .gitignore
================================================
# gradle


*~
\#*



.gradle/
build/
out/
classes/

# eclipse

*.launch

# idea

.idea/
*.iml
*.ipr
*.iws

# vscode
.settings/
.vscode/
.classpath
.project
*/bin

# macos

*.DS_Store

# fabric

run/
logs/



================================================
FILE: Justfile
================================================
clean:
    rm -rf build

compile:
    ./gradlew compileJava

jar:
    ./gradlew jar

release-jars:
    ./gradlew buildReleaseJars

compile-common:
    ./gradlew :common:compileJava

test:
    ./gradlew test

release:
    ./gradlew release

ide:
    ./gradlew cleanIdea idea

pr:
    gh pr view --web 2>/dev/null || gh pr create --web

prs:
    {{ if os() == "macos" { "open" } else { "firefox" } }} https://github.com/pcal43/copper-hopper/pulls

deps:
    ./gradlew -q dependencies --configuration runtimeClasspath

clearCaches:
    ./gradlew --stop
    rm -rf "$HOME/.gradle/caches" "$HOME/.gradle/wrapper/dists" "$HOME/.gradle/daemon" "$HOME/.gradle/native"

run-fabric:
    ./gradlew :fabric:runClient

run-fabric-server:
    ./gradlew :fabric:runServer

run-neoforge:
    ./gradlew :neoforge:runClient

run-neoforge-server:
    ./gradlew :neoforge:runServer


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.



================================================
FILE: README.md
================================================
# FastBack
*Fast, incremental Minecraft world backups powered by Git*

Fastback is a Minecraft mod that backs up your world in incremental snapshots.  When it does a backup,
it only saves the parts of your world that changed.

This means backups are fast.  It also means you can keep snapshots of your world without using up a lot
of disk space.

![](https://pcal43.github.io/fastback/savescreen_animation.gif)

## Features

* Incrementally backup just the changed files
* Faster, smaller backups than zipping
* Back up locally
* Back up remotely to any git server
* Back up remotely to any network volume (no git server required)
* Schedule backups to run automatically
* Easily restore backup snapshots
* Snapshot pruning, retention policies
* Include mod jars and config files in backup snapshots
* Broadcast server-wide notifications during backups
* LuckPerms support
* Works on clients and dedicated servers
* Works on Linux, Mac and Windows
* ..all with easy-to-use minecraft commands

## Acknowledgements

* Russian localization provided by [Felix14-v2](https://github.com/Felix14-v2)
* Chinese localization provided by [buiawpkgew1](https://github.com/buiawpkgew1)
* Spanish localization provided by [rmortes](https://github.com/rmortes)
* Fastback includes and was made possible by the work of committers on these projects:
    * [JGit](https://www.eclipse.org/jgit/) from The Eclipse Software Foundation
    * [sshd](https://mina.apache.org/sshd-project/) from The Apache Software Foundation
    * [JavaEWAH](https://github.com/lemire/javaewah) from Daniel Lemire, et al.
    * [fabric-permissions-api](https://github.com/lucko/fabric-permissions-api) from lucko
    * [server-translations-api](https://github.com/NucleoidMC/Server-Translations) from the Fabric Community

## Legal

FastBack is distributed under [GNU Public License version 2](https://github.com/pcal43/fastback/blob/main/LICENSE).

You can put it in a modpack but please include attribution with a link to this page.

## Questions?

Please start with the [documentation](https://pcal43.github.io/fastback).  If you have more
questions, please join "pcal's minecraft mods" on Discord:

[https://discord.pcal.net](https://discord.pcal.net)

Note that Curseforge comments have been disabled and I will **not** reply to private messages.



================================================
FILE: build.gradle
================================================
plugins {
	id 'net.fabricmc.fabric-loom' version "${fabric_loom_version}" apply false
	id 'net.neoforged.moddev' version "${neoforge_moddev_version}" apply false
	id 'com.modrinth.minotaur' version "${minotaur_plugin_version}" apply false
	id 'net.darkhax.curseforgegradle' version "${curseforgegradle_plugin_version}" apply false
}

ext {
	archivesBaseNameFabric = "${archives_base_name}-fabric"
	archivesBaseNameNeoForge = "${archives_base_name}-neoforge"
}

subprojects {
	tasks.withType(JavaCompile).configureEach {
		it.options.release = project.java_version as Integer
		options.encoding = "UTF-8"
	}
}

// ============================================================================
// Release Tasks
// ============================================================================

/**
 * Validates that the working directory is clean and on the main branch
 */
tasks.register('validateRelease') {
	group = 'release'
	description = 'Validates that the environment is ready for a release'

	doFirst {
		if (!System.getenv("CURSEFORGE_TOKEN")) {
			throw new GradleException("CURSEFORGE_TOKEN environment variable not set")
		}
		if (!System.getenv("MODRINTH_TOKEN")) {
			throw new GradleException("MODRINTH_TOKEN environment variable not set")
		}
	}

	doLast {
		// Check git status
		def gitStatus = 'git status --porcelain'.execute().text.trim()
		if (!gitStatus.isEmpty()) {
			throw new GradleException("Working directory not clean, cannot release:\n${gitStatus}")
		}

		// Check branch
		def currentBranch = 'git rev-parse --abbrev-ref HEAD'.execute().text.trim()
		if (currentBranch != 'main') {
			throw new GradleException("Releases must be performed on main. Currently on '${currentBranch}'")
		}

		println "✓ Working directory is clean"
		println "✓ On main branch"
	}
}

/**
 * Removes the -prerelease suffix from mod_version in gradle.properties
 */
tasks.register('prepareReleaseVersion') {
	group = 'release'
	description = 'Removes -prerelease suffix from mod_version'

	doLast {
		def propsFile = file('gradle.properties')
		def content = propsFile.text

		// Extract current version using regex
		def matcher = content =~ /(?m)^mod_version\s*=\s*(.+)$/
		if (!matcher.find()) {
			throw new GradleException("Could not find mod_version in gradle.properties")
		}

		def currentVersion = matcher.group(1).trim()
		println "Current version: ${currentVersion}"

		if (!currentVersion.contains('-prerelease')) {
			throw new GradleException("Current version is not a prerelease: ${currentVersion}")
		}

		def releaseVersion = currentVersion.replace('-prerelease', '')
		println "Release version: ${releaseVersion}"

		// Replace the line in the file, preserving everything else
		def updatedContent = content.replaceAll(
			/(?m)^mod_version\s*=\s*.+$/,
			"mod_version = ${releaseVersion}"
		)

		propsFile.text = updatedContent

		println "✓ Updated gradle.properties to version ${releaseVersion}"

		// Store for later tasks
		project.ext.releaseVersion = releaseVersion
	}
}

/**
 * Commits the version change to git
 */
tasks.register('commitReleaseVersion') {
	group = 'release'
	description = 'Commits the release version to git'

	doLast {
		def releaseVersion = project.ext.releaseVersion

		// Stage and commit
		def addResult = ["git", "add", "gradle.properties"].execute()
		def addOutput = new StringBuilder()
		def addError = new StringBuilder()
		addResult.consumeProcessOutput(addOutput, addError)
		addResult.waitFor()
		if (addResult.exitValue() != 0) {
			throw new GradleException("Failed to stage gradle.properties: ${addError}")
		}

		def commitResult = ["git", "commit", "-m", "*** Release ${releaseVersion} ***"].execute()
		def commitOutput = new StringBuilder()
		def commitError = new StringBuilder()
		commitResult.consumeProcessOutput(commitOutput, commitError)
		commitResult.waitFor()
		if (commitResult.exitValue() != 0) {
			throw new GradleException("Failed to commit release version:\n${commitOutput}\n${commitError}")
		}

		println "✓ Committed release version ${releaseVersion}"
	}
}

/**
 * Builds the release jars for both Fabric and NeoForge
 * Runs as separate Gradle invocation to pick up updated gradle.properties
 */
tasks.register('buildReleaseJars') {
	group = 'release'
	description = 'Builds Fabric and NeoForge release jars'

	doLast {
		def releaseVersion = project.ext.releaseVersion

		println "Building release jars with version ${releaseVersion}..."

		// Run Gradle as subprocess to pick up updated gradle.properties
		// Clean first to remove any cached artifacts with old version
		def gradleCmd = [
			'./gradlew',
			'clean',
			':fabric:build',
			':neoforge:build',
			'--console=plain'
		]

		def buildProcess = gradleCmd.execute()
		buildProcess.waitForProcessOutput(System.out, System.err)

		if (buildProcess.exitValue() != 0) {
			throw new GradleException("Failed to build release jars")
		}

		println "✓ Built release jars:"
		println "  - fabric/build/libs/${archivesBaseNameFabric}-${releaseVersion}.jar"
		println "  - neoforge/build/libs/${archivesBaseNameNeoForge}-${releaseVersion}.jar"
	}
}

/**
 * Pushes changes and creates a GitHub release
 */
tasks.register('publishGitHub') {
	group = 'release'
	description = 'Pushes to git and creates GitHub release'

	doLast {
		def releaseVersion = project.ext.releaseVersion

		// Push commits
		println "Pushing to origin..."
		def pushResult = ["git", "push"].execute()
		pushResult.waitForProcessOutput(System.out, System.err)
		if (pushResult.exitValue() != 0) {
			throw new GradleException("Failed to push to git")
		}

		// Create GitHub release
		println "Creating GitHub release ${releaseVersion}..."
		def currentBranch = 'git branch --show-current'.execute().text.trim()

		def fabricJar = file("fabric/build/libs/${archivesBaseNameFabric}-${releaseVersion}.jar")
		def neoforgeJar = file("neoforge/build/libs/${archivesBaseNameNeoForge}-${releaseVersion}.jar")

		if (!fabricJar.exists()) throw new GradleException("Fabric release jar not found at ${fabricJar}")
		if (!neoforgeJar.exists()) throw new GradleException("NeoForge release jar not found at ${neoforgeJar}")

		def ghCmd = [
			'gh', 'release', 'create',
			'--target', currentBranch,
			'--generate-notes',
			'--title', releaseVersion,
			'--notes', "release ${releaseVersion}",
			releaseVersion,
			fabricJar.absolutePath,
			neoforgeJar.absolutePath
		]

		def ghResult = ghCmd.execute()
		ghResult.waitForProcessOutput(System.out, System.err)
		if (ghResult.exitValue() != 0) {
			throw new GradleException("Failed to create GitHub release")
		}

		println "✓ Created GitHub release ${releaseVersion}"
	}
}

/**
 * Publishes to CurseForge for both Fabric and NeoForge
 * Runs as separate Gradle invocation to use updated gradle.properties
 */
tasks.register('publishCurseForge') {
	group = 'release'
	description = 'Publishes to CurseForge'

	doLast {
		println "Publishing to CurseForge..."

		def releaseVersion = project.ext.releaseVersion
		def gradleCmd = [
			'./gradlew',
			':fabric:publishCurseforge',
			':neoforge:publishCurseforge',
			'--console=plain'
		]

		def publishProcess = gradleCmd.execute()
		publishProcess.waitForProcessOutput(System.out, System.err)

		if (publishProcess.exitValue() != 0) {
			throw new GradleException("Failed to publish to CurseForge")
		}

		println "✓ Published to CurseForge"
	}
}

/**
 * Publishes to Modrinth for both Fabric and NeoForge
 * Runs as separate Gradle invocation to use updated gradle.properties
 */
tasks.register('publishModrinth') {
	group = 'release'
	description = 'Publishes to Modrinth'

	doLast {
		println "Publishing to Modrinth..."

		def releaseVersion = project.ext.releaseVersion
		def gradleCmd = [
			'./gradlew',
			':fabric:modrinth',
			':neoforge:modrinth',
			'--console=plain'
		]

		def publishProcess = gradleCmd.execute()
		publishProcess.waitForProcessOutput(System.out, System.err)

		if (publishProcess.exitValue() != 0) {
			throw new GradleException("Failed to publish to Modrinth")
		}

		println "✓ Published to Modrinth"
	}
}

/**
 * Increments version and adds -prerelease suffix for next development cycle
 */
tasks.register('bumpVersion') {
	group = 'release'
	description = 'Increments version and adds -prerelease suffix'

	doLast {
		def propsFile = file('gradle.properties')
		def content = propsFile.text

		// Extract current version using regex
		def matcher = content =~ /(?m)^mod_version\s*=\s*(.+)$/
		if (!matcher.find()) {
			throw new GradleException("Could not find mod_version in gradle.properties")
		}

		def releaseVersion = matcher.group(1).trim()
		println "Previous release version: ${releaseVersion}"

		// Parse the version: "0.23.0+1.21.10" -> major.minor.patch + buildMetadata
		def versionParts = releaseVersion.split('\\+')
		def semver = versionParts[0].split('\\.')
		def buildMetadata = versionParts.length > 1 ? versionParts[1] : ''

		// Increment patch version
		def major = semver[0]
		def minor = semver[1]
		def patch = (semver[2] as Integer) + 1

		def nextVersion = "${major}.${minor}.${patch}+${buildMetadata}-prerelease"
		println "Next version: ${nextVersion}"

		// Replace the line in the file, preserving everything else
		def updatedContent = content.replaceAll(
			/(?m)^mod_version\s*=\s*.+$/,
			"mod_version = ${nextVersion}"
		)

		propsFile.text = updatedContent

		println "✓ Updated gradle.properties to version ${nextVersion}"

		// Store for later tasks
		project.ext.nextVersion = nextVersion
	}
}

/**
 * Commits and pushes the version bump
 */
tasks.register('commitVersionBump') {
	group = 'release'
	description = 'Commits and pushes the version bump'

	doLast {
		def nextVersion = project.ext.nextVersion

		// Commit and push
		def addResult = ["git", "add", "gradle.properties"].execute()
		def addOutput = new StringBuilder()
		def addError = new StringBuilder()
		addResult.consumeProcessOutput(addOutput, addError)
		addResult.waitFor()
		if (addResult.exitValue() != 0) {
			throw new GradleException("Failed to stage gradle.properties: ${addError}")
		}

		def commitResult = ["git", "commit", "-m", "Prepare for next version ${nextVersion}"].execute()
		def commitOutput = new StringBuilder()
		def commitError = new StringBuilder()
		commitResult.consumeProcessOutput(commitOutput, commitError)
		commitResult.waitFor()
		if (commitResult.exitValue() != 0) {
			throw new GradleException("Failed to commit version bump:\n${commitOutput}\n${commitError}")
		}

		def pushResult = ["git", "push"].execute()
		pushResult.waitForProcessOutput(System.out, System.err)
		if (pushResult.exitValue() != 0) {
			throw new GradleException("Failed to push version bump")
		}

		println "✓ Committed and pushed version bump to ${nextVersion}"
	}
}

/**
 * Complete release workflow
 *
 * Executes tasks in this order:
 *   1. validateRelease       - Check git status
 *   2. prepareReleaseVersion - Remove -prerelease suffix
 *   3. commitReleaseVersion  - Commit version change
 *   4. buildReleaseJars      - Build both loaders
 *   5. publishGitHub         - Push and create GitHub release
 *   6. publishCurseForge     - Publish to CurseForge
 *   7. publishModrinth       - Publish to Modrinth
 *   8. bumpVersion           - Increment version, add -prerelease
 *   9. commitVersionBump     - Commit and push next version
 */
tasks.register('release') {
	group = 'release'
	description = 'Complete release: GitHub, CurseForge, Modrinth, and version bump'

	// All tasks that need to run
	dependsOn validateRelease,
	          prepareReleaseVersion,
	          commitReleaseVersion,
	          buildReleaseJars,
	          publishGitHub,
	          publishCurseForge,
	          publishModrinth,
	          bumpVersion,
	          commitVersionBump

	// Execution order
	prepareReleaseVersion.mustRunAfter validateRelease
	commitReleaseVersion.mustRunAfter prepareReleaseVersion
	buildReleaseJars.mustRunAfter commitReleaseVersion
	publishGitHub.mustRunAfter buildReleaseJars
	publishCurseForge.mustRunAfter publishGitHub
	publishModrinth.mustRunAfter publishCurseForge
	bumpVersion.mustRunAfter publishModrinth
	commitVersionBump.mustRunAfter bumpVersion

	doFirst {
		println ""
		println "╔════════════════════════════════════════════════════════════╗"
		println "║                   Starting Release Process                 ║"
		println "╚════════════════════════════════════════════════════════════╝"
		println ""
	}

	doLast {
		println ""
		println "╔════════════════════════════════════════════════════════════╗"
		println "║                   Release Complete! 🎉                     ║"
		println "╚════════════════════════════════════════════════════════════╝"
		println ""
	}
}


================================================
FILE: common/build.gradle
================================================
plugins {
	id 'net.fabricmc.fabric-loom'
}

repositories {
    maven { url = 'https://maven.fabricmc.net/' }
    maven { url = 'https://repo.spongepowered.org/maven/' }
    maven { url = 'https://maven.nucleoid.xyz/' }
    mavenCentral()
}

dependencies {
	minecraft "com.mojang:minecraft:${minecraft_version}"

	compileOnly "org.spongepowered:mixin:${spongepowered_version}"
	annotationProcessor "org.spongepowered:mixin:${spongepowered_version}:processor"

	testImplementation "org.junit.jupiter:junit-jupiter-api:${junit_version}"
	testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_version}"
	testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"

	// jgit
	api("org.eclipse.jgit:org.eclipse.jgit:${jgit_version}") { transitive = false }

	// jgit needs this
	runtimeOnly("com.googlecode.javaewah:JavaEWAH:${JavaEWAH_version}") { transitive = false }

	// So jgit can do modern ssh:
	// sshd-common is NOT listed here - the fabric module provides a patched version that strips the
	// FileSystemProvider service entry to prevent a ClassCastException during Fabric's jar scanning.
	runtimeOnly("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:${jgit_version}") { transitive = false }
	runtimeOnly("org.apache.sshd:sshd-core:${apache_sshd_version}") { transitive = false }

	// this enables ed25519 support in apache_sshd
	// https://github.com/apache/mina-sshd/blob/dfa109b7b535d64e8ee395ddd0419e7696fb24ee/docs/dependencies.md
	runtimeOnly("net.i2p.crypto:eddsa:${project.eddsa_version}") { transitive = false }

	runtimeOnly("me.lucko:fabric-permissions-api:${fabric_permissions_version}") { transitive = false }
	runtimeOnly("xyz.nucleoid:server-translations-api:${server_translations_version}")  { transitive = false }
}

loom {
	runs {}
}

test {
	useJUnitPlatform()
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/Command.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;

public interface Command {

    void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf);

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/Commands.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.Repo;
import net.pcal.fastback.common.repo.RepoFactory;
import net.pcal.fastback.common.utils.Executor.ExecutionLock;

import java.nio.file.Path;
import java.util.function.Predicate;

import static net.pcal.fastback.common.config.FastbackConfigKey.IS_BACKUP_ENABLED;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.ERROR;
import static net.pcal.fastback.common.logging.UserMessage.styledLocalized;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.utils.EnvironmentUtils.isNativeOk;
import static net.pcal.fastback.common.utils.Executor.executor;

public class Commands {

    static final int FAILURE = 0;
    static final int SUCCESS = 1;


    public static LiteralArgumentBuilder<CommandSourceStack> createBackupCommand(final PermissionsFactory<CommandSourceStack> pf) {

        final LiteralArgumentBuilder<CommandSourceStack> root = LiteralArgumentBuilder.<CommandSourceStack>literal("backup").
                requires(pf.require("fastback.command")).
                executes(HelpCommand::generalHelp);

        InitCommand.INSTANCE.register(root, pf);
        LocalCommand.INSTANCE.register(root, pf);
        FullCommand.INSTANCE.register(root, pf);
        InfoCommand.INSTANCE.register(root, pf);

        RestoreCommand.INSTANCE.register(root, pf);
        CreateFileRemoteCommand.INSTANCE.register(root, pf);

        PruneCommand.INSTANCE.register(root, pf);
        DeleteCommand.INSTANCE.register(root, pf);
        GcCommand.INSTANCE.register(root, pf);
        ListCommand.INSTANCE.register(root, pf);
        PushCommand.INSTANCE.register(root, pf);

        RemoteListCommand.INSTANCE.register(root, pf);
        RemoteDeleteCommand.INSTANCE.register(root, pf);
        RemotePruneCommand.INSTANCE.register(root, pf);
        RemoteRestoreCommand.INSTANCE.register(root, pf);

        SetCommand.INSTANCE.register(root, pf);

        HelpCommand.INSTANCE.register(root, pf);
        return root;

    }

    static Predicate<CommandSourceStack> subcommandPermission(String subcommandName, PermissionsFactory<CommandSourceStack> pf) {
        final String permName = "fastback.command." + subcommandName;
        return pf.require(permName);
    }

    /**
     * Retrieve a command argument. If they forgot to provide it, return null
     * and log a helpful message rather than blowing up the world.  This is needed in the
     * cases where the list of arguments is dynamic (e.g., retention policies) and we can't
     * rely on brigadier's static parse trees.
     */
    static <V> V getArgumentNicely(final String argName, final Class<V> clazz, final CommandContext<?> cc, UserLogger log) {
        try {
            return cc.getArgument(argName, clazz);
        } catch (IllegalArgumentException iae) {
            missingArgument(argName, log);
            return null;
        }
    }

    static int missingArgument(final String argName, final CommandContext<CommandSourceStack> cc) {
        return missingArgument(argName, UserLogger.ulog(cc));
    }

    static int missingArgument(final String argName, final UserLogger log) {
        log.message(styledLocalized("fastback.chat.missing-argument", ERROR, argName));
        return FAILURE;
    }

    interface GitOp {
        void execute(Repo repo) throws Exception;
    }

    static void gitOp(final ExecutionLock lock, final UserLogger ulog, final GitOp op) {
        try {
            executor().execute(lock, ulog, () -> {
                final Path worldSaveDir = mod().getWorldDirectory();
                final RepoFactory rf = RepoFactory.rf();
                if (!rf.isGitRepo(worldSaveDir)) { // FIXME this is not the right place for these checks
                    // If they haven't yet run 'backup init', make sure they've installed native.
                    if (!isNativeOk(true, ulog, true)) return;
                    ulog.message(styledLocalized("fastback.chat.not-enabled", ERROR));
                    return;
                }
                try (final Repo repo = rf.load(worldSaveDir)) {
                    final GitConfig repoConfig = repo.getConfig();
                    if (!isNativeOk(repoConfig, ulog, false)) return;
                    if (!repoConfig.getBoolean(IS_BACKUP_ENABLED)) {
                        ulog.message(styledLocalized("fastback.chat.not-enabled", ERROR));
                    } else {
                        op.execute(repo);
                    }
                } catch (Exception e) {
                    ulog.message(styledLocalized("fastback.chat.internal-error", ERROR));
                    syslog().error(e);
                } finally {
                    mod().clearHudText();
                }
            });
        } catch (Exception e) {
            ulog.internalError();
            syslog().error(e);
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/CreateFileRemoteCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.StoredConfig;

import java.nio.file.Path;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.missingArgument;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_FILE_REMOTE_BARE;
import static net.pcal.fastback.common.config.OtherConfigKey.REMOTE_PUSH_URL;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.ERROR;
import static net.pcal.fastback.common.logging.UserMessage.styledLocalized;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;
import static net.pcal.fastback.common.utils.FileUtils.mkdirs;

enum CreateFileRemoteCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "create-file-remote";
    private static final String ARGUMENT = "file-path";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(cc -> missingArgument(ARGUMENT, cc)).
                        then(argument(ARGUMENT, StringArgumentType.greedyString()).
                                executes(CreateFileRemoteCommand::setFileRemote)
                        )
        );
    }

    private static int setFileRemote(final CommandContext<CommandSourceStack> cc) {
        final UserLogger ulog = UserLogger.ulog(cc);
        gitOp(NONE, ulog, repo -> {
            final String targetPath = cc.getArgument(ARGUMENT, String.class);
            final Path fupHome = Path.of(targetPath);
            if (fupHome.toFile().exists()) {
                ulog.message(styledLocalized("fastback.chat.create-file-remote-dir-exists", ERROR, fupHome.toString()));
                return;
            }
            mkdirs(fupHome);
            GitConfig conf = repo.getConfig();
            try (Git targetGit = Git.init().setBare(conf.getBoolean(IS_FILE_REMOTE_BARE)).setDirectory(fupHome.toFile()).call()) {
                final StoredConfig targetGitc = targetGit.getRepository().getConfig();
                targetGitc.setInt("pack", null, "window", 0);
                targetGitc.setInt("core", null, "bigFileThreshold", 1);
                targetGitc.save();
            }
            final String targetUrl = "file://" + fupHome.toAbsolutePath();
            repo.getConfig().updater().set(REMOTE_PUSH_URL, targetUrl).save();
            ulog.message(UserMessage.localized("fastback.chat.create-file-remote-created", targetPath, targetUrl));
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/DeleteCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.SnapshotId;

import java.util.List;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.getArgumentNicely;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;

enum DeleteCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "delete";
    private static final String ARGUMENT = "snapshot";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(literal(COMMAND_NAME).
                requires(subcommandPermission(COMMAND_NAME, pf)).then(
                        argument(ARGUMENT, StringArgumentType.string()).
                                suggests(SnapshotNameSuggestions.local()).
                                executes(DeleteCommand::delete)
                )
        );
    }

    private static int delete(final CommandContext<CommandSourceStack> cc) {
        final UserLogger log = ulog(cc);
        gitOp(WRITE, log, repo -> {
            final String snapshotName = getArgumentNicely(ARGUMENT, String.class, cc.getLastChild(), log);
            final SnapshotId sid = repo.createSnapshotId(snapshotName);
            final String branchName = sid.getBranchName();
            repo.deleteLocalBranches(List.of(branchName));
            log.message(UserMessage.localized("fastback.chat.delete-done", snapshotName));
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/FullCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;

import java.io.IOException;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.logging.UserMessage.localized;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;

/**
 * Perform a local backup.
 *
 * @author pcal
 * @since 0.2.0
 */
enum FullCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "full";

    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(cc -> run(cc.getSource()))
        );
    }

    public static int run(CommandSourceStack scs) {
        final UserLogger ulog = ulog(scs);
        try {
            saveWorldBeforeBackup(ulog);
        } catch (IOException e) {
            ulog.internalError();
            syslog().error(e);
        }
        gitOp(WRITE, ulog, repo -> repo.doCommitAndPush(ulog));
        return SUCCESS;
    }

    /**
     * NOTE: this MUST be called in the game thread; calling it from one of our executor threads causes things
     * to seize up (at least on shutdown backup?)
     * <p>
     * Workaround for https://github.com/pcal43/fastback/issues/112
     */
    static void saveWorldBeforeBackup(UserLogger ulog) throws IOException {
        ulog.message(localized("fastback.chat.world-save"));
        mod().saveWorld();
        ulog.message(localized("fastback.message.backing-up"));
    }
}

================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/GcCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;


/**
 * Runs garbage collection to try to free up disk space.
 *
 * @author pcal
 * @since 0.0.12
 */
enum GcCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "gc";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(GcCommand::gc)
        );
    }

    private static int gc(CommandContext<CommandSourceStack> cc) {
        final UserLogger ulog = ulog(cc);
        gitOp(WRITE, ulog, repo -> {
            repo.doGc(ulog);
            //log.chat(localized("fastback.chat.gc-done", byteCountToDisplaySize(gc.getBytesReclaimed())));
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/HelpCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.FAILURE;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.ERROR;
import static net.pcal.fastback.common.logging.UserMessage.styledLocalized;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.repo.RepoFactory.rf;

enum HelpCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "help";
    private static final String ARGUMENT = "subcommand";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(HelpCommand::generalHelp).
                        then(
                                argument(ARGUMENT, StringArgumentType.word()).
                                        suggests(new HelpTopicSuggestions()).
                                        executes(this::subcommandHelp)
                        )
        );
    }

    private static class HelpTopicSuggestions implements SuggestionProvider<CommandSourceStack> {


        HelpTopicSuggestions() {
        }

        @Override
        public CompletableFuture<Suggestions> getSuggestions(final CommandContext<CommandSourceStack> cc,
                                                             final SuggestionsBuilder builder) {
            CompletableFuture<Suggestions> completableFuture = new CompletableFuture<>();
            getSubcommandNames(cc).forEach(builder::suggest);
            try {
                completableFuture.complete(builder.buildFuture().get());
            } catch (InterruptedException | ExecutionException e) {
                syslog().error("looking up help topics", e);
                return null;
            }
            return completableFuture;
        }
    }

    static int generalHelp(final CommandContext<CommandSourceStack> cc) {
        try (final UserLogger ulog = ulog(cc)) {
            StringWriter subcommands = null;
            for (final String available : getSubcommandNames(cc)) {
                if (subcommands == null) {
                    subcommands = new StringWriter();
                } else {
                    subcommands.append(", ");
                }
                subcommands.append(available);
            }
            ulog.message(UserMessage.localized("fastback.help.subcommands", String.valueOf(subcommands)));
            if (!rf().isGitRepo(mod().getWorldDirectory())) {
                ulog.message(UserMessage.localized("fastback.help.suggest-init"));
            }
            return SUCCESS;
        }
    }

    private int subcommandHelp(final CommandContext<CommandSourceStack> cc) {
        try (final UserLogger ulog = ulog(cc)) {
            final String subcommand = cc.getLastChild().getArgument(ARGUMENT, String.class);
            for (String available : getSubcommandNames(cc)) {
                if (subcommand.equals(available)) {
                    final String prefix = "/backup " + subcommand + ": ";
                    ulog.message(UserMessage.localized("fastback.help.command." + subcommand, prefix));
                    return SUCCESS;
                }
            }
            ulog.message(styledLocalized("fastback.chat.invalid-input", ERROR, subcommand));
        }
        return FAILURE;
    }

    private static List<String> getSubcommandNames(CommandContext<CommandSourceStack> cc) {
        final List<String> out = new ArrayList<>();
        cc.getNodes().get(0).getNode().getChildren().forEach(node -> out.add(node.getName()));
        return out;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/InfoCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.config.GitConfigKey;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.Repo;
import net.pcal.fastback.common.retention.RetentionPolicy;
import net.pcal.fastback.common.retention.RetentionPolicyCodec;
import net.pcal.fastback.common.retention.RetentionPolicyType;

import java.util.function.Function;

import static java.util.Objects.requireNonNull;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.FAILURE;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.config.FastbackConfigKey.AUTOBACK_ACTION;
import static net.pcal.fastback.common.config.FastbackConfigKey.AUTOBACK_WAIT_MINUTES;
import static net.pcal.fastback.common.config.FastbackConfigKey.BROADCAST_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.BROADCAST_MESSAGE;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_BACKUP_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_MODS_BACKUP_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.LOCAL_RETENTION_POLICY;
import static net.pcal.fastback.common.config.FastbackConfigKey.REMOTE_RETENTION_POLICY;
import static net.pcal.fastback.common.config.FastbackConfigKey.RESTORE_DIRECTORY;
import static net.pcal.fastback.common.config.FastbackConfigKey.SHUTDOWN_ACTION;
import static net.pcal.fastback.common.config.OtherConfigKey.REMOTE_PUSH_URL;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.logging.UserMessage.raw;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.repo.RepoFactory.rf;
import static net.pcal.fastback.common.utils.EnvironmentUtils.isNativeOk;
import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.apache.commons.io.FileUtils.sizeOfDirectory;

// TODO move this to Repo.doInfo
enum InfoCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "info";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(cc -> info(cc.getSource()))
        );
    }

    private static int info(final CommandSourceStack scs) {
        requireNonNull(scs);
        try (final UserLogger ulog = ulog(scs)) {
            try {
                ulog.message(UserMessage.localized("fastback.chat.info-header"));
                ulog.message(UserMessage.localized("fastback.chat.info-fastback-version", mod().getModVersion()));
                if (!rf().isGitRepo(mod().getWorldDirectory())) {
                    // If they haven't yet run 'backup init', make sure they've installed native.
                    if (!isNativeOk(true, ulog, true)) return FAILURE;
                } else {
                    try (final Repo repo = rf().load(mod().getWorldDirectory())) {
                        final GitConfig conf = repo.getConfig();
                        if (!isNativeOk(conf, ulog, true)) return FAILURE;
                        ulog.message(UserMessage.localized("fastback.chat.info-uuid", repo.getWorldId().toString()));
                        // FIXME? this could be implemented more efficiently
                        final long backupSize = sizeOfDirectory(repo.getDirectory());
                        final long worldSize = sizeOfDirectory(repo.getWorkTree()) - backupSize;
                        ulog.message(UserMessage.localized("fastback.chat.info-world-size", byteCountToDisplaySize(worldSize)));
                        ulog.message(UserMessage.localized("fastback.chat.info-backup-size", byteCountToDisplaySize(backupSize)));

                        show(IS_BACKUP_ENABLED, conf::getBoolean, ulog);
                        show(REMOTE_PUSH_URL, conf::getString, ulog);
                        show(RESTORE_DIRECTORY, conf::getString, ulog);
                        show(AUTOBACK_WAIT_MINUTES, conf::getInt, ulog);
                        show(IS_MODS_BACKUP_ENABLED, conf::getBoolean, ulog);
                        show(BROADCAST_ENABLED, conf::getBoolean, ulog);
                        show(BROADCAST_MESSAGE, conf::getString, ulog);

                        final SchedulableAction shutdownAction = SchedulableAction.forConfigValue(conf.getString(SHUTDOWN_ACTION));
                        ulog.message(UserMessage.localized("fastback.chat.info-shutdown-action", getActionDisplay(shutdownAction)));
                        final SchedulableAction autobackAction = SchedulableAction.forConfigValue(conf.getString(AUTOBACK_ACTION));
                        ulog.message(UserMessage.localized("fastback.chat.info-autoback-action", getActionDisplay(autobackAction)));

                        showRetentionPolicy(ulog,
                                conf.getString(LOCAL_RETENTION_POLICY),
                                "fastback.chat.retention-policy-set",
                                "fastback.chat.retention-policy-none"
                        );
                        showRetentionPolicy(ulog,
                                conf.getString(REMOTE_RETENTION_POLICY),
                                "fastback.chat.remote-retention-policy-set",
                                "fastback.chat.remote-retention-policy-none"
                        );
                    }
                }
            } catch (final Exception e) {
                ulog.internalError(e);
            }
        }
        return SUCCESS;
    }

    private static void show(GitConfigKey key, Function<GitConfigKey, Object> valueFn, UserLogger ulog) {
        ulog.message(raw(key.getDisplayName() + " = " + valueFn.apply(key)));
    }

    private static String getActionDisplay(SchedulableAction action) {
        return action == null ? SchedulableAction.NONE.getArgumentName() : action.getArgumentName();
    }

    private static void showRetentionPolicy(UserLogger log, String encodedPolicy, String setKey, String noneKey) {
        if (encodedPolicy == null) {
            log.message(UserMessage.localized(noneKey));
        } else {
            final RetentionPolicy policy = RetentionPolicyCodec.INSTANCE.
                    decodePolicy(RetentionPolicyType.getAvailable(), encodedPolicy);
            if (policy == null) {
                log.message(UserMessage.localized(noneKey));
            } else {
                log.message(UserMessage.localized(setKey));
                log.message(policy.getDescription());
            }
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/InitCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.RepoFactory;

import java.io.IOException;
import java.nio.file.Path;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;
import static net.pcal.fastback.common.utils.Executor.executor;

/**
 * @author pcal
 * @since 0.15.0
 */
enum InitCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "init";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(InitCommand::init)
        );
    }

    private static int init(final CommandContext<CommandSourceStack> cc) {
        try (final UserLogger ulog = ulog(cc)) {
            executor().execute(NONE, ulog, () -> {
                        final Path worldSaveDir = mod().getWorldDirectory();
                        final RepoFactory rf = RepoFactory.rf();
                        try {
                            rf.doInit(worldSaveDir, ulog);
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
            );
        }
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/ListCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.SnapshotId;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.FAILURE;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.repo.RepoFactory.rf;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;

enum ListCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "list";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(this::execute)
        );
    }

    private int execute(final CommandContext<CommandSourceStack> cc) {
        try (final UserLogger ulog = UserLogger.ulog(cc)) {
            if (!rf().doInitCheck(mod().getWorldDirectory(), ulog)) return FAILURE;
            gitOp(NONE, ulog, repo -> {
                final List<SnapshotId> snapshots = new ArrayList<>(repo.getLocalSnapshots());
                Collections.sort(snapshots);
                for (final SnapshotId sid : snapshots) {
                    ulog.message(UserMessage.raw(sid.getShortName()));
                }
            });
        }
        return SUCCESS;
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/LocalCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.FAILURE;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.commands.FullCommand.saveWorldBeforeBackup;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.repo.RepoFactory.rf;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;

/**
 * Perform a local backup.
 *
 * @author pcal
 * @since 0.2.0
 */
enum LocalCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "local";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(cc -> run(cc.getSource()))
        );
    }

    private static int run(CommandSourceStack scs) {
        try (final UserLogger ulog = ulog(scs)) {
            if (!rf().doInitCheck(mod().getWorldDirectory(), ulog)) return FAILURE;
            try {
                saveWorldBeforeBackup(ulog);
            } catch (Exception e) {
                ulog.internalError();
                syslog().error(e);
            }
            gitOp(WRITE, ulog, repo -> repo.doCommitSnapshot(ulog));
        }
        return SUCCESS;
    }
}

================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/PermissionsFactory.java
================================================
package net.pcal.fastback.common.commands;

/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

import java.util.function.Predicate;

public interface PermissionsFactory<S> {

    Predicate<S> require(String permissionName);
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/PruneCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.SnapshotId;

import java.util.Collection;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;

/**
 * Command to prune all snapshots that are not to be retained per the retention policy.
 *
 * @author pcal
 * @since 0.2.0
 */
enum PruneCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "prune";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(cc -> prune(cc.getSource()))
        );
    }

    private static int prune(final CommandSourceStack scs) {
        final UserLogger ulog = ulog(scs);
        gitOp(WRITE, ulog, repo -> {
            final Collection<SnapshotId> pruned = repo.doLocalPrune(ulog);
            if (pruned != null) {
                ulog.message(UserMessage.localized("fastback.chat.prune-done", pruned.size()));
                if (!pruned.isEmpty()) ulog.message(UserMessage.localized("fastback.chat.prune-suggest-gc"));
            }
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/PushCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.SnapshotId;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.getArgumentNicely;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;


/**
 * @author pcal
 * @since 0.15.0
 */
enum PushCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "push";
    private static final String ARGUMENT = "snapshot-date";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(literal(COMMAND_NAME).
                requires(subcommandPermission(COMMAND_NAME, pf)).then(
                        argument(ARGUMENT, StringArgumentType.string()).
                                suggests(SnapshotNameSuggestions.local()).
                                executes(PushCommand::execute)
                )
        );
    }

    private static int execute(CommandContext<CommandSourceStack> cc) {
        final UserLogger log = UserLogger.ulog(cc);
        gitOp(NONE, log, repo -> {
            final String snapshotName = getArgumentNicely(ARGUMENT, String.class, cc.getLastChild(), log);
            final SnapshotId sid = repo.createSnapshotId(snapshotName);
            repo.doPushSnapshot(sid, log);
        });
        return SUCCESS;
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/RemoteDeleteCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.SnapshotId;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;

enum RemoteDeleteCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "remote-delete";
    private static final String ARGUMENT = "snapshot";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(literal(COMMAND_NAME).
                requires(subcommandPermission(COMMAND_NAME, pf)).then(
                        argument(ARGUMENT, StringArgumentType.string()).
                                suggests(SnapshotNameSuggestions.remote()).
                                executes(RemoteDeleteCommand::delete)
                )
        );
    }

    private static int delete(CommandContext<CommandSourceStack> cc) {
        final UserLogger log = ulog(cc);
        gitOp(WRITE, log, repo -> {
            final String snapshotName = cc.getLastChild().getArgument(ARGUMENT, String.class);
            final SnapshotId sid = repo.createSnapshotId(snapshotName);
            repo.deleteRemoteBranch(sid.getBranchName());
            log.message(UserMessage.localized("fastback.chat.remote-delete-done", snapshotName));
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/RemoteListCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.SnapshotId;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.config.OtherConfigKey.REMOTE_PUSH_URL;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;

enum RemoteListCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "remote-list";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(RemoteListCommand::execute)
        );
    }

    private static int execute(final CommandContext<CommandSourceStack> cc) {
        final UserLogger log = UserLogger.ulog(cc);
        gitOp(NONE, log, repo -> {
            final List<SnapshotId> snapshots = new ArrayList<>(repo.getRemoteSnapshots());
            Collections.sort(snapshots);
            snapshots.forEach(sid -> log.message(UserMessage.raw(sid.getShortName())));
            log.message(UserMessage.localized("fastback.chat.remote-list-done", snapshots.size(), repo.getConfig().getString(REMOTE_PUSH_URL)));
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/RemotePruneCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.repo.SnapshotId;

import java.util.Collection;

import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.WRITE;

/**
 * Command to prune all snapshots that are not to be retained per the retention policy.
 *
 * @author pcal
 * @since 0.2.0
 */
enum RemotePruneCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "remote-prune";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).
                        executes(cc -> remotePrune(cc.getSource()))
        );
    }

    private static int remotePrune(final CommandSourceStack scs) {
        final UserLogger ulog = ulog(scs);
        gitOp(WRITE, ulog, repo -> {
            final Collection<SnapshotId> pruned = repo.doRemotePrune(ulog);
            ulog.message(UserMessage.localized("fastback.chat.prune-done", pruned.size()));
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/RemoteRestoreCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;

enum RemoteRestoreCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "remote-restore";
    private static final String ARGUMENT = "snapshot";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).then(
                                argument(ARGUMENT, StringArgumentType.string()).
                                        suggests(SnapshotNameSuggestions.remote()).
                                        executes(RemoteRestoreCommand::remoteRestore)
                        )
        );
    }

    private static int remoteRestore(final CommandContext<CommandSourceStack> cc) {
        final UserLogger ulog = ulog(cc);
        gitOp(NONE, ulog, repo -> {
            final String snapshotName = cc.getLastChild().getArgument(ARGUMENT, String.class);
            repo.doRestoreRemoteSnapshot(snapshotName, ulog);
        });
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/RestoreCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;

enum RestoreCommand implements Command {

    INSTANCE;

    private static final String COMMAND_NAME = "restore";
    private static final String ARGUMENT = "snapshot";

    @Override
    public void register(LiteralArgumentBuilder<CommandSourceStack> argb, PermissionsFactory<CommandSourceStack> pf) {
        argb.then(
                literal(COMMAND_NAME).
                        requires(subcommandPermission(COMMAND_NAME, pf)).then(
                                argument(ARGUMENT, StringArgumentType.string()).
                                        suggests(SnapshotNameSuggestions.local()).
                                        executes(RestoreCommand::restore)
                        )
        );
    }

    private static int restore(final CommandContext<CommandSourceStack> cc) {
        try (final UserLogger ulog = UserLogger.ulog(cc)) {
            gitOp(NONE, ulog, repo -> {
                final String snapshotName = cc.getLastChild().getArgument(ARGUMENT, String.class);
                repo.doRestoreLocalSnapshot(snapshotName, ulog);
            });
        }
        return SUCCESS;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/SchedulableAction.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import net.pcal.fastback.common.config.FastbackConfigKey;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.Repo;

import java.util.concurrent.Callable;

import static java.util.Objects.requireNonNull;

/**
 * Encapsulates an action that can be performed in response to events such as shutdown or autosaving.
 *
 * @author pcal
 * @since 0.2.0
 */
public enum SchedulableAction {

    NONE("none") {
        @Override
        public Callable<Void> getTask(final Repo repo, final UserLogger ulog) {
            return () -> null;
        }
    },

    LOCAL("local") {
        @Override
        public Callable<Void> getTask(final Repo repo, final UserLogger ulog) {
            return () -> {
                repo.doCommitSnapshot(ulog);
                return null;
            };
        }
    },

    FULL("full") {
        @Override
        public Callable<Void> getTask(final Repo repo, final UserLogger ulog) {
            return () -> {
                repo.doCommitAndPush(ulog);
                return null;
            };
        }
    },

    FULL_GC("full-gc") {
        @Override
        public Callable<Void> getTask(final Repo repo, final UserLogger ulog) {
            return () -> {
                repo.doCommitAndPush(ulog);
                repo.doLocalPrune(ulog);
                repo.doGc(ulog);
                return null;
            };
        }
    };

    public static SchedulableAction forConfigValue(final GitConfig c, final FastbackConfigKey key) {
        String configValue = c.getString(key);
        if (configValue == null) return null;
        return forConfigValue(configValue);
    }

    public static SchedulableAction forConfigValue(String configValue) {
        if (configValue == null) return null;
        for (SchedulableAction action : SchedulableAction.values()) {
            if (action.configValue.equals(configValue)) {
                return action == SchedulableAction.NONE ? null : action;
            }
        }
        return null;
    }

    private final String configValue;

    SchedulableAction(String configValue) {
        this.configValue = requireNonNull(configValue);
    }

    public String getConfigValue() {
        return this.configValue;
    }

    public String getArgumentName() {
        return this.configValue;
    }

    public abstract Callable<?> getTask(Repo repo, UserLogger ulog);
}



================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/SetCommand.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.commands;

import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.config.FastbackConfigKey;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.config.GitConfigKey;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.Repo;
import net.pcal.fastback.common.repo.RepoFactory;
import net.pcal.fastback.common.retention.RetentionPolicy;
import net.pcal.fastback.common.retention.RetentionPolicyCodec;
import net.pcal.fastback.common.retention.RetentionPolicyType;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
import static net.pcal.fastback.common.commands.Commands.FAILURE;
import static net.pcal.fastback.common.commands.Commands.SUCCESS;
import static net.pcal.fastback.common.commands.Commands.getArgumentNicely;
import static net.pcal.fastback.common.commands.Commands.missingArgument;
import static net.pcal.fastback.common.commands.Commands.subcommandPermission;
import static net.pcal.fastback.common.config.FastbackConfigKey.AUTOBACK_ACTION;
import static net.pcal.fastback.common.config.FastbackConfigKey.AUTOBACK_WAIT_MINUTES;
import static net.pcal.fastback.common.config.FastbackConfigKey.BROADCAST_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.BROADCAST_MESSAGE;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_BACKUP_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_LOCK_CLEANUP_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_MODS_BACKUP_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.LOCAL_RETENTION_POLICY;
import static net.pcal.fastback.common.config.FastbackConfigKey.REMOTE_RETENTION_POLICY;
import static net.pcal.fastback.common.config.FastbackConfigKey.RESTORE_DIRECTORY;
import static net.pcal.fastback.common.config.FastbackConfigKey.SHUTDOWN_ACTION;
import static net.pcal.fastback.common.config.OtherConfigKey.REMOTE_PUSH_URL;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserLogger.ulog;
import static net.pcal.fastback.common.logging.UserMessage.localized;
import static net.pcal.fastback.common.logging.UserMessage.raw;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.repo.RepoFactory.rf;

/**
 * Sets various configuration values.
 *
 * @author pcal
 * @since 0.13.0
 */
enum SetCommand implements Command {

    INSTANCE;

    // ======================================================================
    // Command implementation

    private static final String COMMAND_NAME = "set";

    @Override
    public void register(final LiteralArgumentBuilder<CommandSourceStack> root, PermissionsFactory<CommandSourceStack> pf) {
        final LiteralArgumentBuilder<CommandSourceStack> sc = literal(COMMAND_NAME).
                requires(subcommandPermission(COMMAND_NAME, pf)).
                executes(cc -> missingArgument("key", cc));
        registerBooleanConfigValue(IS_LOCK_CLEANUP_ENABLED, sc);
        registerBooleanConfigValue(IS_BACKUP_ENABLED, sc);
        registerBooleanConfigValue(IS_MODS_BACKUP_ENABLED, sc);
        registerBooleanConfigValue(BROADCAST_ENABLED, sc);
        registerStringConfigValue(BROADCAST_MESSAGE, "message", sc);
        registerStringConfigValue(RESTORE_DIRECTORY, "full-directory-path", sc);
        registerStringConfigValue(REMOTE_PUSH_URL, "url", sc);
        registerIntegerConfigValue(AUTOBACK_WAIT_MINUTES, "minutes", sc);

        {
            final List<String> schedulableActions = new ArrayList<>();
            for (final SchedulableAction sa : SchedulableAction.values()) {
                schedulableActions.add(sa.getConfigValue());
            }
            registerSelectConfigValue(AUTOBACK_ACTION, schedulableActions, sc);
            registerSelectConfigValue(SHUTDOWN_ACTION, schedulableActions, sc);
        }

        registerSetRetentionCommand(LOCAL_RETENTION_POLICY, sc);
        registerSetRetentionCommand(REMOTE_RETENTION_POLICY, sc);

        registerForceDebug(sc);
        root.then(sc);
    }


    // ======================================================================
    // Boolean config values

    private static void registerBooleanConfigValue(final GitConfigKey key, final LiteralArgumentBuilder<CommandSourceStack> setCommand) {
        final LiteralArgumentBuilder<CommandSourceStack> builder = literal(key.getDisplayName());
        builder.then(literal("true").executes(cc -> setBooleanConfigValue(key, true, cc)));
        builder.then(literal("false").executes(cc -> setBooleanConfigValue(key, false, cc)));
        setCommand.then(builder);
    }

    private static int setBooleanConfigValue(final GitConfigKey key, final boolean newValue, final CommandContext<CommandSourceStack> cc) {
        try (UserLogger ulog = ulog(cc)) {
            final Path worldSaveDir = mod().getWorldDirectory();
            final RepoFactory rf = rf();
            if (rf.isGitRepo(worldSaveDir)) {
                try (Repo repo = rf.load(worldSaveDir)) {
                    final GitConfig conf = repo.getConfig();
                    boolean current = conf.getBoolean(key);
                    if (current == newValue) {
                        ulog.message(localized("fastback.chat.no-change"));
                    } else {
                        repo.getConfig().updater().set(key, newValue).save();
                        ulog.message(raw(key.getDisplayName() + " = " + newValue));
                    }
                } catch (Exception e) {
                    ulog.internalError(e);
                    return FAILURE;
                }
            }
        }
        return SUCCESS;
    }

    // ======================================================================
    // Integer config values

    private static void registerIntegerConfigValue(final GitConfigKey key, final String argName, final LiteralArgumentBuilder<CommandSourceStack> setCommand) {
        final LiteralArgumentBuilder<CommandSourceStack> builder = literal(key.getDisplayName());
        builder.then(argument(argName, IntegerArgumentType.integer()).
                executes(cc -> setIntegerConfigValue(key, argName, cc)));
        setCommand.then(builder);
    }

    private static int setIntegerConfigValue(final GitConfigKey key, final String argName, final CommandContext<CommandSourceStack> cc) {
        try (UserLogger ulog = ulog(cc)) {
            final Path worldSaveDir = mod().getWorldDirectory();
            final RepoFactory rf = rf();
            if (rf.isGitRepo(worldSaveDir)) {
                try (Repo repo = rf.load(worldSaveDir)) {
                    final Integer newValue = cc.getArgument(argName, Integer.class);
                    repo.getConfig().updater().set(key, newValue).save();
                    ulog.message(raw(key.getDisplayName() + " = " + newValue));
                } catch (Exception e) {
                    ulog.internalError(e);
                    return FAILURE;
                }
            }
        }
        return SUCCESS;
    }

    // ======================================================================
    // String config values

    private static void registerStringConfigValue(final GitConfigKey key, final String argName, final LiteralArgumentBuilder<CommandSourceStack> setCommand) {
        final LiteralArgumentBuilder<CommandSourceStack> builder = literal(key.getDisplayName());
        builder.then(argument(argName, StringArgumentType.greedyString()).
                executes(cc -> setStringConfigValue(key, argName, cc)));
        setCommand.then(builder);
    }

    private static int setStringConfigValue(final GitConfigKey key, final String argName, final CommandContext<CommandSourceStack> cc) {
        try (UserLogger ulog = ulog(cc)) {
            final Path worldSaveDir = mod().getWorldDirectory();
            final RepoFactory rf = rf();
            if (rf.isGitRepo(worldSaveDir)) {
                try (Repo repo = rf.load(worldSaveDir)) {
                    final String newValue = cc.getArgument(argName, String.class);
                    repo.getConfig().updater().set(key, newValue).save();
                    ulog.message(raw(key.getDisplayName() + " = " + newValue));
                } catch (Exception e) {
                    ulog.internalError(e);
                    return FAILURE;
                }
            }
        }
        return SUCCESS;
    }

    // ======================================================================
    // Selection config values

    private static void registerSelectConfigValue(GitConfigKey key, List<String> selections, final LiteralArgumentBuilder<CommandSourceStack> setCommand) {
        final LiteralArgumentBuilder<CommandSourceStack> builder = literal(key.getDisplayName());
        for (final String selection : selections) {
            builder.then(literal(selection).executes(cc -> setSelectionConfigValue(key, selection, cc)));
        }
        setCommand.then(builder);
    }

    private static int setSelectionConfigValue(final GitConfigKey key, final String newValue, final CommandContext<CommandSourceStack> cc) {
        try (UserLogger ulog = ulog(cc)) {
            final Path worldSaveDir = mod().getWorldDirectory();
            if (rf().isGitRepo(worldSaveDir)) {
                try (final Repo repo = rf().load(worldSaveDir)) {
                    repo.getConfig().updater().set(key, newValue).save();
                    ulog.message(raw(key.getDisplayName() + " = " + newValue));
                } catch (Exception e) {
                    ulog.internalError(e);
                    return FAILURE;
                }
            }
        }
        return SUCCESS;
    }

    // ======================================================================
    // force-debug

    private static final String FORCE_DEBUG_SETTING = "force-debug-enabled";

    private static void registerForceDebug(final LiteralArgumentBuilder<CommandSourceStack> setCommand) {
        final LiteralArgumentBuilder<CommandSourceStack> debug = literal(FORCE_DEBUG_SETTING);
        debug.then(literal("true").executes(cc -> setForceDebug(cc, true)));
        debug.then(literal("false").executes(cc -> setForceDebug(cc, false)));
        setCommand.then(debug);
    }

    private static int setForceDebug(final CommandContext<CommandSourceStack> cc, boolean value) {
        syslog().setForceDebugEnabled(value);
        try (final UserLogger ulog = ulog(cc)) {
            ulog.message(raw("force-debug-enabled = " + value));
        }
        return SUCCESS;
    }


    // ======================================================================
    // Retention policy commands

    /**
     * Register a 'set retention' command that tab completes with all the policies and the policy arguments.
     * Broken out as a helper methods so this logic can be shared by set-retention and set-remote-retention.
     * <p>
     * FIXME? The command parsing here could be more user-friendly.  Not really clear how to implement
     * argument defaults.  Also a lot of noise from bugs like this: https://bugs.mojang.com/browse/MC-165562
     * Just generally not sure how to beat brigadier into submission here.
     */
    private static void registerSetRetentionCommand(final FastbackConfigKey key,
                                                    final LiteralArgumentBuilder<CommandSourceStack> argb) {
        final LiteralArgumentBuilder<CommandSourceStack> retainCommand = literal(key.getSettingName());
        for (final RetentionPolicyType rpt : RetentionPolicyType.getAvailable()) {
            final LiteralArgumentBuilder<CommandSourceStack> policyCommand = literal(rpt.getCommandName());
            policyCommand.executes(cc -> setRetentionPolicy(cc, rpt, key));
            if (rpt.getParameters() != null) {
                for (RetentionPolicyType.Parameter<?> param : rpt.getParameters()) {
                    policyCommand.then(argument(param.name(), param.type()).
                            executes(cc -> setRetentionPolicy(cc, rpt, key)));
                }
            }
            retainCommand.then(policyCommand);
        }
        argb.then(retainCommand);
    }


    /**
     * Does the work to encode a policy configuration and set it in git configuration.
     * Broken out as a helper methods so this logic can be shared by set-retention and set-remote-retention.
     * <p>
     * TODO this should probably move to Repo.
     */
    public static int setRetentionPolicy(final CommandContext<CommandSourceStack> cc,
                                         final RetentionPolicyType rpt,
                                         final FastbackConfigKey confKey) {
        final UserLogger ulog = ulog(cc);
        final Path worldSaveDir = mod().getWorldDirectory();
        try (final Repo repo = rf().load(worldSaveDir)) {
            final Map<String, String> config = new HashMap<>();
            for (final RetentionPolicyType.Parameter<?> p : rpt.getParameters()) {
                final Object val = getArgumentNicely(p.name(), p.clazz(), cc, ulog);
                if (val == null) return FAILURE;
                config.put(p.name(), String.valueOf(val));
            }
            final String encodedPolicy = RetentionPolicyCodec.INSTANCE.encodePolicy(rpt, config);
            final RetentionPolicy rp =
                    RetentionPolicyCodec.INSTANCE.decodePolicy(RetentionPolicyType.getAvailable(), encodedPolicy);
            if (rp == null) {
                syslog().error("Failed to decode policy " + encodedPolicy, new Exception());
                return FAILURE;
            }
            final GitConfig conf = repo.getConfig();
            conf.updater().set(confKey, encodedPolicy).save();
            ulog.message(localized("fastback.chat.retention-policy-set"));
            ulog.message(rp.getDescription());
            return SUCCESS;
        } catch (Exception e) {
            syslog().error("Failed to set retention policy", e);
            return FAILURE;
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/commands/SnapshotNameSuggestions.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
package net.pcal.fastback.common.commands;

import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.Repo;
import net.pcal.fastback.common.repo.SnapshotId;

import java.util.Iterator;
import java.util.concurrent.CompletableFuture;

import static net.pcal.fastback.common.commands.Commands.gitOp;
import static net.pcal.fastback.common.utils.Executor.ExecutionLock.NONE;

abstract class SnapshotNameSuggestions implements SuggestionProvider<CommandSourceStack> {

    static SnapshotNameSuggestions local() {
        return new SnapshotNameSuggestions() {
            @Override
            protected Iterator<SnapshotId> getSnapshotIds(Repo repo, UserLogger ulog) throws Exception {
                return repo.getLocalSnapshots().iterator();
            }
        };
    }

    static SnapshotNameSuggestions remote() {
        return new SnapshotNameSuggestions() {
            @Override
            protected Iterator<SnapshotId> getSnapshotIds(Repo repo, UserLogger ulog) throws Exception {
                return repo.getRemoteSnapshots().iterator();
            }
        };
    }

    @Override
    public CompletableFuture<Suggestions> getSuggestions(final CommandContext<CommandSourceStack> cc,
                                                         final SuggestionsBuilder builder) {
        CompletableFuture<Suggestions> completableFuture = new CompletableFuture<>();
        try (final UserLogger ulog = UserLogger.ulog(cc)) {
            gitOp(NONE, ulog, repo -> {
                final Iterator<SnapshotId> i = getSnapshotIds(repo, ulog);
                // Note to self: there's no point sorting here because the mc code (Suggestion.java) is
                // going to resort it anyway.
                while (i.hasNext()) builder.suggest(i.next().getShortName());
                completableFuture.complete(builder.buildFuture().get());
            });
        }
        return completableFuture;
    }

    abstract protected Iterator<SnapshotId> getSnapshotIds(Repo repo, UserLogger log) throws Exception;

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/config/FastbackConfigKey.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.config;

/**
 * .gitconfig settings that fastback cares about.
 *
 * @author pcal
 */
public enum FastbackConfigKey implements GitConfigKey {

    AUTOBACK_ACTION("autoback-action", null),
    AUTOBACK_WAIT_MINUTES("autoback-wait", 0),
    BROADCAST_ENABLED("broadcast-enabled", true),
    BROADCAST_MESSAGE("broadcast-message", null),
    IS_BACKUP_ENABLED("backup-enabled", true),
    IS_BRANCH_CLEANUP_ENABLED(true),
    IS_FILE_REMOTE_BARE(true),
    IS_LOCK_CLEANUP_ENABLED("lock-cleanup-enabled", true),
    IS_NATIVE_GIT_ENABLED("native-git-enabled", true),
    IS_MODS_BACKUP_ENABLED("mods-backup-enabled", false),
    IS_REFLOG_DELETION_ENABLED(true),
    IS_REMOTE_TEMP_BRANCH_CLEANUP_ENABLED(true),
    IS_SMART_PUSH_ENABLED("smart-push-enabled", false),
    IS_TEMP_BRANCH_CLEANUP_ENABLED(true),
    IS_TRACKING_BRANCH_CLEANUP_ENABLED(true),
    IS_UUID_CHECK_ENABLED(true),
    LOCAL_RETENTION_POLICY("retention-policy", null),
    REMOTE_NAME("remote-name", "origin"),
    REMOTE_RETENTION_POLICY("remote-retention-policy", null),
    RESTORE_DIRECTORY("restore-directory", null),
    SHUTDOWN_ACTION("shutdown-action", "local"),
    UPDATE_GITATTRIBUTES_ENABLED("update-gitattributes-enabled", true),
    UPDATE_GITIGNORE_ENABLED("update-gitignore-enabled", true);

    private final String settingName;
    private final Boolean booleanDefault;
    private final String stringDefault;
    private final Integer intDefault;

    FastbackConfigKey(boolean booleanDefaultValue) {
        this(null, booleanDefaultValue, null, null);
    }

    FastbackConfigKey(final String settingName, boolean booleanDefaultValue) {
        this(settingName, booleanDefaultValue, null, null);
    }

    FastbackConfigKey(final String settingName, String stringDefaultValue) {
        this(settingName, null, stringDefaultValue, null);
    }

    FastbackConfigKey(final String settingName, int intDefault) {
        this(settingName, null, null, intDefault);
    }

    FastbackConfigKey(final String settingName, final Boolean booleanDefault, String stringDefault, Integer intDefault) {
        this.settingName = settingName; //requireNonNull(settingName);
        this.booleanDefault = booleanDefault;
        this.stringDefault = stringDefault;
        this.intDefault = intDefault;
    }


    @Override
    public String getSectionName() {
        return "fastback";
    }

    @Override
    public String getSubSectionName() {
        return null;
    }

    @Override
    public String getSettingName() {
        return this.settingName;
    }

    @Override
    public boolean getBooleanDefault() {
        if (this.booleanDefault == null) throw new IllegalStateException(this + " is not a boolean");
        return this.booleanDefault;
    }

    @Override
    public String getStringDefault() {
        return this.stringDefault;
    }

    @Override
    public int getIntDefault() {
        if (this.intDefault == null) throw new IllegalStateException(this + " is not an int");
        return this.intDefault;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/config/GitConfig.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.config;

import org.eclipse.jgit.api.Git;

import java.io.IOException;

/**
 * Abstract representation of a git worktree's configuration.
 *
 * @author pcal
 */
public interface GitConfig {

    // @Deprecated - eventually we want the public contract to stop being
    // coupled to jgit
    static GitConfig load(Git jgit) {
        return GitConfigImpl.load(jgit);
    }

    boolean getBoolean(GitConfigKey key);

    String getString(GitConfigKey key);

    int getInt(GitConfigKey key);

    boolean isSet(GitConfigKey key);

    Updater updater();

    /**
     * Helper for updating the local .git/config file.
     */
    interface Updater {

        Updater set(GitConfigKey key, boolean newValue);

        Updater set(GitConfigKey key, String newValue);

        Updater set(GitConfigKey key, int newValue);

        Updater setCommented(GitConfigKey key, boolean newValue);

        Updater setCommented(GitConfigKey key, String newValue);

        Updater setCommented(GitConfigKey key, int newValue);

        void save() throws IOException;
    }
}

================================================
FILE: common/src/main/java/net/pcal/fastback/common/config/GitConfigImpl.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.config;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.StoredConfig;

import java.io.IOException;

import static java.util.Objects.requireNonNull;

/**
 * JGit-based implementation of GitConfig.
 *
 * @author pcal
 */
class GitConfigImpl implements GitConfig {

    static GitConfig load(final Git jgit) {
        return new GitConfigImpl(jgit.getRepository().getConfig());
    }

    public final StoredConfig storedConfig;

    GitConfigImpl(StoredConfig jgitConfig) {
        this.storedConfig = requireNonNull(jgitConfig);
    }

    @Override
    public boolean getBoolean(GitConfigKey key) {
        if (key.getSettingName() == null) return key.getBooleanDefault();
        return storedConfig.getBoolean(key.getSectionName(), key.getSubSectionName(), key.getSettingName(), key.getBooleanDefault());
    }

    @Override
    public String getString(GitConfigKey key) {
        if (key.getSettingName() == null) return key.getStringDefault();
        final String out = storedConfig.getString(key.getSectionName(), key.getSubSectionName(), key.getSettingName());
        return out != null ? out : key.getStringDefault();
    }

    @Override
    public int getInt(GitConfigKey key) {
        if (key.getSettingName() == null) return key.getIntDefault();
        return storedConfig.getInt(key.getSectionName(), key.getSubSectionName(), key.getSettingName(), key.getIntDefault());
    }

    @Override
    public boolean isSet(GitConfigKey key) {
        final String out = storedConfig.getString(key.getSectionName(), key.getSubSectionName(), key.getSettingName());
        return out != null;
    }

    @Override
    public Updater updater() {
        return new UpdaterImpl();
    }

    private class UpdaterImpl implements Updater {

        @Override
        public Updater set(GitConfigKey key, boolean newValue) {
            storedConfig.setBoolean(key.getSectionName(), key.getSubSectionName(), key.getSettingName(), newValue);
            return this;
        }

        @Override
        public Updater set(GitConfigKey key, String newValue) {
            storedConfig.setString(key.getSectionName(), key.getSubSectionName(), key.getSettingName(), newValue);
            return this;
        }

        @Override
        public Updater set(GitConfigKey key, int newValue) {
            storedConfig.setInt(key.getSectionName(), key.getSubSectionName(), key.getSettingName(), newValue);
            return this;
        }

        // ======================================================================
        // Methods for adding commented-out settings.  Useful for making the
        // initial git config a little more self-documenting.  jgit evidently
        // doesn't know the difference.

        @Override
        public Updater setCommented(GitConfigKey key, boolean newValue) {
            storedConfig.setBoolean(key.getSectionName(), key.getSubSectionName(), "# " + key.getSettingName(), newValue);
            return this;
        }

        @Override
        public Updater setCommented(GitConfigKey key, String newValue) {
            storedConfig.setString(key.getSectionName(), key.getSubSectionName(), "# " + key.getSettingName(), newValue);
            return this;
        }

        @Override
        public Updater setCommented(GitConfigKey key, int newValue) {
            storedConfig.setInt(key.getSectionName(), key.getSubSectionName(), "# " + key.getSettingName(), newValue);
            return this;
        }

        @Override
        public void save() throws IOException {
            storedConfig.save();
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/config/GitConfigKey.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.config;

/**
 * @author pcal
 * @since 0.14.0
 */
public interface GitConfigKey {

    String getSectionName();

    String getSubSectionName();

    String getSettingName();

    boolean getBooleanDefault();

    String getStringDefault();

    default String getDisplayName() {
        return this.getSettingName();
    }

    int getIntDefault();
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/config/OtherConfigKey.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.config;

import static java.util.Objects.requireNonNull;

/**
 * .gitconfig settings that fastback cares about.
 *
 * @author pcal
 */
public enum OtherConfigKey implements GitConfigKey {

    REMOTE_PUSH_URL("remote", "origin", "url") {
        @Override
        public String getDisplayName() {
            return "remote-url";
        }
    },

    /**
     * We disable commit signing on git init.  https://github.com/pcal43/fastback/issues/165
     */
    COMMIT_SIGNING_ENABLED("commit", null, "gpgsign"),

    // Allow reading the user's name and email from .gitconfig
    USER_NAME("user", null, "name"),
    USER_EMAIL("user", null, "email");

    private final String sectionName, subSectionName, settingName;

    OtherConfigKey(final String sectionName,
                   final String subsectionName,
                   final String settingName) {
        this.sectionName = requireNonNull(sectionName);
        this.subSectionName = subsectionName;
        this.settingName = requireNonNull(settingName);
    }

    @Override
    public String getSectionName() {
        return this.sectionName;
    }

    @Override
    public String getSubSectionName() {
        return this.subSectionName;
    }

    @Override
    public String getSettingName() {
        return this.settingName;
    }

    @Override
    public boolean getBooleanDefault() {
        return false;
    }

    @Override
    public String getStringDefault() {
        return null;
    }

    @Override
    public int getIntDefault() {
        return 0;
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/AutosaveLogger.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;

import static net.pcal.fastback.common.mod.Mod.mod;

/**
 * Handles messages in the context of an autosave operation.
 *
 * @author pcal
 * @since 0.15.0
 */
enum AutosaveLogger implements UserLogger {

    INSTANCE;

    @Override
    public void message(final UserMessage message) {
    }

    @Override
    public void update(final UserMessage message) {
        mod().setHudText(message);
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/CommandLogger.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;

import net.minecraft.commands.CommandSourceStack;

import static java.util.Objects.requireNonNull;
import static net.pcal.fastback.common.mod.Mod.mod;

/**
 * Handles messages in the context of a command executed by the user in the console or chat box.
 *
 * @author pcal
 * @since 0.15.0
 */
class CommandLogger implements UserLogger {

    private final CommandSourceStack scs;

    CommandLogger(final CommandSourceStack scs) {
        this.scs = requireNonNull(scs);
    }

    @Override
    public void message(final UserMessage message) {
        mod().sendChat(message, this.scs);
    }

    @Override
    public void update(final UserMessage message) {
        mod().setHudText(message);
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/Log4jLogger.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;

import net.pcal.fastback.common.utils.ProcessException;

import static java.util.Objects.requireNonNull;

public class Log4jLogger implements SystemLogger {

    private final org.apache.logging.log4j.Logger log4j;
    private boolean forceDebugEnabled = false;

    public Log4jLogger(org.apache.logging.log4j.Logger log4j) {
        this.log4j = requireNonNull(log4j);
    }

    @Override
    public void setForceDebugEnabled(boolean forceDebugEnabled) {
        this.forceDebugEnabled = forceDebugEnabled;
    }

    @Override
    public void error(String message) {
        this.log4j.error(message);
    }

    @Override
    public void error(String message, Throwable t) {
        // In the case of process execution failure, ensure we always dump the output along with the stacktrace so
        // we have a prayer of understanding what actually went wrong
        if (t instanceof ProcessException pe) pe.writeProcessOutput(this::error);
        this.log4j.error(message, t);
    }

    @Override
    public void warn(String message) {
        this.log4j.warn(message);
    }

    @Override
    public void info(String message) {
        this.log4j.info(message);
    }

    @Override
    public void debug(String message) {
        if (this.forceDebugEnabled) {
            this.log4j.info("[DEBUG] " + message);
        } else {
            this.log4j.debug(message);
        }
    }

    @Override
    public void debug(String message, Throwable t) {
        if (this.forceDebugEnabled) {
            // In the case of process execution failure, ensure we always dump the output along with the stacktrace so
            // we have a prayer of understanding what actually went wrong
            if (t instanceof ProcessException pe) pe.writeProcessOutput(line -> this.log4j.info("[DEBUG] " + message));
            this.log4j.info("[DEBUG] " + message, t);
        } else {
            this.log4j.debug(message, t);
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/ShutdownLogger.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;

import static net.pcal.fastback.common.mod.Mod.mod;

/**
 * Handles messages in the context of the server shutting down.
 *
 * @author pcal
 * @since 0.15.0
 */
enum ShutdownLogger implements UserLogger {

    INSTANCE;

    @Override
    public void message(final UserMessage message) {
        mod().setMessageScreenText(message);
    }

    @Override
    public void update(final UserMessage message) {
        mod().setHudText(message);
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/SystemLogger.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;

import java.util.function.Supplier;

/**
 * Singleton logger instance that writes to the serverside console.
 *
 * @author pcal
 * @since 0.12.0
 */
public interface SystemLogger {

    static SystemLogger syslog() {
        return Singleton.INSTANCE;
    }

    void setForceDebugEnabled(boolean debug);

    void error(String message);

    void error(String message, Throwable t);

    default void error(Throwable e) {
        this.error(e.getMessage(), e);
    }

    void warn(String message);

    void info(String message);

    void debug(String message);

    default void trace(Supplier<String> message) {
        debug(message.get()); //FIXME
    }

    void debug(String message, Throwable t);

    default void debug(Throwable t) {
        this.debug(t.getMessage(), t);
    }

    class Singleton {
        private static SystemLogger INSTANCE = null;

        public static void register(SystemLogger logger) {
            Singleton.INSTANCE = logger;
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/UserLogger.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;

import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;

import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.ERROR;
import static net.pcal.fastback.common.logging.UserMessage.styledLocalized;
import static net.pcal.fastback.common.mod.Mod.mod;

/**
 * Logging interface for messages which *might* be displayed in the UI.
 *
 * @author pcal
 * @since 0.13.0
 */
public interface UserLogger extends AutoCloseable {

    /**
     * Send a fairly important message that should be displayed in the UI a relatively prominent and durable manner.
     * Typically, this means in the chat dialog.
     */
    void message(UserMessage message);

    /**
     * Send a bit of low-level detail that is useful for indicating progress or activity but isn't of critical
     * importance.  This will typically be displayed in the HUD area.
     */
    void update(UserMessage message);

    @Override
    default void close() {
        mod().clearHudText();
    }

    default void internalError() {
        this.message(styledLocalized("fastback.chat.internal-error", ERROR));
    }

    default void internalError(Exception e) {
        syslog().error(e);
        internalError();
    }

    static UserLogger ulog(final CommandContext<CommandSourceStack> cc) {
        return new CommandLogger(cc.getSource());
    }

    static UserLogger ulog(final CommandSourceStack scs) {
        return new CommandLogger(scs);
    }

    static UserLogger forShutdown() {
        return ShutdownLogger.INSTANCE;
    }

    static UserLogger forAutosave() {
        return ShutdownLogger.INSTANCE;
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/logging/UserMessage.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.logging;


import static java.util.Objects.requireNonNull;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.ERROR;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.NORMAL;

/**
 * Abstract representation of a message to be displayed on the user's screen.  The message may or may
 * not be localizable.
 *
 * @author pcal
 */
public record UserMessage(LocalizedUserMessage localized, String raw, UserMessageStyle style) {

    public enum UserMessageStyle {
        NORMAL,
        WARNING,
        ERROR,
        JGIT,
        NATIVE_GIT,
        BROADCAST,
    }

    public record LocalizedUserMessage(String key, Object... params) {

        @Override
        public String toString() {
            return this.key + " " + (this.params != null ? params : "[]");
        }
    }

    public static UserMessage localized(String key, Object... params) {
        return styledLocalized(key, NORMAL, params);
    }

    public static UserMessage styledLocalized(String key, UserMessageStyle style, Object... params) {
        return new UserMessage(new LocalizedUserMessage(key, params), null, style);
    }

    public static UserMessage raw(String text) {
        return styledRaw(text, NORMAL);
    }

    public static UserMessage styledRaw(String text, UserMessageStyle style) {
        return new UserMessage(null, requireNonNull(text), style);
    }

    @Override
    public String toString() {
        return this.raw != null ? raw : this.localized != null ? this.localized.toString() : null;
    }

    // ======================================================================
    // Deprecated stuff

    @Deprecated
    public static UserMessage localizedError(String key, Object... params) {
        return styledLocalized(key, ERROR, params);
    }

    @Deprecated
    public static UserMessage rawError(String text) {
        return styledRaw(text, ERROR);
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mixins/FileFixerUpperMixin.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
package net.pcal.fastback.common.mixins;

import net.minecraft.util.filefix.FileFixerUpper;
import net.minecraft.util.filefix.virtualfilesystem.CopyOnWriteFileSystem;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.PathMatcher;

/**
 * Prevents MC 26.1+'s CopyOnWriteFileSystem (used during world upgrade) from choking
 * on read-only git object files inside the .git directory.
 *
 * @author pcal
 * @since 0.31.2
 */
@Mixin(FileFixerUpper.class)
public class FileFixerUpperMixin {

    @Redirect(
        method = "applyFileFixersOnCow",
        at = @At(
            value = "INVOKE",
            target = "Lnet/minecraft/util/filefix/virtualfilesystem/CopyOnWriteFileSystem;create(Ljava/lang/String;Ljava/nio/file/Path;Ljava/nio/file/Path;Ljava/nio/file/PathMatcher;)Lnet/minecraft/util/filefix/virtualfilesystem/CopyOnWriteFileSystem;",
            remap = false
        ),
        remap = false
    )
    private CopyOnWriteFileSystem fastback_skipGitDir(
            String name, Path baseDir, Path tmpDir, PathMatcher original) throws IOException {
        PathMatcher withGitSkip = path -> {
            for (Path component : path) {
                if (".git".equals(component.toString())) return true;
            }
            return original.matches(path);
        };
        return CopyOnWriteFileSystem.create(name, baseDir, tmpDir, withGitSkip);
    }
}



================================================
FILE: common/src/main/java/net/pcal/fastback/common/mixins/MessageScreenMixin.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
package net.pcal.fastback.common.mixins;

import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.GenericMessageScreen;
import net.pcal.fastback.common.mod.Mod;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

/**
 * Implements a callback that lets us render extra text on MessageScreens (i.e., exit/saving screen).
 *
 * @author pcal
 * @since 0.14.0
 */
@Mixin(GenericMessageScreen.class)
public class MessageScreenMixin {

    @Inject(method = "extractBackground", at = @At("TAIL"), remap = false)
    public void fastback_render(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
        Mod.mod().renderMessageScreen(context);
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mixins/MinecraftServerMixin.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
package net.pcal.fastback.common.mixins;

import net.minecraft.server.MinecraftServer;
import net.pcal.fastback.common.mod.Mod;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import static net.pcal.fastback.common.logging.SystemLogger.syslog;

/**
 * Allows us to disable vanilla saving during 'git add' to avoid coherency problems in the backup snapshots.  Also
 * sends notifications when autosaving completes so we can follow them with automated backups.
 *
 * @author pcal
 * @since 0.0.1
 */
@Mixin(MinecraftServer.class)
public class MinecraftServerMixin {

    /**
     * Intercept the call to saveAll that triggers on autosave, pass it through and then send out notification that
     * the autosave is done.
     */
    @Redirect(method = "autoSave()V",
            at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;saveEverything(ZZZ)Z"), remap = false)
    public boolean fastback_saveAll(MinecraftServer instance, boolean suppressLogs, boolean flush, boolean force) {
        boolean result = instance.saveEverything(suppressLogs, flush, force);
        Mod.mod().autoSaveCompleted();
        return result;
    }

    /**
     * Intercept save so we can hard-disable saving during critical parts of the backup.
     */
    @Inject(at = @At("HEAD"), method = "saveAllChunks(ZZZ)Z", cancellable = true, remap = false)
    public void fastback_save(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable<Boolean> ci) {
        synchronized (this) {
            if (Mod.mod().isWorldSaveEnabled()) {
                syslog().debug("world saves are enabled, doing requested save");
            } else {
                syslog().warn("Skipping requested save because a backup is in progress.");
                ci.setReturnValue(false);
                ci.cancel();
            }
        }
    }

    /**
     * Intercept saveAll so we can hard-disable saving during critical parts of the backup.
     */
    @Inject(at = @At("HEAD"), method = "saveEverything(ZZZ)Z", cancellable = true, remap = false)
    public void fastback_saveAll(boolean suppressLogs, boolean flush, boolean force, CallbackInfoReturnable<Boolean> ci) {
        synchronized (this) {
            if (Mod.mod().isWorldSaveEnabled()) {
                syslog().debug("world saves are enabled, doing requested saveAll");
                //TODO should call save here to ensure all synced?
            } else {
                syslog().warn("Skipping requested saveAll because a backup is in progress.");
                ci.setReturnValue(false);
                ci.cancel();
            }
        }
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mixins/ScreenAccessors.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mixins;

import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;

/**
 * @author pcal
 * @since 0.0.11
 */
@Mixin(Screen.class)
public interface ScreenAccessors {

    @Accessor(remap = false)
    @Mutable
    Component getTitle();

    @Invoker(remap = false)
    @Mutable
    void invokeRebuildWidgets();

    @Accessor(remap = false)
    @Mutable
    void setTitle(Component text);
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mixins/ServerAccessors.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mixins;

import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

/**
 * @author pcal
 * @since 0.0.1
 */
@Mixin(MinecraftServer.class)
public interface ServerAccessors {

    @Accessor(remap = false)
    int getTickCount();

    @Accessor(remap = false)
    LevelStorageSource.LevelStorageAccess getStorageSource();
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mixins/SessionAccessors.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mixins;

import net.minecraft.world.level.storage.LevelStorageSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

/**
 * @author pcal
 * @since 0.0.1
 */
@Mixin(LevelStorageSource.LevelStorageAccess.class)
public interface SessionAccessors {
    @Accessor(remap = false)
    LevelStorageSource.LevelDirectory getLevelDirectory();
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mod/AutosaveListener.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mod;

import net.pcal.fastback.common.commands.SchedulableAction;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.repo.Repo;
import net.pcal.fastback.common.repo.RepoFactory;
import net.pcal.fastback.common.utils.Executor;

import java.nio.file.Path;
import java.time.Duration;

import static net.pcal.fastback.common.commands.SchedulableAction.NONE;
import static net.pcal.fastback.common.commands.SchedulableAction.forConfigValue;
import static net.pcal.fastback.common.config.FastbackConfigKey.AUTOBACK_ACTION;
import static net.pcal.fastback.common.config.FastbackConfigKey.AUTOBACK_WAIT_MINUTES;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_BACKUP_ENABLED;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.mod.Mod.mod;
import static net.pcal.fastback.common.utils.Executor.executor;

/**
 * Responds to vanilla autosaves and follows them with an automatic backup (autoback).
 *
 * @author pcal
 * @since 0.2.0
 */
class AutosaveListener implements Runnable {

    private long lastBackupTime = System.currentTimeMillis();

    @Override
    public void run() {
        try (final UserLogger ulog = UserLogger.forAutosave()) {
            executor().execute(Executor.ExecutionLock.WRITE, ulog, () -> {
                try {
                    final RepoFactory rf = RepoFactory.rf();
                    final Path worldSaveDir = mod().getWorldDirectory();
                    if (!rf.isGitRepo(worldSaveDir)) return;
                    try (final Repo repo = rf.load(worldSaveDir)) {
                        final GitConfig config = repo.getConfig();
                        if (!config.getBoolean(IS_BACKUP_ENABLED)) return;
                        final SchedulableAction autobackAction = forConfigValue(config, AUTOBACK_ACTION);
                        if (autobackAction == null || autobackAction == NONE) return;
                        final Duration waitTime = Duration.ofMinutes(config.getInt(AUTOBACK_WAIT_MINUTES));
                        final Duration timeRemaining = waitTime.
                                minus(Duration.ofMillis(System.currentTimeMillis() - lastBackupTime));
                        if (!timeRemaining.isZero() && !timeRemaining.isNegative()) {
                            syslog().debug("Skipping auto-backup until at least " +
                                    (timeRemaining.toSeconds() / 60) + " more minutes have elapsed.");
                            return;
                        }
                        syslog().info("Starting auto-backup");
                        autobackAction.getTask(repo, ulog).call();
                    }
                    lastBackupTime = System.currentTimeMillis();
                } catch (Exception e) {
                    syslog().error("auto-backup failed.", e);
                }
            });
        }
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mod/ClientHelper.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mod;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.screens.GenericMessageScreen;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.mixins.ScreenAccessors;

import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.mod.UserMessageUtil.messageToText;

/**
 * Client-only helper services. Holds vanilla Minecraft client state and provides
 * concrete implementations for HUD and message screen management.
 *
 * @author pcal
 * @since 0.2.0
 */
public final class ClientHelper {

    // ======================================================================
    // Constants

    private static final long TEXT_TIMEOUT = 10 * 1000;

    // ======================================================================
    // Fields

    private final Minecraft client;
    private Component hudText;
    private long hudTextTime;

    // ======================================================================
    // Constructor

    public ClientHelper(Minecraft client) {
        this.client = client;
    }

    // ======================================================================
    // Concrete — vanilla Minecraft implementations

    public void setHudText(UserMessage userMessage) {
        if (userMessage == null) {
            clearHudText();
        } else {
            this.hudText = messageToText(userMessage);
            this.hudTextTime = System.currentTimeMillis();
        }
    }

    public void clearHudText() {
        this.hudText = null;
    }

    public void setMessageScreenText(UserMessage userMessage) {
        if (this.client == null) return;
        final Screen screen = client.screen;
        if (screen instanceof GenericMessageScreen) {
            ((ScreenAccessors) screen).setTitle(messageToText(userMessage));
            ((ScreenAccessors) screen).invokeRebuildWidgets(); // force it to rebuild the message component with the new title
        }
    }

    public void renderMessageScreen(GuiGraphicsExtractor guiGraphics) {
        renderHud(guiGraphics);
    }

    public void renderHud(GuiGraphicsExtractor guiGraphics) {
        if (this.client == null) return;
        if (this.hudText == null) return;
        if (!this.client.options.showAutosaveIndicator().get()) return;
        if (System.currentTimeMillis() - this.hudTextTime > TEXT_TIMEOUT) {
            this.hudText = null;
            syslog().debug("hud text timed out.  somebody forgot to clean up");
            return;
        }
        guiGraphics.text(this.client.font, this.hudText, 2, 2, 0xFFFFFF, false);
    }
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mod/LoaderHelper.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mod;

import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.pcal.fastback.common.commands.PermissionsFactory;

import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;

/**
 * Abstracts away loader/environment-specific services that the mod framework
 * (e.g. Fabric) must provide.
 *
 * @author pcal
 * @since 0.2.0
 */
public interface LoaderHelper {

    /**
     * @return the version string of the fastback mod as reported by the loader.
     */
    String getModVersion();

    /**
     * Appends loader-specific properties (e.g. the installed mod list) to the backup
     * properties map. Common minecraft-* and fastback-version entries are added by ModImpl.
     */
    void addLoaderBackupProperties(Map<String, String> props);

    /**
     * @return path to the client 'saves' directory, or null on a dedicated server.
     */
    Path getSavesDir();

    /**
     * @return paths that should be included when mods-backup is enabled.
     */
    Collection<Path> getModsBackupPaths();

    /**
     * Create the /backup command using the given builder and register it.
     *
     * @param isClient true when running on an integrated (client-embedded) server.
     */
    void registerBackupCommand(boolean isClient,
                               Function<PermissionsFactory<CommandSourceStack>, LiteralArgumentBuilder<CommandSourceStack>> builder);
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mod/Mod.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mod;

import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.pcal.fastback.common.logging.UserMessage;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
 * Singleton that provides various mod-wide services.
 *
 * @author pcal
 * @since 0.1.0
 */
public interface Mod {

    // ======================================================================
    // Singleton

    static Mod mod() {
        return SingletonHolder.INSTANCE;
    }

    class SingletonHolder {
        private static Mod INSTANCE = null;

        public static void register(Mod mod) {
            requireNonNull(mod);
            if (INSTANCE != null) throw new IllegalStateException("Mod singleton initialized twice");
            SingletonHolder.INSTANCE = mod;
        }
    }

    // ======================================================================
    // Loader-facing methods

    /**
     * Initializes the mod for a dedicated server. Call once at startup.
     */
    static void initializeForDedicatedServer(LoaderHelper loaderHelper) {
        ModImpl.initialize(loaderHelper, null);
    }

    /**
     * Initializes the mod for a client (integrated or dedicated-server-from-client). Call once at startup.
     */
    static void initializeForClient(LoaderHelper loaderHelper, ClientHelper clientHelper) {
        ModImpl.initialize(loaderHelper, clientHelper);
    }

    /**
     * Must be called when a world is starting so that we can have a reference
     * to the active world.
     */
    void onWorldStart(MinecraftServer server);

    /**
     * Must be called when a world is stopping to ensure we can run a
     * shutdown backup.
     */
    void onWorldStop();

    /**
     * Allows loaders to plugin HUD rendering.
     */
    void renderHud(GuiGraphicsExtractor drawContext);

    // ======================================================================
    // Mixin-facing methods

    /**
     * Called from the mixins to check whether vanilla autosaving should
     * be disabled.  Autosaving while a backup is in progress could result
     * in an inconsistent backup state.
     */
    boolean isWorldSaveEnabled();

    /**
     * Called from the mixins to tell us that an autosave just finished.
     * This may trigger a backup, depending on configuration.
     */
    void autoSaveCompleted();

    /**
     * Called from the shutdown message screen mixins to render additional text.
     */
    void renderMessageScreen(GuiGraphicsExtractor drawContext);


    // ======================================================================
    // Command-facing methods

    /**
     * @return path to where snapshots should be restored.
     */
    Path getDefaultRestoresDir() throws IOException;

    /**
     * @return the version of the fastback mod.
     */
    String getModVersion();

    /**
     * Enables or disables world saving.
     */
    void setWorldSaveEnabled(boolean enabled);

    /**
     * Forces a save of the world.  We often want to do this before doing a backup.
     */
    void saveWorld();

    /**
     * If we're clientside and the user is looking at a MessageScreen, set the title.
     */
    void setMessageScreenText(UserMessage message);

    /**
     * Send a chat message to user.
     */
    void sendChat(UserMessage message, CommandSourceStack scs);

    /**
     * If on a dedicated server, broadcast a message to the chat window of all connected users.
     */
    void sendBroadcast(UserMessage message);

    /**
     * Set magical floating text.  You MUST call clearHudText
     */
    void setHudText(UserMessage message);

    /**
     * Remove the magical floating text.
     */
    void clearHudText();

    /**
     * @return path to the save directory of the currently-loaded world (aka the git worktree).
     */
    Path getWorldDirectory();

    /**
     * @return name of the currently-loaded world.
     */
    String getWorldName();

    /**
     * @return paths to backup when mods-backup enabled.
     */
    Collection<Path> getModsBackupPaths();

    /**
     * Add extra properties that will be stored in .fastback/backup.properties.
     */
    void addBackupProperties(Map<String, String> props);
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/mod/ModImpl.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */
package net.pcal.fastback.common.mod;

import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.pcal.fastback.common.commands.Commands;
import net.pcal.fastback.common.commands.SchedulableAction;
import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.logging.Log4jLogger;
import net.pcal.fastback.common.logging.SystemLogger;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.logging.UserMessage;
import net.pcal.fastback.common.mixins.ServerAccessors;
import net.pcal.fastback.common.mixins.SessionAccessors;
import net.pcal.fastback.common.repo.Repo;
import net.pcal.fastback.common.repo.RepoFactory;
import org.apache.logging.log4j.LogManager;
import org.eclipse.jgit.transport.SshSessionFactory;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;

import static java.nio.file.Files.createTempDirectory;
import static java.util.Objects.requireNonNull;
import static net.pcal.fastback.common.config.FastbackConfigKey.IS_BACKUP_ENABLED;
import static net.pcal.fastback.common.config.FastbackConfigKey.SHUTDOWN_ACTION;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;
import static net.pcal.fastback.common.logging.UserMessage.UserMessageStyle.ERROR;
import static net.pcal.fastback.common.logging.UserMessage.localized;
import static net.pcal.fastback.common.mod.UserMessageUtil.messageToText;
import static net.pcal.fastback.common.utils.EnvironmentUtils.getGitLfsVersion;
import static net.pcal.fastback.common.utils.EnvironmentUtils.getGitVersion;
import static net.pcal.fastback.common.utils.Executor.executor;

class ModImpl implements Mod {

    // ======================================================================
    // Fields

    private final LoaderHelper loaderHelper;
    private final ClientHelper clientHelper; // null on a dedicated server
    private final Runnable autoSaveListener;
    private MinecraftServer minecraftServer = null; // currently open world
    private boolean isWorldSaveEnabled = true;
    private Path tempRestoresDirectory = null;

    // ======================================================================
    // Factory — called by loader initializers

    /**
     * Creates, registers, and initializes a ModImpl.
     *
     * @param loaderHelper loader-specific services (always present)
     * @param clientHelper client-specific services, or null on a dedicated server
     */
    static Mod initialize(final LoaderHelper loaderHelper, final ClientHelper clientHelper) {
        SystemLogger.Singleton.register(new Log4jLogger(LogManager.getLogger("fastback")));
        final ModImpl mod = new ModImpl(loaderHelper, clientHelper);
        SingletonHolder.register(mod);
        mod.onInitialize();
        return mod;
    }

    private ModImpl(LoaderHelper loaderHelper, ClientHelper clientHelper) {
        this.loaderHelper = requireNonNull(loaderHelper);
        this.clientHelper = clientHelper; // nullable — null means dedicated server
        this.autoSaveListener = new AutosaveListener();
    }

    // ======================================================================
    // Mod implementation

    @Override
    public void onWorldStart(final MinecraftServer minecraftServer) {
        this.minecraftServer = requireNonNull(minecraftServer);
        executor().start();
        syslog().debug("onWorldStart complete");
    }

    @Override
    public void onWorldStop() {
        try (final UserLogger ulog = UserLogger.forShutdown()) {
            final Path worldSaveDir = this.getWorldDirectory();
            if (executor().getActiveCount() > 0) {
                this.setMessageScreenText(localized("fastback.chat.thread-waiting"));
            }
            executor().stop();
            this.clearHudText();
            final RepoFactory rf = RepoFactory.rf();
            if (rf.isGitRepo(worldSaveDir)) {
                try (final Repo repo = rf.load(worldSaveDir)) {
                    final GitConfig config = repo.getConfig();
                    if (config.getBoolean(IS_BACKUP_ENABLED)) {
                        final SchedulableAction action = SchedulableAction.forConfigValue(config, SHUTDOWN_ACTION);
                        if (action != null) {
                            this.setMessageScreenText(localized("fastback.message.backing-up"));
                            action.getTask(repo, ulog).call();
                            this.setMessageScreenText(localized("fastback.chat.backup-complete"));
                        }
                    }
                } catch (Exception e) {
                    syslog().error("Shutdown action failed.", e);
                }
            }
            syslog().debug("onWorldStop complete");
        }
        this.minecraftServer = null;
    }

    @Override
    public Path getDefaultRestoresDir() throws IOException {
        Path savesDir = this.loaderHelper.getSavesDir();
        if (savesDir != null) return savesDir;
        if (tempRestoresDirectory == null) {
            tempRestoresDirectory = createTempDirectory("fastback-restore");
        }
        return tempRestoresDirectory;
    }

    @Override
    public String getModVersion() {
        return this.loaderHelper.getModVersion();
    }

    @Override
    public void setWorldSaveEnabled(boolean enabled) {
        this.isWorldSaveEnabled = enabled;
    }

    @Override
    public void saveWorld() {
        if (this.minecraftServer == null) throw new IllegalStateException();
        this.minecraftServer.saveEverything(false, true, true);
    }

    @Override
    public void sendChat(UserMessage message, CommandSourceStack scs) {
        if (message.style() == ERROR) {
            scs.sendFailure(messageToText(message));
        } else {
            scs.sendSuccess(() -> messageToText(message), false);
        }
    }

    @Override
    public void sendBroadcast(UserMessage userMessage) {
        if (this.minecraftServer != null && this.minecraftServer.isDedicatedServer()) {
            this.minecraftServer.getPlayerList().broadcastSystemMessage(messageToText(userMessage), false);
        }
    }

    @Override
    public void setHudText(UserMessage message) {
        if (this.clientHelper == null) return;
        if (message == null) {
            syslog().debug("null unexpectedly passed to setHudText, ignoring");
            this.clearHudText();
        } else {
            this.clientHelper.setHudText(message);
        }
    }

    @Override
    public void clearHudText() {
        if (this.clientHelper != null) this.clientHelper.clearHudText();
    }

    @Override
    public void setMessageScreenText(UserMessage message) {
        if (this.clientHelper != null)
            this.clientHelper.setMessageScreenText(message);
    }

    @Override
    public Path getWorldDirectory() {
        if (this.minecraftServer == null) throw new IllegalStateException();
        final LevelStorageSource.LevelStorageAccess session =
                ((ServerAccessors) this.minecraftServer).getStorageSource();
        return ((SessionAccessors) session).getLevelDirectory().path();
    }

    @Override
    public String getWorldName() {
        if (this.minecraftServer == null) throw new IllegalStateException();
        return this.minecraftServer.getWorldData().getLevelName();
    }

    @Override
    public void addBackupProperties(Map<String, String> props) {
        props.put("fastback-version", this.getModVersion());
        if (this.minecraftServer != null) {
            props.put("minecraft-version", minecraftServer.getServerVersion());
            props.put("minecraft-game-mode", String.valueOf(minecraftServer.getWorldData().getGameType()));
            props.put("minecraft-level-name", minecraftServer.getWorldData().getLevelName());
        }
        this.loaderHelper.addLoaderBackupProperties(props);
    }

    @Override
    public Collection<Path> getModsBackupPaths() {
        return this.loaderHelper.getModsBackupPaths();
    }

    // ======================================================================
    // Mod implementation (continued)

    @Override
    public boolean isWorldSaveEnabled() {
        return this.isWorldSaveEnabled;
    }

    @Override
    public void autoSaveCompleted() {
        if (this.autoSaveListener != null) {
            this.autoSaveListener.run();
        } else {
            syslog().warn("Autosave just happened but, unexpectedly, no one is listening.");
        }
    }

    @Override
    public void renderMessageScreen(GuiGraphicsExtractor drawContext) {
        if (this.clientHelper != null) {
            this.clientHelper.renderMessageScreen(drawContext);
        } else {
            syslog().warn("renderMessageScreen called when clientHelper not set.");
        }
    }

    @Override
    public void renderHud(GuiGraphicsExtractor drawContext) {
        if (this.clientHelper != null) {
            this.clientHelper.renderHud(drawContext);
        } else {
            syslog().warn("renderHud called when clientHelper not set.");
        }
    }

    // ======================================================================
    // Private methods

    private void onInitialize() {
        final String gitVersion = getGitVersion();
        if (gitVersion == null) {
            syslog().warn("git is not installed.");
        } else {
            syslog().info("git is installed: " + gitVersion);
        }
        final String gitLfsVersion = getGitLfsVersion();
        if (gitLfsVersion == null) {
            syslog().warn("git-lfs is not installed.");
        } else {
            syslog().info("git-lfs is installed: " + gitLfsVersion);
        }
        if (SshSessionFactory.getInstance() == null) {
            syslog().warn("An ssh provider was not initialized for jgit.  Operations on a remote repo over ssh will fail.");
        } else {
            syslog().info("SshSessionFactory: " + SshSessionFactory.getInstance());
        }
        this.loaderHelper.registerBackupCommand(clientHelper != null, Commands::createBackupCommand);
        syslog().debug("onInitialize complete");
    }
}

================================================
FILE: common/src/main/java/net/pcal/fastback/common/mod/UserMessageUtil.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.mod;

import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.TextColor;
import net.pcal.fastback.common.logging.UserMessage;

import static net.minecraft.ChatFormatting.GRAY;
import static net.minecraft.ChatFormatting.GREEN;
import static net.minecraft.ChatFormatting.RED;
import static net.minecraft.ChatFormatting.YELLOW;
import static net.minecraft.network.chat.Style.EMPTY;

/**
 * Utility for converting {@link UserMessage} to Minecraft {@link Component}.
 *
 * @author pcal
 * @since 0.2.0
 */
public class UserMessageUtil {

    public static Component messageToText(final UserMessage m) {
        final MutableComponent out;
        if (m.localized() != null) {
            out = Component.translatable(
                m.localized().key(),
                messageParamsToComponentArgs(m.localized().params())
            );
        } else {
            out = Component.literal(m.raw());
        }
        switch (m.style()) {
            case ERROR -> out.setStyle(EMPTY.withColor(TextColor.fromLegacyFormat(RED)));
            case WARNING -> out.setStyle(EMPTY.withColor(TextColor.fromLegacyFormat(YELLOW)));
            case JGIT -> out.setStyle(EMPTY.withColor(TextColor.fromLegacyFormat(GRAY)));
            case NATIVE_GIT -> out.setStyle(EMPTY.withColor(TextColor.fromLegacyFormat(GREEN)));
        }
        return out;
    }

    private static Object[] messageParamsToComponentArgs(final Object[] params) {
        if (params == null) return new Object[0];

        final Object[] out = new Object[params.length];
        for (int i = 0; i < params.length; i++) {
            final Object param = params[i];
            if (param instanceof Component) {
                out[i] = param;
            } else {
                out[i] = String.valueOf(param);
            }
        }
        return out;
    }

    private UserMessageUtil() {}
}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/repo/BranchUtils.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.repo;

import net.pcal.fastback.common.repo.SnapshotIdUtils.SnapshotIdCodec;
import org.eclipse.jgit.api.errors.GitAPIException;

import java.io.IOException;
import java.text.ParseException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import static java.util.Objects.requireNonNull;
import static net.pcal.fastback.common.logging.SystemLogger.syslog;


/**
 * @author pcal
 */
abstract class BranchUtils {

    /**
     * Get the snapshots for this repo.  Snapshot branches for worlds other than the Repo's are ignored.
     */
    static Set<SnapshotId> listSnapshots(RepoImpl repo, JGitSupplier<Collection<String>> refProvider) throws GitAPIException, IOException {
        final Collection<String> refs = refProvider.get();
        final SnapshotIdCodec codec = repo.getSidCodec();
        final Set<SnapshotId> out = new HashSet<>();
        for (final String ref : refs) {
            String branchName = getBranchName(ref);
            if (repo.getSidCodec().isSnapshotBranchName(repo.getWorldId(), branchName)) {
                final SnapshotId sid;
                try {
                    sid = requireNonNull(codec.fromBranch(branchName));
                } catch (ParseException pe) {
                    syslog().error("Unexpected parse error, ignoring branch " + branchName, pe);
                    continue;
                }
                if (sid.getWorldId().equals(repo.getWorldId())) {
                    out.add(sid);
                } else {
                    syslog().debug("Ignoring branch from other world " + branchName);
                }
            } else {
                syslog().debug("Ignoring unrecognized branch " + branchName);
            }
        }
        return out;
    }

    static String getBranchName(String name) {
        final String REFS_HEADS = "refs/heads/";
        if (name.startsWith(REFS_HEADS)) {
            return name.substring(REFS_HEADS.length());
        } else {
            return null;
        }
    }

}


================================================
FILE: common/src/main/java/net/pcal/fastback/common/repo/CommitUtils.java
================================================
/*
 * FastBack - Fast, incremental Minecraft backups powered by Git.
 * Copyright (C) 2022 pcal.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 */

package net.pcal.fastback.common.repo;

import net.pcal.fastback.common.config.GitConfig;
import net.pcal.fastback.common.logging.UserLogger;
import net.pcal.fastback.common.utils.EnvironmentUtils;
import net.pcal.fastback.common.utils.ProcessException;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.RmComma
Download .txt
gitextract_2zf8f3wn/

├── .github/
│   └── workflows/
│       └── build-pull-request.yml
├── .gitignore
├── Justfile
├── LICENSE
├── README.md
├── build.gradle
├── common/
│   ├── build.gradle
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   │   └── net/
│       │   │       └── pcal/
│       │   │           └── fastback/
│       │   │               └── common/
│       │   │                   ├── commands/
│       │   │                   │   ├── Command.java
│       │   │                   │   ├── Commands.java
│       │   │                   │   ├── CreateFileRemoteCommand.java
│       │   │                   │   ├── DeleteCommand.java
│       │   │                   │   ├── FullCommand.java
│       │   │                   │   ├── GcCommand.java
│       │   │                   │   ├── HelpCommand.java
│       │   │                   │   ├── InfoCommand.java
│       │   │                   │   ├── InitCommand.java
│       │   │                   │   ├── ListCommand.java
│       │   │                   │   ├── LocalCommand.java
│       │   │                   │   ├── PermissionsFactory.java
│       │   │                   │   ├── PruneCommand.java
│       │   │                   │   ├── PushCommand.java
│       │   │                   │   ├── RemoteDeleteCommand.java
│       │   │                   │   ├── RemoteListCommand.java
│       │   │                   │   ├── RemotePruneCommand.java
│       │   │                   │   ├── RemoteRestoreCommand.java
│       │   │                   │   ├── RestoreCommand.java
│       │   │                   │   ├── SchedulableAction.java
│       │   │                   │   ├── SetCommand.java
│       │   │                   │   └── SnapshotNameSuggestions.java
│       │   │                   ├── config/
│       │   │                   │   ├── FastbackConfigKey.java
│       │   │                   │   ├── GitConfig.java
│       │   │                   │   ├── GitConfigImpl.java
│       │   │                   │   ├── GitConfigKey.java
│       │   │                   │   └── OtherConfigKey.java
│       │   │                   ├── logging/
│       │   │                   │   ├── AutosaveLogger.java
│       │   │                   │   ├── CommandLogger.java
│       │   │                   │   ├── Log4jLogger.java
│       │   │                   │   ├── ShutdownLogger.java
│       │   │                   │   ├── SystemLogger.java
│       │   │                   │   ├── UserLogger.java
│       │   │                   │   └── UserMessage.java
│       │   │                   ├── mixins/
│       │   │                   │   ├── FileFixerUpperMixin.java
│       │   │                   │   ├── MessageScreenMixin.java
│       │   │                   │   ├── MinecraftServerMixin.java
│       │   │                   │   ├── ScreenAccessors.java
│       │   │                   │   ├── ServerAccessors.java
│       │   │                   │   └── SessionAccessors.java
│       │   │                   ├── mod/
│       │   │                   │   ├── AutosaveListener.java
│       │   │                   │   ├── ClientHelper.java
│       │   │                   │   ├── LoaderHelper.java
│       │   │                   │   ├── Mod.java
│       │   │                   │   ├── ModImpl.java
│       │   │                   │   └── UserMessageUtil.java
│       │   │                   ├── repo/
│       │   │                   │   ├── BranchUtils.java
│       │   │                   │   ├── CommitUtils.java
│       │   │                   │   ├── JGitConsumer.java
│       │   │                   │   ├── JGitFunction.java
│       │   │                   │   ├── JGitIncrementalProgressMonitor.java
│       │   │                   │   ├── JGitPercentageProgressMonitor.java
│       │   │                   │   ├── JGitSupplier.java
│       │   │                   │   ├── PreflightUtils.java
│       │   │                   │   ├── PruneUtils.java
│       │   │                   │   ├── PushUtils.java
│       │   │                   │   ├── ReclamationUtils.java
│       │   │                   │   ├── Repo.java
│       │   │                   │   ├── RepoFactory.java
│       │   │                   │   ├── RepoFactoryImpl.java
│       │   │                   │   ├── RepoImpl.java
│       │   │                   │   ├── RestoreUtils.java
│       │   │                   │   ├── SnapshotId.java
│       │   │                   │   ├── SnapshotIdUtils.java
│       │   │                   │   ├── WorldId.java
│       │   │                   │   └── WorldIdUtils.java
│       │   │                   ├── retention/
│       │   │                   │   ├── AllRetentionPolicy.java
│       │   │                   │   ├── DailyRetentionPolicy.java
│       │   │                   │   ├── FixedCountRetentionPolicy.java
│       │   │                   │   ├── GFSRetentionPolicy.java
│       │   │                   │   ├── RetentionPolicy.java
│       │   │                   │   ├── RetentionPolicyCodec.java
│       │   │                   │   └── RetentionPolicyType.java
│       │   │                   └── utils/
│       │   │                       ├── EnvironmentUtils.java
│       │   │                       ├── Executor.java
│       │   │                       ├── ExecutorImpl.java
│       │   │                       ├── FileUtils.java
│       │   │                       ├── ProcessException.java
│       │   │                       └── ProcessUtils.java
│       │   └── resources/
│       │       ├── assets/
│       │       │   └── fastback/
│       │       │       └── lang/
│       │       │           ├── de_de.json
│       │       │           ├── en_us.json
│       │       │           ├── es_es.json
│       │       │           ├── ja_jp.json
│       │       │           ├── ru_ru.json
│       │       │           └── zh_cn.json
│       │       ├── fastback.mixins.json
│       │       └── world/
│       │           ├── gitattributes-jgit
│       │           ├── gitattributes-native
│       │           └── gitignore
│       └── test/
│           └── java/
│               └── net/
│                   └── pcal/
│                       └── fastback/
│                           └── common/
│                               ├── repo/
│                               │   ├── V1SnapshotIdTest.java
│                               │   └── V2SnapshotIdTest.java
│                               └── retention/
│                                   ├── DailyRetentionPolicyTest.java
│                                   ├── GFSRetentionPolicyTest.java
│                                   └── RetentionPolicyCodecTest.java
├── docs/
│   ├── _config.yml
│   ├── actions-list.md
│   ├── advanced.md
│   ├── commands-list.md
│   ├── commands.md
│   ├── diskspace.md
│   ├── faq.md
│   ├── index.md
│   ├── native-git.md
│   ├── permissions-list.md
│   ├── permissions.md
│   ├── remote.md
│   ├── retention-list.md
│   ├── scheduling.md
│   └── usage.md
├── etc/
│   ├── blurb.md
│   └── docgen.sh
├── fabric/
│   ├── build.gradle
│   └── src/
│       └── main/
│           ├── java/
│           │   └── net/
│           │       └── pcal/
│           │           └── fastback/
│           │               └── fabric/
│           │                   ├── FabricClientInitializer.java
│           │                   ├── FabricLoaderHelper.java
│           │                   └── FabricServerInitializer.java
│           └── resources/
│               └── fabric.mod.json
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── neoforge/
│   ├── build.gradle
│   └── src/
│       └── main/
│           ├── java/
│           │   └── net/
│           │       └── pcal/
│           │           └── fastback/
│           │               └── neoforge/
│           │                   ├── NeoForgeClientInitializer.java
│           │                   ├── NeoForgeLoaderHelper.java
│           │                   └── NeoForgeModInitializer.java
│           └── resources/
│               ├── META-INF/
│               │   └── neoforge.mods.toml
│               └── pack.mcmeta
└── settings.gradle
Download .txt
SYMBOL INDEX (624 symbols across 90 files)

FILE: common/src/main/java/net/pcal/fastback/common/commands/Command.java
  type Command (line 24) | public interface Command {
    method register (line 26) | void register(final LiteralArgumentBuilder<CommandSourceStack> argb, P...

FILE: common/src/main/java/net/pcal/fastback/common/commands/Commands.java
  class Commands (line 41) | public class Commands {
    method createBackupCommand (line 47) | public static LiteralArgumentBuilder<CommandSourceStack> createBackupC...
    method subcommandPermission (line 79) | static Predicate<CommandSourceStack> subcommandPermission(String subco...
    method getArgumentNicely (line 90) | static <V> V getArgumentNicely(final String argName, final Class<V> cl...
    method missingArgument (line 99) | static int missingArgument(final String argName, final CommandContext<...
    method missingArgument (line 103) | static int missingArgument(final String argName, final UserLogger log) {
    type GitOp (line 108) | interface GitOp {
      method execute (line 109) | void execute(Repo repo) throws Exception;
    method gitOp (line 112) | static void gitOp(final ExecutionLock lock, final UserLogger ulog, fin...

FILE: common/src/main/java/net/pcal/fastback/common/commands/CreateFileRemoteCommand.java
  type CreateFileRemoteCommand (line 46) | enum CreateFileRemoteCommand implements Command {
    method register (line 53) | @Override
    method setFileRemote (line 65) | private static int setFileRemote(final CommandContext<CommandSourceSta...

FILE: common/src/main/java/net/pcal/fastback/common/commands/DeleteCommand.java
  type DeleteCommand (line 40) | enum DeleteCommand implements Command {
    method register (line 47) | @Override
    method delete (line 58) | private static int delete(final CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/FullCommand.java
  type FullCommand (line 43) | enum FullCommand implements Command {
    method register (line 49) | public void register(final LiteralArgumentBuilder<CommandSourceStack> ...
    method run (line 57) | public static int run(CommandSourceStack scs) {
    method saveWorldBeforeBackup (line 75) | static void saveWorldBeforeBackup(UserLogger ulog) throws IOException {

FILE: common/src/main/java/net/pcal/fastback/common/commands/GcCommand.java
  type GcCommand (line 40) | enum GcCommand implements Command {
    method register (line 46) | @Override
    method gc (line 55) | private static int gc(CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/HelpCommand.java
  type HelpCommand (line 49) | enum HelpCommand implements Command {
    method register (line 56) | @Override
    class HelpTopicSuggestions (line 70) | private static class HelpTopicSuggestions implements SuggestionProvide...
      method HelpTopicSuggestions (line 73) | HelpTopicSuggestions() {
      method getSuggestions (line 76) | @Override
    method generalHelp (line 91) | static int generalHelp(final CommandContext<CommandSourceStack> cc) {
    method subcommandHelp (line 110) | private int subcommandHelp(final CommandContext<CommandSourceStack> cc) {
    method getSubcommandNames (line 125) | private static List<String> getSubcommandNames(CommandContext<CommandS...

FILE: common/src/main/java/net/pcal/fastback/common/commands/InfoCommand.java
  type InfoCommand (line 59) | enum InfoCommand implements Command {
    method register (line 65) | @Override
    method info (line 74) | private static int info(final CommandSourceStack scs) {
    method show (line 126) | private static void show(GitConfigKey key, Function<GitConfigKey, Obje...
    method getActionDisplay (line 130) | private static String getActionDisplay(SchedulableAction action) {
    method showRetentionPolicy (line 134) | private static void showRetentionPolicy(UserLogger log, String encoded...

FILE: common/src/main/java/net/pcal/fastback/common/commands/InitCommand.java
  type InitCommand (line 42) | enum InitCommand implements Command {
    method register (line 48) | @Override
    method init (line 57) | private static int init(final CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/ListCommand.java
  type ListCommand (line 41) | enum ListCommand implements Command {
    method register (line 47) | @Override
    method execute (line 56) | private int execute(final CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/LocalCommand.java
  type LocalCommand (line 43) | enum LocalCommand implements Command {
    method register (line 49) | @Override
    method run (line 58) | private static int run(CommandSourceStack scs) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/PermissionsFactory.java
  type PermissionsFactory (line 23) | public interface PermissionsFactory<S> {
    method require (line 25) | Predicate<S> require(String permissionName);

FILE: common/src/main/java/net/pcal/fastback/common/commands/PruneCommand.java
  type PruneCommand (line 42) | enum PruneCommand implements Command {
    method register (line 48) | @Override
    method prune (line 57) | private static int prune(final CommandSourceStack scs) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/PushCommand.java
  type PushCommand (line 41) | enum PushCommand implements Command {
    method register (line 48) | @Override
    method execute (line 59) | private static int execute(CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/RemoteDeleteCommand.java
  type RemoteDeleteCommand (line 37) | enum RemoteDeleteCommand implements Command {
    method register (line 44) | @Override
    method delete (line 55) | private static int delete(CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/RemoteListCommand.java
  type RemoteListCommand (line 39) | enum RemoteListCommand implements Command {
    method register (line 45) | @Override
    method execute (line 54) | private static int execute(final CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/RemotePruneCommand.java
  type RemotePruneCommand (line 42) | enum RemotePruneCommand implements Command {
    method register (line 48) | @Override
    method remotePrune (line 57) | private static int remotePrune(final CommandSourceStack scs) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/RemoteRestoreCommand.java
  type RemoteRestoreCommand (line 35) | enum RemoteRestoreCommand implements Command {
    method register (line 42) | @Override
    method remoteRestore (line 54) | private static int remoteRestore(final CommandContext<CommandSourceSta...

FILE: common/src/main/java/net/pcal/fastback/common/commands/RestoreCommand.java
  type RestoreCommand (line 34) | enum RestoreCommand implements Command {
    method register (line 41) | @Override
    method restore (line 53) | private static int restore(final CommandContext<CommandSourceStack> cc) {

FILE: common/src/main/java/net/pcal/fastback/common/commands/SchedulableAction.java
  type SchedulableAction (line 36) | public enum SchedulableAction {
    method getTask (line 39) | @Override
    method getTask (line 46) | @Override
    method getTask (line 56) | @Override
    method getTask (line 66) | @Override
    method forConfigValue (line 77) | public static SchedulableAction forConfigValue(final GitConfig c, fina...
    method forConfigValue (line 83) | public static SchedulableAction forConfigValue(String configValue) {
    method SchedulableAction (line 95) | SchedulableAction(String configValue) {
    method getConfigValue (line 99) | public String getConfigValue() {
    method getArgumentName (line 103) | public String getArgumentName() {
    method getTask (line 107) | public abstract Callable<?> getTask(Repo repo, UserLogger ulog);

FILE: common/src/main/java/net/pcal/fastback/common/commands/SetCommand.java
  type SetCommand (line 74) | enum SetCommand implements Command {
    method register (line 83) | @Override
    method registerBooleanConfigValue (line 117) | private static void registerBooleanConfigValue(final GitConfigKey key,...
    method setBooleanConfigValue (line 124) | private static int setBooleanConfigValue(final GitConfigKey key, final...
    method registerIntegerConfigValue (line 150) | private static void registerIntegerConfigValue(final GitConfigKey key,...
    method setIntegerConfigValue (line 157) | private static int setIntegerConfigValue(final GitConfigKey key, final...
    method registerStringConfigValue (line 178) | private static void registerStringConfigValue(final GitConfigKey key, ...
    method setStringConfigValue (line 185) | private static int setStringConfigValue(final GitConfigKey key, final ...
    method registerSelectConfigValue (line 206) | private static void registerSelectConfigValue(GitConfigKey key, List<S...
    method setSelectionConfigValue (line 214) | private static int setSelectionConfigValue(final GitConfigKey key, fin...
    method registerForceDebug (line 235) | private static void registerForceDebug(final LiteralArgumentBuilder<Co...
    method setForceDebug (line 242) | private static int setForceDebug(final CommandContext<CommandSourceSta...
    method registerSetRetentionCommand (line 262) | private static void registerSetRetentionCommand(final FastbackConfigKe...
    method setRetentionPolicy (line 286) | public static int setRetentionPolicy(final CommandContext<CommandSourc...

FILE: common/src/main/java/net/pcal/fastback/common/commands/SnapshotNameSuggestions.java
  class SnapshotNameSuggestions (line 35) | abstract class SnapshotNameSuggestions implements SuggestionProvider<Com...
    method local (line 37) | static SnapshotNameSuggestions local() {
    method remote (line 46) | static SnapshotNameSuggestions remote() {
    method getSuggestions (line 55) | @Override
    method getSnapshotIds (line 71) | abstract protected Iterator<SnapshotId> getSnapshotIds(Repo repo, User...

FILE: common/src/main/java/net/pcal/fastback/common/config/FastbackConfigKey.java
  type FastbackConfigKey (line 26) | public enum FastbackConfigKey implements GitConfigKey {
    method FastbackConfigKey (line 57) | FastbackConfigKey(boolean booleanDefaultValue) {
    method FastbackConfigKey (line 61) | FastbackConfigKey(final String settingName, boolean booleanDefaultValu...
    method FastbackConfigKey (line 65) | FastbackConfigKey(final String settingName, String stringDefaultValue) {
    method FastbackConfigKey (line 69) | FastbackConfigKey(final String settingName, int intDefault) {
    method FastbackConfigKey (line 73) | FastbackConfigKey(final String settingName, final Boolean booleanDefau...
    method getSectionName (line 81) | @Override
    method getSubSectionName (line 86) | @Override
    method getSettingName (line 91) | @Override
    method getBooleanDefault (line 96) | @Override
    method getStringDefault (line 102) | @Override
    method getIntDefault (line 107) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/config/GitConfig.java
  type GitConfig (line 30) | public interface GitConfig {
    method load (line 34) | static GitConfig load(Git jgit) {
    method getBoolean (line 38) | boolean getBoolean(GitConfigKey key);
    method getString (line 40) | String getString(GitConfigKey key);
    method getInt (line 42) | int getInt(GitConfigKey key);
    method isSet (line 44) | boolean isSet(GitConfigKey key);
    method updater (line 46) | Updater updater();
    type Updater (line 51) | interface Updater {
      method set (line 53) | Updater set(GitConfigKey key, boolean newValue);
      method set (line 55) | Updater set(GitConfigKey key, String newValue);
      method set (line 57) | Updater set(GitConfigKey key, int newValue);
      method setCommented (line 59) | Updater setCommented(GitConfigKey key, boolean newValue);
      method setCommented (line 61) | Updater setCommented(GitConfigKey key, String newValue);
      method setCommented (line 63) | Updater setCommented(GitConfigKey key, int newValue);
      method save (line 65) | void save() throws IOException;

FILE: common/src/main/java/net/pcal/fastback/common/config/GitConfigImpl.java
  class GitConfigImpl (line 33) | class GitConfigImpl implements GitConfig {
    method load (line 35) | static GitConfig load(final Git jgit) {
    method GitConfigImpl (line 41) | GitConfigImpl(StoredConfig jgitConfig) {
    method getBoolean (line 45) | @Override
    method getString (line 51) | @Override
    method getInt (line 58) | @Override
    method isSet (line 64) | @Override
    method updater (line 70) | @Override
    class UpdaterImpl (line 75) | private class UpdaterImpl implements Updater {
      method set (line 77) | @Override
      method set (line 83) | @Override
      method set (line 89) | @Override
      method setCommented (line 100) | @Override
      method setCommented (line 106) | @Override
      method setCommented (line 112) | @Override
      method save (line 118) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/config/GitConfigKey.java
  type GitConfigKey (line 25) | public interface GitConfigKey {
    method getSectionName (line 27) | String getSectionName();
    method getSubSectionName (line 29) | String getSubSectionName();
    method getSettingName (line 31) | String getSettingName();
    method getBooleanDefault (line 33) | boolean getBooleanDefault();
    method getStringDefault (line 35) | String getStringDefault();
    method getDisplayName (line 37) | default String getDisplayName() {
    method getIntDefault (line 41) | int getIntDefault();

FILE: common/src/main/java/net/pcal/fastback/common/config/OtherConfigKey.java
  type OtherConfigKey (line 28) | public enum OtherConfigKey implements GitConfigKey {
    method getDisplayName (line 31) | @Override
    method OtherConfigKey (line 48) | OtherConfigKey(final String sectionName,
    method getSectionName (line 56) | @Override
    method getSubSectionName (line 61) | @Override
    method getSettingName (line 66) | @Override
    method getBooleanDefault (line 71) | @Override
    method getStringDefault (line 76) | @Override
    method getIntDefault (line 81) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/logging/AutosaveLogger.java
  type AutosaveLogger (line 29) | enum AutosaveLogger implements UserLogger {
    method message (line 33) | @Override
    method update (line 37) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/logging/CommandLogger.java
  class CommandLogger (line 32) | class CommandLogger implements UserLogger {
    method CommandLogger (line 36) | CommandLogger(final CommandSourceStack scs) {
    method message (line 40) | @Override
    method update (line 45) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/logging/Log4jLogger.java
  class Log4jLogger (line 25) | public class Log4jLogger implements SystemLogger {
    method Log4jLogger (line 30) | public Log4jLogger(org.apache.logging.log4j.Logger log4j) {
    method setForceDebugEnabled (line 34) | @Override
    method error (line 39) | @Override
    method error (line 44) | @Override
    method warn (line 52) | @Override
    method info (line 57) | @Override
    method debug (line 62) | @Override
    method debug (line 71) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/logging/ShutdownLogger.java
  type ShutdownLogger (line 29) | enum ShutdownLogger implements UserLogger {
    method message (line 33) | @Override
    method update (line 38) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/logging/SystemLogger.java
  type SystemLogger (line 29) | public interface SystemLogger {
    method syslog (line 31) | static SystemLogger syslog() {
    method setForceDebugEnabled (line 35) | void setForceDebugEnabled(boolean debug);
    method error (line 37) | void error(String message);
    method error (line 39) | void error(String message, Throwable t);
    method error (line 41) | default void error(Throwable e) {
    method warn (line 45) | void warn(String message);
    method info (line 47) | void info(String message);
    method debug (line 49) | void debug(String message);
    method trace (line 51) | default void trace(Supplier<String> message) {
    method debug (line 55) | void debug(String message, Throwable t);
    method debug (line 57) | default void debug(Throwable t) {
    class Singleton (line 61) | class Singleton {
      method register (line 64) | public static void register(SystemLogger logger) {

FILE: common/src/main/java/net/pcal/fastback/common/logging/UserLogger.java
  type UserLogger (line 35) | public interface UserLogger extends AutoCloseable {
    method message (line 41) | void message(UserMessage message);
    method update (line 47) | void update(UserMessage message);
    method close (line 49) | @Override
    method internalError (line 54) | default void internalError() {
    method internalError (line 58) | default void internalError(Exception e) {
    method ulog (line 63) | static UserLogger ulog(final CommandContext<CommandSourceStack> cc) {
    method ulog (line 67) | static UserLogger ulog(final CommandSourceStack scs) {
    method forShutdown (line 71) | static UserLogger forShutdown() {
    method forAutosave (line 75) | static UserLogger forAutosave() {

FILE: common/src/main/java/net/pcal/fastback/common/logging/UserMessage.java
  type UserMessageStyle (line 34) | public enum UserMessageStyle {
  method toString (line 45) | @Override
  method localized (line 51) | public static UserMessage localized(String key, Object... params) {
  method styledLocalized (line 55) | public static UserMessage styledLocalized(String key, UserMessageStyle s...
  method raw (line 59) | public static UserMessage raw(String text) {
  method styledRaw (line 63) | public static UserMessage styledRaw(String text, UserMessageStyle style) {
  method toString (line 67) | @Override
  method localizedError (line 75) | @Deprecated
  method rawError (line 80) | @Deprecated

FILE: common/src/main/java/net/pcal/fastback/common/mixins/FileFixerUpperMixin.java
  class FileFixerUpperMixin (line 37) | @Mixin(FileFixerUpper.class)
    method fastback_skipGitDir (line 40) | @Redirect(

FILE: common/src/main/java/net/pcal/fastback/common/mixins/MessageScreenMixin.java
  class MessageScreenMixin (line 34) | @Mixin(GenericMessageScreen.class)
    method fastback_render (line 37) | @Inject(method = "extractBackground", at = @At("TAIL"), remap = false)

FILE: common/src/main/java/net/pcal/fastback/common/mixins/MinecraftServerMixin.java
  class MinecraftServerMixin (line 37) | @Mixin(MinecraftServer.class)
    method fastback_saveAll (line 44) | @Redirect(method = "autoSave()V",
    method fastback_save (line 55) | @Inject(at = @At("HEAD"), method = "saveAllChunks(ZZZ)Z", cancellable ...
    method fastback_saveAll (line 71) | @Inject(at = @At("HEAD"), method = "saveEverything(ZZZ)Z", cancellable...

FILE: common/src/main/java/net/pcal/fastback/common/mixins/ScreenAccessors.java
  type ScreenAccessors (line 32) | @Mixin(Screen.class)
    method getTitle (line 35) | @Accessor(remap = false)
    method invokeRebuildWidgets (line 39) | @Invoker(remap = false)
    method setTitle (line 43) | @Accessor(remap = false)

FILE: common/src/main/java/net/pcal/fastback/common/mixins/ServerAccessors.java
  type ServerAccessors (line 30) | @Mixin(MinecraftServer.class)
    method getTickCount (line 33) | @Accessor(remap = false)
    method getStorageSource (line 36) | @Accessor(remap = false)

FILE: common/src/main/java/net/pcal/fastback/common/mixins/SessionAccessors.java
  type SessionAccessors (line 29) | @Mixin(LevelStorageSource.LevelStorageAccess.class)
    method getLevelDirectory (line 31) | @Accessor(remap = false)

FILE: common/src/main/java/net/pcal/fastback/common/mod/AutosaveListener.java
  class AutosaveListener (line 46) | class AutosaveListener implements Runnable {
    method run (line 50) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/mod/ClientHelper.java
  class ClientHelper (line 39) | public final class ClientHelper {
    method ClientHelper (line 56) | public ClientHelper(Minecraft client) {
    method setHudText (line 63) | public void setHudText(UserMessage userMessage) {
    method clearHudText (line 72) | public void clearHudText() {
    method setMessageScreenText (line 76) | public void setMessageScreenText(UserMessage userMessage) {
    method renderMessageScreen (line 85) | public void renderMessageScreen(GuiGraphicsExtractor guiGraphics) {
    method renderHud (line 89) | public void renderHud(GuiGraphicsExtractor guiGraphics) {

FILE: common/src/main/java/net/pcal/fastback/common/mod/LoaderHelper.java
  type LoaderHelper (line 37) | public interface LoaderHelper {
    method getModVersion (line 42) | String getModVersion();
    method addLoaderBackupProperties (line 48) | void addLoaderBackupProperties(Map<String, String> props);
    method getSavesDir (line 53) | Path getSavesDir();
    method getModsBackupPaths (line 58) | Collection<Path> getModsBackupPaths();
    method registerBackupCommand (line 65) | void registerBackupCommand(boolean isClient,

FILE: common/src/main/java/net/pcal/fastback/common/mod/Mod.java
  type Mod (line 39) | public interface Mod {
    method mod (line 44) | static Mod mod() {
    class SingletonHolder (line 48) | class SingletonHolder {
      method register (line 51) | public static void register(Mod mod) {
    method initializeForDedicatedServer (line 64) | static void initializeForDedicatedServer(LoaderHelper loaderHelper) {
    method initializeForClient (line 71) | static void initializeForClient(LoaderHelper loaderHelper, ClientHelpe...
    method onWorldStart (line 79) | void onWorldStart(MinecraftServer server);
    method onWorldStop (line 85) | void onWorldStop();
    method renderHud (line 90) | void renderHud(GuiGraphicsExtractor drawContext);
    method isWorldSaveEnabled (line 100) | boolean isWorldSaveEnabled();
    method autoSaveCompleted (line 106) | void autoSaveCompleted();
    method renderMessageScreen (line 111) | void renderMessageScreen(GuiGraphicsExtractor drawContext);
    method getDefaultRestoresDir (line 120) | Path getDefaultRestoresDir() throws IOException;
    method getModVersion (line 125) | String getModVersion();
    method setWorldSaveEnabled (line 130) | void setWorldSaveEnabled(boolean enabled);
    method saveWorld (line 135) | void saveWorld();
    method setMessageScreenText (line 140) | void setMessageScreenText(UserMessage message);
    method sendChat (line 145) | void sendChat(UserMessage message, CommandSourceStack scs);
    method sendBroadcast (line 150) | void sendBroadcast(UserMessage message);
    method setHudText (line 155) | void setHudText(UserMessage message);
    method clearHudText (line 160) | void clearHudText();
    method getWorldDirectory (line 165) | Path getWorldDirectory();
    method getWorldName (line 170) | String getWorldName();
    method getModsBackupPaths (line 175) | Collection<Path> getModsBackupPaths();
    method addBackupProperties (line 180) | void addBackupProperties(Map<String, String> props);

FILE: common/src/main/java/net/pcal/fastback/common/mod/ModImpl.java
  class ModImpl (line 55) | class ModImpl implements Mod {
    method initialize (line 76) | static Mod initialize(final LoaderHelper loaderHelper, final ClientHel...
    method ModImpl (line 84) | private ModImpl(LoaderHelper loaderHelper, ClientHelper clientHelper) {
    method onWorldStart (line 93) | @Override
    method onWorldStop (line 100) | @Override
    method getDefaultRestoresDir (line 130) | @Override
    method getModVersion (line 140) | @Override
    method setWorldSaveEnabled (line 145) | @Override
    method saveWorld (line 150) | @Override
    method sendChat (line 156) | @Override
    method sendBroadcast (line 165) | @Override
    method setHudText (line 172) | @Override
    method clearHudText (line 183) | @Override
    method setMessageScreenText (line 188) | @Override
    method getWorldDirectory (line 194) | @Override
    method getWorldName (line 202) | @Override
    method addBackupProperties (line 208) | @Override
    method getModsBackupPaths (line 219) | @Override
    method isWorldSaveEnabled (line 227) | @Override
    method autoSaveCompleted (line 232) | @Override
    method renderMessageScreen (line 241) | @Override
    method renderHud (line 250) | @Override
    method onInitialize (line 262) | private void onInitialize() {

FILE: common/src/main/java/net/pcal/fastback/common/mod/UserMessageUtil.java
  class UserMessageUtil (line 38) | public class UserMessageUtil {
    method messageToText (line 40) | public static Component messageToText(final UserMessage m) {
    method messageParamsToComponentArgs (line 59) | private static Object[] messageParamsToComponentArgs(final Object[] pa...
    method UserMessageUtil (line 74) | private UserMessageUtil() {}

FILE: common/src/main/java/net/pcal/fastback/common/repo/BranchUtils.java
  class BranchUtils (line 37) | abstract class BranchUtils {
    method listSnapshots (line 42) | static Set<SnapshotId> listSnapshots(RepoImpl repo, JGitSupplier<Colle...
    method getBranchName (line 68) | static String getBranchName(String name) {

FILE: common/src/main/java/net/pcal/fastback/common/repo/CommitUtils.java
  class CommitUtils (line 64) | abstract class CommitUtils {
    method doCommitSnapshot (line 66) | static SnapshotId doCommitSnapshot(final RepoImpl repo, final UserLogg...
    method doSettingsBackup (line 94) | private static void doSettingsBackup(RepoImpl repo, UserLogger ulog) {
    method native_commit (line 121) | private static void native_commit(final String newBranchName, final Re...
    method jgit_commit (line 150) | private static void jgit_commit(final String newBranchName, final Git ...
    method writeBackupProperties (line 211) | private static void writeBackupProperties(Repo repo) throws IOException {

FILE: common/src/main/java/net/pcal/fastback/common/repo/JGitConsumer.java
  type JGitConsumer (line 26) | @FunctionalInterface
    method accept (line 29) | void accept(T t) throws IOException;

FILE: common/src/main/java/net/pcal/fastback/common/repo/JGitFunction.java
  type JGitFunction (line 30) | @FunctionalInterface
    method apply (line 33) | R apply(T arg) throws IOException, GitAPIException;

FILE: common/src/main/java/net/pcal/fastback/common/repo/JGitIncrementalProgressMonitor.java
  class JGitIncrementalProgressMonitor (line 25) | class JGitIncrementalProgressMonitor implements ProgressMonitor {
    method JGitIncrementalProgressMonitor (line 34) | public JGitIncrementalProgressMonitor(ProgressMonitor delegate, int to...
    method start (line 39) | @Override
    method beginTask (line 44) | @Override
    method update (line 53) | @Override
    method endTask (line 66) | @Override
    method isCancelled (line 71) | @Override
    method showDuration (line 76) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/repo/JGitPercentageProgressMonitor.java
  class JGitPercentageProgressMonitor (line 23) | abstract class JGitPercentageProgressMonitor implements ProgressMonitor {
    method JGitPercentageProgressMonitor (line 29) | protected JGitPercentageProgressMonitor() {
    method start (line 32) | @Override
    method beginTask (line 36) | @Override
    method update (line 44) | @Override
    method endTask (line 51) | @Override
    method isCancelled (line 57) | @Override
    method progressStart (line 62) | protected abstract void progressStart(String taskName);
    method progressUpdate (line 64) | protected abstract void progressUpdate(String taskName, int percentage);
    method progressDone (line 66) | protected abstract void progressDone(String taskName);

FILE: common/src/main/java/net/pcal/fastback/common/repo/JGitSupplier.java
  type JGitSupplier (line 26) | @FunctionalInterface
    method get (line 29) | R get() throws IOException;

FILE: common/src/main/java/net/pcal/fastback/common/repo/PreflightUtils.java
  class PreflightUtils (line 45) | abstract class PreflightUtils {
    method doPreflight (line 54) | static void doPreflight(RepoImpl repo) throws IOException, ProcessExce...
    method updateNativeLfsInstallation (line 82) | private static void updateNativeLfsInstallation(final RepoImpl repo) t...

FILE: common/src/main/java/net/pcal/fastback/common/repo/PruneUtils.java
  class PruneUtils (line 54) | abstract class PruneUtils {
    method deleteRemoteBranch (line 56) | static void deleteRemoteBranch(final RepoImpl repo, String remoteBranc...
    method native_deleteRemoteBranch (line 69) | static void native_deleteRemoteBranch(final RepoImpl repo, String remo...
    method jgit_deleteRemoteBranch (line 74) | static void jgit_deleteRemoteBranch(final RepoImpl repo, String remote...
    method deleteLocalBranches (line 81) | static void deleteLocalBranches(final RepoImpl repo, List<String> bran...
    method doLocalPrune (line 89) | static Collection<SnapshotId> doLocalPrune(final RepoImpl repo, final ...
    method doRemotePrune (line 101) | static Collection<SnapshotId> doRemotePrune(RepoImpl repo, UserLogger ...
    method doPrune (line 113) | private static Collection<SnapshotId> doPrune(Repo repo,

FILE: common/src/main/java/net/pcal/fastback/common/repo/PushUtils.java
  class PushUtils (line 75) | abstract class PushUtils {
    method isTempBranch (line 77) | static boolean isTempBranch(String branchName) {
    method doPush (line 83) | static void doPush(SnapshotId sid, RepoImpl repo, UserLogger ulog) thr...
    method native_doPush (line 136) | private static void native_doPush(final Repo repo, final String branch...
    method jgit_doPush (line 148) | private static void jgit_doPush(final Git jgit, final String branchNam...
    method native_lsRemote (line 156) | static Collection<String> native_lsRemote(final Path worktree, final S...
    method jgit_lsRemote (line 175) | static Collection<String> jgit_lsRemote(final Git jgit, final String r...
    method jgit_doSmartPush (line 198) | private static void jgit_doSmartPush(final RepoImpl repo, List<Snapsho...
    method doWorldIdCheck (line 274) | private static boolean doWorldIdCheck(RepoImpl repo, Set<WorldId> remo...
    method getRemoteUri (line 291) | private static URIish getRemoteUri(Git jgit, String remoteName) throws...
    class JGitPushProgressMonitor (line 309) | private static class JGitPushProgressMonitor extends JGitPercentagePro...
      method JGitPushProgressMonitor (line 313) | public JGitPushProgressMonitor(UserLogger ulog) {
      method progressStart (line 317) | @Override
      method progressUpdate (line 322) | @Override
      method progressDone (line 329) | @Override
      method showDuration (line 336) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/repo/ReclamationUtils.java
  class ReclamationUtils (line 64) | abstract class ReclamationUtils {
    method doReclamation (line 66) | static void doReclamation(RepoImpl repo, UserLogger ulog) throws GitAP...
    method native_doLfsPrune (line 78) | private static void native_doLfsPrune(RepoImpl repo, UserLogger ulog) ...
    method jgit_doGc (line 90) | private static void jgit_doGc(RepoImpl repo, UserLogger ulog) throws G...
    class GcProgressMonitor (line 145) | private static class GcProgressMonitor extends JGitPercentageProgressM...
      method GcProgressMonitor (line 149) | public GcProgressMonitor(UserLogger ulog) {
      method progressStart (line 153) | @Override
      method progressUpdate (line 158) | @Override
      method progressDone (line 165) | @Override
      method showDuration (line 172) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/repo/Repo.java
  type Repo (line 39) | public interface Repo extends AutoCloseable {
    method getLocalSnapshots (line 42) | Set<SnapshotId> getLocalSnapshots() throws IOException;
    method getRemoteSnapshots (line 44) | Set<SnapshotId> getRemoteSnapshots() throws IOException;
    method doCommitAndPush (line 58) | void doCommitAndPush(UserLogger ulog) throws IOException;
    method doCommitSnapshot (line 60) | void doCommitSnapshot(UserLogger ulog) throws IOException;
    method doLocalPrune (line 62) | Collection<SnapshotId> doLocalPrune(UserLogger ulog) throws IOException;
    method doRemotePrune (line 64) | Collection<SnapshotId> doRemotePrune(UserLogger ulog) throws IOException;
    method doRestoreLocalSnapshot (line 66) | void doRestoreLocalSnapshot(String snapshotName, UserLogger ulog);
    method doRestoreRemoteSnapshot (line 68) | void doRestoreRemoteSnapshot(String snapshotName, UserLogger ulog);
    method doGc (line 70) | void doGc(UserLogger ulog);
    method doPushSnapshot (line 72) | void doPushSnapshot(SnapshotId sid, UserLogger ulog);
    method deleteRemoteBranch (line 74) | void deleteRemoteBranch(String remoteBranchName) throws IOException;
    method deleteLocalBranches (line 76) | void deleteLocalBranches(List<String> branchesToDelete) throws GitAPIE...
    method createSnapshotId (line 82) | @Deprecated
    method getConfig (line 85) | @Deprecated
    method getWorldId (line 88) | @Deprecated
    method getDirectory (line 91) | @Deprecated
    method getWorkTree (line 94) | @Deprecated

FILE: common/src/main/java/net/pcal/fastback/common/repo/RepoFactory.java
  type RepoFactory (line 32) | public interface RepoFactory {
    method rf (line 35) | static RepoFactory rf() {
    method doInit (line 39) | void doInit(Path worldSaveDir, UserLogger ulog) throws IOException;
    method load (line 41) | Repo load(final Path worldSaveDir) throws IOException;
    method doInitCheck (line 43) | boolean doInitCheck(Path worldSaveDir, UserLogger ulog);
    method isGitRepo (line 45) | boolean isGitRepo(Path worldSaveDir);

FILE: common/src/main/java/net/pcal/fastback/common/repo/RepoFactoryImpl.java
  class RepoFactoryImpl (line 47) | class RepoFactoryImpl implements RepoFactory {
    method doInit (line 49) | @Override
    method doInitCheck (line 85) | @Override
    method load (line 94) | @Override
    method isGitRepo (line 103) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/repo/RepoImpl.java
  class RepoImpl (line 67) | class RepoImpl implements Repo {
    method RepoImpl (line 84) | RepoImpl(final Git jgit) {
    method doCommitAndPush (line 91) | @Override
    method doCommitSnapshot (line 115) | @Override
    method doPushSnapshot (line 132) | @Override
    method doLocalPrune (line 151) | @Override
    method doRemotePrune (line 156) | @Override
    method doGc (line 161) | @Override
    method doRestoreLocalSnapshot (line 172) | @Override
    method doRestoreRemoteSnapshot (line 177) | @Override
    method getWorldId (line 185) | @Override
    method getLocalSnapshots (line 190) | @Override
    method getRemoteSnapshots (line 206) | @Override
    method getConfig (line 228) | @Override
    method getDirectory (line 236) | @Override
    method getWorkTree (line 241) | @Override
    method deleteRemoteBranch (line 246) | @Override
    method deleteLocalBranches (line 251) | @Override
    method close (line 256) | @Override
    method createSnapshotId (line 261) | @Override
    method getSidCodec (line 269) | SnapshotIdCodec getSidCodec() throws IOException {
    method getJGit (line 273) | Git getJGit() {
    method getDotFasbackDir (line 277) | Path getDotFasbackDir() {
    method getWorldIdInfo (line 284) | private WorldIdInfo getWorldIdInfo() throws IOException {
    method checkIndexLock (line 291) | private void checkIndexLock(UserLogger ulog) {
    method getDuration (line 314) | private static String getDuration(long since) {
    method broadcastBackupNotice (line 324) | private void broadcastBackupNotice() {

FILE: common/src/main/java/net/pcal/fastback/common/repo/RestoreUtils.java
  class RestoreUtils (line 56) | abstract class RestoreUtils {
    method doRestoreLocalSnapshot (line 61) | static void doRestoreLocalSnapshot(final String snapshotNameToRestore,...
    method doRestoreRemoteSnapshot (line 65) | static void doRestoreRemoteSnapshot(final String snapshotNameToRestore...
    method doRestoreSnapshot (line 77) | private static void doRestoreSnapshot(final String snapshotNameToResto...
    method native_restoreSnapshot (line 97) | private static void native_restoreSnapshot(final String branchName, fi...
    method jgit_restoreSnapshot (line 115) | private static void jgit_restoreSnapshot(final String branchName, fina...
    method getTargetDir (line 130) | private static Path getTargetDir(Path allRestoresDir, String worldName...
    class JGitRestoreProgressMonitor (line 145) | private static class JGitRestoreProgressMonitor extends JGitPercentage...
      method JGitRestoreProgressMonitor (line 149) | public JGitRestoreProgressMonitor(UserLogger ulog) {
      method progressStart (line 153) | @Override
      method progressUpdate (line 161) | @Override
      method progressDone (line 168) | @Override
      method showDuration (line 172) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/repo/SnapshotId.java
  type SnapshotId (line 29) | public interface SnapshotId extends Comparable<SnapshotId> {
    method getShortName (line 34) | String getShortName();
    method getDate (line 36) | Date getDate();
    method getBranchName (line 38) | String getBranchName();
    method getWorldId (line 40) | WorldId getWorldId();

FILE: common/src/main/java/net/pcal/fastback/common/repo/SnapshotIdUtils.java
  class SnapshotIdUtils (line 32) | abstract class SnapshotIdUtils {
    method getSnapshotsPerWorld (line 34) | static ListMultimap<WorldId, SnapshotId> getSnapshotsPerWorld(Iterable...
    type SnapshotIdCodec (line 49) | enum SnapshotIdCodec {
      method create (line 54) | @Override
      method create (line 61) | @Override
      method isSnapshotBranchName (line 66) | @Override
      method fromBranch (line 71) | @Override
      method getBranchName (line 83) | private static String getBranchName(WorldId wid, String shortName) {
      method create (line 94) | @Override
      method create (line 102) | @Override
      method isSnapshotBranchName (line 107) | @Override
      method fromBranch (line 113) | @Override
      method getBranchName (line 128) | private static String getBranchName(WorldId wid, String shortName) {
      method create (line 136) | abstract SnapshotId create(WorldId wid);
      method create (line 138) | abstract SnapshotId create(WorldId worldId, String shortName) throws...
      method fromBranch (line 140) | abstract SnapshotId fromBranch(String rawBranchName) throws ParseExc...
      method isSnapshotBranchName (line 142) | abstract boolean isSnapshotBranchName(WorldId bid, String branchName);
    method getShortName (line 152) | public String getShortName() {
    method getDate (line 156) | @Override
    method getBranchName (line 161) | public String getBranchName() {
    method getWorldId (line 165) | @Override
    method compareTo (line 170) | @Override
    method toString (line 175) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/repo/WorldId.java
  type WorldId (line 25) | public interface WorldId {
    method toString (line 27) | String toString();

FILE: common/src/main/java/net/pcal/fastback/common/repo/WorldIdUtils.java
  class WorldIdUtils (line 26) | abstract class WorldIdUtils {
    method getWorldIdInfo (line 57) | static WorldIdInfo getWorldIdInfo(final Path worldSaveDir) throws IOEx...
    method createWorldId (line 77) | static void createWorldId(final Path worldSaveDir) throws IOException {
    method ensureWorldHasId (line 93) | static void ensureWorldHasId(final Path worldSaveDir) throws IOExcepti...
    method toString (line 104) | @Override
    method generateRandomWorldId (line 113) | static String generateRandomWorldId(int length) {
    method migrateFastbackDir (line 131) | private static void migrateFastbackDir(final Path worldSaveDir) {

FILE: common/src/main/java/net/pcal/fastback/common/retention/AllRetentionPolicy.java
  type AllRetentionPolicy (line 36) | enum AllRetentionPolicy implements RetentionPolicy {
    method getDescription (line 42) | @Override
    method getSnapshotsToPrune (line 47) | @Override
    type Type (line 52) | enum Type implements RetentionPolicyType {
      method getName (line 56) | @Override
      method getParameters (line 61) | @Override
      method createPolicy (line 66) | @Override
      method getDescription (line 71) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/retention/DailyRetentionPolicy.java
  class DailyRetentionPolicy (line 44) | class DailyRetentionPolicy implements RetentionPolicy {
    method DailyRetentionPolicy (line 52) | public DailyRetentionPolicy(int gracePeriod) {
    method getDescription (line 56) | @Override
    method getSnapshotsToPrune (line 61) | @Override
    type DailyRetentionPolicyType (line 95) | public enum DailyRetentionPolicyType implements RetentionPolicyType {
      method getName (line 99) | @Override
      method getParameters (line 104) | @Override
      method createPolicy (line 109) | @Override
      method getDescription (line 124) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/retention/FixedCountRetentionPolicy.java
  class FixedCountRetentionPolicy (line 40) | class FixedCountRetentionPolicy implements RetentionPolicy {
    method create (line 48) | public static FixedCountRetentionPolicy create(Map<String, String> con...
    method FixedCountRetentionPolicy (line 60) | private FixedCountRetentionPolicy(int count) {
    method getDescription (line 64) | @Override
    method getSnapshotsToPrune (line 69) | @Override
    type Type (line 80) | enum Type implements RetentionPolicyType {
      method getName (line 84) | @Override
      method getParameters (line 89) | @Override
      method createPolicy (line 94) | @Override
      method getDescription (line 99) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/retention/GFSRetentionPolicy.java
  class GFSRetentionPolicy (line 49) | class GFSRetentionPolicy implements RetentionPolicy {
    method GFSRetentionPolicy (line 55) | public GFSRetentionPolicy() {
    method getDescription (line 58) | @Override
    method getSnapshotsToPrune (line 63) | @Override
    type GFSRetentionPolicyType (line 110) | public enum GFSRetentionPolicyType implements RetentionPolicyType {
      method getName (line 114) | @Override
      method getParameters (line 119) | @Override
      method createPolicy (line 124) | @Override
      method getDescription (line 129) | @Override

FILE: common/src/main/java/net/pcal/fastback/common/retention/RetentionPolicy.java
  type RetentionPolicy (line 34) | public interface RetentionPolicy {
    method getDescription (line 36) | UserMessage getDescription();
    method getSnapshotsToPrune (line 38) | Collection<SnapshotId> getSnapshotsToPrune(final Set<SnapshotId> fromS...

FILE: common/src/main/java/net/pcal/fastback/common/retention/RetentionPolicyCodec.java
  type RetentionPolicyCodec (line 37) | public enum RetentionPolicyCodec {
    method decodePolicy (line 41) | public RetentionPolicy decodePolicy(final List<RetentionPolicyType> av...
    method encodePolicy (line 65) | public String encodePolicy(final RetentionPolicyType policyType, final...
    method encodeMap (line 72) | static String encodeMap(Map<String, String> map) {
    method decodeMap (line 100) | static Map<String, String> decodeMap(String encodedMap) {
    method isValidForEncode (line 114) | private static boolean isValidForEncode(String keyOrVal) {

FILE: common/src/main/java/net/pcal/fastback/common/retention/RetentionPolicyType.java
  type RetentionPolicyType (line 35) | public interface RetentionPolicyType {
    method getAvailable (line 37) | static List<RetentionPolicyType> getAvailable() {
    method getName (line 48) | String getName();
    method getParameters (line 50) | List<Parameter<?>> getParameters();
    method createPolicy (line 52) | RetentionPolicy createPolicy(Map<String, String> config);
    method getEncodedName (line 54) | default String getEncodedName() {
    method getCommandName (line 58) | default String getCommandName() {
    method getDescription (line 62) | UserMessage getDescription();

FILE: common/src/main/java/net/pcal/fastback/common/utils/EnvironmentUtils.java
  class EnvironmentUtils (line 37) | public class EnvironmentUtils {
    method getGitVersion (line 39) | public static String getGitVersion() {
    method getGitLfsVersion (line 43) | public static String getGitLfsVersion() {
    method isNativeOk (line 50) | public static boolean isNativeOk(final GitConfig conf, final UserLogge...
    method isNativeOk (line 57) | public static boolean isNativeOk(boolean isNativeGitEnabled, UserLogge...
    method execForVersion (line 81) | private static String execForVersion(String[] cmd) {

FILE: common/src/main/java/net/pcal/fastback/common/utils/Executor.java
  type Executor (line 29) | public interface Executor {
    method executor (line 31) | static Executor executor() {
    method execute (line 36) | void execute(final ExecutionLock lock, final UserLogger ulog, final Ru...
    method getActiveCount (line 38) | int getActiveCount();
    method start (line 40) | void start();
    method stop (line 42) | void stop();
    type ExecutionLock (line 44) | enum ExecutionLock {
    class Singleton (line 50) | class Singleton {

FILE: common/src/main/java/net/pcal/fastback/common/utils/ExecutorImpl.java
  class ExecutorImpl (line 38) | class ExecutorImpl implements Executor {
    method execute (line 44) | @Override
    method getActiveCount (line 66) | @Override
    method start (line 71) | @Override
    method stop (line 76) | @Override
    method shutdownExecutor (line 86) | private static void shutdownExecutor(final ExecutorService pool) {

FILE: common/src/main/java/net/pcal/fastback/common/utils/FileUtils.java
  class FileUtils (line 29) | public class FileUtils {
    method mkdirs (line 31) | public static void mkdirs(final Path path) throws IOException {
    method rmdir (line 45) | public static void rmdir(final Path path) throws IOException {
    method writeResourceToFile (line 49) | public static void writeResourceToFile(String resourcePath, Path targe...

FILE: common/src/main/java/net/pcal/fastback/common/utils/ProcessException.java
  class ProcessException (line 14) | public class ProcessException extends Exception {
    method ProcessException (line 17) | ProcessException(String[] args, final int exitCode, final List<String>...
    method ProcessException (line 22) | ProcessException(String[] args, final int exitCode, final List<String>...
    method writeProcessOutput (line 30) | public void writeProcessOutput(Consumer<String> consumer) {

FILE: common/src/main/java/net/pcal/fastback/common/utils/ProcessUtils.java
  class ProcessUtils (line 41) | public class ProcessUtils {
    method doExec (line 43) | public static int doExec(String[] args, final Map<String, String> envO...
    method doExec (line 47) | public static int doExec(final String[] args, final Map<String, String...
    class LineWriter (line 89) | private static class LineWriter extends Writer {
      method LineWriter (line 94) | private LineWriter(final Consumer<String> sink) {
      method write (line 98) | @Override
      method outputLines (line 104) | private void outputLines() {
      method findLineEnd (line 116) | private static int findLineEnd(StringBuilder buffer, int lineStart) {
      method flush (line 124) | @Override
      method close (line 132) | @Override
    method drainAndWait (line 137) | private static int drainAndWait(Process process, Writer stdoutSink, Wr...

FILE: common/src/test/java/net/pcal/fastback/common/repo/V1SnapshotIdTest.java
  class V1SnapshotIdTest (line 47) | public class V1SnapshotIdTest {
    method setup (line 49) | @BeforeAll
    method testParseBranch (line 54) | @Test
    method testSorting (line 67) | @Test
    method testSortWorldSnapshots (line 77) | @Test
    method sorted (line 101) | private static List<SnapshotId> sorted(Collection<SnapshotId> sids) {
    method v1sid (line 108) | public static SnapshotId v1sid(WorldId wid, Date date) throws ParseExc...
    method v1sid (line 112) | public static SnapshotId v1sid(String wid, Date date) throws ParseExce...
    method createWorldId (line 116) | public static WorldId createWorldId(String wid) {

FILE: common/src/test/java/net/pcal/fastback/common/repo/V2SnapshotIdTest.java
  class V2SnapshotIdTest (line 46) | public class V2SnapshotIdTest {
    method setup (line 48) | @BeforeAll
    method testWorldIdGeneration (line 53) | @Test
    method testParseBranch (line 58) | @Test
    method testSorting (line 71) | @Test
    method testSortWorldSnapshots (line 81) | @Test
    method sorted (line 105) | private static List<SnapshotId> sorted(Collection<SnapshotId> sids) {

FILE: common/src/test/java/net/pcal/fastback/common/retention/DailyRetentionPolicyTest.java
  class DailyRetentionPolicyTest (line 42) | public class DailyRetentionPolicyTest {
    method setup (line 47) | @BeforeAll
    method testDailyRetention (line 52) | @Test

FILE: common/src/test/java/net/pcal/fastback/common/retention/GFSRetentionPolicyTest.java
  class GFSRetentionPolicyTest (line 43) | public class GFSRetentionPolicyTest {
    method setup (line 45) | @BeforeAll
    method testGFSRetention (line 50) | @Test
    method sid (line 85) | private static SnapshotId sid(int year, int month, int day) throws Par...
    method sid (line 89) | private static SnapshotId sid(int year, int month, int day, int hour) ...

FILE: common/src/test/java/net/pcal/fastback/common/retention/RetentionPolicyCodecTest.java
  class RetentionPolicyCodecTest (line 37) | public class RetentionPolicyCodecTest {
    method setup (line 39) | @BeforeAll
    method testEncodePolicy (line 44) | @Test
    method testDecodePolicy (line 52) | @Test
    method testEncodeMap (line 62) | @Test
    method testDecodeMap (line 69) | @Test
    class MockRetentionPolicy (line 76) | private static class MockRetentionPolicy implements RetentionPolicy {
      method MockRetentionPolicy (line 80) | public MockRetentionPolicy(Map<String, String> config) {
      method getDescription (line 84) | @Override
      method getSnapshotsToPrune (line 89) | @Override
    type MockRetentionPolicyType (line 95) | private enum MockRetentionPolicyType implements RetentionPolicyType {
      method getName (line 99) | @Override
      method getParameters (line 104) | @Override
      method createPolicy (line 109) | @Override
      method getDescription (line 114) | @Override

FILE: fabric/src/main/java/net/pcal/fastback/fabric/FabricClientInitializer.java
  class FabricClientInitializer (line 35) | public class FabricClientInitializer implements ClientModInitializer {
    method onInitializeClient (line 39) | @Override

FILE: fabric/src/main/java/net/pcal/fastback/fabric/FabricLoaderHelper.java
  class FabricLoaderHelper (line 47) | class FabricLoaderHelper implements LoaderHelper {
    method getModVersion (line 51) | @Override
    method addLoaderBackupProperties (line 58) | @Override
    method getSavesDir (line 74) | @Override
    method getModsBackupPaths (line 82) | @Override
    method registerBackupCommand (line 93) | @Override

FILE: fabric/src/main/java/net/pcal/fastback/fabric/FabricServerInitializer.java
  class FabricServerInitializer (line 31) | public class FabricServerInitializer implements DedicatedServerModInitia...
    method onInitializeServer (line 33) | @Override

FILE: neoforge/src/main/java/net/pcal/fastback/neoforge/NeoForgeClientInitializer.java
  class NeoForgeClientInitializer (line 38) | class NeoForgeClientInitializer {
    method init (line 40) | static void init(IEventBus modEventBus) {

FILE: neoforge/src/main/java/net/pcal/fastback/neoforge/NeoForgeLoaderHelper.java
  class NeoForgeLoaderHelper (line 46) | class NeoForgeLoaderHelper implements LoaderHelper {
    method NeoForgeLoaderHelper (line 52) | NeoForgeLoaderHelper(boolean isClient) {
    method getModVersion (line 56) | @Override
    method addLoaderBackupProperties (line 63) | @Override
    method getSavesDir (line 78) | @Override
    method getModsBackupPaths (line 83) | @Override
    method registerBackupCommand (line 94) | @Override

FILE: neoforge/src/main/java/net/pcal/fastback/neoforge/NeoForgeModInitializer.java
  class NeoForgeModInitializer (line 36) | @Mod(NEOFORGE_MOD_ID)
    method NeoForgeModInitializer (line 39) | public NeoForgeModInitializer(IEventBus modEventBus, ModContainer modC...
Condensed preview — 135 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (511K chars).
[
  {
    "path": ".github/workflows/build-pull-request.yml",
    "chars": 1273,
    "preview": "name: build-pull-request\n\non: [ pull_request ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: acti"
  },
  {
    "path": ".gitignore",
    "chars": 206,
    "preview": "# gradle\n\n\n*~\n\\#*\n\n\n\n.gradle/\nbuild/\nout/\nclasses/\n\n# eclipse\n\n*.launch\n\n# idea\n\n.idea/\n*.iml\n*.ipr\n*.iws\n\n# vscode\n.set"
  },
  {
    "path": "Justfile",
    "chars": 862,
    "preview": "clean:\n    rm -rf build\n\ncompile:\n    ./gradlew compileJava\n\njar:\n    ./gradlew jar\n\nrelease-jars:\n    ./gradlew buildRe"
  },
  {
    "path": "LICENSE",
    "chars": 18093,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Fr"
  },
  {
    "path": "README.md",
    "chars": 2314,
    "preview": "# FastBack\n*Fast, incremental Minecraft world backups powered by Git*\n\nFastback is a Minecraft mod that backs up your wo"
  },
  {
    "path": "build.gradle",
    "chars": 12675,
    "preview": "plugins {\n\tid 'net.fabricmc.fabric-loom' version \"${fabric_loom_version}\" apply false\n\tid 'net.neoforged.moddev' version"
  },
  {
    "path": "common/build.gradle",
    "chars": 1820,
    "preview": "plugins {\n\tid 'net.fabricmc.fabric-loom'\n}\n\nrepositories {\n    maven { url = 'https://maven.fabricmc.net/' }\n    maven {"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/Command.java",
    "chars": 1056,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/Commands.java",
    "chars": 6017,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/CreateFileRemoteCommand.java",
    "chars": 4173,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/DeleteCommand.java",
    "chars": 2980,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/FullCommand.java",
    "chars": 2943,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/GcCommand.java",
    "chars": 2288,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/HelpCommand.java",
    "chars": 5605,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/InfoCommand.java",
    "chars": 7860,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/InitCommand.java",
    "chars": 2685,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/ListCommand.java",
    "chars": 2759,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/LocalCommand.java",
    "chars": 2707,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/PermissionsFactory.java",
    "chars": 928,
    "preview": "package net.pcal.fastback.common.commands;\n\n/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copy"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/PruneCommand.java",
    "chars": 2598,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/PushCommand.java",
    "chars": 2720,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/RemoteDeleteCommand.java",
    "chars": 2829,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/RemoteListCommand.java",
    "chars": 2660,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/RemotePruneCommand.java",
    "chars": 2462,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/RemoteRestoreCommand.java",
    "chars": 2647,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/RestoreCommand.java",
    "chars": 2588,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/SchedulableAction.java",
    "chars": 3256,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/SetCommand.java",
    "chars": 15329,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/commands/SnapshotNameSuggestions.java",
    "chars": 3088,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/config/FastbackConfigKey.java",
    "chars": 3816,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/config/GitConfig.java",
    "chars": 1849,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/config/GitConfigImpl.java",
    "chars": 4379,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/config/GitConfigKey.java",
    "chars": 1138,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/config/OtherConfigKey.java",
    "chars": 2329,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/AutosaveLogger.java",
    "chars": 1198,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/CommandLogger.java",
    "chars": 1500,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/Log4jLogger.java",
    "chars": 2741,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/ShutdownLogger.java",
    "chars": 1245,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/SystemLogger.java",
    "chars": 1782,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/UserLogger.java",
    "chars": 2517,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/logging/UserMessage.java",
    "chars": 2720,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mixins/FileFixerUpperMixin.java",
    "chars": 2298,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mixins/MessageScreenMixin.java",
    "chars": 1629,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mixins/MinecraftServerMixin.java",
    "chars": 3610,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mixins/ScreenAccessors.java",
    "chars": 1393,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mixins/ServerAccessors.java",
    "chars": 1246,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mixins/SessionAccessors.java",
    "chars": 1166,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mod/AutosaveListener.java",
    "chars": 3760,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mod/ClientHelper.java",
    "chars": 3569,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mod/LoaderHelper.java",
    "chars": 2275,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mod/Mod.java",
    "chars": 5178,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mod/ModImpl.java",
    "chars": 11077,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/mod/UserMessageUtil.java",
    "chars": 2722,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/BranchUtils.java",
    "chars": 2798,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/CommitUtils.java",
    "chars": 10285,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/JGitConsumer.java",
    "chars": 939,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/JGitFunction.java",
    "chars": 1076,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/JGitIncrementalProgressMonitor.java",
    "chars": 2511,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/JGitPercentageProgressMonitor.java",
    "chars": 2053,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/JGitSupplier.java",
    "chars": 977,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/PreflightUtils.java",
    "chars": 4234,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/PruneUtils.java",
    "chars": 6136,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/PushUtils.java",
    "chars": 17405,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/ReclamationUtils.java",
    "chars": 7930,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/Repo.java",
    "chars": 3233,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/RepoFactory.java",
    "chars": 1372,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/RepoFactoryImpl.java",
    "chars": 4815,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/RepoImpl.java",
    "chars": 12925,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/RestoreUtils.java",
    "chars": 8042,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/SnapshotId.java",
    "chars": 1173,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/SnapshotIdUtils.java",
    "chars": 6628,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/WorldId.java",
    "chars": 889,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/repo/WorldIdUtils.java",
    "chars": 5331,
    "preview": "package net.pcal.fastback.common.repo;\n\nimport net.pcal.fastback.common.repo.SnapshotIdUtils.SnapshotIdCodec;\nimport net"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/AllRetentionPolicy.java",
    "chars": 2084,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/DailyRetentionPolicy.java",
    "chars": 4703,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/FixedCountRetentionPolicy.java",
    "chars": 3382,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/GFSRetentionPolicy.java",
    "chars": 4861,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/RetentionPolicy.java",
    "chars": 1238,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/RetentionPolicyCodec.java",
    "chars": 4184,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/retention/RetentionPolicyType.java",
    "chars": 1951,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/utils/EnvironmentUtils.java",
    "chars": 3936,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/utils/Executor.java",
    "chars": 1502,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/utils/ExecutorImpl.java",
    "chars": 3727,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/utils/FileUtils.java",
    "chars": 2249,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/utils/ProcessException.java",
    "chars": 1075,
    "preview": "package net.pcal.fastback.common.utils;\n\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static java."
  },
  {
    "path": "common/src/main/java/net/pcal/fastback/common/utils/ProcessUtils.java",
    "chars": 6729,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/main/resources/assets/fastback/lang/de_de.json",
    "chars": 10694,
    "preview": "{\n  \"fastback.help.command.create-file-remote\"     : \"Erstellt ein Remote-Backup-Ziel im Dateisystem.\",\n  \"fastback.help"
  },
  {
    "path": "common/src/main/resources/assets/fastback/lang/en_us.json",
    "chars": 9746,
    "preview": "{\n  \"fastback.help.command.create-file-remote\"     : \"Create a remote backup target on the file system.\",\n  \"fastback.he"
  },
  {
    "path": "common/src/main/resources/assets/fastback/lang/es_es.json",
    "chars": 9359,
    "preview": "{\n  \"fastback.help.command.create-file-remote\"     : \"Crea un repositorio de Git para las copias de seguridad en el sist"
  },
  {
    "path": "common/src/main/resources/assets/fastback/lang/ja_jp.json",
    "chars": 8582,
    "preview": "{\n  \"fastback.help.command.create-file-remote\"     : \"ファイルシステムにリモートバックアップターゲットを作成します.\",\n  \"fastback.help.command.delete\""
  },
  {
    "path": "common/src/main/resources/assets/fastback/lang/ru_ru.json",
    "chars": 8487,
    "preview": "{\n  \"fastback.help.command.create-file-remote\"     : \"Создать шаблон для внешней резервной копии в файловой системе.\",\n "
  },
  {
    "path": "common/src/main/resources/assets/fastback/lang/zh_cn.json",
    "chars": 7354,
    "preview": "{\n  \"fastback.help.command.create-file-remote\"     : \"在文件系统上创建远程备份目标。\",\n  \"fastback.help.command.delete\"                "
  },
  {
    "path": "common/src/main/resources/fastback.mixins.json",
    "chars": 362,
    "preview": "{\n  \"required\": true,\n  \"minVersion\": \"0.8\",\n  \"package\": \"net.pcal.fastback.common.mixins\",\n  \"compatibilityLevel\": \"JA"
  },
  {
    "path": "common/src/main/resources/world/gitattributes-jgit",
    "chars": 536,
    "preview": "#\n# DO NOT EDIT.  CHANGES WILL BE OVERWRITTEN.\n#\n# This file is automatically updated by FastBack.  If you need to custo"
  },
  {
    "path": "common/src/main/resources/world/gitattributes-native",
    "chars": 571,
    "preview": "#\n# DO NOT EDIT.  CHANGES WILL BE OVERWRITTEN.\n#\n# This file is automatically updated by FastBack.  If you need to custo"
  },
  {
    "path": "common/src/main/resources/world/gitignore",
    "chars": 259,
    "preview": "#\n# DO NOT EDIT.  CHANGES WILL BE OVERWRITTEN.\n#\n# This file is automatically updated by FastBack.  If you need to custo"
  },
  {
    "path": "common/src/test/java/net/pcal/fastback/common/repo/V1SnapshotIdTest.java",
    "chars": 4828,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/test/java/net/pcal/fastback/common/repo/V2SnapshotIdTest.java",
    "chars": 4320,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/test/java/net/pcal/fastback/common/retention/DailyRetentionPolicyTest.java",
    "chars": 4132,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/test/java/net/pcal/fastback/common/retention/GFSRetentionPolicyTest.java",
    "chars": 4241,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "common/src/test/java/net/pcal/fastback/common/retention/RetentionPolicyCodecTest.java",
    "chars": 3973,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "docs/_config.yml",
    "chars": 406,
    "preview": "title: FastBack - Minecraft World Backups\nlogo: /fastback-icon.png\nremote_theme: just-the-docs/just-the-docs\nsearch_enab"
  },
  {
    "path": "docs/actions-list.md",
    "chars": 409,
    "preview": "Action                 | Use\n---------------------- | ---\n`local`                | Backs up locally only.  Like `/backup"
  },
  {
    "path": "docs/advanced.md",
    "chars": 3190,
    "preview": "---\nlayout: default\ntitle: Advanced Usage\nnav_order: 90\n---\n\n# Advanced Usage\n\nThis page assumes you already know how to"
  },
  {
    "path": "docs/commands-list.md",
    "chars": 3873,
    "preview": "\n| Command                           | Use                                                                              "
  },
  {
    "path": "docs/commands.md",
    "chars": 372,
    "preview": "---\nlayout: default\ntitle: Commands\nnav_order: 20\n---\n\n# Commands\n\n*Note: A number of subcommands changed in version 0.1"
  },
  {
    "path": "docs/diskspace.md",
    "chars": 2115,
    "preview": "---\nlayout: default\ntitle: Disk Space\nnav_order: 30\n---\n\n# Managing Disk Space\n\nFastBack makes it easy for you to store "
  },
  {
    "path": "docs/faq.md",
    "chars": 2817,
    "preview": "---\nlayout: default\ntitle: FAQ\nnav_order: 99\n---\n\n# FAQ\n\n## What is an Incremental Backup?\n\nSay you're playing for a few"
  },
  {
    "path": "docs/index.md",
    "chars": 2247,
    "preview": "---\nlayout: default\ntitle: FastBack\nnav_order: 1\n---\n\n# FastBack\n*Fast, incremental Minecraft world backups powered by G"
  },
  {
    "path": "docs/native-git.md",
    "chars": 1996,
    "preview": "---\nlayout: default\ntitle: Native Git Support\nnav_order: 95\n---\n\n# Native Git\n\nAs of version `0.17.1`, Fastback **requir"
  },
  {
    "path": "docs/permissions-list.md",
    "chars": 521,
    "preview": "\n* `fastback.command`\n* `fastback.command.create-file-remote`\n* `fastback.command.delete`\n* `fastback.command.disable`\n*"
  },
  {
    "path": "docs/permissions.md",
    "chars": 506,
    "preview": "---\nlayout: default \ntitle: Permissions \nnav_order: 80\n---\n\n# Permissions\n\nIn single-player mode, `/backup` can be run w"
  },
  {
    "path": "docs/remote.md",
    "chars": 2136,
    "preview": "---\nlayout: default\ntitle: Remote Backups\nnav_order: 40\n---\n\n# Remote Backups\n\nAn important part of any backup strategy "
  },
  {
    "path": "docs/retention-list.md",
    "chars": 467,
    "preview": "Action                 | Use\n---------------------- | ---\n`daily`                | Daily: Keep the last snapshot from ea"
  },
  {
    "path": "docs/scheduling.md",
    "chars": 1106,
    "preview": "---\nlayout: default\ntitle: Scheduling\nnav_order: 20\n---\n\n# Scheduling\n\nYou can schedule backups to run automatically whe"
  },
  {
    "path": "docs/usage.md",
    "chars": 1850,
    "preview": "---\nlayout: default\ntitle: Using FastBack\nnav_order: 10\n---\n\n# Using FastBack\n\nFastBack adds a custom `/backup` command "
  },
  {
    "path": "etc/blurb.md",
    "chars": 1194,
    "preview": "# Fast Backups\n\n*Fast, incremental Minecraft world backups powered by Git*\n\n***NOTE: FastBack is still in alpha.  See [t"
  },
  {
    "path": "etc/docgen.sh",
    "chars": 786,
    "preview": "#!/bin/sh\n\n#\n# Always run this in the root of the repo\n#\ncd $(git rev-parse --show-toplevel)\n\n#\n# generate commands-list"
  },
  {
    "path": "fabric/build.gradle",
    "chars": 4019,
    "preview": "plugins {\n\tid 'net.fabricmc.fabric-loom'\n\tid 'com.modrinth.minotaur'\n\tid 'net.darkhax.curseforgegradle'\n}\n\nbase {\n\tarchi"
  },
  {
    "path": "fabric/src/main/java/net/pcal/fastback/fabric/FabricClientInitializer.java",
    "chars": 2251,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "fabric/src/main/java/net/pcal/fastback/fabric/FabricLoaderHelper.java",
    "chars": 3930,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "fabric/src/main/java/net/pcal/fastback/fabric/FabricServerInitializer.java",
    "chars": 1526,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2022 pcal.net\n *\n * This program i"
  },
  {
    "path": "fabric/src/main/resources/fabric.mod.json",
    "chars": 819,
    "preview": "{\n  \"schemaVersion\": 1,\n  \"id\": \"fastback\",\n  \"version\": \"${mod_version}\",\n  \"name\": \"FastBack\",\n  \"description\": \"Fast,"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 252,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "gradle.properties",
    "chars": 1779,
    "preview": "#\n# Mod\n#\nmod_version = 0.33.1+26.1.2-prerelease\narchives_base_name = fastback\ngithub_projectUrl = https://github.com/pc"
  },
  {
    "path": "gradlew",
    "chars": 8739,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "gradlew.bat",
    "chars": 2966,
    "preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
  },
  {
    "path": "neoforge/build.gradle",
    "chars": 3705,
    "preview": "plugins {\n    id 'net.neoforged.moddev'\n    id 'com.modrinth.minotaur'\n    id 'net.darkhax.curseforgegradle'\n}\n\nbase {\n "
  },
  {
    "path": "neoforge/src/main/java/net/pcal/fastback/neoforge/NeoForgeClientInitializer.java",
    "chars": 2439,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2026 pcal.net\n *\n * This program i"
  },
  {
    "path": "neoforge/src/main/java/net/pcal/fastback/neoforge/NeoForgeLoaderHelper.java",
    "chars": 4089,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2026 pcal.net\n *\n * This program i"
  },
  {
    "path": "neoforge/src/main/java/net/pcal/fastback/neoforge/NeoForgeModInitializer.java",
    "chars": 2007,
    "preview": "/*\n * FastBack - Fast, incremental Minecraft backups powered by Git.\n * Copyright (C) 2026 pcal.net\n *\n * This program i"
  },
  {
    "path": "neoforge/src/main/resources/META-INF/neoforge.mods.toml",
    "chars": 552,
    "preview": "modLoader = \"javafml\"\nloaderVersion = \"[4,)\"\nlicense = \"GPL-2\"\n[[mixins]]\nconfig = \"fastback.mixins.json\"\n[[mods]]\nmodId"
  },
  {
    "path": "neoforge/src/main/resources/pack.mcmeta",
    "chars": 92,
    "preview": "{\n  \"pack\": {\n    \"description\": \"FastBack NeoForge Resources\",\n    \"pack_format\": 34\n  }\n}\n"
  },
  {
    "path": "settings.gradle",
    "chars": 291,
    "preview": "pluginManagement {\n    repositories {\n        mavenCentral()\n        gradlePluginPortal()\n        maven { url = 'https:/"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the pcal43/fastback GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 135 files (460.8 KB), approximately 111.2k tokens, and a symbol index with 624 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!