 Hi everyone, it's Jeff Chalem. So this is, I guess, the fourth part of our screencast series, first time in zero. And what we're going to do this time is look at Git. I'm going to try to keep this as short as possible. I don't really want to provide a lot of help with Git. We will certainly help you guys on office hours and other places. But the reason for that is that Git is so well-documented online at this point. And the tutorials that you can find there are going to be way better than anything that we could provide, certainly anything I can provide it in this sort of format. But the number one thing I want to encourage you to do is right here, read the output and error messages generated by Git commands. That is so important because Git produces amazingly useful output. I've had several times where students have come to office hours and they said, I've been struggling with Git the last couple of days. And here's the error message that it's giving me. And you look at the error message, and the error message tells them exactly what to do to solve the problem. And so don't waste your time like that. Read the output. It's always important to read the output from commands, especially when things go wrong. And Git will frequently read you and lead you in exactly the right direction. OK, so down here in the tool change section, I just want to do one of these exercises. And the one that I'll pick is one that showcases one really neat feature of Git, which is branches. So Git branches are designed to be extremely lightweight. And they're designed to be a way for you to organize your development. So a lot of people don't really realize when they get started with Git what branches are for. But branches are a great way to develop a feature, make sure it works, and then integrate that feature as a whole back into the main branch without having to, while still being able to save your work using commits, but without having to check and code into the master branch that may potentially not work. So let me show how to do that. OK, so I'm in my vagrant VM. I'm on the master branch. So the first thing I'm going to do is create a branch. Here's one syntax for doing that. And what we're going to do is we're going to create a branch where we have some code that breaks the boot. So you can see in the default prompt, I've got break group right here. So my prompt is actually even telling me what branch I'm on. Another way to tell what branch I'm on is to run Git branch. And at this point, I haven't made any changes with master. So that dip is empty. So that's pretty much what we would expect. OK, so now let's make a change to our kernel that's going to cause it to not boot. And that'll be kind of fun. The way I'm going to do this, it turns out that this is surprisingly kind of hard to do. So I'm in the boot function here. This is the initial boot sequence. And right here at the end of it, let me generate a null pointer exception. So I'm going to create an int j. And I'm going to set it to the result of the referencing a null pointer. Why does C allow you to do this? I don't know. This seems like it could never work out right. And then to silence the compiler warning about an unused variable, I will just write this. What, j? OK, I think that will work. Let's go to compile. And let's try to build. I messed up, of course. Press or can't code. I need to do is do this. Boi j. OK. OK, and then we run do make install. So now I've got a clearly rotten kernel that I've just installed. And let's say for the sake of it, that I've got to commit this change. So now let's just get to see what I've done. Here's the change I've made. I've added this, so I get as complaining about some white space here. That's something that you can fix or choose to ignore. And what I'm going to do now is commit this onto my branch. Let's break the boot. OK. Oh, whoops. OK. Got to do this quickly. Sorry, I should have run this before. Not hard to fix. Just wants to know who I am. All right, and now let's do this. OK. Now, get status is going to show us that our working directory is clean. And get if with master is going to show the change that we made. So now I have a committed change in this branch. And now let's say that at some point, like right now, I'm going to discover something about my kernel, which I would prefer now to know, which is that it doesn't boot, in fact, it panics. So here's the panic that's caused by the change that I just made. It will probably become familiar with this error message. What this means is that they're tried to dereference a virtual address to zero. And this will cause your kernel by default to enter a position where it's waiting for a debugger to connect to it. OK, so this is not good. If I ran it without causing it to wait for the debugger, it would just die. OK, boom, panic. I can't handle this. I think I'll just die now. All right, so now I've clearly and then maybe I've got to the point where I have some changes that don't work on that branch. In fact, I do. I have a kernel that doesn't boot. So the first thing let's do is let's go back to our known good version. So one of the awesome things about branches is it is super easy to switch between them. So I do get check out. And we'll actually allow me to tab complete this so I can check out break boot. I can check out master. And that tells me that it's up to date with staff master. And it changes since I've cloned this. OK, so now I'm on the master branch. Now, if I look at current main.c and go down into the boot function, you'll find that there is no terrible null pointer to reference right here. And if I go to current compile and change it to my compile directory, this will work just fine. So now I can install a working kernel and run it. Check it out. Cool. OK, so now let's say that I'm at the point that I'm on my master branch. And I've decided I don't like the changes I made in that one branch. I just like to sort of get rid of them entirely. So I can diff against break boot. I can see the changes. So what this means is that in order to get from my master branch to break boot, I would have to remove these lines from break boot. So now the great thing about good branch is that they are extremely lightweight. So I'm just going to get rid of this guy. I'm going to get std, break boot. Here we go. So now what good is going to tell me, here's an example of a really helpful good error message. So I tried to delete that branch. What good told me is that you've got changes in there that you haven't merged anywhere else, right? They're not merged into master. And so if you delete that branch, you're going to lose things forever. But that's OK, because I want to. And look what good told me to do. It says, if you are sure you want to do it, run this other command. So check it out. I don't even have to type anything. I'm just going to use my mouse, cut and paste. Boom, right? Now, sorry, good branch tells me I'm on master and there's no other branches, and that's awesome. So there's example number one. And that's what we basically accomplished this particular task. Now let's talk about a really common stumbling block for people would get, which is what happens when a merge fails. So one of the reasons to use good is that it's extremely good about merging changes to files. Let me show you how that works. So I'm actually, I'm going to exploit. I've got to set this up on the temporary here in my host machine. I'm going to exploit the fact that it can actually allow me to synchronize with a remote that's located on my local machine. So I've created two directories here. One is called Alex. And I will use keymux to create two windows. OK, so here's Alice. And Alice and Bob are sharing a remote. That remote is also located in my temp directory. And they're using that remote to share their changes with each other. It turns out the private and temporary temp wrote the same thing, so that's going to work out OK. All right, so Alice and Bob have one file in this directory, which is menu.c. I copied this over from the OS 161 sources. So it looks pretty familiar. And let's talk about a common occurrence, which is that Alice edits menu.c. So right now, these files are identical. You can look, you can see that Git shows that Alice and Bob are currently, their Git branches only have one commit and the commit is the same. So now let's say that Alice and Bob change menu.c. And let's say that those changes are changes that can be merged. So what I'm going to do here is I'm going to have Alice add a comment to the top of the file. It says, I'm Alice, all right? And then I'm going to have Bob add a comment to the bottom of the file that says, I'm Bob, OK? Now, if you were using a DOM system to try to synchronize these files, like, for example, Dropbox, this would be terrible because all Dropbox would know is, hey, these two files are different. I don't know what to do. So what would happen is it would depend on whoever edited the file last. Dropbox would save some other version of the file and things would be terrible, OK? But good is way smaller than that. So here's what's going to happen. Let's say that Bob commits his changes. I'm Bob. And I want to say so in the file. And Alice commits her changes. I'm Alice. I want to say so in the file. OK, so now I run Git diff and I can run Git status and I can show that my working directory is clean, but my branch is ahead of master. So what does that mean? It means that I've made a commit that I haven't pushed yet to the remote, right? And Alice's branch is going to say the same thing. And if I do Git log, what I'll see is that Bob added a commit with this commit ID and Alice added a commit with this commit ID, OK? So there are now two Alice and Bob's repos have diverged. They have divergent changes. And what we're going to do now is show you how Git can actually merge those together. So at some point, somebody, we'll just say Alice here who used to go get her, is going to push to the remote, OK? So she just did that. And what that means is that the remote now has her changes. Now, the remote does not have Bob's changes. So here's what's going to happen to Bob. Oh, no, rejected by the remote Bob. What did you do to deserve that? What he did is that Alice pushed first. So, and here's an example, again, of another extremely useful error message. What does it say? It says that rejected error failed to push some refs. That means that there were objects in your directory that were not pushed to the remote. And what does it mean? It means that updates were rejected because the remote contains work that you did not have. This is usually caused by another repository pushing to the same ref, which we just saw because Alice did it. You may want to first integrate the remote changes. Example, Git pull before pushing again. There we go. So now here's the thing. So Git pull is going to fetch the changes from the remote and try to merge them into Bob's working directory. So what's going to happen? Nah, check this out. So what Git was able to do was that it was able to automatically merge those. So I just used the default commit message. So let's see what happened. Auto-merging menu.c merged by the recursive strategy. So what do you think is in menu.c now on Bob's copy of menu.c? He's got Alice's comment at the top and his comment at the bottom. So Git was smart enough to actually be able to preserve both changes to that file. That's why we use Git. OK, now what's interesting here, let's look at the Git log again. So now what is the commit history? So here is the initial commit. This is Bob's commit. This is Alice's commit. And what the merge did is it added a new commit that merged this commit, which was from Bob, and this commit, which was from Alice. That's pretty awesome. OK, now here's the interesting thing. What does Alice's menu.c look like? Well, Alice has her comment, but she doesn't have Bob's comment. Because Bob's commit that was added by the merge hasn't been pushed to the remote yet. So let's do a Git status. It said, your branch is ahead of origin master by two commits. Before it would have said one, because that was the commit that Bob added, but now there's two. There's the commit that he added, and there's the commit that merges his with Alice's. So let's push to the remote. Now it says I'm up to date with origin master. All my roughs have been pushed. And now Alice can pull. And now she gets that commit. And she's got Bob's comment. So this is a great example of how Git does the right thing in the common case. When two people edit a file and their edits do not overlap, Git allows those edits to be merged correctly. OK, awesome. Now let's talk about the other common case, which let's say that there are overlapping edits to the file. So now let's say that Alice says I'm Alice, and I want to put a comment here. And let's say that Bob at the same time says that Bob also wants to be here. So now I've got an issue, because I've got a comment changed by Bob, and I've got a change by Alice. And those changes overlap. So what's going to happen is this is going to create a conflict. So let me show how that works. Alice checks in her changes. Bob checks in his changes. Neither changes, set of changes, has been pushed to the remote, so everything's OK. Now when Bob pushes his changes to the remote, what's going to happen? It succeeds, because he's the first person there. Now when Alice pushes her changes, what's going to happen? Oh, this time Alice gets rejected, because again, she needs to pull. Now here's where things get interesting. So last time when we pulled, Git was smart enough to automatically merge the changes. This time, not so much. So what you'll see here is Git has identified that there is a conflict. Now the problem is that a lot of times people think that this command has succeeded. It did not, OK? In fact, it returns something that indicates it. Yeah, it returned one to indicate that it didn't succeed, but there's this message here that's super important. Conflict, merge conflict to menu.c. The reason that this has happened is because Git needs your help. Git needs you to figure out what to do with these two commits. There are overlapping changes to the file, and a human has to sort this out. It turns out here that the human is Alice. If Alice had pushed first, the human would have been Bob. Whoever gets the conflict is the person who has to merge it. And what's happened is that Git has left you in a state where you are merging. So it says on branch master, you and origin master are diverged. You have one different commit, respectively, on merge paths. And this is what we're working on. When you're done, so remember before Git automatically created a commit that merged the two changes to the file because it could do that. Those changes didn't overlap. This time, what's going to happen is you're going to make those changes. When you're done, you're going to add the file, commit it, and that is going to mark the merge. So if I do get log, for example, what you'll see is that Alice checks in, and there's no merge commit because I haven't created one yet. So what do I need to do here? Unmerge pass. So I need to edit the files. Here is the helpful message that Git has created for me. What this means is that my head, the head of my repository, has this comment. And here is the content that came in from this commit that I just pulled from Bob's, from the remote that I'm sharing with Bob. So someone has to sort this out. Now the easiest way to do this is to manually edit the file and remove the merge conflicts. If you're looking for merge conflicts in the file, you can just look for something like that. So at this point, you've edited the file, and maybe you say, we want both these comments to be here. That's good. Everybody should be able to comment. So that's one way to do it. Let me show you another way to do it. Let's say that you know that you want your changes. Then what you can do is you can run Git checkout R with menu.c. When I run Git, and now if I open the menu, what's going to happen is all I have are my changes. So in this case, Alice has said, I don't care about Bob's changes. I just want mine. And that has removed the merge conflict marker. If I did Git checkout there with menu.c, I would accomplish. I think I can still do that. I would get Bob's comment. So this is a way to choose between if I want to choose one or the other. So let's say that Alice is nice and she wants Bob's comment. And so we're done. The problem is that when there's a manual merge that has to take place, you have to tell Git when you're done. So in this case, I know I'm done, and I know that menu.c is finished, and I've merged the two conflicts together. So what I'm going to do is do merge done. And what you'll see is that this has, let's see here. So here's a new commit. Now I'm going to push to the remote. So now I've pushed two commits to the remote. I run the Git log, and here we go. So I have to run on menu.c. Go over to Bob's repository. He pulls this, and now you can see what's happened here is that she's chosen. What happened here is because I chose to check out Bob's commit, Git didn't actually create a merge. If I had made changes to both files and started doing Git checkout there, Git would have actually created an merge tag. So you can verify that yourself by creating a version of the file that actually has both changes in it. OK, so now Alice has got her changes. Bob's already up to date because essentially Alice did this to discard her checkout and take Bob's. And now I'm in good shape. So this hopefully walks you through two of the common cases, the merge conflict and the merge that happens normally. And that's pretty much the most complicated thing you'll have to do with Git in this class. But please be careful. Git is an incredible tool. It's super powerful. Read the messages that it creates. We've certainly seen cases, for example, where people have checked in code to the repository that had Git conflict markers in it. And that code, as you would suspect, does not compile. And it certainly doesn't run. All right, the next screencast that we'll do, hopefully the shorter than this one, will be about debugging using GDP.