| Age | Commit message (Collapse) | Author | Files | Lines |
|
Recent Conscrypt (Android 14 mainline updates) registers a KeyFactory
for Ed25519. With sshj leaving provider selection to the JCA, its
Ed25519KeyFactory now gets Conscrypt's key class, whose algorithm name
sshj does not map to ssh-ed25519: KeyType.fromKey returns UNKNOWN and
every Ed25519 host key fails with "Don't know how to encode key", so
clone and sync over SSH break. 0.1.4 is affected since the sshj 0.41.2
update.
Register a provider at position 1 that serves only KeyFactory.Ed25519
from Bouncy Castle (kept by the existing R8 rule). Everything else,
including signatures with Android Keystore keys, still goes through
normal JCA provider selection.
Verified on an Android 14 tablet: clone over SSH failed before and
works with the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ad3c63f665e6264ed1db42d6ae2dfc0947e33393)
|
|
- Look up the .gpg-id from the normalized target's folder, so a
prefilled folder such as a/../b cannot pick a/'s recipients.
- Derive the edited entry's folder from its canonical parent, so a
store path spelled differently does not show as ../../data/...;
the file name still comes from the link.
- Drop the REPO_PATH shortcut extra nothing reads any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Compose editor, generator sheet, OTP dialog and folder picker
replace PasswordCreationActivity, the generator and manual OTP dialog
fragments, SelectFolderActivity with its fragment and adapter, their
layouts and menus, and upstream's unused EditPasswordScreen with the
PasswordField it used. flowbinding and swiperefreshlayout had no other
users. BasePGPActivity keeps what autofill decryption still needs.
Also drops two stale SshKey baseline entries, the snackbar FAB anchor
whose view is gone, and resources only the deleted files used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
AutofillSaveActivity shows the editor itself, prefilled from the form
and FLAG_SECURE, instead of starting the legacy editor for a result. The
autofill match and the generated-password fill work as before: the
username and password are read back from the saved content, as the old
editor did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A successful save left saving set while the destination committed in
the activity's lifecycleScope. An activity recreated then (theme,
locale, split-screen resize) cancelled that scope: onSaved never ran,
and the surviving ViewModel kept Save disabled and back swallowed.
The ViewModel now keeps the success in a memory-only saved state, set
inside the NonCancellable write so a cancelled caller cannot lose it.
A LaunchedEffect on it runs the commit and onSaved, so a recreated
editor finishes the job; "Save and copy" and the stale-original notice
run once only, and left() clears it before onSaved navigates.
Editing a symlinked entry derived its folder from the canonical path
but its name from the link, saving a new entry next to the target. The
folder is now relative to the non-canonical repository directory; the
save still checks the target is inside the store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
EditRoute replaces the legacy editor for new entries and edits. An edit
copies its plaintext from the entry view's ViewModel below it, never
through the route or saved state. Saving returns to the list, closing
returns to where the editor was opened. FLAG_SECURE is now counted in
MainActivity, so moving between the entry view and the editor never
clears it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Stateless editor: name, folder breadcrumbs with Change, password with
reveal and generator, username, extra content and the OTP actions. The
generator sheet has random and diceware tabs with the old options; the
manual OTP dialog and the sheet are FLAG_SECURE.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
- Run the save's write, original delete, autofill and history updates
under NonCancellable so a recreated activity cannot leave a half
done rename.
- fsync the temporary file before the atomic move.
- Move dialog: refuse targets outside the store (symlinks) and moving
a folder into itself or a descendant, with a toast.
- Folder picker: do not navigate into a new folder whose mkdirs failed;
show a toast (new string, en and it).
- Normalize entryFile so "./" and "a/../b" give the same keys as the
list; covered in EditorModelTest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Holds the form in memory only, generates passwords with the stored
generator options, decodes QR images and saves off the main thread:
nothing is written when the target would replace another entry or when
encryption fails, the file is written through a temporary file, and a
renamed or moved entry takes its autofill matches and recently-used time
along.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A dialog with breadcrumbs, subfolder rows and New folder replaces the
legacy folder picker activity for the list's move action; the editor will
share it. New folder reuses the list's name dialog and checks. A second
tap on Select no longer moves twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Pure functions for the Compose editor: the form and the pass file it
writes, validation, the target file, the overwrite check, an atomic
write, OTP URI building and the random generator's options. The folder
picker lists subfolders with the list's hidden filter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Compose entry view replaces DecryptActivity, its layouts, menu and
field adapter, and upstream's unused ViewPasswordScreen. BasePGPActivity
keeps what the editor and autofill still need.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Ignore a submit while no prompt is showing, so OK plus IME Done cannot start
two decrypts. Key the passphrase dialog on the retry count so each attempt
gets a fresh field and shows the wrong password error. Note the process
death gap around key import and the editor. Record the TotpModule,
text selection and file IO decisions in the plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The list navigates to EntryRoute and shortcuts start on it, replacing the
legacy decrypt screen. FLAG_SECURE is on while an entry is composed and
off on the list; the route is checked with isFileInRepo again before
decrypting. Saving in the editor returns to the list. Password and one-time
code copies start the clipboard clear timer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Header with avatar, name and folder; password, username and one-time
code with a countdown bar in the first group; extra fields in the second.
FieldRow can start revealed (the "Show the password" setting) and shows
masked values in JetBrains Mono, as the old screen did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Ports the decrypt screen's unlock flow: passphrase cache after a
biometric success, passphrase prompt with three tries, decryption off
the main thread and the auto-close timer. The decrypted entry is held in
memory only and dropped by the timer or when the destination is popped.
A cached passphrase that fails is no longer retried in a loop.
TotpModule is also installed in ViewModelComponent, so the entry
factory's TotpFinder can be injected into the ViewModel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Compose entry view lives in MainActivity, which is not a
BasePGPActivity. The helpers move unchanged to FragmentActivity
extensions in the same file, so the existing subclasses keep calling
them as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Totp now carries its period, so the entry view can draw how much of the
code's lifetime is left. EntryFields groups a decrypted entry the way the
spec lays it out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Out of touch mode the view system gives its default focus to the first
Compose focusable, the search field, which kept it and brought the
keyboard back on every return to the list. Each return now clears focus
once the window has focus (earlier, the view still holds a stale touch
mode and clearing refocuses the field). Search on start runs on the first
of those returns only, tracked in a saveable flag, so popping back from a
folder or rotating no longer refocuses it.
The extended FAB label sits under clearAndSetSemantics in Material 3, so
the icon now carries the "New entry" description.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Compose list replaces it. The autofill inline suggestion's
attribution intent now opens LaunchActivity, behind the biometric gate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
- MainActivity handles orientation|screenSize itself so rotation no
longer cancels store and git work running in lifecycleScope.
- Search-on-start focus moved into SearchField (own FocusRequester), only
at the store root, fixing "FocusRequester is not initialized".
- refresh() leaves an open folder removed by a pull; used on resume and
after git, move, rename and delete.
- Zero-count delete uses deleteWithoutFollowingLinks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
- renameFolder stops when the move fails instead of recording and committing
- delete runs its file work on the IO dispatcher
- folder delete no longer follows directory symlinks (new test)
- overwrite dialog is dismissed when its coroutine is cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The check used a string contains on canonical paths, so a typed name
such as ../store-evil/x resolved to a sibling directory whose path
contains the store's and passed. New entries and folder renames could
then land outside the store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Folders are back stack entries (ListRoute(path)), so back goes up one
level. MainActivity extends BaseGitActivity for pull to sync and the
commits after move, rename and delete.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A move now waits for the overwrite answer before it commits, and new
folder names must stay inside the store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Slack's ComposeUnstableReceiver lint rejects an instance composable on
the activity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Its two routes hand off to the legacy list and decrypt activities until
those screens are ported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The autofill setup dialog, the publisher-changed warning and the
biometric setting still told users to look for "Password Store", which
is not what the app is called in the autofill service list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The Italian strings were largely machine-translated: "commit" became
"conferma" (confirm), pull and push became "trascina" and "spingi" or
"premi" (drag, push a button), rebase became "ricollocare", the store
became "negozio" (a shop), "pretend" became "pretendere" (a false
friend), and one string showed "/0" instead of "/". Autofill had five
different spellings.
Rewrite those, keep Git terms in English (remote, branch, push, pull,
rebase, merge, commit, detached HEAD, fast-forward), use "compilazione
automatica" as Android's Italian settings do, make "repository"
masculine throughout, replace the "Sei pregato di" calque with plain
imperatives, name the app HardenedPass, and translate the 52 strings
that fell back to English.
Also drop the "TODO: Add a recovery method" that the merge-failure
message showed users, in English and every locale.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Filling OTPs from SMS used Play Services' SMS User Consent API, removed
with the nonFree flavor; since then AutofillSmsActivity was a stub that
never offered the dataset. Delete it, its manifest entry, the dataset
builders in both response builders, its drawable and string.
AutofillAction.FillOtpFromSms stays in autofill-parser: it is public,
metalava-checked API and harmless for a client that never fills it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Entries created in the app said "username:dave". pass users and other
clients write "username: dave"; the parser already trims either form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
sshj 0.40 replaced the unmaintained net.i2p.crypto:eddsa 0.3.0 (last
release 2019) with Bouncy Castle's Ed25519. 0.41.2 still targets Java 8.
The app used eddsa directly for wrapped Ed25519 SSH keys. Generate the
32-byte seed with SecureRandom and build the key pair with sshj's
Ed25519KeyFactory and Bouncy Castle's public key derivation. The wrapped
format is still the raw RFC 8032 seed, so keys generated by 0.1.3 keep
working; Ed25519KeyPairTest pins this with the RFC 8032 test vector.
sshj 0.40 also changed setSecurityProvider(null) to reset the
registerBouncyCastle flag, which undid the earlier
setRegisterBouncyCastle(false): sshj then forced every operation through
Bouncy Castle, which cannot sign with Android Keystore keys ("no encoding
for EC private key"), and hardware-backed SSH keys failed to
authenticate. Call the two in the opposite order; SshjConfigTest checks
that sshj leaves provider selection to the JCA.
Verified with the R8 release build on the emulator against the e2e
server: a hardware ECDSA key, a newly generated wrapped Ed25519 key and
an imported OpenSSH Ed25519 key all authenticated and synced.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
security-crypto is deprecated in 1.1.0 and the app was stuck on
1.1.0-alpha06, since the stable release fails the warnings-as-errors
build. It also pulled in Tink for what is a single AES-GCM operation.
KeystorePreferences is a SharedPreferences whose values are AES-GCM
sealed under an Android Keystore key (random IV, entry name as AAD so
values cannot be swapped between entries); a value that fails to
decrypt reads as absent. It backs the Git credentials (git_secrets),
the proxy credentials (proxy_secrets) and the PGP passphrase cache,
whose key needs the screen lock or a strong biometric within 60s and is
recreated if a new biometric enrollment invalidates it. Wrapped Ed25519
SSH keys are sealed the same way into the private key file. Password
generator settings are not secret and move to plain preferences.
Nothing is migrated: removeSecurityCryptoData deletes the old encrypted
preference files, Tink keysets, their Keystore master keys and a
security-crypto wrapped Ed25519 key once on upgrade. Only 0.1.x installs
from the last few hours have such data. Hardware SSH keys and imported
keys are untouched.
Tests: KeystorePreferencesTest (round trip, defaults, no plaintext,
random IVs, AAD binding, tamper rejection, remove/clear) with a software
AES key. Verified on the emulator by upgrading a 0.1.2 debug build that
held a wrapped Ed25519 key, Tink keysets and a cached passphrase: the old
data was deleted, a new Ed25519 key authenticated over SSH after a
fingerprint unlock, and a passphrase cached on first decrypt was reused
on the second with only the fingerprint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The add and edit commit messages said "using Android Password Store"
in every locale. The Italian remove message translated "store" as
"negozio" (a shop); use "archivio".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
CryptoRepository.encrypt looked up every .gpg-id recipient and dropped
the ones it could not find, so an entry saved on a device that lacked
one recipient's key was encrypted to the others only. With a store
shared between a phone key and a smartcard key, entries created on the
phone could no longer be decrypted with the smartcard alone.
Fail with the lookup error instead (KeyNotFoundException names the
key), so the user imports the missing public key and saves again.
PasswordCreationActivity also ignored the Result of encrypt and wrote
the output buffer regardless: on any encryption failure it wrote an
empty .gpg file, overwriting the entry when editing, and committed it.
Show the error and leave the file untouched.
CryptoRepositoryTest covers both the missing-recipient failure and the
all-present success path, with keys generated by PGPainless (added as
an app test dependency).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
A Keystore SSH key generated with "Protect with screen lock credential"
was bound to AUTH_DEVICE_CREDENTIAL only, while the unlock prompt also
accepted biometrics. A fingerprint passed the prompt, then signing the
SSH auth request threw UserNotAuthenticatedException, sshj logged "User
not authenticated" and the clone failed with "Socket closed".
Bind new keys to AUTH_DEVICE_CREDENTIAL or AUTH_BIOMETRIC_STRONG, and
allow only strong biometrics in the prompt on Android 11+, since weak
ones never unlock Keystore keys (the androidx MasterKey keys already
use credential or strong biometric). Android 9 and 10 cannot combine
BIOMETRIC_STRONG with DEVICE_CREDENTIAL, so they keep the weak class.
Existing protected keys still need the PIN until regenerated.
Verified on the emulator (Class 3 fingerprint): a protected ECDSA key
unlocked with a fingerprint authenticates and clones over SSH.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The fork gets its own application ID and name, so it installs next to
upstream's app.passwordstore instead of replacing it, and could be
submitted to F-Droid. Debug builds keep the .debug suffix and are
labelled "HardenedPass Debug". The Kotlin package stays
app.passwordstore: renaming it would be churn with no visible effect.
"GrapheneOS" is kept out of the name because it is that project's
trademark; "Hardened" nods to its vocabulary. README and AGENTS.md
describe the fork's GrapheneOS goal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
The fork targets GrapheneOS and ships only FOSS builds. The nonFree
flavor added Sentry crash reporting, wired to upstream's Sentry project,
and Play Services SMS one-time-code autofill, which needs Google's
servers. Neither is wanted, so remove the flavor dimension entirely:
- Delete app/src/nonFree, the SentryPlugin, the sentry-stub module, the
Sentry and Play Services catalog entries, and the Sentry scope setup
in Application.
- Move the free AutofillSmsActivity stub into main; it keeps the SMS
OTP feature off exactly as the free build did. A ponytail note marks
the FillOtpFromSms paths for later removal.
- SlimTests no longer needs to skip a nonFree variant.
- Workflows and docs use :app:assembleDebug / :app:assembleRelease. A
bare assembleRelease would also verify library modules in release
mode, where ui:compose fails on a pre-existing AppCompat attribute
reference the old flavored task never touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
JGit 4.5.7 dates from 2019. 5.13 is the last line that needs only Java 8
APIs, so it works with Android's desugaring; it also fixes
GHSA-3p86-9955-h393 (arbitrary file overwrite on checkout).
GHSA-vrpq-qp53-qv56 (XXE in repo manifest parsing) has no 5.x fix but
the app never parses repo manifests.
API changes:
- SshSessionFactory now requires getType().
- CheckoutCommand.setForce is deprecated; setForceRefUpdate is its
documented replacement with the same meaning.
JGit 5 references java.lang.management and javax.management, which
Android lacks; R8 flagged them. Both paths are made unreachable rather
than only silenced:
- WindowCache registers JMX statistics by default; Application.onCreate
now installs a WindowCacheConfig with exposeStatsViaJmx = false.
- GC.gc() takes a PID lock that queries ManagementFactory and would
throw NoClassDefFoundError on a device. GcOperation now runs the GC
steps (packRefs, repack, prune) directly; the lock only guards against
concurrent gc processes, which cannot happen in the app. Aggressive
packing is dropped.
The R8 -dontwarn rules and the lint baseline entries are added only for
those two now-unreachable references.
JGitBehaviourTest covers the operations' command chains plus the GC
steps and passes on 5.13.5. It now ignores the developer's global git
config, which JGit 5 reads (commit.gpgsign made test commits try to
sign).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
When a pull with rebase stops on a conflict, HEAD is detached and
getCurrentBranch() returned null. "Abort rebase and push new branch"
then pushed the local commits to "conflicting-null-<time>" and failed
to switch back with "Branch name <null> is not allowed", leaving the
repository on the conflict branch.
Fall back to the branch JGit records in rebase-merge/head-name (or
rebase-apply/head-name) while a rebase is in progress. Found while
driving the conflict flow on an emulator against the e2e test server;
verified there: the edit is pushed to conflicting-master-<time> and the
operation completes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
AGP 9 migration:
- Configure the shared Android setup through the new DSL
(CommonExtension, ApplicationExtension) instead of the removed
TestedExtension and BaseAppModuleExtension; targetSdk moves to the
application extension.
- Stop applying the Kotlin Android plugin; AGP 9 compiles Kotlin itself.
- Replace kapt with KSP 2.3.13 for Hilt, which built-in Kotlin requires,
and carry the Dagger compiler options over as KSP arguments.
- AGP 9 now applies packaging excludes to unit-test resources, which
stripped the diceware word list (**/*.txt) from its tests; lift that
exclude in the diceware module.
- Use resources.directories instead of the deprecated srcDir.
Plugin updates required by Gradle 9 / AGP 9: Sentry Gradle plugin
6.25.0 (sentry-android 8.62.0), metalava plugin 0.5.1, maven-publish
0.37.0, spotless 8.10.4, version-catalog-update 1.1.2, download task
5.7.0, foojay resolver 1.0.0, with their API changes. Newer metalava
models Kotlin properties, so autofill-parser/api.txt is regenerated;
the diff is representation only, no API was added or removed.
Fix new Kotlin 2.4 and lint findings (explicit return type in a
function with an early return, UseKtx in autofill-parser), drop
deprecated Kotlin Gradle properties, and make the AGENTS.md pre-commit
command cover every task CI runs (both flavors, lint on all modules).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Newest releases that still fit together on the Gradle 8 / AGP 8 line:
- Kotlin stops at 2.1, the newest metadata Gradle 8's embedded Kotlin
2.0 compiler can read when compiling build-logic.
- Hilt stops at 2.58; 2.59 and later require AGP 9. Hilt 2.52's
annotation processor could not read Kotlin 2.1 metadata.
Drop the languageVersion = 1.9 pin so code compiles with the K2
frontend; Kotlin 2.4 no longer accepts 1.9. Drop the R8 8.5.35 pin and
its repository, which is older than the R8 AGP 8.13 bundles. Move the
deprecated buildFeatures.androidResources flag to
androidResources.enable, and replace Uri.parse with toUri for the new
UseKtx lint check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Bouncy Castle 1.78.1 is affected by GHSA-574f-3g2m-x479,
GHSA-9pwp-9qqc-pr26, GHSA-c3fc-8qff-9hwx and GHSA-qp49-qgx5-5m26, all
fixed by 1.85. PGPainless 1.7.8 is the newest 1.x release. A root
resolution rule keeps every Bouncy Castle module on the catalog version;
before, bcpg 1.77 shipped next to bcprov 1.78.1.
The new stack decrypts the OCB (LibrePGP) packets GnuPG 2.3+ emits by
default, which the old one rejected as "non-standard AEAD". Drop the
NonStandardAEAD error, its bottom sheet and strings, and replace the
mislabelled aead_* fixtures (SHA-1 self-signed keys plus a message
encrypted to an unrelated key) with a real GnuPG 2.5 key and OCB message
that the tests now decrypt. The new fixture is a throwaway test key, so
it is allowlisted in .gitleaksignore by fingerprint.
Fix KeyUtils.isKeyUsable, which compared a Boolean to null and so
accepted every key that parsed, including keys with no encryption
subkey. A signing-only key fixture now covers the rejection path.
Also: migrate the removed SelectUserId.firstMatch and deprecated
addDecryptionPassphrase APIs, keep one copy of the Bouncy Castle
META-INF/LICENSE.md in the APK, and move the existing InvalidPackage
lint baseline entries to the bcpkix 1.86 jar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
DecryptActivity was exported without an intent filter, so any installed
app could start it with arbitrary FILE_PATH/REPO_PATH extras (and crash
it by omitting them). Nothing outside the app needs to launch it:
shortcuts and the decrypt action go through LaunchActivity.
LaunchActivity stays exported, so it now treats FILE_PATH as untrusted:
the path is forwarded only when it canonically resolves to a regular
file inside the password store, and REPO_PATH is always the real store
directory instead of the caller-supplied value. Anything else falls
back to the main screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
This reverts commit 04f4b9804f10e4724bac22d40bf01711ff708533.
|
|
This reverts commit cb22561878d4358dba974c16ba9a05cc693d411a.
|
|
This reverts commit 69513bf24613d4ba540bcdeaffe04dc9330a65a4.
|