Skip to content

Instantly share code, notes, and snippets.

@lukechampine
Last active June 4, 2019 22:18
Show Gist options
  • Save lukechampine/6418449 to your computer and use it in GitHub Desktop.
Save lukechampine/6418449 to your computer and use it in GitHub Desktop.

how to into git (and GitHub)

This is a handy reference for setting up and using git to contribute to projects on GitHub. It is by no means a complete guide to using git; use http://gitref.org for that.

Update: This site provides some excellent visualizations of what various git commands do. Highly recommended.

basics

setup

First run the following commands, using your real name and GitHub email address:

$ git config --global user.name "Your Name"
$ git config --global user.email you@somedomain.com

By default, GitHub will use HTTPS for pushing commits. This requires you to provide your GitHub username and password every time you push a commit. You can make this slightly less laborious by having git remember your login information:

$ git config --global credential.helper cache
# By default, git will save your login info for 15 minutes.
# You can set it longer like so:
$ git config --global credential.helper "cache --timeout=[seconds]"

You may find it easier to use SSH. Follow the guide to generating SSH keys on your machine, and then be sure to edit the .git/config file to use git@github.com:username/repo.git instead of https://github.com/<username>/<repo>.git.

creating a new project

There are two ways to create a GitHub repo. One is to use git init to create the repo locally and then push it to GitHub. The other is to create the repo on GitHub and then clone it to a local repo. The latter version is recommended. From the GitHub homepage, click "New repository," add a name/description, and check the "Initialize this repository with a README" option. Then follow the instructions below for cloning.

cloning

To start working on a GitHub project, you must first clone the remote repository:

$ git clone https://github.com/<username>/<repo>
or, if you're using SSH:
$ git clone git@github.com:<username>/<repo>

You can now start modifying files and committing your changes.

pulling

To keep your local repository up to date, you'll have to pull any changes made to the GitHub repo:

$ git pull origin master       # pull from branch 'master' of 'origin'

It's important to keep in mind that a "pull" is actually just a fetch followed by a merge. That is, first you download the changes, then you merge them with your local copy. Merging can lead to merge conflicts, and is covered in more depth below.

Remember, the easiest way to avoid merge conflicts is by pulling before you start making changes!

Note: Fetching updates certain things that a simple pull does not, which is why some people advocate always using git fetch and git merge instead of just git pull.

committing and pushing

Changes to a git repository are stored in the form of commits. A commit is an atomic set of changes to a set of files. The basic git workflow is to modify a file, add it to the staging area, commit the changes represented in the staging area, and then push your changes to the remote repository.

$ git status                   # optional: see what files have been modified
$ git add <file>               # add a file to the staging area
$ git rm <file>                # remove a file from the project
$ git commit -m "<message>"    # commit your changes, along with a commit message
                                   # just "git commit" will drop you into a text editor
                                   # use this for writing lengthier commit messages
$ git push origin master       # push your changes to GitHub

PROTIP
$ git push -u origin master    # tell git to remember this location
$ git push                     # now it can be omitted

It is considered good form to write your commit messages in the imperative, i.e. "fix issue #3" instead of "fixed issue #3" or "fixes issue #3".

merging

Whenever you pull new changes, you must merge them with your local copy. In most cases, git is smart enough to do this automatically. Merge conflicts only arise when two people attempt to change the same lines in the same file. When this happens, git will spit out an ugly error message and you'll have to merge the changes manually.

If you have a merging tool installed (such as emerge, p4merge, etc.) then git mergetool will open it with the file in question. However, it's not too difficult to do it the old fashioned way. git automatically inserts merge conflict markers where merge conflicts are present. They look like this:

<<<<<<< HEAD
Here is your version
=======
Here is the conflicting version
>>>>>>> commit_name

To resolve the conflict, just replace this block with your desired text. It can be the head version, the conflicting version, or something else entirely. If you want, you can now run git diff and it will show you both versions and how you've chosen to resolved the conflict. Once you are satisfied with the resolution, run git add <file> and git commit to finish up.

contributing

If you do not have commit access to a repository, you can still contribute to it using pull requests. A pull request basically works like this: you create your own copy of the repo (a "fork"), make changes and commit them, and then request that your changes be merged into the master repo.

Forking is as easy as clicking the "Fork" button on a GitHub repo. Then you can clone your forked copy and start working on it. Before you begin, though, you need to add the original as a remote repository using git remote add upstream <url> so that you can stay up to date. Now you can pull any changes made to the upstream repo with git pull upstream master.

Another important step before you dive in is to create a new branch for each pull request. You want your master branch to match the remote repo's master branch; otherwise, any new branches you create won't match the remote repo's. To create a branch, run git branch <branch name>. Then you can switch to that branch with git checkout <branch name>. Since you're not on master anymore, you'll have to use git push origin <branch name> when pushing.

Now that you're on a new branch, you need a way to keep it up to date:

$ git stash                     # stash any uncommitted changes
$ git checkout master           # switch to master branch
$ git pull upstream master      # pull changes from original repo
$ git push origin master        # push changes to your fork
$ git checkout <branch name>    # switch back to your working branch
$ git rebase master             # rebase the master branch commits onto your current branch.
$ git stash pop                 # restore uncommitted changes

Rebasing may cause some merge conflicts, so read the section above on how to resolve those! Now you can pop any changes you stashed with git stash pop and get back to work.

When you're ready to submit your pull request, just push to your fork, browse to it on GitHub, and click the big "Compare and Review" button. Here you can review your commits and write a summary of the changes. If you spot an error, refer to the section below on how to amend your commits (it's safe to use push --force here).

Once your pull request has been merged (or rejected...), you can delete the branch:

$ git checkout master            # switch to master branch
$ git branch -d <branch name>    # delete local branch
$ git push origin :<branch name> # delete remote branch (--delete <branch name> also works)

fixing things

I modified a file and I want to discard my changes.

Use git checkout -- <file>.

I added a file to the staging area but then I changed my mind.

Use git reset HEAD <file>.

I messed a bunch of stuff up and I want to start over from the last commit.

If you simply want to undo the commit while preserving your changes, use git reset --soft HEAD^. If you want to discard your changes, use --hard. You can also use git reset <commit> to go back to specific commit.

I REALLY messed up and I want to start over using the copy on GitHub.

Use git fetch to ensure you're up to date, and then git reset --hard origin/master to revert to the master branch.

I had a bunch of uncommitted changes and then I used git reset and now all my changes are gone.

I only mention this one because I just did it myself. The solution is git reflog, which is a sort of safety net for git. It will display a list of your recent git actions, and you can use git reset HEAD@{n} to jump back to the nth action. Phew!

I want to purge a file from my commit history.

Check out this repo, it has all the tools you need.

I committed something, but then I spotted an error.

Fix the error, add the file, and then run git commit --amend. If you don't need to change the commit message, git commit --amend --no-edit saves you a trip to your editor and back.

What if I already pushed the commit?

When you amend, you change the commit hash, so GitHub will complain if you try to push your amendment. You must use git push --force, which will clobber the existing remote source tree with your local copy. Keep in mind that you are highly discouraged from doing this if you believe someone may have pulled your commit prior to the amendment. This would mean that they are now working on an outdated copy, and things will get ugly fast. In that case, just create a new commit that fixes the error.

What if I want to change an older commit, or multiple commits, or combine/split commits?

This is where we bust out the rebase utility. rebase allows you to edit git history in powerful (and sometimes dangerous) ways.

To start rebasing, choose a commit and run git rebase -i <commit>. You'll be dropped into a text editor with a file containing all the commits between <commit> and HEAD (the most recent commit). You'll also be shown a list of commands; in order to rebase, you must changed at least one instance of pick to a different command.

The rebasing process works like this: after saving and exiting the file, git will begin at the oldest commit and move forward until it hits a command other than pick. At this point, you (or it) will make any desired changes, and then you must run git rebase --continue to move on. The process stops when you reach HEAD. This means you can actually remove a commit entirely by deleting its line in the rebase list!

The most useful rebasing commands are edit and squash.

edit will allow you to modify the contents of the specified commit. You can modify, add, or remove files as usual, and then either amend the result or make a new commit (allowing you to split one large commit into multiple smaller commits).

squash will allow you to combine multiple commits into one. This is handy if you have a lot of small, inconsequential fixes that you want to combine, or if you're submitting a pull request and you want to make life easier for the project maintainer. All adjacent commits marked squash will be combined, and you'll have the opportunity to edit the commit message for the single resulting commit. As an alternative, you can use fixup, which is the same as squash but automatically uses the oldest commit message.

Once you finish rebasing, you'll have to use push --force, just like with amend. Note that all of the commit hashes between the specified commit and HEAD will be changed! That makes this a very dangerous command; it's like amend on steroids. Of course, it's fine to use commit --amend and rebase when you're working on a local branch; it's only dangerous if you've already pushed to GitHub.

Something broke and I don't know what commit caused it.

There's a neat tool for this called git bisect. Run git bisect start, then git bisect good <commit> and git bisect bad <commit>. git will now take you to the midpoint between the good and bad commits. Run your build process and see if the problem appears, then mark it either git bisect good or git bisect bad. git will continue jumping to the midpoint until it identifies the commit that introduced the bug.

Something broke and I want to slap the guy that broke it.

git blame <file> will tell you who's responsible for every line in a given file.

misc. tips and tricks

$ git config --global --add color.ui true    # add color to git output (status, diff, etc.)

$ git log                  # display a log of commits
$ git log --graph          # display a pretty commit graph

$ git diff <file>          # show changes made to a modified file

$ git add -p               # choose what sections of a file to add to a commit

$ git push local:remote    # specify what branch to push from

$ git checkout --track remote/branch    # create a new local branch from a remote branch

HEAD^                      # reference commit before HEAD
HEAD~n                     # reference to the hash n commits before HEAD (useful for rebasing)
                           # e.g. git rebase -i HEAD~5
@rafaelbrizola
Copy link

About the --ignore-date flag, isn't that the opposite? Using that flag during rebase will make that your commit and author dates to be the date of the rebase, thus not preserving the original AUTHOR date. At least that's what I understood from the documentation:

https://git-scm.com/docs/git-am

Also:
https://alexpeattie.com/blog/working-with-dates-in-git

@lukechampine
Copy link
Author

Yes, I think you are correct. Fixed

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