Advanced Tricks in the GIT Repository (film, 40 minutes)
Pauline, a software engineer, shared her insights on advanced Git techniques during her presentation. She began by discussing fundamental concepts, introducing the idea of atomic commits, which are essential for organizing and managing commits in Git. Atomic commits are single, irreducible units that make it easier to identify what has been changed and why. She showcased techniques like interactive rebase that allow reorganization of commits, which is particularly useful for maintaining order in programming projects. Interactive rebase enables editing previous commits, effectively aiding in project adjustments based on our requirements.
As Pauline guided the audience through more complex aspects of Git, she highlighted the concept of "time travel" within the Git context. She helped participants understand how easily they can navigate between branches and revert to earlier commits. Her presentation also covered the 'detached head state,' which allows users to introduce experimental changes without affecting the main branch history. She demonstrated how changes in this state are isolated, allowing for safe testing before making permanent modifications.
One of the most engaging parts of Pauline's presentation was her discussion about the reflog command, which logs all local actions taken in Git. This means that users can track even the most intricate changes. She taught attendees how to utilize reflog to recover lost commits or actions, making it an invaluable tool for those who may feel overwhelmed when exploring advanced Git functionalities. Thanks to reflog, one can safely go back and restore changes after accidentally deleting a commit, making version control more intuitive.
In the context of advanced features, she also discussed bisecting as a method for pinpointing issues in code via efficient commit splitting. This functionality allows for rapid identification of problematic commits that may introduce bugs into a project. As an example, she led participants through a bisecting process, teaching them how to automate the bug-fixing procedure using scripts and exit codes. Attendees were able to see the added value of the bisect technique and learned how to save time spent manually sifting through commits.
Finally, Pauline shared statistics from her talk: it garnered 2001 views and 23 likes at the time of writing this article, indicating significant interest among attendees. In summarizing Pauline's presentation, it was rich in knowledge, showcasing not only practical applications of Git but also empowering attendees to confidently navigate through its advanced aspects. In a fast-paced development environment, the ability to efficiently use Git has become essential for every developer.
Toggle timeline summary
-
Introduction and praise for the room and Git usage.
-
Pauline introduces herself as a software engineer and speaker.
-
She mentions this talk as a sequel to her first Git talk.
-
Introduction to advanced Git concepts and interactive participation.
-
Discussion on atomic commits and their importance.
-
Explanation of interactive rebasing for organizing commits.
-
Start of advanced content, explaining time travel with Git.
-
Introduction to reflog and its significance.
-
Introduction to bisecting for finding bugs in commits.
-
Automation of the bisect process with scripts.
-
Highlighting the importance of tests in the bisect process.
-
Demonstration using a test repository example.
-
Finalizing bug fixes and preparing for a pull request.
-
Conclusion and invitation for questions.
Transcription
Hello. Good morning. What a beautiful room. It's pretty, I think it's probably the prettiest room I've ever been in. Okay. So let's talk about some advanced Git magic. I'm glad to see so many hands up at the question who's using Git. It's not surprising, of course, but I'm glad everyone here is using Git pretty much and I'm glad we got a bit of a promo talk just now praising Git. So just to give you a little bit of an introduction, this is me. I'm Pauline. I'm a software engineer from ASM. I'm a freelance software engineer right now. I'm also a speaker, as you can see. Mostly about Git. So I have two Git talks and this is kind of the sequel to the first one. So it's a long shot, but if you've seen my first Git talk, you'll get a little bit of a recap of that and then we'll go into some more advanced stuff. That's my Twitter handle if you're into that. You can follow me, but be aware it's about 20 to 30% tech content probably. And I'm Juni's mom. Juni's very cute, as you can see. She's a bit older now, but you might see her walking around the hotel a little bit today and tomorrow. Okay. So let's move on to advanced Git magic, which is what I called this talk, but it really should have been called Git legit 2. Git legit is my first Git talk. Back to the feature. Because as I said, it's more of a sequel to the first Git talk and we're going to do some crazy time traveling stuff as well. So I do encourage you, if you can, to kind of follow along a little bit. We have very limited time for this talk, so I can't really wait for you to kind of follow along, but I think there's also going to be a recording, so hopefully you'll be able to pause and go back and things like that. If you do want to follow along, there's a repository there. You'll need PHP 7.4 or up, but it also works with Docker. So you can just follow along. This is for kind of the bisect portion that we're going to have at the end. Spoiler alert. So the foundation for all of this is going to be atomic commits. Who here knows about atomic commits, what they are? Okay. So a few hands. So atomic commits is basically the foundation of my first talk. I'm very convinced of the benefits of atomic commits. It's a way of organizing your commits, basically, that will really help you a lot in your day-to-day work. So this is just a little bit of a recap. But atomic commits are basically just regular commits, but you organize them in such a way that they're a single irreducible unit. So every commit will pertain to one fix or feature or whatever you're working on. So not, you know, a commit is not going to be, oh, I did this and then this and then this. You did one thing, and that's the commit. You make sure everything works, so you don't break the build. Tests are green, ideally. You'll see why in a bit. And it's clear and concise. So the commit message is very clear and concise, and the purpose is how I do that is that the purpose is visible from the subject line and the description. So the subject line, I usually mention what I've done really quickly, and then in the description, I'll kind of justify the changes. So that someone who's looking at the laws cannot just see what's been done, but also why those changes were needed. Okay. So those are atomic commits. And you might think how do I organize my commits atomically? Because a lot of people use Git kind of as like a save, like a chronological save point type thing, like when you're working on a Word document or something, and you save and then you type a bit more and you save chronologically, right? If you want to organize your commits atomically, you need to sometimes add more stuff to a certain commit, right? Like a change that still pertains to that commit. You might need to shuffle changes around a little bit, and something that is really core to keeping your commits atomic is interactive rebase. I know this is very like TLDR, because it's a recap. But interactive rebase, is there anyone here who uses interactive rebasing already? Okay. So quite a few more. So interactive rebasing is a bit like regular rebasing, except the interactive flag gives you a bit more control. So if you enter an interactive rebase, it's going to show up with a little swap file like this, and it will, if you rebase onto a branch, it will show the commits that it's going to replay at the top, and then you can, you see that pick that's in the prefix? You can change that pick prefix for something else, and you'll see a list of commands under there. You've got pick, which is just normal replay, don't do anything, just like with the regular rebase. Reword, or R, so you can put the whole word or just the letter. So reword is don't do anything with the commit, but change the commit message, and then edit gives you a lot of control, actually, because edit will check out the commits as if it's your head, and will let you add stuff to the commit, reword it, all of that stuff, and then just keep going over other commits. So that's really good if you want to organize your commits atomically, because you can basically rewrite history so that you can add things to earlier commits, you can adapt them and stuff like that. So if you want to interactively rebase onto your own branch, because I think a lot of people know rebasing from basing your branch onto another branch, right? But interactive rebase is especially powerful if you do it onto your own branch like this, so just get rebase, and then the interactive flag, and then head tilde4, and by that I mean the head is your current point in time, it's the end of your branch, and then tilde4, you're telling Git I want to go back four commits, so I want to replay my last four commits, basically, and I want to be able to reword them, edit them, all that stuff that you just saw. So let's take a look at what that looks like. So you'll see I have some unstaged changes here, I'm going to stash, so save for later, then I'm entering my interactive rebase, and I went back five commits, and I'll just say I want to edit this one, and then edit the fourth one, and then you just write and quit the file in whatever text editor is your default for Mead's Vim. I'm going to replay that a little bit, because I feel like it went a little bit too fast, so we're going to start with a stash, right? Save it for later, interactive rebase, five commits back. Then we're going to change that pick for an E for edit, so it will stop at the commits that we want to edit when we write and quit the file. I'm going to apply my stash to get my unstaged changes back, and now I can stage those changes and actually add them to the commit that I'm editing by going git rebase dash dash continue, and it will let me change the commit message if I want, and then when I write the swap file again, it will go to the next commit that I wanted to edit, so you'll see there it says a little four out of five, and then I can add some changes, do whatever, and do the same thing that I was doing before. It will open the commit message, and then you can change the commit message if you want, and then so it will just keep going through it sort of until it's out of commits, until it's back at your head, and then you've essentially rewritten the history, right? Okay. Is everyone with me still? It's going a little bit faster than I would normally do it if I had the time. Okay. Now on to the exciting more advanced stuff. So one thing you can do with Git is time travel. So, of course, you can travel between branches, right? Check out different branches, but you can also travel back in time to different commits. So if you would be following along, you would just go into any repository, really, and this is how you can basically go back in time to an earlier commit. So that head tilde 5, you probably remember that from the interactive rebase from just now, right? So if you Git checkout a branch, you're going to go to that branch. If you Git checkout head tilde 5, you're going to check out the fifth commit from your head. So you're going to go back in time five commits. Okay. Now, if you were to make some changes and commit them, and you Git checkout dash, this is a really nice one if you're not using this dash. It just means I want to check out the point at which I was just at. So that could be a branch or it could be a point in time like a commit. So in this case, we were just at the head, and then we went back five commits, and now if I Git checkout dash, I'm going to go back to my head. Okay. So I've made some changes in the past, committed them, back to my head, and now if I were to Git log, does anyone know what they notice? Okay. So if you now Git log, you would not see the commit that you made in the past, basically. For some reason, there's loads of builds in this slide. Okay. So why is that? I don't know if you recognize this bit of text. It's pretty confusing. I think this is one of those CLI outputs that, like, you'll just kind of glaze over for years and never really look at what it actually means. So it says you're in a detached head state, whatever that is. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. So basically what that says is you're in a detached head state. It means you're basically a fake kind of virtual head state. You're pretending that your head is somewhere it's not. And so in order to ensure that you're not going to screw up your branch or anything like that, any changes that you make in this fake head state are not permanent unless you make them permanent. And the way to make them permanent is to explicitly check out a new branch. So if I just check out my head or another branch, it's not going to keep my commit there. But if I say, okay, this state that I now have, this detached head state, is one that I want to keep, I can check out a new branch, and it will keep the exact same head state. So let's try that again. This time we're going to check out by commit ID. You can also check out by commit hash, which is kind of the longer version, right? So we're going to do the same. We're going to go back in time to a specific commit. We'll see that detached head state message. We're going to make some changes again and commit them, and then if you check out a fresh branch, any branch name, and you get log, you'll see that the last commit that you made is the top commit because that's your head state. Okay. Who's heard of reflog before? And who's worked with reflog before? Okay. So reflog is where it gets really interesting, I think, and really fun, and it looks a bit intimidating at first, so you saw the discrepancy between the people who had heard of reflog and who had worked with reflog, right? Well, this is what reflog looks like, and at first glance, it looks a little bit like I don't want to use that because it's a little bit confusing, but if you look closely, as with any CLI output, well, if it's well written, of course not just any CLI output, but with this CLI output, if you look closely, it actually makes a lot of sense. So what reflog is, you have your git log, right, that shows your commits, but reflog shows you all the steps that you've taken locally, sort of, so it's not your permanent history in as much as commit history is permanent, but it's not your permanent history, it's a garbage collected history of what you've done on your computer with your version control within this repository, so that's like checkouts, branch checkouts, resetting, rebasing, amending, that will all show up here, and that's for you only, and I don't know what the limit is if it's based on time or based on the amount of records, but it's garbage collected eventually, of course, but usually you're mostly interested in kind of what you've recently done, so you'll see here, I'll just help you kind of read this output, you'll see here at the top, you'll see head at 16, so that is a little bit similar to that head tilde, blah, blah, blah, right, so that is a reference to one of the actions you've taken as opposed to a commit, and then you'll see after that, you'll see rebase dash I, so you'll see it says you've done an interactive rebase, and it says you started the rebase, and so you have all these little actions here, you can even if you go to kind of the ref that starts with 960, kind of just under the middle of the screen, you'll see that I started a rebase there, and then it has all the steps, see, if you go up from there, it will go, okay, I started the rebase, I picked one commit, and then I finished the rebase, and then I started another rebase, and I immediately finished it, see what I mean, so it really logs all of your steps, and that is where it gets interesting, because you can time travel through this as well, not just your commits, so if you say you accidentally dropped something, you accidentally dropped a commit, and you weren't supposed to do that, you can travel back in time with ref log, so if you were to follow along, you would just go into your interactive rebase, drop a commit, and you can do this in any repository at any time, just try it out, because we're going to fix it, right, so git rebase dash I, just drop any commit, and if you go git log, you'll see that that commit is just completely gone, those changes are gone, now, if you do git ref log, you can find the point at which you started that rebase, and you want to go back to the state you had before you started that rebase, so then you check out the ref, so not the one that says start, but the one after that, because that's the step you had before you started the rebase, that's what you want to go back to, you check that out, and then you can check out a new branch to keep the changes, because remember, when you travel back in time, you're in a detached head state, so you need some way to keep those changes. Then you can check out your original branch, and reset your original branch hard to the branch you just created, so reset, you have a default soft mode, and you can also do it in hard mode, this is supposed to be dash dash, by the way, this font makes it weird, but this is supposed to be dash dash hard, and a soft reset will basically reset it, but keep kind of the changes between those two points, and your unstaged changes, so that they won't go away fully, but if you hard reset, you just completely override what you have with what you tell it to override it with, so in this case, you're going to override your branch completely with this new branch that you just made, because you went back in time to the point before you dropped the commit, checked out a new branch, and then you said, OK, take this whole branch, that's now my branch, and then you're back at completely the same state you had before you made the mistake, and you can do this with everything, you can do this with hard resets as well, so if you screw this up somehow, you can also go into your ref log and find the point in time before you did the reset and just go back. So it's really, really convenient, especially since I know a lot of people are a little bit hesitant about using Git for more complicated kind of operations, because it's kind of scary if you can just screw up your entire history, but you can unscrew your entire history very easily, actually, and I think if more people knew that, then more people would be a little bit more confident in doing more difficult stuff. It's really a lot simpler than you might think. Of course, you need to get used to the commands and stuff like that, but I promise you, if you try it a couple of times, it will be in your head, and it will become second nature. So there's also another much easier way to do this, by the way, with less steps, so let me just go back, so if you drop this commit, and you find the ref that you want to go back to, instead of going back in time and time traveling, you can also just Git reset dash dash hard and then that ref, because then you'll do the same thing, essentially, you'll just overwrite your current state completely with the state it was at that ref without all the extra steps of creating a new branch and all that stuff, so if you're feeling a little bit more brave, you can just reset hard immediately to the ref, and then it's basically just a control Z, or command Z, depending on your flavor. Okay. Next up is bisect. Who has worked with bisect before? Okay. A fair few people. So those of you who have worked with bisect, you might wonder why I'm using an egg to illustrate bisect. That's because I usually explain it through a bunch of eggs and a building, because let's say someone gives you a bunch of eggs and shows you a building and says figure out the first floor from which you can drop an egg and it breaks, realistically, of course, that would be the ground floor, but let's say these are very sturdy eggs and this could be a very tall building, you might start at the bottom, see that it doesn't break, then you try the next floor, doesn't break, next floor, doesn't break, doesn't break, and then eventually you'll find, so you iterate through the floors, right, until it breaks and then you have your answer, but it's pretty tiring to have to run up the steps and drop another egg and then run up the steps again, so what you could also do is you start in the middle, and you'll see that it breaks there, and then logically you'll know that between there and the ground floor is the first floor that the egg will break. So you go to the middle of that, and you'll see that it doesn't break, and you just keep doing that until you have reached kind of the absolute middle point where you know that's the first floor it will break. So this is going to give you a way to find a certain spot in much less iterations than if you were to do it kind of manually, and that is exactly what bisect does and you can do that with your code, so bisect means cut in half, and computer science people might know it as binary search as well, but bisect means cut in half, so you're going to cut this in half, this was good, this was bad, so that's going to give you a much more efficient way of finding something. So let's say you want to do this on your website, let's say you have like a page somewhere, and there was a button and it used to be here, and then you weren't looking for like a month, and then suddenly you notice that the button is here, and there's nothing that covers that change, that's very unexpected behavior. Then you might want to kind of like go through the logs and try to find a commit message that could have explained this change, or you know, you might manually check out every commit until you hit the commit where it did change. So the thing is, and bisect is really good for this, and you might not think you'll use it a lot, but actually once you get the hang of it, I've used this so much, and every time I thought I wouldn't have to use it, every time I thought I could just get away with going through my commit logs and figure out from the message what could have changed it, it took me so much more time than if I would have just bisected immediately. So this can be really, really useful once you get into it. So let's take that example from before, and let's see what that looks like. So we have a little commit history here, we're going to go git bisect start, that starts the bisect, and we're going to give it the last good commit that we know, so we know that the button was still here where it was supposed to be, and we're going to go git bisect bad, and by default it will take the head as the bad point, then it will check out the points in the middle of that, and you can check if the button is here or here, and based on if it's here, you say git bisect good, if it's here, you say git bisect bad, then bisect will check out the commit in the middle of those two points again, and you'll just keep saying, oh, this one's good, this one's bad, and you'll see as well it will say roughly one step, roughly zero steps left, it will kind of show you how many, and usually if you go over like a very large amount of commits, it will be something like five iterations or something like that, that's all you're going to need. And then at the end, it will just say, OK, this was the first bad commit, and so that's a really, really fast way of figuring out where a certain point in time is. But you might think, OK, but now I'm doing all this manually, can we automate this? And yes, yes, we can. We can feed bisect a script that will mark a commit good or bad based on the exit code, and that, as someone on the NTV Cribs might have said, is where the magic happens. So if you just feed bisect a script, and it exits with exit code zero for good, and anything non-zero will be marked as bad. So let's say I had, oh, Matthias, you might recognize this directory name, because I actually use your money repository for a lot of Git practice stuff. So let's say I knew that this calculator.php file was somewhere, and now it's gone, and I don't know where, and, yeah, it's very hard to kind of figure out which point in time someone deleted this file. So we're just going to write, like, a really simple script that goes, OK, if this file exists, then great, exit with exit code zero, mark it as good. If it doesn't, go nope, and exit with nope, which is a string which is non-zero, so it will mark it as bad. So we're going to start with the exact same thing as before, so we're just going to start our bisect, and we're going to give it the point in time, so sometimes you have to go back and check, like, wait, let me just start over. What I do is I start my bisect, usually, and to check before I start my bisect, to find a point in time that I know is good, I just go back randomly, like, 50, 60 commits, or however long I think it was when I knew it was OK, so I just get checkout, and then head tilde whatever, and then I check manually, OK, was it still good here, and if it was, then I just take that as my bisect kind of starting point that I can tell bisect, OK, this is the last known good point that I have. So we're going to go star bisect, and then we're going to give it the good commit, and we'll give it the bad commit, just as before, which will be head. You can also specify a commit, but usually you'll do head, and then instead of marking something good or bad, we're going to go git bisect run, and then give it a command, and it's just going to go through, and it will say yes, no, yes, no, yes, no, and just automatically check everything out and mark everything as good or bad, and then it will give us the first commit where this calculator.php file was deleted, OK. So can you think of anything else we could feed bisect? Yes, tests, because tests will pass or fail based on an exit code as well, so you can feed it a test, and it will do the same thing, so let's see what that looks like. Spoiler alert, it looks exactly the same except you give it a test. So usually you'll want to probably give it, like, one specific test if you already know kind of what test covers it, because if you, like, have to run the entire suite, then you'll have more of a chance that if you go back in time that the suite is broken, or you know what I mean, you might have to run compose or whatever on every iteration, so I usually try to keep the tests, the stuff that it runs pretty limited, so you'll do the exact same except give it a test, and then, ooh, that's pretty crucial. At the end of bisect, to get out of it, you go git bisect reset, and then you get out of your bisect state, OK. So this is one reason why the everything works, you know, ideally, is really handy to have. If you're going to bisect and you're going to check states in the past and in the future, well, not in the future, in the present and everything in between, and you're going to check that out, then it is nice that the build actually doesn't break, that you can actually run your application and things like that, otherwise it becomes really hard to check if something is actually good or bad or if it just looks bad because it's broken. So this everything works thing is your ideal state if you want to do bisect, but, again, even if not everything works, you can usually get away with it by just giving it one test instead of running, you know, having to boot up your entire thing, because that's where no magic happens, as one person in MTV Cribs has said in the past. OK. So question. What is your process kind of when someone reports a bug? Cry, ignore it, OK? I like these strategies, very efficient, deny, you go through the five stages of grief, OK, I think it's fairly common to say that usually someone reports a bug, and you look at the ticket and you go, oh, you go digging into the code, you go debugging mode, and eventually through a lot of sweat and tears, you have found what caused the problem, and then you fix it, and if you're a good little developer, you also make a regression test so it won't happen again, if you're a TDD good little developer, you'll even make the regression test first and then fix it, but what if I told you that someone could report the bug, you write the test, and then you fix it immediately, and you skip that entire kind of debugging mode hours of looking for the problem? So you can do that with Bisect as well, and this is, again, if you were to follow along at some point later with the recording, I don't think you'll have time now, that would be really impressive, but you can use this test repo, and it will have instructions on kind of how to go through all the steps, but I'll just show you now. So this kind of assumes that, of course, you have a test suite and 100% coverage and all that, but, you know, because we all have that, but at some point, there's always some unexpected behavior in production that wasn't covered, right? So in those cases, this is really good. If the tests are all green, or maybe if you don't have very much coverage, and some uncovered behavior ended up in prod and gave you grief. Hopefully this is readable enough. So this is the repo, it's just a tiny little application called Trogdor. Who remembers Trogdor? Anyone? Yay, my old people, my old internet people. Okay. So Trogdor is a man or a dragon man, and he goes around villages and burns them, because that's what dragons do. So that is a little CLI application that emulates that. So if we look at the commit history here, we'll see that the last commit is let it burn, and if we run the application, it will say, ah, my cottage is burning, and so everything works as expected, because that's what a dragon is supposed to be doing. So this is the good state. We know this now on the let it burn commit. Now if we were to check out a different branch on the repo, but let's this is like a pretend future state, okay? So let's say there have been some commits that have been made, and we'll see here this is the let it burn commit that we were just on, right? Some other commits have been made since. Now if we run it, it doesn't give us any output, so it's not doing what it's supposed to. So you'll see at the top there, the last commit is a commit called test, so I've just put that there for testing purposes, or for practice purposes, for you to kind of nose around and try this out, but what you could do if this test commit wasn't there is just write a little simple test, like obviously this is not the most robust of tests, but just for illustration's sake, we're just going to check if there's a burn a nation completed CLI output bit, and if there isn't, then the dragon's not supposed to be doing what it's supposed to be doing. So that's our test, but now our test, we've committed it, and it's at the end, it's at our head, right? So we have to kind of to run it through bisect, we have to move it to the past, because bisect is going to check out commits in the past as well, and if that test doesn't exist in the past, then there's no point. So in our head, we see that it fails, right? That's a regression test, and we're writing it for a bug, so now it fails. That's what we would expect. So now we're going to go into our interactive rebase and just take that test commit and put it in the point of time that we knew that we know it would pass, so that's by the let it burn commit. So you just reorder the history, so that test is there. So we see it's there now, and we can even check that it does actually work there by going back in time, we'll just run the test, and we'll go, okay, this passes now. So now we've got our good point and our bad point, right? So now we can go and start our bisect, and we can mark our good point as good, and our head as bad, and then when we give it the test, it will exist in the past, so no problem there, and it will just run and auto mark the commits as good or bad based on the test result. And then it will give us here, the first bad commit is, or maybe he was just a dragon. That's our first bad commit. So then we get bisect reset to get out of our bisect. And then we can go investigate, because now we know what commits caused the problem, and now we can just go and see what the difference is. So we can run get diff between these two commits, and if you run that with a dash W, it will give you also the code around the changes. Which is nice for context. So here we'll see that we're instantiating Chogdor now without his dragon man type, which means this check is going to fail, so we just kind of fix that by going back in time. Oh, by checking out a method name, apparently. So we're going back in time to the bad commit, and then we can just change the file. Here I'm doing it in Vim, but you can just open PhpStorm or whatever you're using. And just fix the bug, basically. So here we're just going to go is dragon, because that's the default type that Chogdor has. Okay. So then you can just start, I'm just going to skip ahead, because we're almost out of time. But you see the point, right? You found the issue, you've seen in the diff what causes the problem, then you can go back in time, fix the issue, commit it, and then because you're in a detached head state, if you want to keep the fix, you can check out a new branch, and you'll still have the fix there. So that's what we're doing now. We've done our bug fix. We've got our test there as well. When we commit our bug fix, we check out a new branch. I love how I've prerecorded demos and still have typos in it. What's the point? Okay. So now what we can do is to just make it all nice and atomic and prepare to make a little pull request, we take this test commit, and we're going to just smush it together with a fix, with squash. Squash is the interactive rebase command that will just squash two commits together, and then it will let you make a nice little commit message. And then you have a beautiful little tiny atomic commit with the fix and the regression test in one nice little neat package, and you can create a PR, and you're done. So that's it. So that's how you can really quickly find and fix uncovered, unexpected behavior in production with bisect instead of screwing around for hours. I hope this was useful. I see I'm just within my time limit. So I don't know if there's time for questions. I hope so. Or we don't do questions, do we? That's very gracious. So if anyone has a question, you have, like, ten seconds. There's one. Yeah, go ahead. I'm not familiar with .NET, but I'm assuming so, because I feel like it's probably fairly standard for unit tests and things like that to work with the exit code thing. So as long as it gives you an exit code for zero or one, you're good. So yeah. Yeah. Do try. Yeah. That's all we have time for. Thank you. Cheers. Cheers. Cheers. Cheers.