Liu's Blog

Git使用

字数统计: 3.6k阅读时长: 14 min
2019/10/07 Share

Git使用

Git是非常好用的版本控制工具,可以用于代码文件的版本管理,创建分支进行新功能的开发和Bug修复,也能用于多人协作的工作当中。

初始设置

注: 为避免字符问题,文件夹命名避免使用中文。

  1. 添加用户信息
    为便于多人协作时查看每个人的提交信息,一般添加用户名和Email,操作如下。
    $ git config --global user.name "Liu Jiting"
    $ git config --global user.email "laujiting@qq.com"
  2. Git初始化
    创建一个空文件夹作为工程目录,进入,然后:
    $ git init//会在当前目录下创建隐藏文件夹.git是版本库,存放git文件,记录修改
    初始化后,当前文件夹内的文件变化都可以使用git记录。

    日常使用

注1:创建的空文件夹除.git文件夹外,都称作工作区,还有暂存区(stage),在.git文件夹内,用于保存git add但未git commit的文件;
注2:git commit会将暂存区的文件提交并清空,工作区会与版本库保持一致,暂存区也是空的。

  1. 文件管理
    $ git add . //添加所有文件
    $ git add <file 1> <file 2> <...> //添加单文件
  2. 查看版本库状态和变化
    $ git status //显示当前仓库状态
    $ git diff //比较工作区和暂存区的差别,存在差别则会显示不同之处
  3. 提交
    $ git commit -m "Describe what you do in this git commit"
  4. 查看提交记录信息
    $ git log //查看提交记录
    $ git log --oneline //print info (commit id, description)
    $ git log --oneline --graph //可显示分支合并信息
  5. 版本回退和跳转
    $ git reset --hard HEAD //跳至最新的版本
    $ git reset --hard HEAD^ //or HEAD^^上上个版本;HEAD~100表示上100个版本
    # git reset --hard <commit id> //回退到某一版本,可以是未来版本(当前git bash/cmd没关闭的话)
    $ git reflog //记录每一次命令,若回退到了之前某个版本,提交之后未来版本消失,可以这样查找未来版本的commit id
  6. 单文件的回退
    $ git checkout -- <file> //回退单文件到最近的commit或是add状态,工作区清空
    ¥ git reset HEAD <file> //回退到最近一次提交,暂存区清空
    以上两者依次各用一次,回退到最近的commit处并清空暂存区和工作区
  7. 删除文件和恢复
    $ git rm <file> //版本库里删除该文件,在此之前或之后应手动删除文件夹里的该文件,再git commit提交这个删除操作的改动
    若是删除错了文件,那么想到已提交的版本库里有这个文件,所以可以选择checkout恢复,撤销刚才的修改(删除):
    $ git checkout -- <file> //从版本库恢复
  8. 标签
    解决commit id难记的问题,像域名之于IP地址。
    $ git tag v1.0 [<commit id>] //设置标签
    $ git tag //查看所有标签
    $ git tag -d <tag> //删除标签
    其他指令:
    $ git show <tag> //查看标签信息
    $ git tag -a <tag> -m "version <tag> released" <commit id> //-a指定标签名,-m指定说明文字
    标签默认不推送到远程,需要手动推送:
    $ git push origin --tags //全部标签
    $ git push origin <tag> //单个标签
    删除远程的标签,需要先从本地删除,再push,push的参数有变化:
    $ git tag -d <tag> //删除本地标签
    $ git push origin :refs/tags/<tag> //删除远端标签

    多分支和协作

注:版本管理和分支的图解
git的版本管理可以理解为指针的移动。两个指针HEAD和master,HEAD指向当前分支,而master指向的是最新的commit。每次提交,master将向前移动一次,始终指向最新的commit,HEAD没有变化,因为它默认指向master。

4e1cf03610b5ba6d17503dc027246865.png
创建新的分支dev,相当于一个新的指针dev,初始化时指向与master相同的提交;HEAD默认不变,需手动修改指向。
切换至dev分支后,所有的修改都在dev上记录,提交也只影响dev,不影响master。
合并时若master没有变化,即dev创建时的commit与master指向的相同,那么合并时master指针将直接移至dev指向的提交;随后也可以输入指令删除dev分支。此处的master指针移动是”Fast-forward“模式。
若master发生过提交,

  1. 创建分支

  2. 查看分支及当前指向
    $ git branch //输出的信息中,*表示当前所在分支

  3. 切换分支
    $ git checkout -b <branch name> //创建并切换分支
    $ git checkout <branch name> //切换至已有分支
    $ git switch -c <branch name> //-c表示创建,此条即创建并切换分支
    $ git switch <branch name> //切换至已有分支

  4. merge合并分支
    默认使用”Fast-forward“模式合并:
    $ git merge <branch name> //合并分支到当前(指向的)分支

  5. rebase合并分支(适用于存在冲突)
    此时的分支情况如下图所示:
    1a58a91ef4e2fec429a4b316be92bf51.png
    两个分支都有新的提交,需要手动修改代码解除冲突。此时可以使用git status查看冲突信息,它会标出冲突代码的位置。
    6d53f79d371f24b1093b2bd8d27bc833.png
    我们可以自由选择,切换到其中一个分支上去修改冲突代码(一般都是修改master分支),确保一致后再提交,合并分支情况如下:
    1834c3ce61238a208a8f90aeceafe6b5.png
    此后可以删除dev分支。

  6. rebase合并分支
    rebase分支合并的适用场景:master上有一些小更新(bug fix),dev上我正在做新功能开发,现在想把master的更新合并到我这里,避免以后merge到主分支上时再修改冲突。(rebase可以直观理解为重新确立基准代码库)
    $ git rebase <branch name>[, <HEAD branch>]

  7. 删除分支
    $ git branch -d <branch name> //-d delete
    $ git branch -D <branch name> //-D 强制删除

  8. 临时修改
    适用场景:存在master和dev分支,我在dev分支上的改动还没提交,需要处理另外分支的功能。
    $ git checkout dev
    $ git stash //暂存当前分支的改动
    $ git checkout -b new //在新分支上处理
    //Do something else
    $ git checkout dev
    $ git stash pop //取出未完成的工作到工作区,删除stash中保存的内容
    //Continue the work…
    另外,还有git stash list可以查看暂存的工作。
    $ git stash pop等价于执行:
    $ git stash apply stash@{$n}
    $ git stash drop stash@{$n}
    清除所有缓存的stash:
    $ git stash clear

  9. 复制特定的提交到当前分支
    $ git cherry-pick <commit id>

    Github使用

    使用Github托管代码仓库,可以方便地实现多人协作编程。一般地工作流程为:Github上托管代码仓库,根据实际情况创建master和dev分支;团队的每个人clone代码到本地后在各自的dev分支上进行开发和提交,功能完成并测试通过后,再将dev合并到master分支上,master作为稳定版本代码发布出去。

  10. 初始化
    本机与Github远程传输使用SSH加密传输,因此需先配置SSH公钥到Github服务器。创建SSH Key命令如下:
    $ ssh-keygen -t rsa -C "<your github account@mail.com>"
    创建的密钥文件在用户目录下的.ssh文件夹内,其中id_rsa是私钥,不能泄露;id_rsa.pub是公钥,放在需要的服务器上即可验证私钥,确认你的身份。
    接下来即是在Github上添加你的公钥。找到Github-Account Settings-SSH Keys添加公钥。

    注:可以在多个电脑上生成密钥并上传到Github,如公司和家里的电脑分别上传上去,即可方便地在两台电脑上同步代码了。

  11. 连接远程代码仓库
    在连接之前需要在Github上创建代码仓库(目前私有仓库也免费了)。随后即可在本地关联到远程仓库:
    $ git remote add origin git@github.com:<your name>/<repo name>.git //注意替换为自己的账号/名称/仓库名

    注:输入的是其他人的仓库也能建立关联,但由于密钥不匹配,你无法上传代码过去。

  12. 推送本地代码到远端
    第一次推送使用-u命令可以让本地master分支与远程的新master分支关联:
    $ git push -u origin master //推送本地分支master到远端,也可以改为其他分支
    此后的推送只需:
    $ git push origin master

    注1:将代码推送到远端和版本控制并不冲突,git在本地保存你每次的commit记录,进行版本管理,可以随时查阅变动和提交修改;git push将你的commit同步到远端,只需在有网的时候git push和git pull即可。
    注2:如果你克隆的代码库是别人的,不会有推送修改的权限,最好先fork别人的库到你的账户中,再克隆到本地,修改也将推送到你的账户fork的库中。
    注3:若你修复了别人库中的bug或增加了功能,可以发起pull request。

  13. 从远端克隆代码到本地
    对于一个新项目,建议先建立远端的代码库,再从远程库克隆。
    克隆指令(会在当前目录下新建名为的文件夹,存放远程克隆的文件):
    $ git clone git@github.com:<your name>/<repo name>.git //确保地址是某一repo的.git文件
    另外的地址还有:https://github.com/<your name>/<repo name>.git,但是https速度较慢,还需验证口令,较为麻烦;git://开头的地址默认使用SSH协议。

  14. 查看远程仓库信息
    $ git remote -v

  15. 冲突合并(不使用rebase)
    适用场景:远端分支有新提交,我推送的提交提示冲突,需要在本地合并解决冲突,再推送。
    $ git pull <remote> <branch name>
    直接pull存在问题,要再建立本地分支和远程分支的链接:
    $ git branch --set-upstream-to=origin/<branch> <local branch>
    $ git pull <remote> <branch name>
    由于远端代码比本地的新,pull能成功但如果存在冲突,则解决冲突,提交,再push
    push也可能提示未建立本地和远程的链接,手动建立:
    $ git branch --set-upstream-to <branch-name> origin/<branch-name>

  16. Github配置

    • 忽略文件
      部分包含敏感信息的文件,如MySQL账户信息等等,也会在工程目录中。可以在工作区的根目录下创建.gitignore文件,往里面添加文件名即可忽略这些文件。
      文件写法可以参考这里
      想添加.gitignore已经忽略的文件,可以-f强制添加:
      $ git add -f App.class
      检查.ignore是否有错:
      $ git check-ignore -v App.class
    • 配置别名
      git status配置别名git st
      $ git config --global alias.st status //global表示所有仓库都可用这个别名,带空格的或较长的参数可以使用"reset HEAD"引起来设置别名。
      神奇别名:git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
      效果如下:
      7d73e6373e752dca329c088435c83ad3.png
      别名存放在配置文件中,加--global对当前用户所有仓库均起作用,存放于用户主目录下的.gitconfig;不添加则只对当前仓库起作用,在当前仓库的.git/config中。
    • 搭建Git服务器
      参见搭建Git服务器 - 廖雪峰个人博客

      码云

      码云是国内的一个Git托管服务,5人以下的小团队免费使用,访问速度快于Github,还集成了代码质量检测/项目演示等功能。
  • 码云的使用方式和Github类似,先添加SSH公钥,远程库的地址一般格式:
    git@gitee.com:<your name>/<repo>.git
  • 删除远程库:
    $ git remote rm origin
  • 关联多个库:
    对于本地代码库关联到了多个远程库,如Github和码云都关联了,就需要起不同名称,如将origin换成其他名字githubgitee等等。

    参考资料

  1. Git使用教程 - 莫烦Python
  2. Git教程 - 廖雪峰个人博客
  3. 这一次彻底搞懂 Git Rebase

    Note

    2019/09/29

  4. 分支合并时commit info的合并是怎么处理的还不太清楚,比如参考资料3提到的情况,这个需要了解。
  5. 实践上,多次低信息量的commit经常需要合并为一个commit,应该怎么操作。
  6. 合并分支提示冲突后,如果并没修改冲突代码就再次提交,也能成功,但代码能运行吗。
  7. “Fast-forward”模式合并后删除分支,会丢失分支信息,想要保留分支信息怎么做。
    答:$ git merge --no-ff -m "no fast-forward",这样会创建一个新的commit并添加描述。
    ff模式下的合并如下图所示:
    8fe11230aee0bb8042dfa5c71e49bcc7.png
    禁用ff模式的合并如下图所示:
    752c5967f06967c1dcaa09d807232d9f.png
  8. 实际开发时的分支策略是怎么样的(最佳实践)。
    答:master确保稳定,发布新版本;dev开发新功能;每个人从dev创建新的分支进行自己的开发,完成后合并到dev上。多人协作开发也需先合并到dev确保正常运行后,再合并到master。如下图所示:
    3529ad989f40a315c223e29de0a19b35.png
    根据需要还可以设置Bug分支Feature分支
    实际开发中建议使用--no-ff确保分支信息不丢失。
  9. git diff 默认不加参数,加参数–cached和HEAD的输出有哪些差别。

2019/10/06

在多机器共同使用同一个repo时,最所有机器均先从远程库(Github Repo)克隆,再开发,这样可以避免push代码时提示:
Updates were rejected because the remote contains work that you do not have locally.
即是出现了这样的问题也不用慌。第一种方法,此时在另一目录下克隆远程库,将本地代码放入,再提交,push。
第二种方法,使用这个强制的方法:
git pull origin master --allow-unrelated-histories
把远程代码拖入本地,再把两段不相干的分支进行强行合并,随后再push即可。加入参数--allow-unrelated-histories,可以避免拉回本地合并时出现:fatal: refusing to merge unrelated histories的情况。

CATALOG
  1. 1. Git使用
    1. 1.1. 初始设置
    2. 1.2. 日常使用
    3. 1.3. 多分支和协作
    4. 1.4. Github使用
    5. 1.5. 码云
    6. 1.6. 参考资料
    7. 1.7. Note
      1. 1.7.1. 2019/09/29
      2. 1.7.2. 2019/10/06