RSS Feed Subscribe to RSS Feed

 

Git Notes

These are some git notes I’ve put together over the years. I refer back to these frequently, but there are many better sources for Git docs out there, so use at your own risk!

If you do choose to use, any feedback welcome.

 

Introduction

Git is a distributed version control system.

A version control system allows you to records changes to a set of files over time without overwriting any older parts. This all allows you to

  • Easily track what changes were made by who and when
  • Revert to a specific historical version

It is actually very difficult to completely lose a piece of work with git. The main idea behind a version control system is to safely store copies of a project that you can always navigate through.

 

The distributed part of git means that there is no central server. Instead every Git working directory is a full-fledged repository with full history version-tracking capabilities.

 

While git helps you track your own changes and work completely independently it also helps enormously with collaboration, allowing you to view and integrate with other developers’ code changes. To this end, it can be useful to make your repository available to others, to allow easier collaboration with other team members etc. You can host your repository on a server and provide access using a number of protocols including ssh, http(s), or the Git protocol itself. However, hosting sites like Github make this whole process much easier, and can provide tools above and beyond what git does, such as forking, pull requests, wikis and issue tracking. More on Git vs Github later.

 

Clone a repo

The “git clone” command clones a repository into a new directory on your local machine. For example:

git clone https://github.com/sabram/SpringMVC.git

More specifically, git clone:

  • Clones the remote repository into a newly created directory on your local machine

By default, the directory name will be the same as the repository name. In this case, main.

This repository is a complete copy (clone) of the remote repository, including every version of every file.

  • Creates a remote-tracking branch called origin/master

Actually, a remote tracking branch is created for every branch in the cloned repository (visible using git branch -r).

The default branch is typically origin/master, but is configurable.

Despite the name, a remote-tracking branch lives on your local machine.

  • Creates a tracking branch called master

Again, this lives on your local machine.

Note that “master” is the default name for a branch in git, but the default branch name can in fact be anything.

Note that the origin/master remote tracking branch is read only for you. It is updated only from the remote repository. It can be updated explicitly by doing git fetch (or git pull, which is a git fetch followed by a git merge). It is updated implicitly by doing a git push, which first updates the remote repository, and then updates the remote tracking branch from the remote repository.

If you are confused about local branches, local tracking branches, remote branches and remote tracking branches (which let’s face, it very confusing), there is a good Stackoverflow answer on it here: http://stackoverflow.com/a/24785777/185942

Forking

This page on Github is the best place I have found for instructions on how create to fork, and this one for synching a fork.

Branching

Create a branch

  • git branch feature/JIRA-Number
  • git checkout feature/JIRA-Number

Or use the shorthand:

  • git checkout -b feature/JIRA-Number

Upstream branch

aka tracking branch

To see what the upstream branch is:

git branch -vv

If you created the branch, rather than checking out an existing one, the upstream branch will likely not be set. Git will suggest a way to set it if you do a git push e.g.

$ git push
fatal: The current branch javadoc_improvements has no upstream branch.
To push the current branch and set the remote as upstream, use

git push –set-upstream origin javadoc_improvements

But you can also set without doing a push:

git branch –set-upstream-to=origin/your_branch

This can be useful for checking what your local commits are via a command like

git log @{u}..

where @{u} is a shortcut for @{upstream}

See more at http://stackoverflow.com/a/6389348/185942

Is a branch merged?

How can I know which branches are already merged into the master branch?

  • git branch –merged master

lists branches merged into master

  • git branch –merged

lists branches merged into HEAD (i.e. tip of current branch)

  • git branch –no-merged

lists branches that have not been merged

By default these commands apply only to local branches. The -a flag will show both local and remote branches, and the -r flag shows only the remote branches.

From http://stackoverflow.com/questions/226976/how-can-i-know-in-git-if-a-branch-has-been-already-merged-into-master

Misc branch commands

Differences between two branches:

git diff --name-status master..branch

e.g. git diff –name-status development..feature/JIRA-123

Delete branch

git branch -d feature/FTR (deletes local branch) (-D deletes a branch irrespective of its merged status)

git push origin :feature/FTR (deletes remote branch)

Rename unpushed branch

git branch -m <oldname> <newname>

If you want to rename the current branch, you can simply do:

git branch -m <newname>

What is Head?

You can think of HEAD as a reference to the current commit on the current (i.e. currently checked out) branch. i.e. a reference to the commit on top of which “git commit” would make a new one. It is not always the latest commit, but usually is.

You can see what HEAD points to by doing:

cat .git/HEAD

HEAD~ is shortcut for HEAD~1 and refers to the commit’s first parent. HEAD~2 means the commit’s first parent’s first parent i.e. the commit’s grandparent. In short, I think ~ is short for ancestor, and picks the first ancestor at the given level if there is more than one.

HEAD^ is shorthand for HEAD^1 and means the commit’s first parent. HEAD^2 means the commit’s second parent (remember, commits can have two parents when they are a merge). In short, ^ is short for parent.

~ = Ancestor

^ = Parent

references:

 

Updating a feature branch

You are on dev, and you create a feature branch off it.

At some point, other developers push commits to develop. How do you get those commits while on your feature branch?

One slightly long winded option is

git checkout develop
git pull
git checkout feature/JIRA-123
git merge develop //or rebase

Another, and my preferred, option is (again while on feature/JIRA-123)

git fetch origin //gives you all commits from all branches in the named remote (origin in this case)
git merge origin/develop

Another option is (again while on feature/JIRA-123)

git pull origin develop //git pull remotename branchname

 

On a side note, for git fetch, when no remote is specified, by default the origin remote will be used, unless there’s an upstream branch configured for the current branch.

Merging

From: http://stackoverflow.com/questions/822811/showing-which-files-have-changed-between-git-branches

 

Also keep in mind that git has cheap and easy branching. If I think a merge could be problematic I create a branch for the merge. So if master has the changes I want to merge in and ba is my branch that needs the code from master I might do the following:

 

git checkout ba
git checkout -b ba-merge
git merge master
.... review new code and fix conflicts....
git commit
git checkout ba
git merge ba-merge
git branch -d ba-merge
git merge master

Hotfixes

See also: hotfix section of GIT 101 for Developers

Cherry picking

Applies to your current branch the changes introduced by a commit on another branch.

e.g. if A12345 is a commit on feature/Jira-1

then

git checkout development

git cherry-pick A12345

will take the changes made in that commit and apply them to development, with a new commit hash.

 

 

Undoing

 

(see also http://git-scm.com/book/en/Git-Basics-Undoing-Things)

 

Undoing unpushed changes: git reset

 

If you have not yet pushed the changes you wish to undo, you can use get reset.

 

Git Command Effect Notes
git reset –soft HEAD^ Undo last commit Undoes the commit, and keeps the changes locally, as if you had already staged (‘git add’ed) them
git reset –mixed HEAD^ Undo last commit AND unstage files Undoes the commit, and keeps the changes locally, but unstaged (so you need to run git add on them again before you commit)

The “–mixed: is the default here, so “git reset HEAD^” has the same effect

You can also use this to unstage a not-yet-commited file.

git reset –hard HEAD^ Undo last commit AND unstage files AND undo changes in working directory Use with caution! This is the only variation with which you may actually lose work!

 

Undo a git merge: git reset –merge ORIG_HEAD (see here)

 

Notes

 

  • You can replace HEAD^ with any variation of HEAD you desire, for example “git reset –hard HEAD~2” to reset the HEAD by 2 commits

 

If you have the commit hash, then you can try this command : git reset –hard <commithash>

 

References:

 

Undoing already pushed changes: git revert

 

If you have already pushed the changed you wish to undo, you can use git revert. Reverting a commit means to create a new commit that undoes all changes that were made in the bad commit. The bad commit remains there, but it no longer affects the the current master and any future commits on top of it. You are not rewriting history, just reverting and fixing

 

Let’s say your log (commit history) currently looks something like this:

 


commit 3582df7
Author: sabram 
Date: Wed Oct 15 11:24:56 2014 -0700
commit2 - changed file2 only

commit be4e89c
Author: sabram 
Date: Wed Oct 15 11:23:57 2014 -0700
commit1 - changed file 1 only

 

and you run this command

  • git revert be4e89c –i.e. revert commit1

You will revert the changes in commit1 ONLY. commit2 and hence the changes made to file2, will be left unchanged.

 

If you want to revert commit1 and commit2, you can either do:

  • git revert be4e89c 3582df7 –i.e. revert commit1 AND commit 2

or

  • git revert HEAD~2..HEAD –using range to revert last 2 commits

 

References:

 

Rewriting history

Changing commit message

 

To change the most recent commit message only:

  • git commit –amend -m “New commit message”

To change anything else:

  • git rebase -i HEAD~X

where X is the number of commit messages you need to change PLUS ONE
In the editor that pops up, change the word pick to reword for each commit message you want to change (leave the commit message as is). Save, and you will then be able to change each commit message.
See more at: http://www.jacopretorius.net/2013/05/amend-multiple-commit-messages-with-git.html

 

Combining multiple commits

 

Combining unpushed changes: git rebase –interactive

 

If you have multiple commits, but have not yet pushed, you can merge some of the unpushed commits using ‘git rebase -i’ (aka ‘git rebase –interactive’)

 

Examples

 

If master has this commit history

 

  • 57b6963 initial commit

 

and you do

 

  • git checkout -b branch

 

and make 4 commits, so that its commit history looks like this:

 

  • e3117a7 commit4
  • d94f900 commit3
  • c6e2eee commit2
  • b273ae4 commit1
  • a7b6963 initial commit

 

The following commands would all have the same effect, and allow you to squash some combination of the 4 commits you made since branching:

 

  • git rebase -i
    • If you get: “There is no tracking information for the current branch. Please specify which branch you want to rebase against.” then you must specify the branch explicitly.
  • git rebase -i origin/development (or master, or whatever you original branch was called)
  • git rebase -i a7b6963
  • git rebase -i HEAD~4 (see http://git-scm.com/book/en/Git-Tools-Rewriting-History)

 

 

In each case, the rebase command will:

 

  • first, brings up your text editor where you can choose which commits to squash
  • then brings up another editor where enter the commit message for the combined commits

 

Note that the rebase editor shows commits in chronological order i.e. earliest at the top, where as git log shows in reverse-chronological order i.e. latest at the top. This can be confusing!

 

When finished, confirm how your git log now looks, and you can them merge back to the master branch.

 

Example 1: combine commit1 through commit4

 

in the first editor specify:

 

  • pick b273ae4 commit1
  • squash c6e2eee commit2
  • squash d94f900 commit3
  • squash e3117a7 commit4

 

and specify a new commit message of ‘commit1..4’, would leave the branch’s “git log” looking like this:

 

  • b273ae4 – commit1..4
  • a7b6963 – initial commit

 

Example 2: combine commit2 and commit3

 

in the first editor specify:

 

  • pick b273ae4 commit1
  • pick c6e2eee commit2
  • squash d94f900 commit3
  • pick e3117a7 commit4

 

and specify a new commit message of ‘commit2..3’, would leave the branch’s “git log” looking like this:

 

  • e3117a7 – commit4
  • d94f900 – commit2 and 3
  • b273ae4 – commit1
  • a7b6963 – initial commit

 

Example 3: combine commit1 through commit3

 

in the first editor specify:

 

  • pick b273ae4 commit1
  • squash c6e2eee commit2
  • squash d94f900 commit3
  • pick e3117a7 commit4

 

and specify a new commit message of ‘commit1..3’, would leave the branch’s “git log” looking like this:

 

  • e3117a7 – commit4
  • d94f900 – commit1..3
  • a7b6963 – initial commit

 

 

Notes:

 

  • You can abbreviate squash with s
  • There are other commands in addition to pick and squash, including reword (just change the commit msg), fixup (commit msg of the ‘pick’ed commit used) and edit (which I don’t fully understand, but seems to pause the commit to allow you to make changes such as other commits). See https://help.github.com/articles/about-git-rebase/

 

See also:

 

 

 

Combining changes as part of a merge: git merge –squash

 

If you want to merge multiple commits from one branch into a single commit to a new branch, you can use git merge –squash <feature branch> then finally do a git commit.

 

  • git checkout master
  • git merge –squash WIP

 

(at this point everything is merged, possibly conflicted, but not committed)

 

  • git add .
  • git commit -m “Merged feature branch”

 

As discussed here however, the big caveat to squash is that branch will not be seen as merged. Instead the squashed commits basically show up as a brand new commit. See more here.

 

A consequence of this is that if you later inadvertently do a regular merge from branch to master, it will not recognise that a ‘squash’ merge was previously done, and you will end up with not only the squash commit, but all the original commits too.

 

e.g. create branch, do a commit1, then a commit2, do a ‘git merge –squash branch’ while on master and git commit -m “commit1 and commit2”.

 

Now if you do a regular merge from branch to master, it’s log would look like:

 

  • Merge branch ‘branch’
  • commit1 and commit2
  • commit2
  • commit1

 

Very likely not what you were going for. So after doing a squash merge, you almost need to delete the branch to avoid this happening.

 

 

 

Combining pushed changes (don’t do this)

 

Don’t do this. This has an explanation on why. Again, don’t.

 

You have been warned but if you are sure no one has pulled your changes, and you’re prepared to live dangerously…

 

  • git rebase -i origin/master (or whatever your remote branch name is)
  • //do your rebasing/squashing
  • git push –force

 

Git Ignore

After making changes to your .gitignore, you may notice that those changes are not taking effect for existing files.

To fix this, first commit any outstanding code changes to make sure you don’t lose any work, then run this command:

  • git rm -r –cached

This removes any changed files from the index (staging area). You can also run in a more selective manner e.g. specifying only the files you just tried to exclude in .gitignore e.g. git rm -r –cached *.config

Now when you run add and commit commands like below, the ignored files will really by ignored, as per .gitignore.

 

  • git add .
  • git commit -m “.gitignore is now working”

See more at http://stackoverflow.com/questions/1139762/ignore-files-that-have-already-been-committed-to-a-git-repository

 Git vs Github

 

 

Git Github
command-line tool provides a web-based graphical interface
features like forking, pull requests, issues

 

 

Misc

http://stackoverflow.com/questions/5283829/why-do-i-have-to-push-the-changes-i-just-pulled-from-origin-in-git

Missing commits

In some circumstances, git log may not show all commits.

From the man pages for “git-log”, the default mode:

Simplifies the history to the simplest history explaining the final state of the tree. Simplest because it prunes some side branches if the end result is the
same (i.e. merging branches with the same content)

The ‘full-history’ mode does not prune any history.

For example, if a merge (perhaps mistakenly) removed some changes, git log may not show those commits since the before and after are basically the same. Git is smart enough to figure out that the net result is zero and so not bother you with the details. However, if that merge was indeed incorrect, seeing the details of those commits is essential. Hence, use:

git log –full-history

Tags:

Leave a Reply