Commit Graph

99 Commits

Author SHA1 Message Date
Adam Simpkins
49b000422f fix bug in py-cstore code deleting unallocated memory
Summary:
Fix subtreeiter_create() to avoid calling the `SubtreeIterator` destructor on
an object that it never constructed.

If an error is thrown constructing the `cmpNodes` vector or calling the
`SubtreeIterator` constructor, then the `SubtreeIterator` destructor should
not be called.  However, the `subtreeiter_create()` previously used
`Py_DECREF` to clean up the object, which always invoked
`subtreeiter_dealloc()` and called the `SubtreeIterator` destructor.

It looks like a lot of the other python object types declared in this file
have similar problems, and may incorrectly invoke C++ destructors on objects
that were never constructed if something goes wrong during object
initialization.  I have not fixed the other types yet as part of this diff.
The initialization logic for each type looks somewhat different, so they will
each need different fixes rather than this simple change of just calling
`PyObject_Del()` rather than `Py_DECREF()`.

Reviewed By: quark-zju

Differential Revision: D8963858

fbshipit-source-id: d610cac16133a7ff2350c17b13b5ebc7b7becc29
2018-07-24 09:32:02 -07:00
Durham Goode
39c3ffbbc5 datapack: add python datapack wrapper
Summary:
Now that all the business logic is reusable, let's create a python
datapack wrapper. In a future diff we'll allow Mercurial to instantiate this
instead of the existing cdatapack and datapack classes.

Reviewed By: quark-zju

Differential Revision: D8774486

fbshipit-source-id: 0df8ec0cc4ae7ebfc380c496b85555c78d5da505
2018-07-17 15:10:01 -07:00
Durham Goode
260e22ee9d pyrevisionstore: move python data store logic to DataStorePyExt trait
Summary:
In a future diff we'll be add a python datapack wrapper, in addition to
the existing datastore wrapper. The logic inside them is basically identical, so
let's move all the existing logic out to a trait that can wrap all DataStore implementations.

Reviewed By: quark-zju

Differential Revision: D8774257

fbshipit-source-id: ecb9a5d174decb803c1b1216db2d366b5215ab89
2018-07-17 15:10:01 -07:00
Stanislau Hlebik
e3c0c7ab65 mysqlutil: new utility for mysql
Summary:
Common library to interact with mysql. Will be used in the next diffs to record
pushrebase requests

Reviewed By: quark-zju

Differential Revision: D8801603

fbshipit-source-id: 1a926742cc40458e8f9da385620f0f3837f3ae09
2018-07-13 09:36:52 -07:00
Durham Goode
1ba7bce984 pyrevisionstore: fix delta chain base case name
Summary:
A recent change made delta base's be Option types, and when we
encounter a None we treat that as the end of a delta chain. In the python
translation layer code we turned this into a Key of ("", nullid) when it
should've been (filename, nullid). This broke a test.

Reviewed By: phillco, quark-zju

Differential Revision: D8768592

fbshipit-source-id: 858a8eadad8699d2e7e99040486f836710381a4f
2018-07-09 10:14:15 -07:00
Durham Goode
97ad2b1c85 pyrevisionstore: fix build breakage
Summary:
The recent change from delta.base being a Key to an Option<Key> needed
to update pyrevisionstore as well. I'll add this to buck build soon so we can
catch this.

Reviewed By: phillco, quark-zju

Differential Revision: D8752910

fbshipit-source-id: ecf6d2a8a74b72d10b6dc0fb24c7b9346f0f7ed8
2018-07-06 16:06:25 -07:00
Jun Wu
a487dacc4b codemod: reformat rest of the code
Summary:
Previous code format attempt (D8173629) didn't cover all files due to `**/*.py`
was not expanded recursively by bash. That makes certain changes larger than
they should be (ex. D8675439). Now use zsh's `**/*.py` to format them.

Also fix Python syntax so black can run on more files, and all lint issues.

Reviewed By: phillco

Differential Revision: D8696912

fbshipit-source-id: 95f07aa0c5eb1b63947b0f77f534957f4ab65364
2018-07-05 17:52:43 -07:00
Durham Goode
24a4751ff0 revisionstore: change Delta.data to Rc
Summary:
In a future diff we'll be returning data read from a pack file out as a
Delta. To avoid copies, we need to be able to return an Rc from DataPack. This
seems like it will be a common pattern, so let's go ahead and make Delta contain
its data as an Rc.

Reviewed By: quark-zju

Differential Revision: D8557949

fbshipit-source-id: 276005360bfa48e9154143dedce579a21129e976
2018-07-05 14:53:18 -07:00
Durham Goode
8057817dc1 revisionstore: add read/write functions to Metadata
Summary:
In a future diff we'll be serializing and deserializing metadata in
datapacks. Let's add the reader and writer functions for Metadata and some unit
tests.

Reviewed By: quark-zju

Differential Revision: D8303603

fbshipit-source-id: 7e7a7aa218c05179b205abf8b151b1488be674b3
2018-07-05 14:53:14 -07:00
Durham Goode
e35fa7b914 treemanifest: remove dependency on limits
Summary:
The windows compiler doesn't support this, so let's just hard code our
default value.

Reviewed By: singhsrb

Differential Revision: D8679518

fbshipit-source-id: 031a6e06d12c4980756e60cdeb76b6828ed8f775
2018-06-28 10:50:45 -07:00
Durham Goode
6d8f64bc0d treemanifest: change native tree walking to allow depth a parameter
Summary:
We're experiencing cases where users are pulling down far more trees
than they need. Let's enable the ability to download a certain depth of trees.

In this diff we just implement the actual iteration limiting in native code. In
a future diff we'll make the appropriate python changes and add a test.

Reviewed By: quark-zju

Differential Revision: D8523215

fbshipit-source-id: ccfeadee1525b86b1a5578631dbafe666de41f4c
2018-06-27 18:06:13 -07:00
Liubov Dmitrieva
7e4154021c Speed up hg status by using the fsmonitor.sockpath from the config
Summary:
provide sockpath in the config, rather than calling `watchman get-sockname` command from python.

the config variable was already deployed on all mac

this improves hg st by 250-300 ms

(the logic gives more priority when env var if specified (the env var override is usually used for testing))

the logic has a retry mechanism if we can't connect to the socket path specified in the config

next step: deploy config on dev servers (but we will have only few ms speed up there)

we use %i  (i for identity) notation for user

 [298] → hg conf | grep sockpath
fsmonitor.sockpath=/opt/facebook/watchman/var/run/watchman/%i-state/sock

Reviewed By: farnz

Differential Revision: D8549130

fbshipit-source-id: 31df9ebf3eb5133d3e6f64794022cda1b0fadec4
2018-06-25 03:51:24 -07:00
Liubov Dmitrieva
6db13f4759 fsmonitor: black format + unused import
Reviewed By: singhsrb

Differential Revision: D8551677

fbshipit-source-id: fe98ae41a382a499823bd52c518a269b5f8e3b40
2018-06-20 15:20:14 -07:00
Kostia Balytskyi
a322b60e97 datapackstore: use C++11-compatible chrono code
Summary:
D8410266 introduces the use of `chrono_literals` namespace, which requires
C++14. Since we don't use C++14 otherwise, let's not use it here as well.

Reviewed By: farnz

Differential Revision: D8418398

fbshipit-source-id: c2243c2cdf51587cda698bf7c2e96691cea2d305
2018-06-14 06:23:26 -07:00
Adam Simpkins
ce351de8fb ensure that DatapackStore::markForRefresh() actually refreshes the store
Summary:
This fixes how DatapackStore::refresh() computes if it needs to perform a
refresh.

Previously this code was using `clock()`, which returns the amount of CPU time
used by the program.  This seems like an undesirable metric to use: the amount
of CPU used by the current program doesn't seem like it should be relevant to
whether the files on disk need to be re-examined or not.  If the program has
slept for an hour we probably still want to refresh the files even though no
CPU time has been consumed.

As a result of how the computation was performed, markForRefresh() also would
not trigger a refresh if the program has used less than 100ms of CPU time so
far.

This was breaking some of the eden tests which create a repository pull some
packs into it, and then immediately refresh the store.  If all of this happens
in less than 100ms the DatapackStore code would not actually refresh the
store.

Reviewed By: chadaustin

Differential Revision: D8410266

fbshipit-source-id: f08d317b261ccfe89cff45bf3e511a2b87cf9fb5
2018-06-13 19:39:12 -07:00
Mark Thomas
af99203255 cstore: add treemanifest.listdir
Summary:
Add a new function to `cstore.treemanifest` to list the contents of a directory
in the tree manifest.

Reviewed By: ryanmce

Differential Revision: D8332425

fbshipit-source-id: db74f11a691cd6b8ac7d51aec6b7da8149c22027
2018-06-12 09:23:44 -07:00
Wez Furlong
31bcfbe58e hg: disable check-code tests for C code
Summary:
They're actively fighting against the clang-format config
and don't have an auto-fix.

Reviewed By: quark-zju

Differential Revision: D8283622

fbshipit-source-id: 2de45f50e6370a5ed14915c6ff23dc843ff14e8a
2018-06-05 19:21:43 -07:00
Durham Goode
c244d056b9 treemanifest: auto-format code
Summary: This fixes various clang formatter lint warnings.

Reviewed By: phillco, singhsrb

Differential Revision: D8283573

fbshipit-source-id: c466ccaf8d5e50a7421fd34e43cf49559431d416
2018-06-05 16:40:44 -07:00
Durham Goode
6bad025052 treemanifest: fix build warning about shadowed variables
Summary:
Optimized builds were giving a warning about shadowed variables, which
broke some of our continuous builds. Let's fix those.

Reviewed By: phillco

Differential Revision: D8287865

fbshipit-source-id: 8a2d023be93499ed6458a35ba8e8cb4d83d59e4a
2018-06-05 14:35:59 -07:00
Durham Goode
dee59f7ffc treemanifest: add a subdir tree walking api
Summary:
In a future diff we'll be adding the ability to request just a
subsection of a tree. In order to do that, we need to expose an api that can
iterate of just a subsection of the tree.

Since the treemanifest object itself acts mostly like a dictionary, and doesn't
expose sub-tree functionality, we expose sub-tree walks as a limited use class
level function.

Reviewed By: quark-zju

Differential Revision: D8216726

fbshipit-source-id: 0d72ef041fd6a973387f975b21e55c82f7ec789c
2018-06-05 11:45:25 -07:00
Saurabh Singh
5427b362d1 fix method naming
Summary:
A recent change renamed the methods assuming they were rust methods
but these methods are used in the python code and therefore, already had the
correct naming. This broke the one of the python tests calling the methods
which will pass now.

Reviewed By: DurhamG, quark-zju

Differential Revision: D8279742

fbshipit-source-id: 01761ad033c6669a0ae54dc1beb853a0d56df27d
2018-06-05 09:08:21 -07:00
Durham Goode
1ac8657f83 treemanifest: require setting the start path for SubTreeIterator
Summary:
In a future diff we'll be adding the ability to iterate over just a
subsection of a tree. In order for the resulting paths to be correct, we need to
be able to prime the start path. To do so, let's add a path argument to the
SubTreeIterator, and set it to empty string everywhere it's currently used.

Reviewed By: quark-zju

Differential Revision: D8216727

fbshipit-source-id: 1969f380f7ad13f54a7dbcd2283b0428c4480ab4
2018-06-04 18:23:03 -07:00
Durham Goode
6370628aeb treemanifest: move store logic to a separate function
Summary:
In a future diff we'll be adding a function that also needs to turn a
python store into a ManifestFetcher. So let's move that logic to a separate
function.

Reviewed By: quark-zju

Differential Revision: D8216728

fbshipit-source-id: f6d2e9577eccfcb015797a519264bd3b241ebae5
2018-06-04 18:23:03 -07:00
Durham Goode
18945e39b6 treemanifest: remove py_treemanifest from iterators
Summary:
Now that the subtree iterator doesn't contain a reference to the
treemanifest directly (it instead holds a ManifestPtr reference), we no longer
need to persist a reference to it at the python layer.

Reviewed By: quark-zju

Differential Revision: D8206301

fbshipit-source-id: 531d4399f0f54a47e0eb741f419e6242188a83ad
2018-06-04 18:23:03 -07:00
Durham Goode
aa1e1a5604 treemanifest: remove resultEntry from iterators
Summary:
Now that markPermanent no longer requires a ManifestEntry to update the
node, the resultEntry field on all the iterators is unused. Let's delete it.

Reviewed By: quark-zju

Differential Revision: D8156808

fbshipit-source-id: 21aeb050c91552c7e8973a33dd7e347457125a64
2018-06-04 18:23:03 -07:00
Durham Goode
4708242f44 treemanifest: remove logic that updates ManifestEntry.node
Summary:
Now that the node field is accessed via a getter that can lazily
compute it, let's get rid of the logic that proactively sets it. This will make
it easier to refactor the subtree iterator in a later diff.

Reviewed By: quark-zju

Differential Revision: D8156809

fbshipit-source-id: aabc3f68cc41baea0c166b9a2bd68bea6b1a6a03
2018-06-04 18:23:03 -07:00
Durham Goode
63834775d7 treemanifest: move ManifestEntry node behind an accessor
Summary:
Previously the ManifestEntry node field was public and consumers could
read it directly. This required that the node field be updated manually, which
added complications to other code paths. In a future diff we'll be simplifying
the SubTreeIterator code to not require setting node, and to do so we make node
calculated in this accessor.

Reviewed By: quark-zju

Differential Revision: D8156807

fbshipit-source-id: e1908b2149bc0ec8fb1279e37b8a66988ff125c6
2018-06-04 18:23:03 -07:00
Durham Goode
0bbc6b6666 treemanifest: switch native iterators to use ManifestPtr
Summary:
This switches the treemanifest native iterators to use the ManifestPtr
class instead of Manifest*. ManifestPtr does ref counting, and in a future diff
we'll use this ability to have the subtree iterator iterate over a portion of a
larger tree so we can serve just parts of trees to gettreepack requests.

Reviewed By: quark-zju

Differential Revision: D8156806

fbshipit-source-id: 4dbb60d008ac8d0c789c3f2db8f7e567c5869539
2018-06-04 18:23:03 -07:00
Saurabh Singh
7c9227818a refactor rust datastore to a consistent naming scene
Summary: This is just a refactor to address the naming scheme.

Reviewed By: quark-zju

Differential Revision: D8269217

fbshipit-source-id: 8c52d2c67837550e0b7dc1a45b3faf9a80319b61
2018-06-04 17:39:47 -07:00
Sergey Zhupanov
7d44cb5f01 Enabled additional compiler warnings in Eden.
Summary:
1. Enabled a number of additional C++ compiler warnings in Eden.
2. Fixed warnings-turned-errors that resulted from this change.

Reviewed By: simpkins

Differential Revision: D8132543

fbshipit-source-id: 2290ffaaab55024d582e29201a1bcaa1152e6b3e
2018-06-01 11:10:54 -07:00
Durham Goode
1aa9db8542 treemanifest: include p2 when creating trees
Summary:
Previously we weren't including p2 when creating trees, this resulted
in incorrect conversions.

Reviewed By: phillco

Differential Revision: D8152253

fbshipit-source-id: c4f8c79b40532c5162b15032962fbc6a78d44b5f
2018-05-30 18:20:57 -07:00
Durham Goode
06e89ef39e treemanifest: check memcmp < 0 instead of memcmp == -1
Summary:
The memcmp contract is that the result is >0, 0, or <0. If <0 it's not
guaranteed to be -1, so let's broaden the check a bit.

Reviewed By: phillco

Differential Revision: D8190843

fbshipit-source-id: 78b864a639851a041c1a393f21a979d13eaa2e9c
2018-05-30 18:20:57 -07:00
Lukasz Langa
dfda82e492 Upgrade to 18.5b1
Summary: Mostly empty lines removed and added.  A few bugfixes on excessive line splitting.

Reviewed By: quark-zju

Differential Revision: D8199128

fbshipit-source-id: 90c1616061bfd7cfbba0b75f03f89683340374d5
2018-05-30 02:23:58 -07:00
Sergey Zhupanov
3288f45188 Minor cleanup of interfaces in some scm classes.
Summary: Added const and override where appropriate to some classes in scm.

Reviewed By: aary

Differential Revision: D8176501

fbshipit-source-id: a9247aee569692973a822d002e951dad42c42be6
2018-05-29 13:26:31 -07:00
Jun Wu
ee83f12849 treestate: move hgext.extlib.treedirstate to mercurial.rust.treestate
Summary:
Going to make changes to `mercurial/` for cleaner fsmonitor support
directly. So let's move the Rust python bridge there first.

Reviewed By: markbt

Differential Revision: D7909174

fbshipit-source-id: 454d784b5dca18a3af9328fc7b2f342cd4188cf6
2018-05-26 14:05:18 -07:00
Jun Wu
bfc47200de treedirstate: expose TreeState APIs
Summary: Expose `TreeState` features to the Python world.

Reviewed By: markbt

Differential Revision: D7886283

fbshipit-source-id: 39e654bfcd611aeff4255e1fff8ce32f20544a22
2018-05-26 10:50:26 -07:00
Jun Wu
b27143828b treestate: change Key from Vec<u8> to Box<[u8]>
Summary: This saves one `usize` per `Key`.

Differential Revision: D7861766

fbshipit-source-id: e44d6b98758966edd0f9823f2f50270ba5481b22
2018-05-23 06:12:45 -07:00
Jun Wu
395edeaea6 treestate: remove clear_filtered_keys
Summary:
The method looks like a foot-gun, and it's O(all entries) instead of
O(cached entries). Change `get_tracked_filtered_key` to take an identity of
the filter function explicitly to solve the problem.

Reviewed By: markbt

Differential Revision: D7861765

fbshipit-source-id: a57ca4a7597120a5b00c63f3f373a62e19e5a834
2018-05-23 06:12:45 -07:00
Shish Girling
1137cc5743 display 'Landing' status in hg ssl output
Summary:
It's super-useful to know the difference between "Accepted (You need to go and click the land button)" and "Accepted (We're just waiting for the land system to do its thing, no work for you right now)"

Alternative approach in D8074898

Reviewed By: mitrandir77

Differential Revision: D8074794

fbshipit-source-id: fdfda64e3a542518b8609e3a415d8cb3156373cf
2018-05-22 02:51:56 -07:00
Michael Bolin
a5e1820702 Split out a new hgevents extension from fsmonitor.
Summary:
This splits out the logic in the `fsmonitor` extension that is responsible for
publishing `hg.filemerge` and `hg.update` state changes to Watchman into
its own extension, `hgevents`. This is because we want the behavior of
`hgevents` when Hg is running in Eden, but we do not want the remaining
behavior of `fsmonitor` when Hg is running in Eden, so splitting the logic
into separate extensions is the most straightforward way to achieve that.

To achieve the split, we move some more logic that is common to both
`fsmonitor` and `hgevents` out of `hgext/fsmonitor/__init__.py` and into
`hgext/extlib/watchmanclient/__init__.py`. Then we move these lines
out of `extsetup()` in `fsmonitor` to create `extsetup()` in `hgevents`:

```
    extensions.wrapfunction(merge, 'update', wrapupdate)
    extensions.wrapfunction(filemerge, '_xmerge', _xmerge)
```

We also have to pull all of the transitive dependencies for this logic
into `hgevents`.

Finally, we also have to define a `reposetup()` function in `hgevents`
that does a subset of what `reposetup()` does in `fsmonitor`. Specifically,
it ensures that a Watchman client is created for a `repo`, as appropriate,
so that it can be used to dispatch state changes to Watchman in
`state_filemerge` and `state_update`.

Note that the utility functions `createclientforrepo()` and
`getclientforrepo()` have been added to ensure that only one
Watchman client is created (and shared) when both `fsmonitor`
and `hgevents` are enabled.

Today, when an Hg repo is created in Eden, we set `extensions.fsmonitor=!`
in the `.hg/hgrc`:

diffusion/FBS/browse/master/fbcode/eden/hooks/hg/post-clone.py$69

Therefore, to get existing repos (both Eden and non-Eden) to pick up
the `hgevents` extension automatically, we add it to the list of
`[extensions]` in `common.rc`:

diffusion/FBS/browse/master/fbcode/scm/hg/fb/staticfiles/etc/mercurial/repo-specific/common.rc$53-60

as this is where `fsmonitor` is configured. We do not enable it in
`scm/hg/fb/staticfiles/etc/mercurial/facebook.rc` because
there is no reason to enable `hgevents` on Hg servers. Therefore, we
also decline to add `hgevents` to the set of `DEFAULT_EXTENSIONS` in
`scm/hg/mercurial/extensions.py`.

Reviewed By: quark-zju

Differential Revision: D8003628

fbshipit-source-id: 4f23881f8c25f4638f5475c292537b0352ae8d15
2018-05-21 09:30:28 -07:00
Michael Bolin
36eb4d26f8 mv fsmonitor/watchmanclient.py to extlib/watchmanclient/__init__.py
Summary:
There is some logic in `hgext/fsmonitor/watchmanclient.py` that needs
to be shared between fsmonitor and the new extension we are trying to
split out of it as part of T29379329.

Reviewed By: quark-zju

Differential Revision: D8003255

fbshipit-source-id: de01c5ba1460d7dde22969980b296afb3a942dd7
2018-05-15 12:06:47 -07:00
Michael Bolin
54b487b301 Move hgext/fsmonitor/pywatchman/ to hgext/extlib/pywatchman/
Summary:
This is a precursor to splitting the fsmonitor extension, as both
it and the new extension will use pywatchman.

Reviewed By: quark-zju

Differential Revision: D8002713

fbshipit-source-id: 37983fe2898d23223d1178eb3f15685f17ff8868
2018-05-15 12:06:47 -07:00
Durham Goode
18697e3fb3 hg: implement getmissing() for Rust hgstore
Summary:
Initial implementation of getmissing for a simple Rust pass through
data store. Future diffs will extend this to implement the union data store
completely in Rust.

Reviewed By: quark-zju

Differential Revision: D7632405

fbshipit-source-id: e660d33f8231410805cfaba6d77c56f27b002f8e
2018-05-14 12:05:13 -07:00
Durham Goode
f2b4d7f2e0 hg: implement getmeta() for Rust hgstore
Summary:
An initial implementation of getmeta for the Rust hg data store. Future
diffs will add more functionality.

Reviewed By: quark-zju

Differential Revision: D7632404

fbshipit-source-id: 53bd3b96b777bc3c5aef2b4d07ce1a9d9a5a52ed
2018-05-14 12:05:13 -07:00
Durham Goode
39dde8552d hg: implement getdeltachain() for Rust hgstore
Summary:
An initial implementation of getdeltachain for a simple pass through
data store. Future diffs will add additional functionality.

Reviewed By: quark-zju

Differential Revision: D7632407

fbshipit-source-id: 1a38089ba8ea70f8772af95afd871ee493082d80
2018-05-14 12:05:13 -07:00
Durham Goode
880ff5d0a9 hg: implement datastore.get() for Rust hgstore
Summary:
Implements the get function for a simple pass through rust data store
layer. Future diffs will implement more functions, and then later we will
implement the entire union data store in Rust.

Reviewed By: quark-zju

Differential Revision: D7632403

fbshipit-source-id: 3a1d0a8500e3110213d70dc1cff637cf8eadd809
2018-05-14 12:05:13 -07:00
Durham Goode
a97e97e413 hg: initial boiler plate for new hgstore crate
Summary:
This will contain all the Python centric hg store code that will let
Python call into the Rust storage layer.

Reviewed By: quark-zju

Differential Revision: D7632406

fbshipit-source-id: 6b7bcc8f47a23e9c0121e1f92de1137369bf584e
2018-05-14 12:05:12 -07:00
Jun Wu
d15213f6f5 treedirstate: move non-Python part to a separate crate
Summary:
This makes it easier to modify and test the core logic without coupling with
the Python logic.

Reviewed By: markbt

Differential Revision: D7734012

fbshipit-source-id: 0d7b19198d85f6ca7314611256e9271be60070d1
2018-04-24 15:59:07 -07:00
Jun Wu
58029547d6 cleanup: remove Cargo.lock from check-in
Summary:
They cause a lot of churn when updating vendored crates. It's unnecessary to
check them in because the vendored crates basically pinned versions of all
dependencies.

Also clean up .gitignore, since it does not support "syntax:re".

Reviewed By: DurhamG

Differential Revision: D7562533

fbshipit-source-id: b9fcade6f38c0652a1b19ac4f90827dac7eed181
2018-04-21 03:42:58 -07:00
David Lai
137fdc5b1c convert string compare to use equality operators
Summary:
The changes in this diff changes string comparisons using the compare method to
using equality operators.

Motivation:
- readability, simplifies code
- compare method is intended for sorting functions

This is clang check used: [Link Here](https://clang.llvm.org/extra/clang-tidy/checks/readability-string-compare.html)

Reviewed By: yfeldblum

Differential Revision: D7674416

fbshipit-source-id: dce375fb4b401f0e012ad4fc99b5dc4babe07f89
2018-04-19 13:54:59 -07:00