SE2030
Git Setup
## Cheat-sheets * [Git Cheat-sheet from Atlassian](https://www.atlassian.com/git/tutorials/atlassian-git-cheatsheet) ## Install Git * [Install Git](https://git-scm.com/download/win) <a name="ssh"></a> ## Git Bash Setup ### Git SSH Keys for Automatic Login Follow these instructions if you do not want to type your username and password every time you push to git. * Make a public/private key pair (Acknowldegement: These instructions based on https://confluence.atlassian.com/bitbucket/set-up-an-ssh-key-728138079.html) * Create the key by running the command ```ssh-keygen``` * Accept the default location by pressing enter. * Specify no password by pressing enter again twice. * Copy the key. * Print it to the command prompt with ```cat ~/.ssh/id_rsa.pub``` * Then copy it by selecting it, right-clicking, and selecting copy (Control-C means something different in a command prompt -- it means "kill the current program") * Go to your user account settings at github.com, go to the section "SSH and GPG keys," create a new key, and paste the public key into it. Give it a name like "CS3841 VM" * Now, github will automatically offer you the ssh-based git pull command. * If you have not cloned the project yet, go to the project page, select "Clone" and "Use SSH" if it is not already showing the SSH URL:<br>![Clone or download](vmsetup_res/clone.png)<br>Copy the SSH URL:<br>![git@github.com:msoe-schilling/...](vmsetup_res/clone2.png) * If you already pulled it with https, get the new url and reset the url with a command something like ```git remote set-url origin git@github.com:username/repositoryname.git``` (Following https://confluence.atlassian.com/bitbucket/change-the-remote-url-to-your-repository-794212774.html) <a name="fx"></a> ## IntelliJ Setup To create a new IntelliJ project and check it into a blank Git project: 1. Clone the empty repository with ```git clone ______.git``` where the URL comes from the github page's "Clone this repository" link. I recommend getting the SSH link rather than the HTTP (once you set up SSH keys as above) 1. Create a .gitignore file containing ``` .idea/workspace.xml out/ ``` 1. Check this in like any other file. 1. Create an empty IntelliJ JavaFX project however you usually do it. Confirm the main window opens. 1. Copy the contents of your IntelliJ project into the contents of your empty git repo. Your .idea folder should show up side-by side with the .git folder if you have done this correctly. Ask me for help if it doesn't. (From the command prompt, use ```ls -a``` to show folders starting with a dot (.) 1. Run ```git status```. It should show your intelliJ files in red. 1. Open this folder as an IntelliJ project. While opening it, it should recognize your top level git folder as an IntelliJ Project. If it does not, do not open the project. Ask for my help at that point. (If you open the project, it will create another IntelliJ project, which is undesirable here!) 1. Confirm that the window still pops up when you run it from your git project. 1. Run ```git status``` again. (I'm kind of compulsive about this -- it's hard to undo stuff if you make a mistake in Git) 1. Run ```git add .``` to add ALL the files that showed in red in the previous command. 1. Run ```git commit -m "Fresh IntelliJ JavaFX Project"``` to commit to your local repository 1. Run ```git push``` to push the files up to the server Now, a second teammate should test the repository: 1. Run ```git clone ____.git``` as above (Or ```git pull``` from within your git directory if you've already cloned it). This pulls down the full project from remote to your local repository, local index, and local working directory. 1. Confirm the .idea folder and src/...fxml file are present. 1. Open this folder as an IntelliJ project. While opening it, it should recognize your top level git folder as an IntelliJ Project. If it does not, do not open the project. Ask for my help at that point. (If you open the project, it will create another IntelliJ project, which is undesirable here!) 1. Confirm that the window still pops up when you run it from your git project. Congratulations! You now have a complete starting repository! I find this [tutorial on Git](https://githowto.com/checking_status) to walk through the sorts of things I would like to teach about Git. This link skips past the setup page, since we do that together in class. ## Fancy git log This command shows a graphical history similar to what you would see on GitHub. ``` git log --graph --decorate --pretty=oneline --abbrev-commit ``` <a name="xkcd"></a> ## Cleaning up messes in Git I strongly recommend avoiding messes in the first place: * ```git status``` before ```git add .``` and ```git commit -m "..."``` * Frequent commits to the shared branch when possible to reduce messy merges * Separate branches when development will take more two commits or when intermediate results will break the current dev branch in some way. (Non-compiling commits MUST be off of the master and dev branches and SHOULD contain a clear message that they don't compile.) In many cases, the quickest way to clean up a mess is to use the ["xkcd approach"](https://xkcd.com/1597/). This can work even if you want to keep your changes: * Re-clone the project in a new folder besides the old project (```cd``` out of your git folder. ```ls``` to confirm you can see your folder, but aren't inside it. Then clone as you did at first, but specifying a destination folder: ```git clone _______.git projectN```. I recommend updating N every time you have to do this, and deleting the old copies once you are sure there is nothing you need in them.) * Copy your changes to the re-cloned project. (E.g., if you were in project4, copy your changes to project5) * Pull, confirm it compiles, then add, commit, and push your changes to the server as usual. It's a fail-safe approach! (Then, once you are sure it worked, remember to delete your old directory so you don't get confused about which one to use!) There is one case where this does not work well. That is if you have attempted a pull, and the merge that comes as part of the pull fails. If there have been many new changes merged in from the remote branch, you may find it hard to extract the code you want to save. In that case, you may want to roll back to your last commit. ***Make sure there are no untracked files in your old worspace that you care about*** and [do a reset](https://stackoverflow.com/questions/1223354/undo-git-pull-how-to-bring-repos-to-old-state) in your old project using the command ```git reset --merge``` or ```git reset --hard```. This will undo the failed merge and go back to the version of your code before you did the pull. See the section on <a href="#wipe">wiping your working directory and returning to the latest commit</a> for more information. <mark>I have found there are some subtleties here. I have a flowchart that I wish to typset and place here</mark> So I recommend the xkcd approach if you ever have a mess. But, if you still want to practice your [git kung-fu](https://devrant.com/rants/855450/a-poem-for-git-in-git), here we go: <a name="mergetool"></a> ### The ```git mergetool``` ### When you do a ```git pull``` or ```git merge ...```, git will attempt to automatically merge the changes on your local branch with either the merges from the remote server or whatever branch you specify (respectively). If their are overlapping changes between the two branches, the automatic merge will fail. Git will not create the new commit. It will instead wait for you to manually edit the files to resolve the conflicts, add the files, and commit them as usual. However, git **does** store several versions of the file for which the merge failed: The latest versions from the two branches you are merging (e.g. your local branch and the branch on the server), the original version that both branches are derived from, and the automatically merged version that is in your working directory. (The other three files are in the index somewhere.) One of the most convenient ways to use these files is to use ```git mergetool``` to view them all at the same time. When you run ```git mergetool```, it opens up a window with all four files: The latest commit on the local bracnch on the left, the common anscestor in the middle, and the latest commit on the other branch on the right. Underneath all of these, it shows the merged version that is in the workspace. This can help you to see where the changes come from that show up in the merged version. **However,** you need to use an editor that is designed to perform a merge like this. The default editor is ```vimdiff```, based on the ```vim``` editor. You will need to know a few keybindings to use this editor: Pressing Control-w and then an arrow key will move you between the windows in this editor. The arrow keys (or, if you prefer, j, k, l, and h) will move the cursor around the screen. dd deletes a whole line. Pressing i takes you into *insert* mode where you can type text or delete existing text. Pressing the escape key takes you out of editing mode. When you are done editing the file in ```vimdiff``` within the ```git mergetool``` command, you can press :xa and then enter to save and close all files, or :qa! and then enter to close all files without saving. Once you do either of these, the ```git mergetool``` is complete. You cannot start it again without undoing the merge. (If you do want to undo the merge, simply use ```git reset --merge```. But you might also have to do ```git status``` and the use ```rm somefile.orig``` to delete any files that you edited) **As an alternative to the command-line ```git mergetool```, you can use IntelliJ's built-in merge conflict resolution tool. To do this, identify the file that has merge conflicts in IntelliJ. It should show up in red. Right-click just about anywhere and select "Git-&gt;Resolve Conflicts...```. This opens the _Files Merged with Conflicts_ window. Double-click on the file whose conflicts you wish to resolve. You will see a window that looks like this: <center><a href="git_res/intellijMerge1.png"><img width="400px" src="git_res/intellijMerge1.png"></img></a></center> Changes in red are the conflicting changes. To automatically resolve the non-conflicting changes, select the All button <img src="git_res/intellijMergeAll.png" /> at the top of the window. When you are done, press "Apply" to save your changes, or "Abort" to close the window without saving them. Github also released 'Github desktop' which has a nice merge conflict resolution tool builtin. However, we prefer that for everything except merges, you learn to do the git management from the command prompt. ### Cleaning up a git mess: General notes ### For general guides to undoing stuff in Git, see [1] and [5]. Throughout this section, I use the term "working directory" where most poeple would say "working tree" [6]. Both terms mean the same thing in this context: All the folders and their subfolders in my git project, excluding the .git folder itself which is ignored by git. I distinguish "working directory" (by which I mean the whole working tree) from "current folder and all its subfolders" (which does not include folders above the current folder in the working directory) Note that IntelliJ also tracks local history, so you can sometimes recover lost files through that even when you have completely removed them from .git (I don't recommend relying on this behavior!) <a name="wipe"></a> ### Wipe directory and return to latest commit. If you want to discard EVERYTHING that you have changed since the last commit, use these commands. You can also use these commands if you want to undo a messy ```git merge``` or ```git pull```, since ```merge``` and ```pull``` do not create a new commit if the automatic merge fails. These commands wipe out ALL changes since the last commit. You must run them commands from within the topmost folder of your project because the ```git clean``` command only works from the current directory and its sub-directories. [4] ``` git status # Check to see what we are about to discard. git reset --hard git clean -dfxn # Check what the next command will delete before running it. git clean -dfx ``` Original source: [2b] Notes on the flags used with ```git clean```: ``` # Add -n to check if you want to remove those files, e.g. git clean -ndfx # -f: force: You MUST use this option to get ANY files deleted by this command # (It's required for safety) # -d: delete directories; # -x: delete even .gitignored files (which are, of course, not backed up by git) ``` If you would like to do the equivalent of the <a href="#xkcd">xkcd command</a> (without saving anything in your local repository!) see, the section on <a href="#exhaustive">an exhaustive wipe</a> below. ### Individual files ### In this section, we undo a git procedure one file at a time. ``` git reset <filename> # Undoes git add <filename> (Take file out of index without changing # the contents of the file in the working directory) # Note that git reset CAN edit much more than one file if you use different arguments such # as git reset . or git reset --hard HEAD git checkout -- <filename> # Undoes edits to a file that is not yet staged for commit. # Note that the -- line helps to distinguish the filename from other git words like origin or master, # just in case the file is called that. See what happens if you type git checkout -- and hit # tab: It tells you the special names in this context. rm <filename> # Removes a file that has never been staged for commit. Such a file is not in the index, # so you can't check it out with git checkout -- <filename>. Here we are using a basic shell command # to delete the file instead of a git command since git doesn't know anything about the file. ``` ### All files ### In this section, we undo a git procedure for the entire working directory. To do this, you must run these commands from the topmost folder within your .git project. Some of the commands (e.g., clean) only work from the current directory and its sub-directories. [4] ``` git reset # Undoes git add for all files in the entire working directory git checkout -- . # Undoes edits to all files in the current folder and its subfolders (but not any folders above it in the working directory.) git reset --hard HEAD # Undoes both edits and add. git clean -dfx # removes all working directory files that are UNTRACKED in the current folder and its subfolders. These are files that are in the working directory, but are not checked in and have not been added to the index yet. Note that -x means to also remove ignored files! ``` To undo ```git commit -m "..."```, please go to the next section. ### Whole commits ### All the commands we have looked at so far deal with resetting the working directory and the index. But what if you have already committed the files? This is the best command for "rolling back" to a previous version when things have already been pushed to master: git revert 0737a # Create a new commit that reverses the effect of previous commits to bring you back to an older commit. This command creates a new commit that undoes all the changes, restoring the state of the selected commit. The random numbers 0727a are the first few digits of the whole commit hash (e.g, 0737abe3131037765d4806fd6ec5f6353d755c28). You only need to type enough to make the hash unique. <!-- The following commands should only be used on your own machine. Once a commit has been pushed to the remote server (e.g., origin/master), it should stay there. Other members of the team might have checked that commit out, and removing it could corrupt your remote Git repository! git reset # Remove all files from index without modifying changes git reset --soft 0737a # "check out" an old version, but keep working directory and index files unchanged. git reset --soft HEAD~ # Undo one commit git reset --mixed 07 # Undo several commits. The intermediate commits are lost from the history. Only use this locally! <a name="rewind"></a> ### How do I blow away the mess in my current repository without doing a clone? See the introduction in the previous section for the most straight-forward approach. But if you want to stick with the current directory, here's how to do it. Our strategy is to rewind our local repository by a few commits, and then fast-forward it to master. [1][2]. First see where you are: ``` $ git status On branch master Your branch and 'origin/master' have diverged, and have 1 and 8 different commits each, respectively. (use "git pull" to merge the remote branch into yours) ``` Next, move back the number of commits you have made since you last aligned with master. In my case, this is one commit: ``` $ git reset HEAD~ Unstaged changes after reset: M README.txt ``` <div class="notetip"> I put in one tilde because I'm behind HEAD by one commit. I could also go back two commits: <br> ``` $ git reset HEAD~2 ``` <br> Or simply clear out my index and working directory by staying at my HEAD: <br> ``` $ git reset HEAD ``` </div> Once you have gone backwards, simply go forwards with a pull: ``` git pull ``` ## Overwrite one branch with another Sometimes you fix something on one branch and you just want to FORCE MERGE it into another branch. In Git, the way to do this is to [actually merge the other branch into yourself](https://stackoverflow.com/a/4624383/1048186), but keeping your files everywhere. ``` git checkout goodbranch git merge -s ours branchtooverwrite # check history carefully with git log and git diff!!! ``` This doesn't always work. In one situation where goodbranch is purely downstream from branchtooverwrite, it simply gives the message "Already up-to-date." --> <a name="exhaustive"></a> #### Exhaustive reset to origin/master script The two commands in the section on <a href="#wipe">wiping your working directory and going back to the latest commit</a> will restore things back to your latest local commit. This script does the equivalent of cloning the repository freshly from the remote repository. But be warned: Unlike the <a href="#xkcd">xkcd approach</a>, this will delete all your changes in the local working directory, and may even discard local commits! ``` git status # Check to see what we are about to DISCARD git reset --hard HEAD # wipe out working directory and index (except untracked files) git clean -ndfx # Check to see what we are about to delete. git clean -dfx # Delete untracked files and directories in the working directory (and perhaps tracked ones as well) # Or git clean -dffx # -ff makes the force more forceful (removes untracked directories that appear to be managed by a different git repository [3]) git checkout master # Move to the master branch git fetch origin master # Pull from master without touching index or working directory git reset --hard origin/master # Replace your current branch with origin/master. This MAY discard local commmits! git pull # One final pull from master (maybe just to make sure we're up-to-date a few commands later?) git status # See how things look. Hopefully clean now! ``` Original source: [2] Note: This script assumes that you are not using any sub-modules. A sub-module is a git repository checked out inside of another git repository. We are not using them this quarter. But if you are curious, see the full script [2]. ## Further Reading [1] https://gist.github.com/hofmannsven/6814451 [2] https://stackoverflow.com/a/4327720/1048186 [2b] https://stackoverflow.com/a/31321921/1048186 [3] https://git-scm.com/docs/git-clean [3b] https://git-scm.com/docs/git-reset [4] https://stackoverflow.com/a/20838589/1048186 [5] https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things [6] https://backlog.com/git-tutorial/git-workflow/ ## Acknowledgements Thanks to Stephen Linn for the fancy git log command