Xiaopei's DokuWiki

These are the good times in your life,
so put on a smile and it'll be alright

User Tools

Site Tools


it:git

Git

tips

git 用 https 端口作 ssh 连接

Using SSH over the HTTPS port - User Documentation

$ vi ~/.ssh/config
Host github.com
  Hostname ssh.github.com
  Port 443

git merge 选择一边

# If you have multiple files and you want to accept local/our version, run:
grep -lr '<<<<<<<' . | xargs git checkout --ours
 
# If you have multiple files and you want to accept remote/other-branch version, run:
grep -lr '<<<<<<<' . | xargs git checkout --theirs

从非 22 port 端口 clone 项目 git clone ssh://

git clone ssh://machine@32.242.111.21:11111/home/git/repo.git

查看一次 git merge (merge commit) 修改的所有代码

同步两个 remote 的代码

# 更新 origin
$ git fetch
 
# 将 origin 的更新都推到 web(另一个 bare repo)
$ git push web refs/remotes/origin/*:refs/heads/*

dry-run merge

这个操作只是保证当前分支不提交,merge 分支的代码还是会过来!

$ git merge --no-commit --no-ff $BRANCH
# --commit, --no-commit
#     Perform the merge and commit the result. This option can be used to override --no-commit.
#
#     With --no-commit perform the merge but pretend the merge failed and do not autocommit, to give the user a chance to inspect
#     and further tweak the merge result before committing.
#
# --no-ff
#     Create a merge commit even when the merge resolves as a fast-forward. This is the default behaviour when merging an
#     annotated (and possibly signed) tag.

archive

git archive -o latest.zip HEAD

merge 两个不同的 repo 到一个

gerrit

  • push 创建 change,而非直接 push
    [remote "origin"]
      # url is a gerrit server
      url = ssh://xiaopei@gerrit.server/some_repo.git
      # push to "new change for master"
      push = HEAD:refs/for/master
      # and add some reviewers automatically
      receivepack = git receive-pack --reviewer foo@bar.com --reviewer bar@foo.com
  • 修改 change。即将 amend 的 commit 或新的 commit 提交到已有的 change 中。但好像只能修改最近一个 change(见下例)
    $ git push origin HEAD:refs/changes/123456
     
    # 若尝试修改较早的 change,push 会被拒绝,报错如下
     ! [remote rejected] HEAD -> refs/changes/10000 (squash commits first)
     
    # 更新之前的 change (commit id 为 123456789abcdef)
    $ git push origin 123456789abcdef:refs/changes/123456
     
    # 但更新之前的 change 后, 之后的 change 会出现 dependency outdated,
    # 所以之后的更新也都需要 git push origin $commit_id:refs/changes/$change_id
  • 使用 git hook 自动增加 change_id
    • 需要从 gerrit 服务器下一个 hook 到本地
    • scp -p -P 29418 review.example.com:hooks/commit-msg .git/hooks/
  • 如果一个 change 被 abandon 了, 则一般希望在本地删除这个 commit, 如果这个 commit 不是最近的 commit 则可用以下方法删除 (参考自 On undoing, fixing, or removing commits in git):
    git rebase -p --onto SHA^ SHA

rebase: 减少 merge 的协作方法

使用 rebase + branch 能保证 master 分支是“一段 commit 就是 一个 feature”,这样若刚 merge 的 feature 是错的,就能用 checkout 撤销

同步远程代码(替换 git pull)

# 在 push 之前
 
$ git fetch
# 更新本地的 origin/master
 
$ git rebase origin/master
# 把本地的 commit rebase(修改基于的 commit)到最新的 origin/master 上
#
# git-rebase - Forward-port local commits to the updated upstream head
#
# Assume the following history exists and the current branch is "topic":
#
#               A---B---C topic
#              /
#         D---E---F---G master
#
# From this point, the result of either of the following commands:
#
#     git rebase master
#     git rebase master topic
#
# would be:
#
#                       A'--B'--C' topic
#                      /
#         D---E---F---G master
#
# NOTE: The latter form is just a short-hand of git checkout topic followed by git
# rebase master. When rebase exits topic will remain the checked-out branch.

同步本地不同分支(替换 git merge)

// 先将 dev 分支 rebase 到最新的 master 上,
// 此时需解决冲突
$ git checkout dev
$ git rebase master
 
// 再将 dev 分支 merge 到 master 上,
// 此时不会发生冲突
$ git checkout master
$ git merge devices

ssh 到服务器后仍用本地的 key

make an existing Git branch track a remote branch

As of Git 1.8.0:

git branch -u upstream/foo

# Or, if local branch foo is not the current branch:

git branch -u upstream/foo foo

# Or, if you like to type longer commands, these are equivalent to the above two:

git branch --set-upstream-to=upstream/foo

git branch --set-upstream-to=upstream/foo foo

As of Git 1.7.0:

git branch --set-upstream foo upstream/foo

Removing untracked files from your git working copy

Removing untracked files from your git working copy - Stack Overflow

# 只清除文件
git clean -f
# ...也清除目录
git clean -f -d.
# ...还清除 ignored files
git clean -f -x. 

# 只清除 ignored files
git clean -f -X

Global .gitignore

Ignoring files · github:help

# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so

# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

# Logs and databases #
######################
*.log
*.sql
*.sqlite

# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

.gitignore file not ignoring

git - .gitignore file not ignoring - Stack Overflow

git rm -r --cached .

# This removes everything from the index, then just run:

git add .

# Commit it:

git commit -m ".gitignore is now working"

从 git 历史中删除 .gitignore 中的文件

还有办法是删除问题的分支(删除前可能需要将 commit squash merge 到其他分支)1)

http://stackoverflow.com/a/1274126

$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch <filename>' HEAD
  • WARNING: this will delete the file from your entire history. This was what I was looking for though, to remove a completely unnecessary and oversized file (output that should never have been committed) that was committed a long time ago in the version history. – zebediah49 Mar 13 '13 at 5:49
  • This will change all commit IDs, thus breaking merges from branches outside of your copy of the repository. – bdonlan Aug 13 '09 at 19:56
  • 协作的项目中,不应用此命令

git 使用别的 ssh private key (identity file)

修改 ~/.ssh/config

Host github.com
        Hostname github.com
        User git
        IdentityFile ~/.ssh/somekey

tag

tag 不需知道 branch 即可还原至某特定状态, 适用于打包

# create tag
$git tag tag_name
# push tags
$git push --tags
 
# pull tags
$git fetch --tags
# create branch from the tag
$git checkout -b branch_name tag_name
 
# 删除 tag
# 本地
$ git tag -d 12345
# 远程 (tag 前缀为 refs/tags/)
$ git push origin :refs/tags/12345

如何 track 空目录

If you really need a directory to exist in checkouts you should create a file in it. .gitignore works well for this purpose; you can leave it empty, or fill in the names of files you expect to show up in the directory.

How do I add an empty directory to a git repository - Stack Overflow

disable push

.git/config
[remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = <url>
        pushurl = www.non-existing-url.com

Git disable pushing from local repository - Stack Overflow

enable push

非 bare 的 git 库, 默认是不能被 push 的, 如要允许 push, 需(按其他库 push 时报错提示所说的)

$ git config receive.denyCurrentBranch warn

patch & apply

$ git format-patch <since_commit_id> 
# 自 <since_commit_id> 至今的所有 commit, 每个 commit 生成一个 patch 文件
 
$ git format-patch <since_commit_id> --stdout > filename
# 只生成一个文件
 
$ git format-patch -1 <sha>
# 对某一 commit 打 patch
 
$ git diff --no-color   origin/master..HEAD > diff_till_now.patch
# 将当前分支与 origin/master 的区别生成一个 patch.
# git diff 生成 patch 时, 应使用 --no-color, 否则内容带 color 代码, 
# apply 可能出现 fatal: unrecognized input 的错误
 
$ git apply <patch_file_name>
# 应用 patch 文件, 修改当前文件, 不会 stage 或 commit
 
## 除 apply 外, 也可使用 am 直接提交
# 清理环境
$ git am --abort
# 应用 patch
$ git am <patch_file_name>
$ git log
# patch 已经在 log 中了

使用 reflog + reset 撤销 git 操作

# 不小心 amend 了已 push 的 commit, 为避免冲突, 通过以下操作撤销最后一次 amend 
$ git reflog 
4ecaaff HEAD@{0}: commit (amend): [FEATURE] 前台增加商品排序 # 这条是错误的操作
49609f4 HEAD@{1}: commit (amend): [FEATURE] 前台增加商品排序
5359f14 HEAD@{2}: commit: [Type] Id: Title
808eb29 HEAD@{3}: commit: genee.cc -> genee.cn
6ce677a HEAD@{4}: commit (amend): [BUG] 系统有两个 class People (FIXED)
e1cbb16 HEAD@{5}: commit: [Type] Id: Title
 
$ git reset HEAD@{1} # 撤销到正确的位置

撤销 git 操作 » A Geek's Page

workflow

此工作流方法来自文章理解Git工作流

描述

理想的Git工作流应该是:

  1. 从一个公开分支创建一个私有分支
  2. 经常新的在私有分支上提交你的工作
  3. 一旦你的代码已经完美了,那么清除它的历史
  4. 合并干净的分支到你的公共分支

方法

短期开发

假设我创建了一个特性分支,并在接下来的一个小时做了许多checkpoint 提交。

git checkout -b private_feature_branch
touch file1.txt
git add file1.txt
git commit -am “WIP”

在我完成我的工作之后,我会运行下面的命令来完成合并:

git checkout master
git merge --squash private_feature_branch
git commit -v

然后花上一分钟详细的写上这次变更的注释。

大改动

有时一个特性可能需要几天才能够完成,并且包含许多checkpoint commits。如果我的checkpoint commit包含逻辑上的递增,我可以使用rebase的互动模式。互动模式非常强大,你可以使用它来编辑老的commit,分割它们,重新排序,在某些情况下,你还可以压缩(squash)它们。

git checkout private
git rebase --interactive master
# rebase 时 ''s, squash'' 会自动触发 ''r, reword''
# ''f, fixup'' 功能与 s 相同但忽略本条提交信息

# 为防止当前分支与外界区别太多, 可经常 pull 外界分支更新本分支
git pull origin/master

git checkout master
git merge private

废弃旧分支

git checkout master
git checkout -b cleaned_up_branch
git merge --squash private_feature_branch
git reset

项目方法(gitflow)

nvie 的文章 A successful Git branching model 描诉了一份与上述工作方法类似, 但加入项目版本发布的 workflow.

nvie 还有一套 git 插件方便于此方法: git flow.

log

筛选

选项说明
-(n)仅显示最近的 n 条提交
–since, –after仅显示指定时间之后的提交
–until, –before仅显示指定时间之前的提交
–author仅显示指定作者相关的提交
–committer仅显示指定提交者相关的提交
–grep搜索提交说明中的关键字
–all-match如果要得到同时这两个选项搜索条件的提交

展示

git log 有 `–pretty` 选项,可以指定使用完全不同于默认格式的方式展示提交历史。最有意思的是 `format`,可以定制要显示的记录格式,这样的输出便于后期编程提取分析,像这样:

$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 11 months ago : changed the version number
085bb3b - Scott Chacon, 11 months ago : removed unnecessary test code
a11bef0 - Scott Chacon, 11 months ago : first commit
常用的格式占位符写法及其代表的意义
选项说明
%H提交对象(commit)的完整哈希字串
%h提交对象的简短哈希字串
%T树对象(tree)的完整哈希字串
%t树对象的简短哈希字串
%P父对象(parent)的完整哈希字串
%p父对象的简短哈希字串
%an作者(author)的名字
%ae作者的电子邮件地址
%ad作者修订日期(可以用 -date= 选项定制格式)
%ar作者修订日期,按多久以前的方式显示
%cn提交者(committer)的名字
%ce提交者的电子邮件地址
%cd提交日期
%cr提交日期,按多久以前的方式显示
%s提交说明

submodule 和 subtree

subtree

从 git v1.5.2,官方就推荐使用 subtree 代替 submodule。 但也不好用!!!!! 最好用 npm 等额外的模块管理工具!!!

remove a submodule

  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/submodule_name
  6. Commit
  7. Delete the now untracked submodule files rm -rf path_to_submodule

How do I remove a Git submodule? - Stack Overflow

Changing remote repository for a git submodule

You should just be able to edit the .gitmodules file to update the URL and then run git submodule sync to reflect that change to the superproject and your working copy.

Changing remote repository for a git submodule - Stack Overflow

.gitignore

文件 .gitignore 的格式规范如下:

  • 所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式匹配。
  • 匹配模式最后跟反斜杠(`/`)说明要忽略的是目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(`!`)取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(`*`)匹配零个或多个任意字符;`[abc]` 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(`?`)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 `[0-9]` 表示匹配所有 0 到 9 的数字)。

我们再看一个 .gitignore 文件的例子:

# 此为注释 – 将被 Git 忽略
*.a       # 忽略所有 .a 结尾的文件
!lib.a    # 但 lib.a 除外
/TODO     # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO
build/    # 忽略 build/ 目录下的所有文件
doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt

# 关于子目录(subdirectory)的 ignore
/sites/*/labs/*/logs/*		# 忽略 logs 目录下的所有文件
/sites/*/labs/*/private/*	# 忽略 private 目录下的所有文件
!/sites/*/labs/*/private/css	# 但 track private 下的 css
!/sites/*/labs/*/private/js	# 但 track private 下的 js

# 不能忽略一个目录,再 track 目录下的某文件,即以下写法不正确:
# /sites/*/labs/*/private
# !/sites/*/labs/*/private/css

refs

init

## configs
# Force Unix-style line endings
$ git config --global core.autocrlf true
 
## alias
git config --global alias.amend  'commit --amend'
git config --global alias.sg     'diff --staged'
git config --global alias.staged 'diff --staged'
 
# 取消暂存文件
$ git config --global alias.unstage 'reset HEAD --'
# 查看最后一次的提交信息
$ git config --global alias.last 'log -1 HEAD'
# 运行某个外部命令,而非 Git 的附属工具,需要在命令前加上 `!` 
$ git config --global alias.visual "!gitk"
 
带参数 param argument arg 的 alias 
files = "!f() { git diff --name-status $1^ $1; }; f"

git 服务器

bare

# server
$ mkdir -p ~/git/depot.git
$ cd ~/git/depot.git
$ git --bare init
 
# client
$ git remote add origin $URI
$ git push origin master

增加git服务器

$ git remote add teamone git://git.teamone.our.com

使用 gitolite 对服务器增加权限管理

安装

  1. aptitude install gitolite
  2. 创建 git 用户:
    # gitolite
    user { 'git':
      ensure => present,
      managehome => true,
      shell => '/bin/bash',
    }
  3. 上传初始管理员的 pubkey, 如 /tmp/xp.pub
  4. 切换到 git 用户: su git
  5. 进入 gitolite 的目录 (建议用 ~): cd
  6. 初始化 gitolite: gl-setup /tmp/xp.pub
  7. 之后, xp.pub 的 owner 就可 git clone git@gitolite:gitolite-admin 并在 gitolite-admin 中管理 git 服务器了

branch

远程分支

推送本地分支

推送到远程服务器

# git push [远程名] [本地分支名]:[远程分支名]
$ git push origin serverfix

跟踪远程分支

获取远程分支:

$git fetch teamone

值得注意的是,在 `fetch` 操作下载好新的远程分支之后,你仍然无法在本地编辑该远程仓库中的分支。换句话说,在本例中,你不会有一个新的 `serverfix` 分支,有的只是一个你无法移动的 `origin/serverfix` 指针。 如果要把该内容合并到当前分支,可以运行 `git merge origin/serverfix`。 如果想要一份自己的 `serverfix` 来开发,可以在远程分支的基础上分化出一个新的分支来:

# 创建远程同名分支并跟踪之
$ git checkout --track origin/serverfix

# 创建别名分支
# git checkout -b [分支名] [远程名]/[分支名]
$ git checkout -b serverfix origin/serverfix

这会切换到新建的 `serverfix` 本地分支,其内容同远程分支 `origin/serverfix` 一致,这样你就可以在里面继续开发了。

从远程分支 `checkout` 出来的本地分支,称为_跟踪分支(tracking branch)_。跟踪分支是一种和远程分支有直接联系的本地分支。在跟踪分支里输入 `git push`,Git 会自行推断应该向哪个服务器的哪个分支推送数据。反过来,在这些分支里运行 `git pull` 会获取所有远程索引,并把它们的数据都合并到本地分支中来。

删除远程分支

# git push [远程名] :[分支名]
$ git push origin :serverfix

有种方便记忆这条命令的方法:记住我们不久前见过的 `git push [远程名] [本地分支]:[远程分支]` 语法,如果省略 `[本地分支]`,那就等于是在说“在这里提取空白然后把它变成`[远程分支]`”。

检查本地与远程的区别

$ git log origin/master..HEAD
$ git diff origin/master..HEAD
1)
src.tgz
it/git.txt · Last modified: 2017/09/19 15:05 by admin