Tag Archives: git

Git Basics: branch cleanup

通常我们的项目都是创建分支开发来做功能迭代,随着项目的开发周期延长功能的不断迭代,git 远程库里会出现大量的分支。
理想状态下,当我们开发完一个功能后提交测试的时候,在处理 Merge Request 时,git merge 业务分支,同时勾选删除源开发分支 Delete source-branch,这样可以防止过多无用的 git 分支。这里记录一下如何手动清理分支:

Delete a branch both locally and remotely

To remove a local branch from your machine:

git branch -d <branch_name>

NOTE: If using -d (lowercase d), the branch will only be deleted if it has been merged. To force the delete to happen, you will need to use -D (uppercase D).

To remove a remote branch:

git push origin :<branch_name>

Thought : wasn’t a good indicator for [delete], As of Git 1.7.0, use this alternative syntax to delete remote branches:

git push origin --delete <branch_name>

[Tips]: if you want to complete both these steps with a single command, you can make an alias for it by adding the below to your ~/.gitconfig:

[alias]
  rmbranch = "!f(){ git branch -d ${1} && git push origin --delete ${1}; };f"

Alternatively, you can add this to your global config from the command line using

git config --global alias.rmbranch '!f(){ git branch -d ${1} && git push origin --delete ${1}; };f'

Sync local refs with remote repository

git remote prune <upstream>

will remove any remote refs you have locally that have been removed from your remote.

Also can fetch and prune with git fetch -p | --prune or git remote update [upstream] -p

The -p argument prunes deleted upstream branches. Thus, if the foo branch is deleted in the origin repository, git remote update -p will automatically delete your origin/foo ref.

Cleanup branches already merged

To delete local branches which have already been merged into master:

git checkout dev
git fetch -p # sync remote to local, also cleanup branches that have already been deleted
git branch --merged master |grep -v "\* master" |xargs -n 1 git branch -d

Note: For above command, if omit the master branch argument, we will get local branches which have already been merged into the current HEAD.

Now we need remove all remote branches have already been merged into dev:

git branch -r --merged origin/dev |grep -v '\*\|master\|dev' |sed 's#\s*origin/##' |xargs -n 1 echo

Make sure these branches are expected, And then remove from remote by replacing the echo with git push origin --delete

Another syntax to cleanup:

git push origin $(git branch -r --merged origin/master | sed "s/origin\\//:/" | egrep -v "HEAD|master|dev")

Note: Please make sure you known what these command to do before you execute it.

Now we can easily write a bash script (or ruby, or whatever) that goes through (maybe as a cron job) and deletes merged branches.

Reference Links

http://stevenharman.net/git-clean-delete-already-merged-branches

Git get back some commit from (no branch)

Sometimes you would find that you’re not on any branch when git branch, is described here. This usually happens when you’re using a submodule inside another project. Sometimes you’ll make some changes to this submodule, commit them and then try to push them up to a remote repository, for more details see here:

git branch
* (no branch)
  master

But,, when checkout master, you’ll lost the specific branch named (no branch)

git branch
* master

So, how can we got it back again?

As long as you’ve not done a git gc, then you’ve not lost anything. All you need to do is find it again :) What do you get with:

git reflog # (the commit-ish will be on the first line)

That should show you what happened, and the id of the missing node(s).

To get back to it, just do a git checkout <commit-ish> and your old pseudo branch is restored. also your can merge the specific <commit-ish> to your master.

git checkout master
git merge <commit-ish>

GitLab Installation on CentOS with nginx integration

Installation gitlab

S1. Follow the local installation guideline: https://about.gitlab.com/downloads/

wget https://downloads-packages.s3.amazonaws.com/centos-6.5/gitlab-7.1.1_omnibus-1.el6.x86_64.rpm
yum install openssh-server
yum install postfix # Select 'Internet Site', using sendmail instead also works, exim has problems
rpm -ivh gitlab-7.1.1_omnibus-1.el6.x86_64.rpm

S2. Initial basic gitlab config in /etc/gitlab/gitlab.rb

For troubleshooting and configuration options please see the Omnibus GitLab readme

# Change the external_url to the address your users will type in their browser
external_url 'http://git.iallex.com'
#git_data_dir '/home/git/git-data'

S3. Setup gitlab services configurations

gitlab-ctl reconfigure

That’s all if your server just for gitlab standalone.

You can login as an admin user with username root and password 5iveL!fe

Separation Nginx Server from gitlab Suite kit

Stop gitlab service first:

gitlab-ctl stop

Give nginx access to git group:

ensure your Nginx running with a specific user www in /etc/nginx/nginx.conf

usermod -a -G git www

Change some gitlab permissions:

# ensure gitlab-rails is owner by git group
chown git.git /var/opt/gitlab/gitlab-rails/ -R

# ensure `/var/opt/gitlab/gitlab-rails/tmp/sockets/gitlab.socket` and `uploads` accessable by nginx
chmod g+rwx /var/opt/gitlab/gitlab-rails/ -R

# link gitlab nginx config
ln -sf "/var/opt/gitlab/nginx/etc/gitlab-http.conf" /etc/nginx/conf.d/

# disable gitlab internal nginx service and symbolics link to global nginx config 
rm -f /opt/gitlab/service/nginx
ln -sf "/var/opt/gitlab/nginx/etc/gitlab-http.conf" /etc/nginx/conf.d/

# test permission
sudo -u www ls "/var/opt/gitlab/gitlab-rails/tmp/sockets/gitlab.socket"

Restart gitlab services and nginx

gitlab-ctl start
/etc/init.d/nginx restart

Enjoy! http://git.iallex.com

Related Links:

Git recover from git reset –hard

How to recover uncommitted changes to the working directory from a

git reset --hard HEAD?

You can try git fsck --lost-found to see if your changes still in lost-found:

$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
Checking objects: 100% (54/54), done.
dangling blob 15f9af8379f13672ca0e75d56df100edfd67fe6b
dangling commit 18fc9548f20eb8938dde68ab4a3dd0b7a0212dc3
dangling commit 33a832866e3855e300504ea6b584732e9c3c286c
dangling blob 568ca393d5e21cdc9eda2824111a5429a70d5113
dangling blob 89cdac4d3fc03546b5ab485aa8a9905b34702a4a
dangling blob abf03d6c84484a2b096a4d7f0ee5a85361f8a3d6 <- it's this one
dangling commit bc05be5eac21134b63ca51fbd20fee5c8782a640
dangling commit c0fa59cfaa0bad5f8ca8a1a845ba1673bb207b2d
dangling commit d140d6f693d8ef83d040d483bec3db95db084cd9
dangling blob e9c3eb31aa0589ab59f46630f7926681f7a14476  <- it's this one

Then you will get a dangling blob by git show

git show e9c3eb31aa0589ab59f46630f7926681f7a14476

will give you the file content back of reset.

To find unreferenced commits I found a tip somewhere suggesting this.

gitk --all $(git log -g --pretty=format:%h)

I found them in the other directory within the <path to repo>/.git/lost-found/. From there, I can see the uncommitted files, copy out the blobs, and rename them.

Note: This only works if you added the files you want to save to the index (using git add .). If the files weren’t in the index, they are lost.

Git daily tips

Retrieve a single file from specific revision in Git

git show somebranch:path/to/your/file

We can also do multiple files and have them concatenated: git show branchA~10:fileA branchB^^:fileB

NOTE:

If you want to get the file in the local directory (revert just one file) you can checkout: git checkout somebranch^^^ -- path/to/file

Remove local (untracked) files from my current Git branch

git-clean

git clean -f -d

If needed to remove untracked files from particular subdirectory:

git clean -f {dir_path}

And combined way to delete untracked dir/files and ignored files:

git clean -fxd {dir_path}

Git status give the output in an easy-to-parse format for scripts.

git status --porcelain

Pull with rebase instead of merge

$ git pull --rebase

# e.g. if on branch "master": performs a `git fetch origin`,
# then `git rebase origin/master`

When across merge commits, we’re get a [merge commits] with a message reading something like Merge branch 'master' of 'origin/master'.So we can avoid the unnecessary micro-merges on regular git pull by --rebase options.

Rebasing ensures that the commits are always re-applied so that the history stays linear. git will move your local commit aside, synchronise with the remote and then try to apply your commits from the new state.

You can configure certain branches to always do this without the --rebase flag:

# make `git pull` on master always use rebase
$ git config branch.master.rebase true

You can also set up a global option to set the last property for every new tracked branch:

# setup rebase for every tracking branch
$ git config --global branch.autosetuprebase always

You can configure all of pull with rebase option: git config --global pull.rebase true, and use git pull --no-rebase to disable this feature.

I usually use a fetch/rebase combination so my current (local) work stays at the top:

git fetch
git rebase origin/release-1.0.0

Git get specific branch head version.

git ls-remote --heads git@git.n.xiaomi.com:yp-develweb/devel-web-home.git release-1.0.0 | awk '{print $1}' | cut -c1-10

NOTE:

if in git shell context, can use these commonds:

last_commit=$(git rev-parse --short HEAD)
last_commit=$(git log --pretty=format:'%h' -n 1)

Stashing changes in a dirty working directory away

Stashing is a great way to pause what you’re currently working on and come back to it later.

read from Stashing your changes
Normally, we can use different local branches git branch xxx for jobs. But this may causes lot of unexpected logs in commits.
Use git stash when you want to record the current state of the working directory and the index, but want to go back to a clean working directory. The command saves your local modifications away and reverts the working directory to match the HEAD commit.

For some issues, we need git rebase to HEAD and fix some bugs:

git stash -u save current state, with git clean, leaving the working directory in a very clean state.

do some bugfixes and commit …

git stash apply Like pop, but do not remove the state from the stash list. to restore previous jobs states.

As of git 1.7.7, git stash accepts the –include-untracked option (or short-hand -u). To include untracked files in your stash, use either of the following commands:

git stash --include-untracked
git stash -u

Tips: Force git stash to overwrite added files

Use git checkout instead of git stash apply:

git checkout stash -- .
git commit

This will restore all the files to their stashed version.

If there are changes to other files in the working directory that should be kept, here is a less heavy-handed alternative:

git merge --squash --strategy-option=theirs stash

Note: for more details about stash, please view docs: git stash

Git Tags

# Create a new tag from your current HEAD (i.e. the HEAD of your current branch)
git tag <TAGNAME>

git tag <TAGNAME> <COMMIT> you can even specify which commit to use for creating the tag.
Regardless, a tag is still simply a “pointer” to a certain commit (not a branch).

Rename a Git tag

# build an alias of the old tag name:
git tag new_tag_name old_tag_name

# Then you need to delete the old one locally:
git tag -d old_tag_name

# delete the tag on you remote location(s)
# can be simplified to `git push origin :old_tag_name`
git push origin :refs/tags/old_tag_name

# add your new tag to the remote location
git push origin --tags

Other useful commands

Bypassing the git hooks by -n, like git commit -n [...]

-n, –no-verify
This option bypasses the pre-commit and commit-msg hooks.

# rebase these commit since dd61ab32 from HEAD
git rebase -i dd61ab32^
# Deleting the last commit
git push mathnet +dd61ab32^:master

Where git interprets x^ as the parent of x and + as a forced non-fastforward push.
This command same as:

# do it in two simpler steps: First reset the branch to the parent of the current commit, then force-push it to the remote.
git reset HEAD^ --hard
git push mathnet -f

Using pre-push git hook to runs unit tests on every push

read from git pre-push
Below is an example pre-push script that let’s us specify a branch to ‘protect’ so that our tests will only run if there are commits to push and we are on ‘master’. Also because pre-push will execute regardless of if there are commits to push or not, the script ensures we don’t fire off a lengthy test command, only to find out we actually didn’t need to.

#!/bin/bash 

CMD="ls -l" # Command that runs your tests
protected_branch='master'

# Check if we actually have commits to push
commits=`git log @{u}..`
if [ -z "$commits" ]; then
    exit 0
fi

current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')

if [[ $current_branch = $protected_branch ]]; then
    $CMD
    RESULT=$?
    if [ $RESULT -ne 0 ]; then 
        echo "failed $CMD"
        exit 1
    fi
fi
exit 0

For more about git hooks we can read from the manual of githooks.

More git hooks articles:

Git Basics: diff commit and merge branches

GIT DIFF

Use git diff show the difference between commits.

git co master
git diff 0da94be..59ff30c > /tmp/my.patch
# Checkout new branch: (this will not reset your work)
git co branches-dev git apply /tmp/my.patch

Diff a single file by different commits:

git diff 5bb72a8 HEAD -- twentytwelve/style.cs

Compare files from two different branches:

# You can do this: `git diff branch1:file branch2:file`
git diff master:twentytwelve/style.css branche-dev:twentytwelve/style.css

Git – Create a branch with current changes

Just use:

git checkout -b topic/newbranch

Any uncommitted work will be taken along to the new branch.


GIT Merge

Merge some (not all) files from one Git branch to another.

As it turns out, we’re trying too hard. Our good friend git checkout is the right tool for the job.

git checkout <branch> <paths>...

We can simply give git checkout the name of the feature branch and the paths to the specific files that we want to add to our master branch.

# merge a single commit from another branch
git cherry-pick <commit_sha1>

# merge without auto commit
git merge v1.0 --no-commit --no-ff

Syncing a fork

Merge the changes from upstream/master into your local master branch. This brings your fork’s master branch into sync with the upstream repository, without losing your local changes.
We can configure git to sync your fork with the original repository git remote add upstream <ORIGINAL_REPOSITORY.git>

git merge upstream/master
# Updating a422352..5fdff0f
# Fast-forward
#  README                    |    9 -------
#  README.md                 |    7 ++++++
#  2 files changed, 7 insertions(+), 9 deletions(-)
#  delete mode 100644 README
#  create mode 100644 README.md

Git merge “accept theirs” or “accept mine” options

git checkout --ours -- <filename>
git checkout --theirs -- <filename>

Resolving the conflict

Just modify the files conflicting. You would need to call git add to mark the conflict as resolved:

$ git add file_with_resolved_conflicts

This will also add the file to the index. After you repeat the same process for other conflicting files, you can safely commit your changes using git commit command:

git commit -m 'Merged with branch src-branch and resolved the conflicts.'

ref: http://softwarecave.org/2014/03/03/git-how-to-resolve-merge-conflicts/


Helper commands

  • use git difftool to manally merge files: git config merge.tool vimdiff
  • undo current changess: git reset --hard origin/master

Reference Links

Adding And Removing Remote Branches – Git Branch

git squashing commits with rebase

git rebase – Forward-port local commits to the updated upstream head

We can use git rebase --interactive (or -i) mode to squash multiple snapit commit.

git rebase -i HEAD~2
pick b76d157 b
pick a931ac7 c

Changing b’s pick to squash will result in the error you saw, but if instead you squash c into b by changing the text to:

pick b76d157 b
s a931ac7 c

For more details reference:

Git submodule

add a submodule

git submodule add [-b <branch>] [-f|--force] [--name <name>] [--] <repository> [<path>]

example:

git submodule add -b master --name wp-theme-twentytwelve git@git.iallex.com:iallex-com/wp-theme-twentytwelve.git wp-content/themes/twentytwelve

remove a submodule

Via the page Git Submodule Tutorial:

To remove a submodule you need to:

  1. Delete the relevant section from the .gitmodules file.
  2. Stage the .gitmodules changes git add .gitmodules
  3. Delete the relevant section from .git/config.
  4. Run git rm --cached path_to_submodule (no trailing slash).
  5. Run rm -rf .git/modules/path_to_submodule
  6. Commit git commit -m "Removed submodule <name>"
  7. Delete the now untracked submodule files rm -rf path_to_submodule

Also can use this script git-delete-submodule

#!/bin/sh

submodule=$1

test -z $submodule && echo "submodule required" 1>&2 && exit 1
test ! -f .gitmodules && echo ".gitmodules file not found" 1>&2 && exit 2

NAME=$(echo $submodule | sed 's/\/$//g')
test -z $(git config --file=.gitmodules submodule.$NAME.url) && echo "submodule not found" 1>&2 && exit 3

git config --remove-section submodule.$NAME
git config --file=.gitmodules --remove-section submodule.$NAME
git rm --cached $NAME

Re-edit an commit message in Git

git commit --amend -m "your new message"

To amend previous commit make the changes you want and stage those changes, and then use

git commit --amend

to amend previous commit, and keep the same log message use

git commit --amend -C HEAD

to fix the previous commit by removing it entirely use

git reset --hard HEAD^

If you want to edit more than one commit message use

git rebase -i HEAD~COMMIT_COUNT

Do not forget to replace COMMIT_COUNT with number of commits that you want to edit.

This command launches editor, mark the first commit (the one that you want to change) as “edit” instead of “pick”, then save and exit your editor.

make the change you want to commit then:

git commit --amend
git rebase --continue

git commit --amend is the way to overwrite the last commit. One note: if you would like to also overwrite the files, the command would be

git commit -a --amend -m "My new commit message"