New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HMR] Multiple HMR clients #6179

Closed
wants to merge 2 commits into
from

Conversation

Projects
None yet
@JohnyDays
Contributor

JohnyDays commented Feb 26, 2016

Multiple HMR clients

This pull request adds support for multiple HMR clients 馃巿 馃巶 馃巿 (as per discussed in #5338 and e018aa3)

How it works

Dependency caches are being kept per unique bundle/platform combination in a structure such as

{
  ios: {
    'path/to/index.ios.bundle': [/* this bundle's dependencies */],
  },
  android: {
    'path/to/index.android.bundle': [/* this bundle's dependencies */],
  },
}

Upon receiving a connection request, it's added to a similar structure per bundle

{ 
  [bundle]: [/*connected clients for this bundle*/] 
}

The HMR bundle changes are then forwarded to each device when their bundle's/platform dependencies are invalid/changed.

EDIT: Quick demo 馃槃 https://www.youtube.com/watch?v=etk3Bb7z1eQ

Test Plan

I did some manual tests using 2 emulators and 2 real devices, all connected at the same time, hot reloading successfully! (1 android emulator, 1 android devices, 1 iOS emulator and 1 iOS device).

Is there any way I can automate this? Not familiar with the testing practices for similar features on the project.

@martinbigio You seem like the guy to ask for code review 馃槃 (EDIT: Oh that's an helpful bot)

[HMR] Allow for multiple HMR client connections, from different platf鈥
鈥rms and apps, to receive HMR updates in parallel
@facebook-github-bot

This comment has been minimized.

Show comment
Hide comment
@facebook-github-bot

facebook-github-bot Feb 26, 2016

By analyzing the blame information on this pull request, we identified @martinbigio, @skevy and @davidaurelio to be potential reviewers.

By analyzing the blame information on this pull request, we identified @martinbigio, @skevy and @davidaurelio to be potential reviewers.

@skevy

This comment has been minimized.

Show comment
Hide comment
@skevy

skevy Feb 26, 2016

Collaborator

This looks pretty good @JohnyDays!

One nit: can you remove the new lines after each function definition, such as here: https://github.com/facebook/react-native/pull/6179/files#diff-927b035f11215f6000c2e633e88d8991R29?

I can't test right now...but will do so later. This will be a helpful feature.

Collaborator

skevy commented Feb 26, 2016

This looks pretty good @JohnyDays!

One nit: can you remove the new lines after each function definition, such as here: https://github.com/facebook/react-native/pull/6179/files#diff-927b035f11215f6000c2e633e88d8991R29?

I can't test right now...but will do so later. This will be a helpful feature.

@facebook-github-bot

This comment has been minimized.

Show comment
Hide comment
@facebook-github-bot

facebook-github-bot Feb 26, 2016

@JohnyDays updated the pull request.

@JohnyDays updated the pull request.

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Feb 26, 2016

Contributor

@skevy Yeah sure, it's done 馃憤

Contributor

JohnyDays commented Feb 26, 2016

@skevy Yeah sure, it's done 馃憤

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Feb 26, 2016

Contributor

Added a quick demo showing it off, still got those ugly warnings tho 馃槩 it'd be cool if that was fixed before release.

Contributor

JohnyDays commented Feb 26, 2016

Added a quick demo showing it off, still got those ugly warnings tho 馃槩 it'd be cool if that was fixed before release.

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Feb 27, 2016

Contributor

Oops commented on the other thread by mistake. Copying post here.

Wow, this indeed will be helpful for a lot of people!.

I recently added support for redux stores and modules that export pure functions and modified this file (436db67). Most likely you'll have to rebase.

Contributor

martinbigio commented Feb 27, 2016

Oops commented on the other thread by mistake. Copying post here.

Wow, this indeed will be helpful for a lot of people!.

I recently added support for redux stores and modules that export pure functions and modified this file (436db67). Most likely you'll have to rebase.

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Feb 27, 2016

Contributor

Looks like you're getting the yellow box only on Android. No errors on the packager server, right?

Contributor

martinbigio commented Feb 27, 2016

Looks like you're getting the yellow box only on Android. No errors on the packager server, right?

let clients = [];
let clientsPerBundleEntry = {};
let platformCache = {

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

Some of the logic on the file would become simpler if we remove one level here (i.e.: concatenate the platform with the bundleEntry). Maybe we could turn client into a class and define a hash function based on the bundleEntry and the platform. After all you want a different cache for each client.

@martinbigio

martinbigio Feb 27, 2016

Contributor

Some of the logic on the file would become simpler if we remove one level here (i.e.: concatenate the platform with the bundleEntry). Maybe we could turn client into a class and define a hash function based on the bundleEntry and the platform. After all you want a different cache for each client.

This comment has been minimized.

@JohnyDays

JohnyDays Feb 27, 2016

Contributor

I thought about merging the bundle entry and the platform, but I was unsure of whether the platform was relevant for something beyond the bundle entry, in another part of the packager.

Also, do we really want a cache for each client? The client's only identifying features are the bundleEntry and it's platform, so if we have a cache for each of those, it should be enough right? And this way, for the most common case (Developing one application, on multiple devices) we are at most compiling 2 bundles, one for iOS, and one for android, since all the devices for the same platform share the same bundleEntry. And we still support multiple applications at the same time, since they have different bundleEntries, and as such different caches 馃憤

@JohnyDays

JohnyDays Feb 27, 2016

Contributor

I thought about merging the bundle entry and the platform, but I was unsure of whether the platform was relevant for something beyond the bundle entry, in another part of the packager.

Also, do we really want a cache for each client? The client's only identifying features are the bundleEntry and it's platform, so if we have a cache for each of those, it should be enough right? And this way, for the most common case (Developing one application, on multiple devices) we are at most compiling 2 bundles, one for iOS, and one for android, since all the devices for the same platform share the same bundleEntry. And we still support multiple applications at the same time, since they have different bundleEntries, and as such different caches 馃憤

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

I thought about merging the bundle entry and the platform, but I was unsure of whether the platform was relevant for something beyond the bundle entry, in another part of the packager

I was suggesting doing that only on this file which is pretty isolated from the rest of the packager.

Also, do we really want a cache for each client?

On your PR we're having different caches for different combinations of bundleEntry + platform, which represents different clients except rare case on which you have both the simulator and a device connected to the Packager. We could easily make the client class support multiple web socket connections to support this use case as well. I think introducing the client class will make the code simpler because:

  1. State (the cache) will be collocated with behavior (processFileChange) which will make this code a bit more object oriented.
  2. You'll have to deal with the cache for the client instead of with a cache for multiple clients.
  3. Sending updates will be per client so you won't have to maintain an object with the updates you have to send.
  4. Error handling per client will be easy to implement.
  5. All this code will become much easier to test in isolation.

What do you think? :)

@martinbigio

martinbigio Feb 27, 2016

Contributor

I thought about merging the bundle entry and the platform, but I was unsure of whether the platform was relevant for something beyond the bundle entry, in another part of the packager

I was suggesting doing that only on this file which is pretty isolated from the rest of the packager.

Also, do we really want a cache for each client?

On your PR we're having different caches for different combinations of bundleEntry + platform, which represents different clients except rare case on which you have both the simulator and a device connected to the Packager. We could easily make the client class support multiple web socket connections to support this use case as well. I think introducing the client class will make the code simpler because:

  1. State (the cache) will be collocated with behavior (processFileChange) which will make this code a bit more object oriented.
  2. You'll have to deal with the cache for the client instead of with a cache for multiple clients.
  3. Sending updates will be per client so you won't have to maintain an object with the updates you have to send.
  4. Error handling per client will be easy to implement.
  5. All this code will become much easier to test in isolation.

What do you think? :)

This comment has been minimized.

@JohnyDays

JohnyDays Feb 28, 2016

Contributor

except rare case on which you have both the simulator and a device connected to the Packager

I was imagining my own use case, which is testing on different screen sizes at the same time, by connecting an iPhone and an iPad for example, or an android phone and a tablet, or all of the above! 馃槃
This would effectively cut the processing time by half in that specific case.

On the other hand, I definitely agree with your approach, this should be isolated, which would lead to better error handling, and more maintainability.

I think the tradeoff in speed is more than acceptable, especially since my use case should be pretty rare, most people would only want to develop on android and iOS simultaneously, and this complexity wouldn't speed that up at all.

@JohnyDays

JohnyDays Feb 28, 2016

Contributor

except rare case on which you have both the simulator and a device connected to the Packager

I was imagining my own use case, which is testing on different screen sizes at the same time, by connecting an iPhone and an iPad for example, or an android phone and a tablet, or all of the above! 馃槃
This would effectively cut the processing time by half in that specific case.

On the other hand, I definitely agree with your approach, this should be isolated, which would lead to better error handling, and more maintainability.

I think the tradeoff in speed is more than acceptable, especially since my use case should be pretty rare, most people would only want to develop on android and iOS simultaneously, and this complexity wouldn't speed that up at all.

client.ws.on('close', () => disconnect(client));
clients.push(client);
clientsPerBundleEntry[client.bundleEntry] = [...(

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

we can simplify this code if we do what I suggested above :)

@martinbigio

martinbigio Feb 27, 2016

Contributor

we can simplify this code if we do what I suggested above :)

if (clients.length === 1) {
packagerServer.setHMRFileChangeListener(onHMRChange);
}

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

nit: remove

@martinbigio

martinbigio Feb 27, 2016

Contributor

nit: remove

}
function disconnect (disconnectedClient) {

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

nit: remove space between function name and parenthesis.

I've seen this on other places of the PR, do you mind removing them all :)?

@martinbigio

martinbigio Feb 27, 2016

Contributor

nit: remove space between function name and parenthesis.

I've seen this on other places of the PR, do you mind removing them all :)?

function disconnect (disconnectedClient) {
clients = clients.filter(client => client !== disconnectedClient);
clientsPerBundleEntry[disconnectedClient.bundleEntry] = clientsPerBundleEntry[disconnectedClient.bundleEntry].filter(client => client !== disconnectedClient);

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

this would become simpler as well

@martinbigio

martinbigio Feb 27, 2016

Contributor

this would become simpler as well

function disconnect (disconnectedClient) {
clients = clients.filter(client => client !== disconnectedClient);
clientsPerBundleEntry[disconnectedClient.bundleEntry] = clientsPerBundleEntry[disconnectedClient.bundleEntry].filter(client => client !== disconnectedClient);
//Only clear change listener if there are no more listening clients

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

nit: // only clear change listener if there are no more clients

@martinbigio

martinbigio Feb 27, 2016

Contributor

nit: // only clear change listener if there are no more clients

}
console.log(`[Hot Module Replacement] File change detected (${time()})`);
sendToClients({type: 'update-start'});

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

Looks like you forgot to tweak the arguments here

@martinbigio

martinbigio Feb 27, 2016

Contributor

Looks like you forgot to tweak the arguments here

}
//Check the depedencies for each bundle entry
return forEachBundleEntry((bundleEntry, platform) => {

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

I like how the code results when using this function, it makes iterating over the clients easy!. But couldn't you just iterate over the clients and use the platform and bundleEntry values stored on the objects?

@martinbigio

martinbigio Feb 27, 2016

Contributor

I like how the code results when using this function, it makes iterating over the clients easy!. But couldn't you just iterate over the clients and use the platform and bundleEntry values stored on the objects?

// if the file dependencies have change we need to invalidate the
// dependencies caches because the list of files we need to send
// to the client may have changed
const oldDependencies = platformCache[platform][bundleEntry].shallowDependencies[filename];

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

same comment as above here - would be simpler if this would be just an object instead of an object of objects

@martinbigio

martinbigio Feb 27, 2016

Contributor

same comment as above here - would be simpler if this would be just an object instead of an object of objects

};
}
return forEachBundleEntry((bundleEntry, platform) => ({type: 'error', body}));

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

Hmm not sure if it's correct to send the error to all the clients. What if you run into an adge case that affected only one of the clients?

@martinbigio

martinbigio Feb 27, 2016

Contributor

Hmm not sure if it's correct to send the error to all the clients. What if you run into an adge case that affected only one of the clients?

This comment has been minimized.

@JohnyDays

JohnyDays Feb 28, 2016

Contributor

Definitely, this should check which client the error belongs to, the reason it doesn't as is right now, is that Promise.all is an all or nothing schtick, if one of them errors, it just errors for all of them.
We can use something else, although with your proposed changes this wouldn't be necessary 馃槃

@JohnyDays

JohnyDays Feb 28, 2016

Contributor

Definitely, this should check which client the error belongs to, the reason it doesn't as is right now, is that Promise.all is an all or nothing schtick, if one of them errors, it just errors for all of them.
We can use something else, although with your proposed changes this wouldn't be necessary 馃槃

return forEachBundleEntry((bundleEntry, platform) => ({type: 'error', body}));
})
.then(update => {

This comment has been minimized.

@martinbigio

martinbigio Feb 27, 2016

Contributor

I think you want to do this then for each of the clients instead of waiting for all of the to compute their update. The resulting code would be simpler as you'll be able to handle each client individually. Error handling will become easier.

Moving forward: I think we need to model each client with a class and have a function that returns a promise to handle the file change, update the cache if needed, send the HMR update and do error handling. The cache should live inside of this class as it's not really shared across multiple clients. The only case where it could actually be reused is if you're testing something both on the simulator and on the device. On this case we should share the cache but I don't think it's that big of a deal not to do it for now. To add support for this the client class would have to have an array of web sockets to which it should send the updates. I say we separate the PR in 2: one to add support for multiple platforms/apps and other one to support this very special use case. What do you think?

@martinbigio

martinbigio Feb 27, 2016

Contributor

I think you want to do this then for each of the clients instead of waiting for all of the to compute their update. The resulting code would be simpler as you'll be able to handle each client individually. Error handling will become easier.

Moving forward: I think we need to model each client with a class and have a function that returns a promise to handle the file change, update the cache if needed, send the HMR update and do error handling. The cache should live inside of this class as it's not really shared across multiple clients. The only case where it could actually be reused is if you're testing something both on the simulator and on the device. On this case we should share the cache but I don't think it's that big of a deal not to do it for now. To add support for this the client class would have to have an array of web sockets to which it should send the updates. I say we separate the PR in 2: one to add support for multiple platforms/apps and other one to support this very special use case. What do you think?

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Feb 27, 2016

Contributor

The demo you posted is amazing man!

Contributor

martinbigio commented Feb 27, 2016

The demo you posted is amazing man!

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Feb 28, 2016

Contributor

Thanks @martinbigio for the code review! I think your approach makes sense, by isolating the process per client, and then distributing just the changed filename to each client, we can have more robust per-client error handling, simpler code and easier testing, for marginal performance loss in most cases.

We can also optimize it later using some tricks, though this isolated approach does make that harder.

Contributor

JohnyDays commented Feb 28, 2016

Thanks @martinbigio for the code review! I think your approach makes sense, by isolating the process per client, and then distributing just the changed filename to each client, we can have more robust per-client error handling, simpler code and easier testing, for marginal performance loss in most cases.

We can also optimize it later using some tricks, though this isolated approach does make that harder.

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Feb 28, 2016

Contributor

When do you guys think HMR will go out in a stable release? I'll try to get these changes done for that.

Contributor

JohnyDays commented Feb 28, 2016

When do you guys think HMR will go out in a stable release? I'll try to get these changes done for that.

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Feb 28, 2016

Contributor

We'll cut 0.22 mid/late this week. Would be awesome if we could include this PR :)

Contributor

martinbigio commented Feb 28, 2016

We'll cut 0.22 mid/late this week. Would be awesome if we could include this PR :)

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Feb 28, 2016

Contributor

@JohnyDays feel free to ping me on messenger if you want (facebook.com/martin.bigio). I tend to be way more responsive than on Github :)

Contributor

martinbigio commented Feb 28, 2016

@JohnyDays feel free to ping me on messenger if you want (facebook.com/martin.bigio). I tend to be way more responsive than on Github :)

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Mar 1, 2016

Contributor

Pretty busy week, I'll try to sneak this in. If it's not done in the meanwhile, we'll try to get it in 0.23.
@martinbigio sure, if I need some guidance I'll ask you there 馃槃

Contributor

JohnyDays commented Mar 1, 2016

Pretty busy week, I'll try to sneak this in. If it's not done in the meanwhile, we'll try to get it in 0.23.
@martinbigio sure, if I need some guidance I'll ask you there 馃槃

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Mar 21, 2016

Contributor

@JohnyDays any update on this? would be awesome releasing it on 0.24 :)

Contributor

martinbigio commented Mar 21, 2016

@JohnyDays any update on this? would be awesome releasing it on 0.24 :)

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Mar 21, 2016

Contributor

I took some time to work on it this weekend but I didn't have the opportunity to finish it.
I'll try to take some time this week, cheers 馃幐

Contributor

JohnyDays commented Mar 21, 2016

I took some time to work on it this weekend but I didn't have the opportunity to finish it.
I'll try to take some time this week, cheers 馃幐

@martinbigio

This comment has been minimized.

Show comment
Hide comment
@martinbigio

martinbigio Mar 21, 2016

Contributor

Awesome! FYI I'm writing a blog post about HMR and I think I'll mention this as one of the hot features we'll soon support :)

Contributor

martinbigio commented Mar 21, 2016

Awesome! FYI I'm writing a blog post about HMR and I think I'll mention this as one of the hot features we'll soon support :)

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Mar 22, 2016

Contributor

Cool! Having followed the community for a while, I'm glad I get to add another drop of awesome into the pool

Contributor

JohnyDays commented Mar 22, 2016

Cool! Having followed the community for a while, I'm glad I get to add another drop of awesome into the pool

if (valueOrPromise == null) {
return Promise.resolve(valueOrPromise)
}
else if (valueOrPromise.then && typeof valueOrPromise === "function") {

This comment has been minimized.

@solomonhawk

solomonhawk Mar 28, 2016

When duck-typing Promises, I'm used to seeing:

if ('then' in thing && typeof thing.then === 'function') { .. }

Should this be checking whether valueOrPromise.then is a function rather than valueOrPromise directly?

Apologies if I'm overlooking something here.

@solomonhawk

solomonhawk Mar 28, 2016

When duck-typing Promises, I'm used to seeing:

if ('then' in thing && typeof thing.then === 'function') { .. }

Should this be checking whether valueOrPromise.then is a function rather than valueOrPromise directly?

Apologies if I'm overlooking something here.

This comment has been minimized.

@JohnyDays

JohnyDays Mar 29, 2016

Contributor

Definitely! It was a mistake. In practice if the first one is true then it rarely matters but it could be an issue for some promise-likes. This code is being rewritten in a new style so I won't fix it right now.
Thanks though!

@JohnyDays

JohnyDays Mar 29, 2016

Contributor

Definitely! It was a mistake. In practice if the first one is true then it rarely matters but it could be an issue for some promise-likes. This code is being rewritten in a new style so I won't fix it right now.
Thanks though!

@ashleydw

This comment has been minimized.

Show comment
Hide comment
@ashleydw

ashleydw Mar 30, 2016

Great work @JohnyDays!

Looking forward to this landing. I've modified my run-ios command to allow me to run multiple ios simulators using the command format:

react-native run-ios --simulator "iPhone 6, iPad Retina"

Demo: https://drive.google.com/open?id=0BwTfufgK1OxfUW5vYzM0WEpaVFU

Once merged, I'll PR my updated run-ios file

Great work @JohnyDays!

Looking forward to this landing. I've modified my run-ios command to allow me to run multiple ios simulators using the command format:

react-native run-ios --simulator "iPhone 6, iPad Retina"

Demo: https://drive.google.com/open?id=0BwTfufgK1OxfUW5vYzM0WEpaVFU

Once merged, I'll PR my updated run-ios file

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Apr 11, 2016

@martinbigio would you mind taking a look at this pull request? It's been a while since the last commit was reviewed.

ghost commented Apr 11, 2016

@martinbigio would you mind taking a look at this pull request? It's been a while since the last commit was reviewed.

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays Apr 11, 2016

Contributor

Yeah I wanna take a look at it, work has been chaos but hopefully I get this done soon

Contributor

JohnyDays commented Apr 11, 2016

Yeah I wanna take a look at it, work has been chaos but hopefully I get this done soon

@ptomasroos

This comment has been minimized.

Show comment
Hide comment
@ptomasroos

ptomasroos Apr 12, 2016

Contributor

Great work! Looking forward to it!

Contributor

ptomasroos commented Apr 12, 2016

Great work! Looking forward to it!

if (platform && bundleEntry) {
clientsPerBundleEntry[bundleEntry].map(client => {
if (client.platform === platform) {
client.ws.send(JSON.stringify(message))

This comment has been minimized.

@cpunion

cpunion Apr 19, 2016

Contributor

I modified a similar feature for myself, sometimes send raises exceptions, so need catch it. Maybe Broken Pipe, I don't remember.

@cpunion

cpunion Apr 19, 2016

Contributor

I modified a similar feature for myself, sometimes send raises exceptions, so need catch it. Maybe Broken Pipe, I don't remember.

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays May 9, 2016

Contributor

I got around to doing this, should I open a new pull request? @martinbigio
The code is quite different, and I'm not sure how to change which branch the pull request is pointing to, is that even possible? Cheers

Contributor

JohnyDays commented May 9, 2016

I got around to doing this, should I open a new pull request? @martinbigio
The code is quite different, and I'm not sure how to change which branch the pull request is pointing to, is that even possible? Cheers

@JohnyDays

This comment has been minimized.

Show comment
Hide comment
@JohnyDays

JohnyDays May 9, 2016

Contributor

You can't change the branch so I created a new PR, check #7475

Contributor

JohnyDays commented May 9, 2016

You can't change the branch so I created a new PR, check #7475

@JohnyDays JohnyDays closed this May 9, 2016

@asteingass

This comment has been minimized.

Show comment
Hide comment
@asteingass

asteingass Mar 24, 2017

Hello everyone. It looks like this feature got implemented. How can I use it now? How can I do hot reload on 2 devices at the same time?
Thanks
Andreas

Hello everyone. It looks like this feature got implemented. How can I use it now? How can I do hot reload on 2 devices at the same time?
Thanks
Andreas

@dk0r

This comment has been minimized.

Show comment
Hide comment
@dk0r

dk0r Jul 21, 2017

@asteingass Would you please link where you see it was implemented?

dk0r commented Jul 21, 2017

@asteingass Would you please link where you see it was implemented?

@iBasit

This comment has been minimized.

Show comment
Hide comment
@iBasit

iBasit Dec 9, 2017

no links?

iBasit commented Dec 9, 2017

no links?

@asteingass

This comment has been minimized.

Show comment
Hide comment
@asteingass

asteingass Jan 8, 2018

Sorry guys, I just saw the PR #7475 and it's closed so I thought its done.

Sorry guys, I just saw the PR #7475 and it's closed so I thought its done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment