git version # 查看版本
git config -l # 查看当前配置
git config --global user.name "Dean" # 设置用户名,邮箱
git config --global user.email [email protected] # 设置用户名,邮箱
git config --global alias.ci commit # 设置git命令的别名
git config --global alias.co checkout # 设置git命令的别名
# 创建一个本地的git仓库并命名:
git init demo
# 克隆一个远程的git仓库到指定路径:
git clone https://github.com/a396901990/android.git /path/workpsace
git branch # 查看分支
git remote show origin # 查看所有分支
git branch <branchname> # 创建新分支
git checkout <branchname> # 切换到分支
git checkout -b <new_branch> # 创建并切换到新分支
git branch -d <branchname> # 删除分支(-D强删)
git branch -m <old> <new> # 本地分支重命名
git add <file> # 将本地指定文件名或目录(新增和修改,没有删除)的文件添加到暂存区
git add . # 将本地所有的(新增和修改,没有删除)文件添加到暂存区
git add -u # 将本地的(修改和删除,没有新增)文件添加到暂存区
git add -A # 将本地所有改动添加到暂存区(git add -A = git add . + git add -u)
git add -i # 打开一个交互式界面按需求添加文件
git rm <file> # 删除文件
git rm -r <floder> # 删除文件夹
git rm --cached <file> # 从版本库中删除文件,但不删除文件
git mv <old_name> <new_name> # 文件重命名
git commit -m "comment" # 提交暂存区中的内容(已经add)并添加注释
git commit -a # 把修改的文件添加到暂存区(不包括新建(untracked)的文件),然后提交。
git commit --amend # 修改提交的commit(没有push)
git commit --amend -m "comment" # 修改commit注解
git diff # 查看工作目录(working tree)暂存区(index)的差别
git diff --cached # 查看暂存起来的文件(stage)与并未提交(commit)的差别
git diff --staged # 同上
git diff HEAD # 查看最后一次提交之后的的差别(HEAD代表最近一次commit的信息)
git diff --stat # 查看显示简略结果(文件列表)
git diff commit1 commit2 # 对比两次提交的内容(也可以是branch,哈希值)
git log
git log -3 # 查看前3次修改
git log --oneline # 一行显示一条log
git log -p # 查看详细修改内容
git log --stat # 查看提交统计信息
git log --graph # 显示何时出现了分支和合并等信息
git status # 查看你的代码在缓存与当前工作目录的状态
git status -s # 将结果以简短的形式输出
git status --ignored # 显示被忽略的文件
git stash # 保存当前的工作进度
git stash save "message" # 保存进度加说明
git stash list # 显示进度列表
git stash pop # 恢复最新保存的工作进度,并将恢复的工作进度从存储的列表中删除
git stash apply # 恢复最新保存工作进度,但不删除
git stash drop # 删除一个进度,默认删除最新的
git stash clear # 删除所有
git reset --mixed # 同不带任何参数的git reset一样,重置暂存区,但不改变工作区
git reset --soft # 回退到某个版本,不改变暂存区和工作区(如果还要提交,直接commit即可)
git reset --hard # 彻底回退到某个版本,替换暂存区和工作区,本地的源码也会变为上一个版本的内容
git reset # 将之前用git add命令添加到暂存区的内容撤出暂存区(相当于git add -A 的反向操作)
git reset HEAD # HEAD 效果同上,因为引用重置到HEAD相当与没有重置
git reset filename # 将文件撤出暂存区(相当于git add filename的反向操作)
git reset HEAD^ # 引用回退一次(工作区不变,暂存区回退)
git reset --soft HEAD~3 # 引用回退三次(工作区不变,暂存区不变)
git revert commit # 撤销指定commit
git revert HEAD # 撤销上一次commit
git revert -no-edit HEAD # 撤销上一次并直接使用默认注释
git revert -n HEAD # 撤销上一次但不commit
git merge <branch_name> # 合并
git merge --no-ff <branch_name> # 采用no fast forward的合并方式,这种方式在合并的同时会生成一个新的commit
git merge --abort # 尽量回退到merge前的状态(可能会失败)
git rebase <branch_name> #
git rebase --continue # 执行rebase出现冲突解决后,执行该命令会继续应用(apply)余下的补丁
git rebase --skip # 跳过当前提交
git rebase --abort # 终止rebase, 分支会回到rebase开始前的状态
git fetch # 从远程获取最新版本到本地,不会自动merge
git pull # 从远程获取最新版本并merge到本地
git pull --rebase # 暂存本地变更,合并远程最新改动,合并刚刚暂存的本地变更(不产生无用的merge的同步)
git push origin master # 将本地分支推送到origin主机的master分支
git push -u origin master # -u指定origin为默认主机,后面就可以不加任何参数使用git push了
git push -f origin # -f强推,在远程主机产生一个"非直进式"的合并(non-fast-forward merge)
git push --all origin # 将所有本地分支都推送到origin主
许多人认为Git太混乱或是复杂的版本控制系统,这篇文章是面向一些人想快速上手使用Git,
对于大多数基本需求这篇文章涵盖了使用的70%至90%
入门
使用Git前 需要先建立一个仓库(repository)。你可以使用一个已经存在的目录作为Git仓库或创建一个空目录
使用您当前目录作为Git仓库,我们只需使它初始化
添加新文件
我们有一个仓库,但什么也没有,可以使用add命令添加文件
提交版本
现在我们已经添加了这些文件,我们希望他们能够真正被保存在Git仓库,
为此,我们将他们提交到仓库
当我们修改了很多文件,而不想每一个都add,想commit自动来提交本地修改,我们可以使用-a标识
千万注意,-a不会造成新文件被提交,只能修改。
发布版本
我们先从服务器克隆一个库并上传
取回更新
如果你已经按上面的进行push,下面命令表示,当前分支自动与唯一一个追踪分支进行合并。
已经超过了五分钟?
删除
如何你想从资源库中删除文件,我们使用rm
分支与合并
分支在本地完成,速度快。要创建一个新的分支,我们使用branch命令。
分布式 : Git版本控制系统是一个分布式的系统, 是用来保存工程源代码历史状态的命令行工具;
保存点 : Git的保存点可以追踪源码中的文件, 并能得到某一个时间点上的整个工程项目额状态; 可以在该保存点将多人提交的源码合并, 也可以会退到某一个保存点上;
Git离线操作性 :Git可以离线进行代码提交, 因此它称得上是完全的分布式处理, Git所有的操作不需要在线进行; 这意味着Git的速度要比SVN等工具快得多, 因为SVN等工具需要在线时才能操作, 如果网络环境不好, 提交代码会变得非常缓慢;
Git基于快照 : SVN等老式版本控制工具是将提交点保存成补丁文件, Git提交是将提交点指向提交时的项目快照, 提交的东西包含一些元数据(作者, 日期, GPG等);
Git的分支和合并 : 分支模型是Git最显著的特点, 因为这改变了开发者的开发模式, SVN等版本控制工具将每个分支都要放在不同的目录中, Git可以在同一个目录中切换不同的分支;
分支即时性 : 创建和切换分支几乎是同时进行的, 用户可以上传一部分分支, 另外一部分分支可以隐藏在本地, 不必将所有的分支都上传到GitHub中去;
分支灵活性 : 用户可以随时 创建 合并 删除分支, 多人实现不同的功能, 可以创建多个分支进行开发, 之后进行分支合并, 这种方式使开发变得快速, 简单, 安全。
http://git-scm.com/
欢迎界面 : 直接下一步;
协议 : 必须接受;
安装位置 : 预留100M空间, 自定义安装位置;
选择安装组件 :也可以默认选择;
— 图标组件(Addition icons) : 选择是否创建快速启动栏图标 或者 是否创建桌面快捷方式;
— 桌面浏览(Windows Explorer integration) : 浏览源码的方法, 单独的上下文浏览 只使用bash 或者 只用Git GUI工具; 高级的上下文浏览方法 使用git-cheetah plugin插件;
— 关联配置文件 : 是否关联git配置文件, 该配置文件主要显示文本编辑器的样式;
— 关联shell脚本文件 : 是否关联Bash命令行执行的脚本文件;
— 使用TrueType编码 : 在命令行中是否使用TruthType编码, 该编码是微软和苹果公司制定的通用编码;
开始菜单快捷方式目录 : 设置开始菜单中快捷方式的目录名称, 也可以选择不再开始菜单中创建快捷方式;
设置环境变量 : 选择使用什么样的命令行工具, 一般情况下我们默认使用Git Bash即可, 默认选择;
— Git自带 : 使用Git自带的Git Bash命令行工具;
— 系统自带CMD : 使用Windows系统的命令行工具;
— 二者都有 : 上面二者同时配置, 但是注意, 这样会将windows中的find.exe 和 sort.exe工具覆盖, 如果不懂这些尽量不要选择;
选择换行格式 :
— 检查出windows格式转换为unix格式 : 将windows格式的换行转为unix格式的换行在进行提交;
— 检查出原来格式转为unix格式 : 不管什么格式的, 一律转为unix格式的换行在进行提交;
— 不进行格式转换 : 不进行转换, 检查出什么, 就提交什么;
开始安装 :
安装结束 : over;
在开始菜单找到Git Bash右键属性,修改Git Bash的配置 : 将Git Bash设置为快速编辑模式, 可以更好的使用该命令行工具 :
ssh-keygen -t rsa -C "[email protected]"
GitHub邮箱 : 该命令后面的邮箱就是GitHub的注册邮箱;
路径选择 : 使用该命令之后, 会出现提示选择ssh-key生成路径, 这里直接点回车默认即可, 生成的ssh-key在默认路径中;
密码确认 : 这里我们不使用密码进行登录, 用密码太麻烦;
进入生成的ssh目录 : C:\Documents and Settings\Administrator\.ssh (或者 C:\Users\自己电脑用户名\.ssh)中, 使用记事本打开 id_rsa.pub 文件, 将该文件中的内容复制;
id_rsa.pub 文件内容 :
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAtT1YCeaNulpfC+ARqAWrCdfpi6CpW3gkGT0hp6Q8by7NnEfy4dah9CwSrNbWJH5eS4tiqckE+bdbSVNvAboFD1MtGZjtzE4GDweG/6J/SDYV/ADFN/RLWGb+5rQ8wMCjc/fODgLJDFxk1Fwk/TTqTcbtLab1toLcts3zGIW5DstA3RQ0CCX/sPew5m7vh7DcKXluj2TBd9hw== [email protected]
进入GitHub网站 : 登录GitHub, 选择Account Setting 用户设置:
选择左侧的SSH-KEY选项 :
点击右侧的Add SSH key :
将上面复制好的ssh-key复制进去 :
验证是否配置成功 :
ssh -T [email protected]
验证时可能让你输入YES。
成功提示 : 如果出现Hi han1202012! You’ve successfully authenticated, but GitHub does not provide shell access. 就说明配置成功, 可以连接上GitHub;
用户名邮箱作用 : 我们需要设置一个用户名 和 邮箱, 这是用来上传本地仓库到GitHub中, 在GitHub中显示代码上传者;
使用命令 :
git config --global user.name "HanShuliang" //设置用户名 git config --global user.email "[email protected]" //设置邮箱
到此Git客户端已安装及GitHub配置完成,现在可以从GitHub传输代码了。
如果设置了,就不用每次打开Git再cd打开目录了。方法:右键git快捷图标(名叫:Git Bash),找到快捷方式-起始位置,把你的项目地址放在这里就可以了。如下图:
Git 并不像 Subversion 那样有个中心服务器。 目前为止所有的命令都是本地执行的,更新的知识本地的数据库。 要通过 Git 与其他开发者合作,你需要将数据放到一台其他开发者能够连接的服务器上。 Git 实现此流程的方式是将你的数据与另一个仓库同步。在服务器与客户端之间并没有实质的区别 —— Git 仓库就是 Git 仓库,你可以很容易地在两者之间同步。
一旦你有了个 Git 仓库,不管它是在你自己的服务器上,或者是由 GitHub 之类的地方提供, 你都可以告诉 Git 推送你拥有的远端仓库还没有的数据,或者叫 Git 从别的仓库把差别取过来。
联网的时候你可以随时做这个,它并不需要对应一个 commit
或者别的什么。 一般你会本地提交几次,然后从你的项目克隆自的线上的共享仓库提取数据以保持最新,将新完成的合并到你完成的工作中去,然后推送你的改动会服务器。
简而言之 使用 git fetch
更新你的项目,使用 git push
分享你的改动。 你可以用 git remote
管理你的远程仓库。
不像中心化的版本控制系统(客户端与服务端很不一样),Git 仓库基本上都是一致的,并且并可以同步他们。 这使得拥有多个远端仓库变得容易 —— 你可以拥有一些只读的仓库,另外的一些也可写的仓库。
当你需要与远端仓库同步的时候,不需要使用它详细的链接。Git 储存了你感兴趣的远端仓库的链接的别名或者昵称。 你可以使用 git remote
命令管理这个远端仓库列表。
如果没有任何参数,Git 会列出它存储的远端仓库别名了事。默认情况下,如果你的项目是克隆的(与本地创建一个新的相反), Git 会自动将你的项目克隆自的仓库添加到列表中,并取名“origin”。 如果你执行时加上 -v
参数,你还可以看到每个别名的实际链接地址。
$ git remote origin $ git remote -v origin [email protected]:github/git-reference.git (fetch) origin [email protected]:github/git-reference.git (push)
在此你看到了该链接两次,是因为 Git 允许你为每个远端仓库添加不同的推送与获取的链接,以备你读写时希望使用不同的协议。
如果你希望分享一个本地创建的仓库,或者你想要获取别人的仓库中的贡献 —— 如果你想要以任何方式与一个新仓库沟通,最简单的方式通常就是把它添加为一个远端仓库。 执行 git remote add [alias] [url]
就可以。 此命令将 [url]
以 [alias]
的别名添加为本地的远端仓库。
例如,假设我们想要与整个世界分享我们的 Hello World 程序。 我们可以在一台服务器上创建一个新仓库(我以 GitHub 为例子)。 它应该会给你一个链接,在这里就是“[email protected]:schacon/hw.git”。 要把它添加到我们的项目以便我们推送以及获取更新,我们可以这样:
$ git remote $ git remote add github [email protected]:schacon/hw.git $ git remote -v github [email protected]:schacon/hw.git (fetch) github [email protected]:schacon/hw.git (push)
像分支的命名一样,远端仓库的别名是强制的 —— 就像“master”,没有特别意义,但它广为使用, 因为 git init
默认用它;“origin”经常被用作远端仓库别名,就因为 git clone
默认用它作为克隆自的链接的别名。此例中,我决定给我的远端仓库取名“github”,但我叫它随便什么都可以。
Git addeth and Git taketh away. 如果你需要删除一个远端 —— 不再需要它了、项目已经没了,等等 —— 你可以使用 git remote rm [alias]
把它删掉。
$ git remote -v github [email protected]:schacon/hw.git (fetch) github [email protected]:schacon/hw.git (push) $ git remote add origin git://github.com/pjhyett/hw.git $ git remote -v github [email protected]:schacon/hw.git (fetch) github [email protected]:schacon/hw.git (push) origin git://github.com/pjhyett/hw.git (fetch) origin git://github.com/pjhyett/hw.git (push) $ git remote rm origin $ git remote -v github [email protected]:schacon/hw.git (fetch) github [email protected]:schacon/hw.git (push)
简而言之 你可以用 git remote
列出你的远端仓库和那些仓库的链接。 你可以使用 git remote add
添加新的远端仓库,用 git remote rm
删掉已存在的那些。
Git 有两个命令用来从某一远端仓库更新。 git fetch
会使你与另一仓库同步,提取你本地所没有的数据,为你在同步时的该远端的每一分支提供书签。 这些分支被叫做“远端分支”,除了 Git 不允许你检出(切换到该分支)之外,跟本地分支没区别 —— 你可以将它们合并到当前分支,与其他分支作比较差异,查看那些分支的历史日志,等等。同步之后你就可以在本地操作这些。
第二个会从远端服务器提取新数据的命令是 git pull
。 基本上,该命令就是在 git fetch
之后紧接着 git merge
远端分支到你所在的任意分支。 我个人不太喜欢这命令 —— 我更喜欢 fetch
和 merge
分开来做。少点魔法,少点问题。 不过,如果你喜欢这主意,你可以看一下 git pull
的 官方文档。
假设你配置好了一个远端,并且你想要提取更新,你可以首先执行 git fetch [alias]
告诉 Git 去获取它有你没有的数据,然后你可以执行 git merge [alias]/[branch]
以将服务器上的任何更新(假设有人这时候推送到服务器了)合并到你的当前分支。 那么,如果我是与两三个其他人合作 Hello World 项目,并且想要将我最近连接之后的所有改动拿过来,我可以这么做:
$ git fetch github
remote: Counting objects: 4006, done.
remote: Compressing objects: 100% (1322/1322), done.
remote: Total 2783 (delta 1526), reused 2587 (delta 1387)
Receiving objects: 100% (2783/2783), 1.23 MiB | 10 KiB/s, done.
Resolving deltas: 100% (1526/1526), completed with 387 local objects.
From github.com:schacon/hw
8e29b09..c7c5a10 master -> github/master
0709fdc..d4ccf73 c-langs -> github/c-langs
6684f82..ae06d2b java -> github/java
* [new branch] ada -> github/ada
* [new branch] lisp -> github/lisp
可以看到自从上一次与远端仓库同步以后,又新赠或更新了五个分支。 “ada”与“lisp”分支是新的,而“master”、“clang”与“java”分支则被更新了。 在此例中,我的团队在合并入主分支之前,将提议的更新推送到远端分支以审核。
你可以看到 Git 做的映射。远端仓库的主分支成为了本地的一个叫做“github/master”的分支。 这样我就可以执行 git merge github/master
将远端的主分支和并入我的本地主分支。 或者,我可以 git log github/master ^master
看看该分支上的新提交。 如果你的远端仓库叫做“origin”,那远端主分支就会叫做 origin/master
。几乎所有能在本地分支上执行的命令都可以在远端分支上用。
如果你有多个远端仓库,你可以执行 git fetch [alias]
提取特定的远端仓库, 或者执行 git fetch --all
告诉 Git 同步所有的远端仓库。
简而言之 执行 git fetch [alias]
来将你的仓库与远端仓库同步,提取所有它独有的数据到本地分支以合并或者怎样。
想要与他人分享你牛鼻的提交,你需要将改动推送到远端仓库。 执行 git push [alias] [branch]
,就会将你的 [branch] 分支推送成为 [alias] 远端上的 [branch] 分支。 让我们试试推送我们的主分支到先前添加的“github”远端仓库上去。
$ git push github master Counting objects: 25, done. Delta compression using up to 2 threads. Compressing objects: 100% (25/25), done. Writing objects: 100% (25/25), 2.43 KiB, done. Total 25 (delta 4), reused 0 (delta 0) To [email protected]:schacon/hw.git * [new branch] master -> master
挺简单。现在如果有人从该仓库克隆,他会得到我提交的完完全全的一份历史记录了。
如果有个像之前创建的“erlang”分支那样的主题分支,想只分享这个,该怎么办呢?你可以相应的只推送该分支。
$ git push github erlang Counting objects: 7, done. Delta compression using up to 2 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 652 bytes, done. Total 6 (delta 1), reused 0 (delta 0) To [email protected]:schacon/hw.git * [new branch] erlang -> erlang
现在当人们从该仓库克隆时,他们就会得到一个“erlang”分支以查阅、合并。 用这种方式,你可以推送任何分支到任何你有写权限的仓库。 如果你的分支已经在该仓库中了,它会试着去更新,如果它不再,Git 会把它加上。
最后一个当你推送到远端分支时会碰到的主要问题是,其他人在此期间也推送了的情况。 如果你和另一个开发者同时克隆了,又都有提交,那么当她推送后你也想推送时,默认情况下 Git 不会让你覆盖她的改动。 相反的,它会在你试图推送的分支上执行 git log
,确定它能够在你的推送分支的历史记录中看到服务器分支的当前进度。 如果它在在你的历史记录中看不到,它就会下结论说你过时了,并打回你的推送。 你需要正式提取、合并,然后再次推送 —— 以确定你把她的改动也考虑在内了。
当你试图推送到某个以被更新的远端分支时,会出现下面这种情况:
$ git push github master To [email protected]:schacon/hw.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '[email protected]: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 服务器会拒绝你的推送,知道你是最新的为止。
Git 的工作就是创建和保存你的项目的快照及与之后的快照进行对比。本章将对有关创建与提交你的项目的快照的命令作介绍。
这里有个重要的概念,Git 有一个叫做“索引”的东东,有点像是你的快照的缓存区。这就使你能够从更改的文件中创建出一系列组织良好的快照,而不是一次提交所有的更改。
简而言之,使用 git add
添加需要追踪的新文件和待提交的更改, 然后使用 git status
和 git diff
查看有何改动, 最后用 git commit
将你的快照记录。这就是你要用的基本流程,绝大部分时候都是这样的。
在 Git 中,在提交你修改的文件之前,你需要把它们添加到缓存。如果该文件是新创建的,你可以执行 git add
将该文件添加到缓存,但是,即使该文件已经被追踪了 —— 也就是说,曾经提交过了 —— 你仍然需要执行 git add 将新更改的文件添加到缓存去。让我们看几个例子:
回到我们的 Hello World 示例,初始化该项目之后,我们就要用 git add
将我们的文件添加进去了。 我们可以用 git status
看看我们的项目的当前状态。
$ git status -s ?? README ?? hello.rb
我们有俩尚未被追踪的文件,得添加一下。
$ git add README hello.rb
现在我们再执行 git status
,就可以看到这俩文件已经加上去了。
$ git status -s A README A hello.rb
新项目中,添加所有文件很普遍,可以在当前工作目录执行命令:git add .
。 因为 Git 会递归地将你执行命令时所在的目录中的所有文件添加上去,所以如果你将当前的工作目录作为参数, 它就会追踪那儿的所有文件了。如此,git add .
就和 git add README hello.rb
有一样的效果。 此外,效果一致的还有 git add *
,不过那只是因为我们这还木有子目录,不需要递归地添加新文件。
好了,现在我们改个文件,再跑一下 git status
,有点古怪。
$ vim README $ git status -s AM README A hello.rb
“AM” 状态的意思是,这个文件在我们将它添加到缓存之后又有改动。这意味着如果我们现在提交快照, 我们记录的将是上次跑 git add
的时候的文件版本,而不是现在在磁盘中的这个。 Git 并不认为磁盘中的文件与你想快照的文件必须是一致的 —— (如果你需要它们一致,)得用 git add
命令告诉它。
一言以蔽之, 当你要将你的修改包含在即将提交的快照里的时候,执行 git add
。 任何你没有添加的改动都不会被包含在内 —— 这意味着你可以比绝大多数其他源代码版本控制系统更精确地归置你的快照。
请查看《Pro Git》中 git add
的 “-p” 参数,以了解更多关于提交文件的灵活性的例子。
正如你在 git add
小节中所看到的,你可以执行 git status
命令查看你的代码在缓存与当前工作目录的状态。我演示该命令的时候加了 -s
参数,以获得简短的结果输出。 若没有这个标记,命令 git status
将告诉你更多的提示与上下文欣喜。 以下便是同样状态下,有跟没有 -s
参数的输出对比。简短的输出如下:
$ git status -s AM README A hello.rb
而同样的状态,详细的输出看起来是这样的:
$ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: README # new file: hello.rb # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: README #
你很容易发现简短的输出看起来很紧凑。而详细输出则很有帮助,提示你可以用何种命令完成你接下来可能要做的事情。
Git 还会告诉你在你上次提交之后,有哪些文件被删除、修改或者存入缓存了。
$ git status -s M README D hello.rb
你可以看到,在简短输出中,有两栏。第一栏是缓存的,第二栏则是工作目录的。 所以假设你临时提交了 README 文件,然后又改了些,并且没有执行 git add
,你会看到这个:
$ git status -s MM README D hello.rb
一言以蔽之,执行 git status
以查看在你上次提交之后有啥被修改或者临时提交了, 从而决定自己是否需要提交一次快照,同时也能知道有什么改变被记录进去了。
git diff
有两个主要的应用场景。我们将在此介绍其一, 在 检阅与对照 一章中,我们将介绍其二。 我们这里介绍的方式是用此命令描述已临时提交的或者已修改但尚未提交的改动。
如果没有其他参数,git diff
会以规范化的 diff 格式(一个补丁)显示自从你上次提交快照之后尚未缓存的所有更改。
$ vim hello.rb $ git status -s M hello.rb $ git diff diff --git a/hello.rb b/hello.rb index d62ac43..8d15d50 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "hello world" + puts "hola mundo" end end
所以,git status
显示你上次提交更新至后所更改或者写入缓存的改动, 而 git diff
一行一行地显示这些改动具体是啥。 通常执行完 git status
之后接着跑一下 git diff
是个好习惯。
git diff --cached
命令会告诉你有哪些内容已经写入缓存了。 也就是说,此命令显示的是接下来要写入快照的内容。所以,如果你将上述示例中的 hello.rb
写入缓存,因为 git diff
显示的是尚未缓存的改动,所以在此执行它不会显示任何信息。
$ git status -s M hello.rb $ git add hello.rb $ git status -s M hello.rb $ git diff $
如果你想看看已缓存的改动,你需要执行的是 git diff --cached
。
$ git status -s M hello.rb $ git diff $ $ git diff --cached diff --git a/hello.rb b/hello.rb index d62ac43..8d15d50 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "hello world" + puts "hola mundo" end end
如果你想一并查看已缓存的与未缓存的改动,可以执行 git diff HEAD
—— 也就是说你要看到的是工作目录与上一次提交的更新的区别,无视缓存。 假设我们又改了些 ruby.rb
的内容,那缓存的与未缓存的改动我们就都有了。 以上三个 diff
命令的结果如下:
$ vim hello.rb $ git diff diff --git a/hello.rb b/hello.rb index 4f40006..2ae9ba4 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld + # says hello def self.hello puts "hola mundo" end end $ git diff --cached diff --git a/hello.rb b/hello.rb index 2aabb6e..4f40006 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "hello world" + puts "hola mundo" end end $ git diff HEAD diff --git a/hello.rb b/hello.rb index 2aabb6e..2ae9ba4 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,8 @@ class HelloWorld + # says hello def self.hello - puts "hello world" + puts "hola mundo" end end
如果我们不想要看整个 diff 输出,但是又想比 git status
详细点, 就可以用 --stat
选项。该选项使它显示摘要而非全文。上文示例在使用 --stat
选项时,输出如下:
$ git status -s MM hello.rb $ git diff --stat hello.rb | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ git diff --cached --stat hello.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) $ git diff HEAD --stat hello.rb | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
你还可以在上述命令后面制定一个目录,从而只查看特定文件或子目录的 diff
输出。
简而言之, 执行 git diff
来查看执行 git status
的结果的详细信息 —— 一行一行地显示这些文件是如何被修改或写入缓存的。
现在你使用 git add
命令将想要快照的内容写入了缓存, 执行 git commit
就将它实际存储快照了。 Git 为你的每一个提交都记录你的名字与电子邮箱地址,所以第一步是告诉 Git 这些都是啥。
$ git config --global user.name 'Your Name'
$ git config --global user.email [email protected]
让我们写入缓存,并提交对 hello.rb
的所有改动。在首个例子中,我们使用 -m
选项以在命令行中提供提交注释。
$ git add hello.rb $ git status -s M hello.rb $ git commit -m 'my hola mundo changes' [master 68aa034] my hola mundo changes 1 files changed, 2 insertions(+), 1 deletions(-)
现在我们已经记录了快照。如果我们再执行 git status
,会看到我们有一个“干净的工作目录”。 这意味着我们在最近一次提交之后,没有做任何改动 —— 在我们的项目中没有未快照的工作。
$ git status
# On branch master
nothing to commit (working directory clean)
如果你漏掉了 -m
选项,Git 会尝试为你打开一个编辑器以填写提交信息。 如果 Git 在你对它的配置中找不到相关信息,默认会打开 vim
。屏幕会像这样:
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: hello.rb # ~ ~ ".git/COMMIT_EDITMSG" 9L, 257C
在此,你在文件头部添加实际的提交信息。以“#”开头的行都会被无视 ——Git 将 git status
的输出结果放在那儿以提示你都改了、缓存了啥。
通常,撰写良好的提交信息是很重要的。以开放源代码项目为例,多多少少以以下格式写你的提示消息是个不成文的规定:
简短的关于改动的总结(25个字或者更少) 如果有必要,更详细的解释文字。约 36 字时换行。在某些情况下, 第一行会被作为电子邮件的开头,而剩余的则会作为邮件内容。 将小结从内容隔开的空行是至关重要的(除非你没有内容); 如果这两个待在一起,有些 git 工具会犯迷糊。 空行之后是更多的段落。 - 列表也可以 - 通常使用连字符(-)或者星号(*)来标记列表,前面有个空格, 在列表项之间有空行,不过这些约定也会有些变化。 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: hello.rb # ~ ~ ~ ".git/COMMIT_EDITMSG" 25L, 884C written
提交注解是很重要的。因为 Git 很大一部分能耐就是它在组织本地提交和与他人分享的弹性, 它很给力地能够让你为逻辑独立的改变写三到四条提交注解,以便你的工作被同仁审阅。因为提交与推送改动是有区别的, 请务必花时间将各个逻辑独立的改动放到另外一个提交,并附上一份良好的提交注解, 以使与你合作的人能够方便地了解你所做的,以及你为何要这么做。
如果你觉得 git add
提交缓存的流程太过繁琐,Git 也允许你用 -a
选项跳过这一步。 基本上这句话的意思就是,为任何已有记录的文件执行 git add
—— 也就是说,任何在你最近的提交中已经存在,并且之后被修改的文件。 这让你能够用更 Subversion 方式的流程,修改些文件,然后想要快照所有所做的改动的时候执行 git commit -a
。 不过你仍然需要执行 git add
来添加新文件,就像 Subversion 一样。
$ vim hello.rb $ git status -s M hello.rb $ git commit -m 'changes to hello file' # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: hello.rb # no changes added to commit (use "git add" and/or "git commit -a") $ git commit -am 'changes to hello file' [master 78b2670] changes to hello file 1 files changed, 2 insertions(+), 1 deletions(-)
注意,如果你不缓存改动,直接执行 git commit
,Git 会直接给出 git status
命令的输出,提醒你啥也没缓存。我已将该消息中的重要部分高亮,它说没有添加需要提交的缓存。 如果你使用 -a
,它会缓存并提交每个改动(不含新文件)。
现在你就完成了整个快照的流程 ——改些文件,然后用 git add
将要提交的改动提交到缓存, 用 git status
和 git diff
看看你都改了啥,最后 git commit
永久地保存快照。
简而言之,执行 git commit
记录缓存区的快照。如果需要的话,这个快照可以用来做比较、共享以及恢复。
git reset
可能是人类写的最费解的命令了。 我用 Git 有些年头了,甚至还写了本书,但有的时候还是会搞不清它会做什么。 所以,我只说三个明确的,通常有用的调用。请你跟我一样尽管用它 —— 因为它可以很有用。
在此例中,我们可以用它来将不小心缓存的东东取消缓存。假设你修改了两个文件,想要将它们记录到两个不同的提交中去。 你应该缓存并提交一个,再缓存并提交另外一个。如果你不小心两个都缓存了,那要如何才能取消缓存呢? 你可以用 git reset HEAD -- file
。 技术上说,在这里你不需要使用 --
—— 它用来告诉 Git 这时你已经不再列选项,剩下的是文件路径了。 不过养成使用它分隔选项与路径的习惯很重要,即使在你可能并不需要的时候。
好,让我们看看取消缓存是什么样子的。这里我们有两个最近提交之后又有所改动的文件。我们将两个都缓存,并取消缓存其中一个。
$ git status -s M README M hello.rb $ git add . $ git status -s M README M hello.rb $ git reset HEAD -- hello.rb Unstaged changes after reset: M hello.rb $ git status -s M README M hello.rb
现在你执行 git commit
将只记录 README
文件的改动,并不含现在并不在缓存中的 hello.rb
。
如果你好奇,它实际的操作是将该文件在“索引”中的校验和重置为最近一次提交中的值。 git add
会计算一个文件的校验和,将它添加到“索引”中, 而 git reset HEAD
将它改写回原先的,从而取消缓存操作。
如果你想直接执行 git unstage
,你可以在 Git 中配置个别名。 执行 git config --global alias.unstage "reset HEAD"
即可。 一旦执行完它,你就可以直接用 git unstage [file]
作为代替了。
如果你忘了取消缓存的命令,Git 的常规 git status
输出的提示会很有帮助。 例如,在你有已缓存的文件时,如果你不带 -s
执行 git status
,它将告诉你怎样取消缓存:
$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: README # modified: hello.rb #
简而言之,执行 git reset HEAD
以取消之前 git add
添加,但不希望包含在下一提交快照中的缓存。
git rm
会将条目从缓存区中移除。这与 git reset HEAD
将条目取消缓存是有区别的。 “取消缓存”的意思就是将缓存区恢复为我们做出修改之前的样子。 在另一方面,git rm
则将该文件彻底从缓存区踢出,因此它不再下一个提交快照之内,进而有效地删除它。
默认情况下,git rm file
会将文件从缓存区和你的硬盘中(工作目录)删除。 如果要在工作目录中留着该文件,可以使用 git rm --cached
不像绝大多数其他版本控制系统,Git 并不记录记录文件重命名。它反而只记录快照,并对比快照以找到有啥文件可能被重命名了。 如果一个文件从更新中删除了,而在下次快照中新添加的另一个文件的内容与它很相似,Git 就知道这极有可能是个重命名。 因此,虽然有 git mv
命令,但它有点多余 —— 它做得所有事情就是 git rm --cached
, 重命名磁盘上的文件,然后再执行 git add
把新文件添加到缓存区。 你并不需要用它,不过如果觉得这样容易些,尽管用吧。
我自己并不使用此命令的普通形式 —— 删除文件。通常直接从硬盘删除文件,然后执行 git commit -a
会简单些。 它会自动将删除的文件从索引中移除。
简而言之, 执行 git rm
来删除 Git 追踪的文件。它还会删除你的工作目录中的相应文件。