Git Tips

Posted by Bryan on August 22, 2017

git working directory/index/stage/master

The index is also called stage, git add will update index contents. The index actually stores only the file hash IDs. The file data, the actual contents of each file, are kept in Git’s objects, inside the repository (master).
:rocket: What is the index anyway?

  1. the index mainly serves as a way for Git to stage “the next commit” one piece at a time, so that git commit itself is very fast.
  2. Git can switch to that commit (use git checkout dev, git checkout master) on that branch by comparing that commit’s files to those in the current index. It only needs to change out files that are different.
  3. When using git checkout -b dev, git will take index contents to another branch dev. So make sure the index is clean.

在github新建分支安全操作

Doing carefully is better than fixing after errors happen. Another saying is that discretion is the better part of valor.

git checkout -b <branch_name>  新建分支并将当前分支带到新建分支,git branch <branch_name>和git checkout <branch_name>
git add -A
git commit -m "×××××"
git push
git push --set-upstream origin <branch_name>   push到新建分支上
git checkout master      check到master分支
git merge <branch_name>   合并修改 <branch_name>和master指向同一个修改,merge后的修改,如果冲突需要手工修改
git branch -d test     删除本地分支
git push origin --delete <branch_name>   删除远程分支(github)上的分支

windows中git配置代理

$ git config --global http.proxy http://127.0.0.1:8087
$ git config --global https.proxy https://127.0.0.1:8087

使用https链接

$ git remote add origin https://github.com/username/××.git

版本回退

  • Just modify file in working directory but not add to temporary directory
git checkout -- <filename> // undo modified
git clean -xdf // delete new files
  • Add to temporary directory but not commit
//unstage modification,如果是全部文件使用 . 符号;
//使用--代替HEAD,对于没有commit的仓库
git reset HEAD 'filename'  
git checkout -- 'filename'
  • Commit finished
git log
git reset --hard HEAD^

保存当前分支修改

使用下面的命令可以在不commit当前分支的基础上,切换到另外一个分支

git stash

切回主分支(stash所在分支),然后键入git stash pop

将当前在master下的修改push到dev分支上

git stash // Hide away your changes (basically making a temporary commit),
git checkout {another-branch}
git pop // Pop re-applies them

如果整个work tree都乱了,那就

# Unstage everything (warning: this leaves files with conflicts in your tree)
git reset
# Add the things you *do* want to commit here _manually_ using gedit or vim
git add -p     # or maybe git add -i
git commit

如果只是某几个文件无法自动merge,那就

git checkout -- <filename1>
# Add the things you *do* want to commit here _manually_ with gedit or vim
git add -p     # or maybe git add -i
git commit

由于original-branch分支还没有处理,现在切回原来分支:

# The stash still exists; pop only throws it away if it applied cleanly
git checkout original-branch
git stash pop
# Add the changes meant for this branch
git add -p
git commit
# And throw away the rest
git reset --hard

git push and merge local branch

When push.default is set to ‘matching’, git will push local branches to the remote branches that already exist with the same name.

当 push.default 的值设置成 ‘matching’ ,git 将会推送所有本地已存在的同名分支到远程仓库

从 Git 2.0 开始,git 采用更加保守的值’simple’,只会推送当前分支到相应的远程仓库,’git pull’ 也将值更新当前分支。

git config --global push.default simple //同步当前分支
git config --global push.default matching //同步与当前工作区相同名称的所有分支

git删除远程或本地分支

Deleting a remote branch:

git push origin --delete <branch>  # Git version 1.7.0 or newer
git push origin :<branch>          # Git versions older than 1.7.0

Deleting a local branch:

git branch --delete <branch>
git branch -d <branch> # Shorter version
git branch -D <branch> # Force delete un-merged branches

.gitignore

1. .gitignore uses globbing patterns to match against file names

Pattern Example matches Explanation*
**/logs logs/debug.log
logs/monday/foo.bar
build/logs/debug.log
You can prepend a pattern with
a double asterisk to
match directories anywhere
in the repository.
/debug.log debug.log
but not
logs/debug.log
Prepending a slash
matches files only
in the repository root.
debug.log debug.log
logs/debug.log
By default, patterns match
files in any directory.
logs logs
logs/debug.log
logs/latest/foo.bar
build/logs
build/logs/debug.log
If you don’t append a slash, the
pattern will match both files and the contents of
directories with that name. In the example matches on the
left, both directories and files named logs are ignored.
logs/ logs/debug.log
logs/latest/foo.bar
build/logs/foo.bar
build/logs/latest/debug.log
Appending a slash indicates
the pattern is a directory. The
entire contents of any directory
in the repository matching
that name – including all of its files
and subdirectories – will be ignored

And more details can refer to the link.

2. Ignoring a previously committed file

If you want to ignore a file that you’ve committed in the past, you’ll need to delete the file from your repository and then add a .gitignore rule for it. Using the --cached option with git rm means that the file will be deleted from your repository, but will remain in your working directory as an ignored file.

$ echo debug.log >> .gitignore
$ git rm --cached debug.log
rm 'debug.log'
$ git status
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	deleted:    debug.log

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	debug.log<br/>
$ git commit -m "Start ignoring debug.log"

You can omit the --cached option if you want to delete the file from both the repository and your local file system.

Git Submodule

You want to be able to treat the two projects as separate yet still be able to use one from within the other.This lets you clone another repository into your project and keep your commits separate. If your project contains other libraries which you never or seldom modify, you should adopt submodule to manage your project. And if one of subdirectories is independent to others, which means the subdirectory is special or distinguished, such as communication message subdirectory, you could employ that to it.

Cloning a Project with Submodules

If a new submodule is created by one person, the other people in the team need to initial this submodule. First you have to get the information about the submodule, this is retrieved by a normal git pull. If there are new submodules you’ll see it in the output of git pull. Then you’ll have to initial them with:git submodule init.

If someone updated a submodule, the other team-members should update the code of their submodules. This is not automatically done by git pull, because with git pull it only retrieves the information that the submodule is pointing to another commit, but doesn’t update the submodule’s code. To update the code of your submodules, you should run:git submodule update.

The above two commands can be combined to one command: git submodule update --init.

Initialize your local configuration file and fetch all the data from that project. Another way is to pass --recurse-submodules to the git clone command, it will automatically initialize and update each submodule in the repository.

git clone --recurse-submodules git@github.com:bryanibit/DGPS.git

Add config to git status of submodules

git config (--global) status.submoduleSummary true

Basic Operation (clone push merge and remove)

:rocket: Add submodule to repoA and update it from repoB

A $ git submodule add <url> <path> # staged .gitmodules and a subdir
A $ git commit -m "add submodule"
A $ git push
B $ git submodule init # register .gitmodules
B $ git submodule update # append files to subdir
# at this time, B) subdir is HEAD detached status like commitID (fe64799)

When we’ve run the git submodule update command to fetch changes from the submodule repositories, Git would get the changes and update the files in the subdirectory but will leave the sub-repository in what’s called a detached HEAD state. This means that there is no local working branch (like “master”, for example) tracking changes. With no working branch tracking changes, that means even if you commit changes to the submodule, those changes will quite possibly be lost the next time you run git submodule update. You have to do some extra steps if you want changes in a submodule to be tracked.

In order to set up your submodule to be easier to go in and hack on, you need to do two things. You need to go into each submodule and check out a branch to work on. The options are that you can merge them into your local work, or you can try to rebase your local work on top of the new changes.

$ git checkout stable
$ git submodule update --remote --merge

The foregoing commands means that you should create a new branch to save your modifications if you want to modify submodules. If not, those changes will be lost the next time running git submodule update.

:penguin: Modify submodule in repoA and update it from repoB

A $ git commit -am "modify submodule" # in submodule
A $ git push # in submodule  
A $ git commit -m "update submodule" # in root  
A $ git push   # That needs two commits and two necessary pushes  
B $ git pull # in root, git status shows "following shown SH" at this time  
B $ git submodule update # in root, subdir is still in HEAD Detached like another commitID (5e73195)

The shown SH is:

B (master * u=) $ git status
On branch master
Your branch is up-to-date with 'origin/master'
Changes not staged for commit:  
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  modified: vendor/plugins/demo (new commits)
Submodules changed but not updated:
* vendor/plugins/demo 0e90143...fe64799 (2):
  < Pseudo-commit #2
  < Pseudo-commit #1
no changes added to commit (use "git add" and/or "git commit -a")

In SH, The current prompt, with its asterisk (*), does hint at local modifications, because our working directory is not in sync with the index, the latter being aware of the newly referenced submodule commit IDs. The angle brackets point left (<)? Git sees that the current WD does not have these two commits, contrary to the container project’s expectations.

:banana: Push changes in submodules
The above said we need two commits and two necessary pushes. If not, there will be fatal: reference is not a tree, because of lack of push submodules. So the following commands can make sure one push with some arguments.

$ git push --recurse-submodules=check

The “check” option will make push simply fail if any of the committed submodule changes haven’t been pushed. It is deployed after git push each submodule.

$ git push --recurse-submodules=on-demand

Git went into the submodule and pushed it before pushing the main project. If that submodule push fails for some reason, the main project push will also fail.

$ git push

In each submodule and then main project.
If you think it is too nagging, append the config to git: git config --global alias.spush 'push --recurse-submodules=on-demand

At last but not least, how to removing a submodule, please refer to medium of Christophe Porteneuve.

:bear: The left content is how to solve the HEAD detached status:

  • Directly method
    subdir ((remotes/origin/HEAD)) $ git checkout master
    Previous HEAD position was 0e90143... Pseudo-commit
    
    Switched to branch 'master'
    Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
    
    (use "git pull" to update your local branch)
    
    subdir (master u-2) $ git pull --rebase
    First, rewinding head to replay your work on top of it...
    Fast-forwarded master to 0e9014309fe6c663e806c9f91297a592ee04cb6c.
    demo (master u=) $
    
  • Simple way
    B) (master u=) $ git submodule update --remote --rebase -- <subdir>
    

git submodules alternatives

Repo

Repo is a tool created by Google to manage the rather large Android project, which is spread across multiple different Git project repositories. It essentially works by providing a way to check out multiple projects (Git repositories) in parallel based on a manifest file (which basically serves the purpose that a parent repository does for Git submodules – tracking which submodule commits go together). It also provides a way to submit an atomic changeset that includes changes to multiple different projects.
The downside is that Repo doesn’t handle merging very well: it essentially expects you to rebase your changes when you want to bring in outside updates, effectively bringing things back to the equivalent of svn update. If you’re a fan of many small commits over a few large ones, this can get onerous.

Gitslave

Gitslave is a wrapper around Git that multiplexes git commits into multiple repositories. It effectively implements the “have parallel branches for each of your projects” solution to the merging problem by doing that for you – if you create a branch, it gets created everywhere. If you commit, all of your repositories create a commit, and so on.
Of course, this can get rather hectic if you have a large number of projects and start running into things like merge conflicts in 5 different repositories. It also means you potentially wind up making a lot of pointless extra branches in projects that you didn’t happen to touch while touching another project.

Git Subtree

Git Subtree is a tool that uses Git’s “subtree merge” functionality to get a similar result to submodules, but via actually storing the files in the main repository and merging in changes directly to that repository.
The upside is that you avoid all the issues with submodule merging because the contents of your subprojects are stored directly in the parent repository and thus are treated like any other tracked files when pulling and merging.
The downside is that all of your subproject files are present in the parent repository, which means you’re giving up some of the reason for originally splitting up your project repositories: having one canonical repository for a given set of shared code. If someone makes a change to a subproject, they can merge it with other changes locally, but they’d have to explicitly split that change back out of their project if they wanted to share it with projects.

untracked content

If something new is created in submodules, git status in parent repo will show

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  (commit or discard the untracked or modified content in submodules)

	modified:   Channel_ANS_test (untracked content)

If the content in submodules has been committed, the status shows that

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   Channel_ANS_test (new commits)

Submodules changed but not updated:

* Channel_ANS_test 480c2e3...78636ff (1):
  > add gitignore for n.cc and codegenProtos.hh

no changes added to commit (use "git add" and/or "git commit -a")

Workflow for integration

The workflow for integrating a package is as follows:

  1. The official code repository is in the github (known as official tree).
  2. Each team has a corresponding repository in the github (known as team repository)
  3. The code to integrate should be in the local git repository
  4. The code has to be pushed to the team repository in the github and a pull request has to be opened to integrator.
  5. Jenkins server is then activated:
  6. Pull the code
  7. Compile it.
  8. Perform sanity tests
  9. Integrator then pull the new code and makes its own checks: functionality, bugs correction, integration
  10. Issues are opened and tracked via trac server
  11. If the new code is good enough, it is pushed to the official tree.

Not afraid of “git rebase”

:rocket: git rebase in its simplest form is a command which will merge another branch into the branch where you are currently working, and move all of the local commits that are ahead of the rebased branch to the top of the history on that branch. So it avoid merge conflict.

git rebase origin/master # remote
git rebase dev # local

If there are conflicts, and you can use git rebase --continue after fixing them. If the conflicts are too bad and you need to bail out and attempt a normal fast-forward merge, you can easily do so with git rebase --abort (leaving you where you were before attempting the rebase). Do not forget to use git log --oneline to check the result sometimes.

:sailboat: Not to mention that interactive rebase is fantastically useful.

git rebase -i HEAD~n # n means showing first three commits
		     # -i means interative
  • Quickly removing a commit which snuck into a different branch.
    In interative mode, just delete the line: pick <commitID> <commitDescription>, the same value with git reset --hard <commidID>, but different essence.
    And make sure that you have commited at least 3 times before, if not, it will do nothing.

  • Squashing several commits into one.
    In interative mode, just revise key word pick to squash, save and quit.

  • Rapidly changing the message on a series of commits.
    In interative mode, just revise key word pick to edit, save and quit. At the moment, you have entered editable commit mode:
    According to git reminds, use git commit --amend to modify commit messages. If you are satified with your edit, type git rebase --continue. Save and quit.

Duplicating a repositoty via mirroring a repository

The following method exhibits how to duplicate a remote repository and do not update with old repo.

  1. Create a bare clone of the repository. To be honest, it will download all your project. Because .git dir has it all.
    git clone --bare https://github.com/exampleuser/old-repository.git

  2. Mirror-push to the new repository. It will push all local things to remote.
    cd old-repository.git
    git push --mirror https://github.com/exampleuser/new-repository.git
    
  3. Remove the temporary local repository you created in step 1.
cd ..
rm -rf old-repository.git

git diff

Use git diff to check the difference of branches.

cd dir
for f in *n.hh; do
    git diff brnach1 branch2 -- $f >> ../branch12.txt
done

Using Git submodules with Gitlab CI

  1. Move submodules and super project to the same group, this is called transfer project, you can find it in setting-advance setting. Before transfering project, you should build a group or use existing group. If you add new project to a group, you can click group and click New Project next. For existing project, transfer project means a new namespace and you should update their remote url of local projects with git remote set-url origin <new ssh or http url>.
  2. Of course, starting with GitLab 10.3, existing Git remote URLs for projects under the namespace will redirect to the new remote URL. Every time you push/pull to a repository that has changed its location, a warning message to update your remote will be displayed instead of rejecting your action. This means that any automation scripts, or Git clients will continue to work after a rename, making any transition a lot smoother.
  3. Make sure your super project and subprojects are on the same GitLab server. If subproject and superproject are the same level of a group, use relative path in .gitmodules: modify your .gitmodules file to
    [submodule "project"]
      path = project
      url = ../../group/project.git
    

Git Basics - Tagging

Git supports two types of tags: lightweight and annotated. A lightweight tag is very much like a branch that doesn’t change — it’s just a pointer to a specific commit. Annotated tags, however, are stored as full objects in the Git database. They’re checksummed; contain the tagger name, email, and date; have a tagging message; and can be signed and verified with GNU Privacy Guard (GPG). You can use git show <tag-name> to recognise which one it belongs to.

git tag %show tag list
git tag -a v1.4 -m "my version 1.4" %create an annotated tag
git tag v1.4-lw %create a lightweight tag
git tag -a v1.2 <commit-id> %create tag later

git large file

Install git-lfs

curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo zsh
sudo apt-get install git-lfs
git lfs install

If you don’t wanna install lfs globally, you can use git lfs uninstall to delete it and use git lfs install --local instead to make it usable in the repository.

Name explaination

origin vs master

Just like the branch name “master” does not have any special meaning in Git, neither does “origin”. While “master” is the default name for a starting branch when you run git init which is the only reason it’s widely used, “origin” is the default name for a remote when you run git clone. If you run git clone -o booyah instead, then you will have booyah/master as your default remote branch.
For better understanding, you can see the origin and master changing with these commands.

primitive.png This is the primitive condition.

someone_push_and_I_commit.png Someone pushes some commit to remote(origin), I(My computer) commit something locally. As you can see, my origin/master pointer doesn’t move.

git_fetch_origin.png I use command git fetch origin to my project locally. The origin/master header pointer has been moved.

add_remote_url_called_teamone.png Others add another origin called teamone remotely.

git_fetch_teamone.png I use command ‘git fetch teamone` to my project locally. Because that server has a subset of the data your origin server has right now, Git fetches no data but sets a remote-tracking branch called teamone/master to point to the commit that teamone has as its master branch.

Tracking Branch
Checking out a local branch from a remote-tracking branch automatically creates what is called a “tracking branch” (and the branch it tracks is called an “upstream branch”).
Tracking branches are local branches that have a direct relationship to a remote branch. If you’re on a tracking branch and type git pull, Git automatically knows which server to fetch from and which branch to merge in.