参考自:
安装配置
在 Linux 上安装 Git
首先,你可以试着输入 git,看看系统有没有安装 Git:
$ git
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git
像上面的命令,有很多 Linux 会友好地告诉你 Git 没有安装,还会告诉你如何安装 Git。
Debian/Ubuntu Git 安装命令为:
$ sudo apt-get install git
Centos/RedHat Git 安装命令为:
$ yum -y install git
老一点的Linux 系统,要把命令中的git改为git-core ,因为以前有个软件也叫 GIT(GNU Interactive Tools),结果 Git 就只能叫git-core 了。由于 Git 名气实在太大,后来就把GNU Interactive Tools 改成 gnuit,git-core 正式改为 git。
在 Mac OS X 上安装 Git
从 AppStore 安装 Xcode,Xcode 集成了 Git,不过默认没有安装,你需要运行 Xcode,选择菜单“Xcode”->“Preferences”,在弹出窗口中找到“Downloads”,选择“Command Line Tools”,点“Install”就可以完成安装了。
Xcode 是 Apple 官方 IDE,功能非常强大,是开发 Mac 和 iOS App 的必选装备,而且是免费的!
在 Windows 上安装 Git
msysgit 是 Windows 版的 Git,从 http://msysgit.github.io/ 下载,然后按默认选项安装即可。
完成安装之后,就可以使用命令行的 git 工具(已经自带了 ssh 客户端)了,另外还有一个图形界面的 Git 项目管理工具。
在开始菜单里找到"Git"->“Git Bash”,会弹出 Git 命令窗口,你可以在该窗口进行 Git 操作。
查看版本验证安装
git --version
git配置
配置个人的用户名称和电子邮件地址
安装完成后,还需要最后一步设置,在命令行输入:
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
如果用了 --global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。
如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。
查看配置信息
$ git config --list
http.postbuffer=2M
user.name="Your Name"
user.email="email@example.com"
有时候会看到重复的变量名,那就说明它们来自不同的配置文件(比如 /etc/gitconfig 和 ~/.gitconfig),不过最终 Git 实际采用的是最后一个。
这些配置我们也可以在 ~/.gitconfig 或 /etc/gitconfig 看到,也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可,像这样:
$ git config user.name
工作流程
Git 的一般工作流程如下:
- 克隆 Git 资源作为工作目录(git clone url)
- 在克隆的资源上添加或修改文件
- 如果其他人修改了,你可以更新资源
- 在提交前查看修改(git status)
- 提交修改(git commit)
- 在修改完成后,如果发现错误,可以撤回提交并再次修改并提交
工作区、暂存区和版本库
你的本地仓库由 git 维护的三棵“树”组成。第一个是你的 工作目录,它持有实际文件;第二个是 缓存区(Index|Stage),它像个缓存区域,临时保存你的改动,一般存放在 “.git目录下” 下的index文件(.git/index)中,所以我们把暂存区有时也叫作索引(index);最后是 HEAD,指向你最近一次提交后的结果。
版本库:工作区有一个隐藏目录.git,它是Git的版本库。这个目录里面的所有文件都可以被 Git 管理起来,每个文件的修改、删除,Git 都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:
图中左侧为工作区,右侧为版本库。在版本库中标记为 “index” 的区域是暂存区(stage, index),标记为 “master” 的是 master 分支所代表的目录树。
图中我们可以看出此时 “HEAD” 实际是指向 master 分支的一个"游标"。所以图示的命令中出现 HEAD 的地方可以用 master 来替换。
图中的 objects 标识的区域为 Git 的对象库,实际位于 “.git/objects” 目录下,里面包含了创建的各种对象及内容。
-
当对工作区修改(或新增)的文件执行
git add
命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。 -
当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
-
当执行
git reset HEAD
命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。 -
当执行
git rm --cached <file>
命令时,会直接从暂存区删除文件,工作区则不做出改变。 -
当执行
git checkout .
或者git checkout -- <file>
命令时,会用暂存区全部或指定的文件替换工作区的文件。这个操作很危险,会清除工作区中未添加到暂存区的改动。 -
当执行
git checkout HEAD .
或者git checkout HEAD <file>
命令时,会用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。
创建仓库
使用Git前,需要先建立一个仓库(repository)。您可以使用一个已经存在的目录作为Git仓库或创建一个空目录。
git init 初始化仓库
使用您当前目录作为Git仓库,我们只需使它初始化。
git init
使用我们指定目录作为Git仓库。
git init newrepo_directory
git clone 克隆仓库
git clone <repo_url> <directory>
基本操作
使用 git add
添加需要追踪的新文件和待提交的更改, 然后使用 git status
和 git diff
查看有何改动, 最后用git commit
将你的快照记录。
git add 添加修改到缓存
git add file1 file2 ... fileN
git add -A
添加所有修改文件
git add .
递归添加当前项目的所有文件
git add *
不递归添加所有文件
Git 跟踪并管理的是修改,而非文件。如果你修改了某文件,然后使用git add
添加文件到缓存后,又再次修改了文件,然后直接就git commit
提交的话,第二次修改的内容是没有提交的。即,每次修改,如果不 add 到暂存区,那就不会加入到 commit 中。所以,每次你修改文件内容后,在提交前最好都先执行git add
以保证万无一失。
git status 查看文件在工作目录与缓存的状态
git status [-s]
-s: 输出简短结果
“AM” 状态的意思是,这个文件在我们将它添加到缓存之后又有改动。这意味着如果我们现在提交快照, 我们记录的将是上次跑git add
的时候的文件版本,而不是现在在磁盘中的这个。 Git 并不认为磁盘中的文件与你想快照的文件必须是一致的 —— (如果你需要它们一致,)得用git add
命令告诉它。
git diff 显示已写入缓存与已修改但尚未写入缓存的改动的区别
-
尚未缓存的改动:
git diff
-
查看已缓存的改动:
git diff --cached
-
查看已缓存的与未缓存的所有改动:
git diff HEAD
-
显示摘要而非整个 diff:
git diff --stat
git status
显示你上次提交更新后的更改或者写入缓存的改动, 而 git diff
一行一行地显示这些改动具体是啥。
git commit 记录缓存内容的快照
现在我们已经添加了这些文件,我们希望它们能够真正被保存在Git仓库。为此,我们将它们提交到仓库。
git commit -m "Adding files"
如果您不使用-m,会出现编辑器来让你写自己的注释信息。
当我们修改了很多文件,而不想每一个都add,想commit自动来提交本地修改,我们可以使用-a标识。
git commit -a -m "Changed some files"
git commit
命令的-a选项可将所有被修改或者已删除的且已经被git管理的文档提交到仓库中。
千万注意,-a不会造成新文件被提交,只能提交已经存在并且之后被修改的文件。
git checkout – file 丢弃工作区的修改
git checkout -- readme.txt
命令git checkout -- readme.txt
意思就是,把 readme.txt 文件在工作区的修改全部撤销,这里有两种情况:
-
一种是 readme.txt 自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
-
一种是 readme.txt 已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
git reset HEAD file 撤销缓存区的修改
git reset HEAD file
可以把暂存区的修改撤销掉(unstage),重新放回工作区。
git reset
命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用 HEAD 时,表示最新的版本。
用git status
查看一下,现在暂存区是干净的,工作区有修改,可用git checkout -- file
丢弃工作区的修改。
git rm 将文件从缓存区移除
git rm
会将条目从缓存区中移除。这与 git reset HEAD
将条目取消缓存是有区别的。 "取消缓存"的意思就是将缓存区恢复为我们做出修改之前的样子。
默认情况下,git rm file
会将文件从缓存区和你的硬盘中(工作目录)删除。
如果你要在工作目录中留着该文件,可以使用 git rm --cached
git mv 重命名文件
git mv
命令做的所有事情就是先执行 git rm --cached
命令的操作, 重命名磁盘上的文件,然后再执行 git add
把新文件添加到缓存区。
分支管理
git branch 列出、创建和删除分支
git branch (branchname)
没有参数时,git branch
会列出你在本地的分支。
branch命令不会将我们带入分支,只是创建一个新分支。所以我们使用checkout命令来更改分支。
加上选项-d表示删除分支
git branch -d|-D (branchname)
如果要丢弃一个没有被合并过的分支,可以通过git branch -D <name>
强行删除。
git checkout 切换分支
git checkout (branchname)
当你切换分支的时候,Git 会用该分支的最后提交的快照替换你的工作目录的内容, 所以多个分支不需要多个目录。
我们也可以使用 git checkout -b (branchname)
命令来创建新分支并立即切换到该分支下,从而在该分支中操作。
在你创建仓库的时候,master 是“默认的”,被称为主分支。
对其他分支的更改不会反映在主分支上。如果想将更改提交到主分支,则需切换回master分支,然后使用合并。
git merge 合并分支到当前分支
git merge (branchname)
将任何分支合并到当前分支中去。
合并并不仅仅是简单的文件添加、移除的操作,Git 也会合并修改。
当出现合并冲突时,我们需要手动修改冲突的文件内容,修改好后用 git add
告诉 Git 文件冲突已经解决,最后用git commit
完成合并。
git log 显示一个分支中提交的更改记录
用 --oneline 选项来查看历史记录的简洁的版本
用 --graph 选项查看历史中什么时候出现了分支、合并
用 --reverse 参数来逆向显示所有日志
如果只想查找指定用户的提交日志可以使用命令:git log --author="author"
如果你要指定日期,可以执行几个选项:–since 和 --before,但是你也可以用 --until 和 --after。
git tag 标签管理
- 命令
git push origin <tagname>
可以推送一个本地标签; - 命令
git push origin --tags
可以推送全部未推送过的本地标签; - 命令
git tag -d <tagname>
可以删除一个本地标签; - 命令
git push origin :refs/tags/<tagname>
可以删除一个远程标签。
标签介绍
发布一个版本时,我们通常先在版本库中打一个标签(tag),这样就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
Git 的标签虽然是版本库的快照,但其实它就是指向某个 commit 的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
标签类型
Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。
轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。
而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。
一般我们都建议使用含附注型的标签,以便保留相关信息;
当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。
创建标签
git tag <tabname> [-a] [log_id]
[root@Git git]# git tag v1.0
-a 选项意为"创建一个带注解的标签"。
如果我们忘了给某个提交打标签,又将它发布了,我们可以给它追加标签:
git tag -a v0.9 85fc7e7
指定标签信息命令:
git tag -a <tagname> -m "w3cschool.cc标签"
查看已有标签
[root@Git git]# git tag
v1.0
[root@Git git]# git tag v1.1
[root@Git git]# git tag
v1.0
v1.1
执行git log --decorate
时,我们可以看到我们的标签
删除标签
[root@Git git]# git tag -d v1.1
Deleted tag ‘v1.1’ (was 91388f0)
[root@Git git]# git tag
v1.0
查看此版本所修改的内容
[root@Git git]# git show v1.0
commit 91388f0883903ac9014e006611944f6688170ef4
Author: "syaving" <"819044347@qq.com">
Date: Fri Dec 16 02:32:05 2016 +0800
commit dir
diff –git a/readme b/readme
index 7a3d711..bfecb47 100644
— a/readme
+++ b/readme
@@ -1,2 +1,3 @@
text
hello git
+use commit
[root@Git git]# git log –oneline
91388f0 commit dir
e435fe8 add readme
2525062 add readme
版本回退和替换本地改动
假如你想要丢弃你所有的本地改动与提交,可以到服务器上获取最新的版本并将你本地主分支指向到它:
git fetch origin
git reset --hard origin/master
HEAD 指向的版本就是当前版本,因此,Git 允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id
。
穿梭前,用git log
可以查看提交历史,以便确定要回退到哪个版本。
要重返未来,用git reflog
查看命令历史,以便确定要回到未来的哪个版本。
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file
,就回到了场景 1,第二步按场景 1 操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,则进行版本回退,不过前提是没有推送到远程库。
分享与更新项目
使用 git fetch
更新你的项目,使用 git push
分享你的改动。 你可以用 git remote
管理你的远程仓库。
多人协作的工作模式通常是这样:
- 首先,可以试图用
git push origin branch-name
推送自己的修改; - 如果推送失败,则因为远程分支比你的本地更新,需要先用
git pull
试图合并; - 如果合并有冲突,则解决冲突,并在本地提交;
- 没有冲突或者解决掉冲突后,再用
git push origin branch-name
推送就能成功!
如果git pull
提示“no tracking information”,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream branch-name origin/branch-name
。
git remote 罗列、添加和删除远端仓库别名
要添加一个新的远程仓库,可以指定一个简单的名字,以便将来引用,命令格式如下:
git remote add [alias] [url]
查看当前配置有哪些远程仓库,可以用命令:
git remote
执行时加上 -v 参数,你还可以看到每个别名的实际链接地址。
git remote rm [alias]
删除现存的某个别名
git fetch 从远端仓库下载新分支与数据
git fetch [alias]
该命令执行完后需要执行git merge
远程分支到你所在的分支。
假设你配置好了一个远程仓库,并且你想要提取更新的数据,你可以首先执行 git fetch [alias]
告诉 Git 去获取它有你没有的数据,然后你可以执行 git merge [alias]/[branch]
以将服务器上的任何更新(假设有人这时候推送到服务器了)合并到你的当前分支。
git pull 从远端仓库提取数据并尝试合并到当前分支
该命令就是在执行 git fetch
之后紧接着执行 git merge
远程分支到你所在的任意分支。
合并远端的next分支到当前分支:
$ git pull origin next
相当于:
$ git fetch origin
$ git merge origin/next
git push 推送你的新分支与数据到某个远端仓库
git push [alias] [branch]
会将你的 [branch] 分支推送成为 [alias] 远端上的 [branch] 分支。
加入-f参数,强制提交
推送当前分支到远端指定分支
git push origin HEAD:master
按当前分支到远程REF匹配master的 origin存储库。这种形式方便地推送当前分支,而不考虑其本地名称。
创建远程分支
git push origin <local_branch>:<remote_branch>
删除远端分支
git push origin :<remote_branch>
先删除本地分支(git br -d <branch>
),然后再push删除远程分支
git push origin :experimental
找到experimental在存储origin库(例如refs/heads/experimental)中匹配的引用,并将其删除。
non-fast-forward问题
当你推送到远端分支时会碰到的主要问题是,其他人在此期间也推送了的情况。 如果你和另一个开发者同时克隆了,又都有提交,那么当她推送后你也想推送时,默认情况下 Git 不会让你覆盖她的改动。 相反的,它会在你试图推送的分支上执行git log
,确定它能够在你的推送分支的历史记录中看到服务器分支的当前进度。如果它在你的历史记录中看不到,它就会下结论说你过时了,并打回你的推送。 你需要正式提取、合并,然后再次推送 —— 以确定你把她的改动也考虑在内了。
当你试图推送到某个已被更新的远端分支时,会出现下面这种情况:
$ git push github master
To git@github.com:schacon/hw.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:schacon/hw.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.
你可以修正这个问题。执行git fetch github; git merge github/master
,然后再推送。
简而言之,执行 git push [alias] [branch]
将你的本地改动推送到远端仓库。 如果可以的话,它会依据你的 [branch] 的样子,推送到远端的 [branch] 去。 如果在你上次提取、合并之后,另有人推送了,Git 服务器会拒绝你的推送,直到你是最新的为止。
将现有的代码,推送到一个新的GitHub Repo中
在GitHub上创建新的仓库。为了避免发生合并冲突,在初始化你的仓库时,请不要创建README, license 或 gitignore文件。
打开终端,并切换到代码的根目录
初始化本地git仓库
$ git init
将根目录下的代码,存入仓库,准备第一次commit
$ git add .
$ git commit -m"第一次提交"
获取GitHub仓的URL,一般在仓库的首页
在终端里,为你的本地仓库,添加一个远程仓库URL
$ git remote add origin 远程GitHub仓的URL
验证新的远程URL
$ git remote -v
将本地的代码,推动到远程的GitHub
$ git push origin master
Git提交信息
commit信息的第一行需要注明此次修改相关的需求,或者任务,或者bug的id比如下面的信息:
- bug#123,234, 1234,也可以是bug:123,234 1234,id列表之间,用逗号和空格都可以。
- story#123 task#123 bug, story, task是必须标注的。
填写commit信息时注意:
- [IMP] 提升改善正在开发或者已经实现的功能
- [FIX] 修正Bug
- [REF] 重构一个功能,对整个功能重写
- [ADD] 添加实现新功能
- [REM] 删除一些不再需要的文件
Git分支管理
上图涵盖了开发过程中各种情况的分支流程
(主分支)master
- 线上分支:时刻保持与线上代码一致,理论上是每次master更新后,都需要通过自动化部署工具进行上线发布
- master分支只能由develop以及hotfix分支合并,不能直接修改代码。
(开发分支)develop(dev)
- 开发分支:任何迭代需求分支都以这个分支为父分支进行建立
- 通常是部署在sit环境,是一个稳定版本。
- develop分支由hotfix以及release,feature合并。
(预发布分支)release(test)
- 预发布分支:开发完成和将一个迭代的所有修改合并到该分支供测试人员测试
- 命名规则:release/1103/refact 后面跟 日期,接着是功能。
(临时分支)feature
feature分支是短期的一个需求开发过程中创建的一个特性分支,理论上每一个需求可以细分成一个特性分支,一次迭代可能会细分出5-6个特性分支
(临时分支)hotfix
hotfix分支是为了解决一个紧急的线上问题而建立的分支
.gitignore语法规则
.gitignore 配置文件用于配置不需要加入版本管理的文件,配置好该文件可以为我们的版本管理带来很大的便利。
配置语法
文件 .gitignore 的格式规范如下:
- 所有空行或者以
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式可以以(
/
)开头防止递归。 - 匹配模式可以以(
/
)结尾指定目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。
- 星号(
*
)匹配零个或多个任意字符; [abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个b,要么匹配一个 c);- 问号(
?
)只匹配一个任意字符; - 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如
[0-9]
表示匹配所有 0 到 9 的数字)。 - 使用两个星号(*) 表示匹配任意中间目录,比如
a/**/z
可以匹配 a/z, a/b/z 或 a/b/c/z等。
示例
# no .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# ignore all .pdf files in the doc/ directory
doc/**/*.pdf
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在https://github.com/github/gitignore 找到它.
merge和rebase的区别
rebase
假设你现在基于远程分支"origin",创建一个叫"mywork"的分支。
$ git checkout -b mywork origin
现在我们在这个分支做一些修改,然后生成两个提交(commit).
$ vi file.txt
$ git commit
$ vi otherfile.txt
$ git commit
...
但是与此同时,有些人也在"origin"分支上做了一些修改并且做了提交了.
这就意味着"origin"和"mywork"这两个分支各自"前进"了,它们之间"分叉"了。
在这里,你可以用"pull"命令把"origin"分支上的修改拉下来并且和你的修改合并;结果看起来就像一个新的"合并的提交"(merge commit):
但是,如果你想让"mywork"分支历史看起来像没有经过任何合并一样,你也许可以用 git rebase:
$ git checkout mywork
$ git rebase origin
这些命令会把你的"mywork"分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(patch)(这些补丁放到".git/rebase"目录中),然后把"mywork"分支更新
到最新的"origin"分支,最后把保存的这些补丁应用到"mywork"分支上。
当’mywork’分支更新之后,它会指向这些新创建的提交(commit),而那些老的提交会被丢弃。如果运行垃圾收集命令(pruning garbage collection), 这些被丢弃的提交就会删除.(请查看 git gc)
现在我们可以看一下用合并(merge)和用rebase所产生的历史的区别:
在rebase的过程中,也许会出现冲突(conflict). 在这种情况,Git会停止rebase并会让你去解决冲突;在解决完冲突后,用"git-add"命令去更新这些内容的索引(index), 然后,你无需执行git-commit,只要执行:
$ git rebase --continue
这样git会继续应用(apply)余下的补丁。
在任何时候,你可以用--abort
参数来终止rebase的行动,并且"mywork"分支会回到rebase开始前的状态。
$ git rebase --abort
merge和rebase的区别
-
采用merge和rebase后,git log的区别,merge命令不会保留merge的分支的commit:
-
处理冲突的方式:
- (一股脑)使用
merge
命令合并分支,解决完冲突,执行git add .
和git commit -m'fix conflict'
。这个时候会产生一个commit。 - (交互式)使用
rebase
命令合并分支,解决完冲突,执行git add .
和git rebase --continue
,不会产生额外的commit。这样的好处是,‘干净’,分支上不会有无意义的解决分支的commit;坏处,如果合并的分支中存在多个commit
,需要重复处理多次冲突。
- (一股脑)使用
-
git pull
和git pull --rebase
区别:git pull
做了两个操作分别是‘获取’和合并。所以加了rebase就是以rebase的方式进行合并分支,默认为merge。