GIT简单操作命令
以下操作都是在Ubuntu上进行的。
以下内容转自廖雪峰的网站
- 安装git
sudo apt-get install git - 创建版本库
版本库又名仓库,英文名responsitory,你可以简单理解成一个目录,这个目录里面的所有的文件都可以被Git管理起来,每个文件的修改、删除、Git都能跟踪,以便任何时刻都可以追踪历史,或者将来某个时刻可以“还原”。
首先,选择一个合适的地方,创建一个空目录:
$ mkdir learngit # 创建一个名为learngit的目录
$ cd learngit # 进入该文件夹
$ pwd # 显示文件夹的路径
- 把目录变成Git可以管理的仓库
$ git init
$ ls -ah # 查看隐藏文件,发现有.git的目录
- 将文件添加到版本库
- 首先编写一个readme.txt的文件,内容如下:
Git is a version control system
Git is free software
一定放到learngit目录文件下(子目录也行)
2. 用命令git add 告诉Git,把文件添加到仓库:
$ git add readme.txt
执行上面命令没有任何显示
3. 用git commit 告诉Git,把文件提交到仓库:
$ git commit -m "wrote a readme file"
[master (root-commit) eaadf4e] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
简单解释一下git commit命令,-m后面输入的是本次提交的说明,可以输入任意内容,当然最好是有意义的,这样你就能从历史记录里方便地找到改动记录。
嫌麻烦不想输入-m "xxx"行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入说明对自己对别人阅读都很重要。实在不想输入说明的童鞋请自行Google,我不告诉你这个参数。
git commit命令执行成功后会告诉你,1 file changed:1个文件被改动(我们新添加的readme.txt文件);2 insertions:插入了两行内容(readme.txt有两行内容)。
为什么Git添加文件需要add,commit一共两步呢?因为commit可以一次提交很多文件,所以你可以多次add不同的文件,比如:
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
- git status
之前我写了readme文件,假设我现在要将它修改,改成如下内容:
Git is a distributed version control system.
Git is free software.
现在运行git status 命令看看结果:
$ git status
On branch 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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,readme.txt被修改过了,但还没有准备提交的修改。
- git diff
虽然Git告诉我们readme.txt被修改了,但如果能看看具体修改了什么内容,自然是很好的。比如你休假两周从国外回来,第一天上班时,已经记不清上次怎么修改的readme.txt,所以,需要用git diff这个命令看看:
$ git diff readme.txt
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
- git log
git log 查看我们修改的历史记录。
如果嫌输出信息太多,看得眼花缭乱,可以试一试加上 --pretty=oneline参数
$ git log --pretty=oneline
1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master) append GPL
e475afc93c209a690c39c13a46716e8fa000c366 add distributed
eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0 wrote a readme file
需要友情提示的是,你看到的一大串类似1094adb…的是commit id(版本号),和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准。为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,就可以更清楚地看到提交历史的时间线:
- git reset --hard HEAD
将当前版本回退到之前的一个版本就用,git reset --hard HEAD
$ git reset --hard HEAD
$ cat readme.txt
Git is a distributed version control system.
Git is free software.
还原到之前版本了。
但是好像还原之后就看不到之前那个版本了,那么现在怎么办呢?
其实办法还是有的,只要上面的命令窗口没有关闭,你就可以找到上面找到之前的那个文件的id, commit id 是 d5590
commit d55901f3301ac21bbedf1a51eb81f7c6a992a6e0 (HEAD -> master)
Author: Faithe-LIZ <29885328@qq.com>
Date: Tue Apr 30 20:45:29 2019 +0800
append GPL
commit 597b2765a8e88de772feea425b674b025cb65aaf
Author: Faithe-LIZ <29885328@qq.com>
Date: Tue Apr 30 15:33:50 2019 +0800
add distributed
commit d445626047799c11685097aa1cddc15ac66b69da
Author: Faithe-LIZ <29885328@qq.com>
Date: Tue Apr 30 14:49:11 2019 +0800
wrote a readme file
现在我们指定回到之前的版本:
$ git reset --hard d5590 # 没有必要写全,写前几位GIT会自动去查找
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL
GIT版本回退速度非常快,以为GIt在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git只是把HEAD的指向变了:
┌────┐
│HEAD│
└────┘
│
└──> ○ append GPL
│
○ add distributed
│
○ wrote a readme file
改成了:
┌────┐
│HEAD│
└────┘
│
│ ○ append GPL
│ │
└──> ○ add distributed
│
○ wrote a readme file
然后顺便把工作区的文件更新了,所以你让HEAD指向哪个版本号,你就把当前版本定位在哪。
- git reflog
如果我们关闭了终端无法查看之前的操作指令怎么办?在Git中也是可以找到commit id 的。
$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file
小结
1. HEAD指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令git reset --hard commit_id。
2. 穿梭前,用git log可以查看提交历史,以便确定要回退到哪个版本。
3. 要重返未来,用git reflog查看命令历史,以便确定要回到未来的哪个版本。
工作区和暂存区
- 工作区(Working Directory)
就是你在电脑里面看到的目录,比如我们之前创建的learngit文件夹就是一个工作区 - 版本库(Repository)
工作区有一个隐藏目录 .git (ubuntu可以用ls -ah查看),这个不算工作区,而是Git的版本库。
Git的版本库里存放了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD。
我们把文件往Git版本库里添加的时候,是分两步进行的:
第一步是git add把文件添加进去,实际上是把文件修改添加到暂存区;
第二步是git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。
因为我们创建Git版本库是,Git自动为我们创建了唯一一个master分支,所有现在git commit就是往master分支上提交更改。
简单理解为,需要提交的文件修改全部放到暂存区,然后一次性提交暂存区的所有修改。
管理修改
现在,假定你已经完全掌握了暂存区的概念。下面,我们要讨论的就是,为什么Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。
你会问,什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
为什么说Git管理的是修改,而不是文件呢?我们还是做实验。第一步,对readme.txt做一个修改,比如加一行内容:
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes.
然后,添加:
$ git add readme.txt
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: readme.txt
#
然后,再修改readme.txt:
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
提交:
$ git commit -m "git tracks changes"
[master 519219b] git tracks changes
1 file changed, 1 insertion(+)
提交后,再看看状态:
$ git status
On branch 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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
咦,怎么第二次的修改没有被提交?
别激动,我们回顾一下操作过程:
第一次修改 -> git add -> 第二次修改 -> git commit
你看,我们前面讲了,Git管理的是修改,当你用git add命令后,在工作区的第一次修改被放入暂存区,准备提交,但是,在工作区的第二次修改并没有放入暂存区,所以,git commit只负责把暂存区的修改提交了,也就是第一次的修改被提交了,第二次的修改不会被提交。
提交后,用git diff HEAD – readme.txt命令可以查看工作区和版本库里面最新版本的区别:
$ git diff HEAD -- readme.txt
diff --git a/readme.txt b/readme.txt
index 76d770f..a9c5755 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,4 @@
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
-Git tracks changes.
+Git tracks changes of files.
撤销修改
当你修改了文件的时候,你发现你修改错误,或者不想提交了。我们可以通过使用 git checkout – filename
下面通过一个例子来解释:
$ cat readme.txt
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
My stupid boss still prefers SVN. # 这一行是我之后添加的
现在我发现错误,想删掉做最后一行,先用git status 查看
$ git status
On branch 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: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
# 这个命令告诉我们, git checkout -- file 可以丢弃工作区的修改
命令 git checkout – readme.txt 的意思就是把readme.txt文件在工作区的修改全部撤销,这里有两种情况:
- 命令git checkout – readme.txt自修改后还没有被放到暂存区,现在撤销修改就回到和版本库一模一样的状态;
- readme.txt已经添加到暂存区后,又做了修改,现在撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一个git commit 或 git add 时的状态。
如果我们修改了,并且使用git add readme.txt 将修改添加到了暂存区,但是还没有提交,我们可以用一下方法:
首先查看一下状态,
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
Git 告诉我们,用命令git reset HEAD 可以把暂存区的修改撤销掉(unstage),重新放到工作区:
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset 命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示新的的版本。
再用git status查看一下,现在暂存区时干净的,工作区有修改。
$ git status
On branch 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: readme.txt
然后现在的情况就和之前第一种情况一样了:
$ git checkout -- readme.txt
$ git status
On branch master
nothing to commit, working tree clean
现在我们回到了修改前的状态
删除
在Git中,删除也是一个修改,我们实战一下,先添加一个新文件test.txt到Git并提交,一般情况下,我们一般直接在电脑中直接通过rm test.txt 删除文件,但是这个时候工作区和版本库就不一样了,工作区已经没有test文件了,但是版本库中有test文件。
现在我们有两种选择,一是确定要删除,接下来我们需要将版本库里的同时删除了,先使用git rm 删掉,再git commit
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
二是我们误删除,删错了。因为版本库里面还有,我们可以很轻松的把误删文件恢复到最新版本:
$ git checkout -- test.txt
git checkout 其实是用版本库里的版本来替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
远程仓库
- 首先创建SSH Key。在用户目录下,看看有没有.SSH目录,如果有再看看有没有id_ras和id_rsa.pub(windows好像不一样,一个是id_rsa,另一个是known_hosts)这两个文件。如果有直接跳过这一步,如果没有就打开shell(Windows下打开Git Bash),创建SSH Key:
$ ssh-keygen -t rsa -C "youremail@example.com"
需要把邮件地址换成你自己的邮件地址,然后一直回车。如果进行顺利的话,你会在用户主目录下找到之前所说的那两个文件,这两个文件就是SSH Key的密钥对,id_rsa是私钥,id_rsa_pub是公钥,这个可以放心给别人。
- 登陆GitHub,打开Account setting,SSH Key页面,然后点击Add SSh Key,填上任意title,在key文本框里粘贴id_rsa文件的内容(Windows粘贴id_rsa_pub里面的,注意是以ssh-rsa开头的一部分)
添加远程仓库
首先,登陆GitHub,然后,在右上角找到“Create a new repo”按钮,创建一个新的仓库:
在Repository name填入learngit,其他保持默认设置,点击“Create repository”按钮,就成功地创建了一个新的Git仓库:
目前,在GitHub上的这个learngit仓库还是空的,GitHub告诉我们,可以从这个仓库克隆出新的仓库,也可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
现在,我们根据GitHub的提示,在本地的learngit仓库下运行命令,执行界面中,Push an existing responsitory from the command line下面两条命令,可以直接复制。
但是我在执行命令的时候报错,
fatal: remote origin already exists.
如果你的问题和我一样,可以试一试下面这个命令,
$ git remote rm origin
然后再输入命令。
其中,
$ git push -u origin master
是将本地库的内容推送到远程,用git push 命令,实际上是把当前分支master推送到远程。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送到远程新的master分支,还会把本地的master分支和远程master分支关联起来,在以后的推送或者拉取时就可以简化命令。
小结
要关联一个远程库,使用命令git remote add origin git@server-name:path/repo-name.git;
关联后,使用命令git push -u origin master第一次推送master分支的所有内容;
此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改;
分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,真是太方便了!
从远程库克隆
之前我们先有了本地库,后有远程库。
现在,假设哦我们从零开发,那么最好的方式是先创建远程库,然后从远程库克隆。
首先,登陆GitHub,创建一个新的仓库,名字叫做gitskills:
我们勾选Initialize this repository with a README,这样GitHub会自动为我们创建一个README.md文件。创建完毕后,可以看到README.md文件:
现在,远程库已经准备好了,下一步是用命令git clone克隆一个本地库:
$ git clone https://github.com/michaelliao/gitskills.git
Cloning into 'gitskills'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
注意把Git库的地址换成你自己的,然后进入gitskills目录看看,已经有README.md文件了.
分支管理
在版本回退里,我们已经知道,每次提交Git都把它们串成一条直线,这条时间线就是分支。截至到目前,只要一条时间线,在Git里,这个分支加主分支,即master分支。HEAD才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
每次提交,master分支都会向前移动一步,这样,随着你不断的提交,master分支的线也就越来越长。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
Git创建分支很快,因为除了新增一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如一个新提交后,dev指针往前移动一步,而master不变。
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一个条master分支:
下面开始实战
首先,我们创建dev分支,然后切换到dev分支;
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout 命令加上 -b 参数表示创建并切换,相当于一下两条命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
然后,用git branch 命令查看当前分支
$ git branch
* dev
master
git branch 命令会列出所有分支,当前分支前会标一个‘*’号。
然后,我们就可以在dev分支上正常提交,比如对readme.py 做个修改,加上一行:
Creating a new branch is quick.
然后提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev b17d20e] branch test
1 file changed, 1 insertion(+)
现在,dev分支的工作完成,我们就可以切回到master 分支:
$ git checkout master
Switched to branch 'master'
切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
现在,我们把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev分支了:
$ git branch -d dev
Deleted branch dev (was b17d20e).
删除后,查看branch,就只剩下master分支了:
$ git branch
* master
小结
Git鼓励大量使用分支:
查看分支:git branch
创建分支:git branch
切换分支:git checkout
创建+切换分支:git checkout -b
合并某分支到当前分支:git merge
删除分支:git branch -d