学习使用Git 版本控制 代码管理

时间:2023-03-08 19:06:45

title: 学习使用Git 版本控制 代码管理

notebook: 经验累积

tags:Git

Git 版本控制 学习教程

Git版本控制器,可以作为程序员、计算机科学和软件工程的研究人员在编写代码、工程开发过程中的文件管理和代码管理的工具。在基本的Git Bash和Git GUI之外,有很多在MacOS、Linux和Windows下的Git 管理工具以可视化的方法辅助版本控制和代码控制。

在我的实际工作和学习中,需要用到Git实现本地代码的版本管理和代码控制,在坚果云上进行代码同步和迁移,使得实验室的台式机和个人笔记本上都有最新版的代码,并且可以记录每天修改和新增代码的进度。

由于javascript代码和py代码的复用场景较多,如果实现新功能需要在既有的代码上修改添加,在逐渐重构的过程中,有一些中间的代码是适合发布在github上的,个人用的代码和文档,特别是研究进度的org文档,最好是保存在自己的云账户中(比如坚果云)。还有,在开始一天的工作前,作为一个非常好的习惯,可以先备份前一天的工作内容,接着做一天的工作。这些场景都需要在本地或云端使用Git。下面说一下我的Git学习和配置过程。

学习Git 版本控制,一个觉得比较不错的网站是githowto.com

比如在 https://githowto.com/what_is_origin 中,介绍了git的remote 仓库中origin是指哪个仓库。

There is nothing so special about the name “origin”, but there is a convention to use it for the primary centralized repository (if any).

Git 的工作流程

1. 克隆Git资源作为工作目录

克隆Git资源应该是开始工作的第一步,使用了Git以后,需要养成的习惯是:永远不要在原资源库上直接修改工程、代码和文档。 目前可以认为,把原资源库只用于代码和文档的托管,比如原资源库是保存在Github上、坚果云上或者是本地的文件仓库中。

  • 创建仓库 git init

    参考runoob上Git创建仓库的介绍

    Git 使用 git init 命令来初始化一个 Git 仓库,Git 的很多命令都需要在 Git 的仓库中运行,所以 git init 是使用 Git 的第一个命令。在执行完成 git init 命令后,Git 仓库会生成一个 .git目录,该目录包含了资源的所有元数据,其他的项目目录保持不变。

    1. 在Git Bash中使用当前目录路径作为Git仓库,我们只需使它初始化。

       git init

      该命令执行完后会在当前目录生成一个.git 目录。

      使用我们制定的目录路径作为Git仓库(Git仓库,是指包括代码、文档和.git目录的整体)。

       git init newrepo

      初始化后,会在 newrepo 目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。

    2. 如果当前目录下有几个文件想要纳入版本控制,需要先用 git add 命令告诉 Git 开始对这些文件进行跟踪,然后提交:

       git add *.c
      git add README
      git add README
      git commit -m '初始化项目版本'

      以上命令将目录下以 .c 结尾及 README 文件提交到仓库中。

  • 创建远程仓库 git init --bare

    bare选项是会在仓库中不显示工作目录,即仓库中的代码、文档都不会显示出来(可能bare就是指这个意思)。也是因为在仓库中没有显示代码、文档等文件,使得在bare仓库中没法编辑和提交更改,也就是说,bare repository只可以用作存储用途。在git init后面加上bare选项,表明这个repository是用来存储的,不是用来开发的

    2018年9月6日:所有的Git 代码配置远程仓库的过程,记录如下:

    • 在 /e/kPro_nutStore/testGit路径下,输入命令git init --bare,创建一个bare repository。

    • 在 /f/kPro/repoSource路径下,输入命令git init,创建一个开发环境repository。

      1. 编译一个新文档 repoSource.txt,保存。

      2. git add repoSource.txt

      3. git commit -m "some message"

      4. git remote add origin /e/kPro_nutStore/testGit

      5. git push --set-upstream origin master

        这时,在第一个*那一步的bare repository testGit中,已经有记录repoSource.txt的信息。

    • 在 /e/kPro_nutStore下新建一个目录repoSource2。

      1. mkdir repoSource2
      2. 在/e/kPro_nutStore路径下 git clone /e/kPro_nutStore/testGit repoSource2
      3. cd repoSource2,可以看到出现了刚刚编辑的repoSOurce.txt文件。
    • 在初始化远程仓库时最好使用git --bare init,而不要使用:git init

  • 克隆仓库 git clone

    以下是runoob上的介绍:

    我们使用 git clone 从现有 Git 仓库中拷贝项目(类似 svn checkout)。克隆仓库的命令格式为:

      git clone "repo"

    如果我们需要克隆到指定的目录,可以使用以下命令格式:

      git clone "repo" "directory"

    执行该命令后,会在当前目录下创建一个名为grit的目录,其中包含一个 .git 的目录,用于保存下载下来的所有版本记录。

    如果要自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:

      $ git clone git://github.com/schacon/grit.git mygrit

    上面的命令是针对托管在Github上的代码仓库进行clone,也可以存储在本地的代码仓库进行clone操作。需要注意的是,在Git Bash中的需要处在原仓库的所在目录路径的上一级位置。比如需要clone以下javascript/tree路径下的tree这个仓库,Git Bash中命令行的当前位置应当是处在javasript这一层。

      git clone tree "directory"

    这里,"directory"应当是已创建的空文件夹。上面的命令完成后,可以在"directory"文件夹内看到原tree文件夹里的代码、文档和.git文件夹。

如果有人修改原仓库了,还可以更新资源

2. 在克隆的资源上添加或修改文件 git add

将对代码和文档的修改,提交到仓库中之前的第一步是git add <代码or文档>。在一次git add之后,如果又对代码or文档有修改或更新,那么还需要git add一次,这样提交到仓库中的才是最新的修改内容。

为了提醒自己,是否有一些对代码和文档的更新没有git add,可以通过git status命令来查看。

git mv

git mv 命令用于移动或重命名一个文件、目录、软连接。 如对README重命名:

$ git mv README  README.md
$ ls
README.md

3. 在提交前查看修改-->提交修改

参考git how to中的介绍,对于一段时间(一小时或一上午的工作),可以一次git add 多个文件,这些文件有逻辑上的相关性,然后git commit一次。接着git add 另外一些文件,并在git commit一次。将stage 和commit分开,并且不同文件关联的commit也是分开的。

在提交后,需要小幅修改上次提交的时候,可以使用git commit --amend -m "amend message"这样的方法。

在提交修改后,可以撤回提交、再次修改并提交

4. 在仓库中查看提交记录

This is what I use to review the changes made within the last week. I will add --author=alex if I want to see only the changes made by me.

git log --all --pretty=format:"%h %cd %s (%an)" --since='7 days ago'

git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short

 git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short
* fa3c141 2011-03-09 | Added HTML header (HEAD, master) [Alexander Shvets]
* 8c32287 2011-03-09 | Added standard HTML page tags [Alexander Shvets]
* 43628f7 2011-03-09 | Added h1 tag [Alexander Shvets]
* 911e8c9 2011-03-09 | First Commit [Alexander Shvets]

Let’s look at it in detail:

  1. --pretty="..." defines the output format.
  2. %h is the abbreviated hash of the commit
  3. %d commit decorations (e.g. branch heads or tags)
  4. %ad is the commit date
  5. %s is the comment
  6. %an is the name of the author
  7. --graph tells git to display the commit tree in the form of an ASCII graph layout
  8. --date=short keeps the date format short and nice

因为在git中查看log的命令较为复杂,可以考虑设定命令的简短替代:

git config --global alias.hist "log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short"

4. 在仓库中查看提交记录

This is what I use to review the changes made within the last week. I will add --author=alex if I want to see only the changes made by me.

git log --all --pretty=format:"%h %cd %s (%an)" --since='7 days ago'

git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short

 git log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short
* fa3c141 2011-03-09 | Added HTML header (HEAD, master) [Alexander Shvets]
* 8c32287 2011-03-09 | Added standard HTML page tags [Alexander Shvets]
* 43628f7 2011-03-09 | Added h1 tag [Alexander Shvets]
* 911e8c9 2011-03-09 | First Commit [Alexander Shvets]

Let’s look at it in detail:

  1. --pretty="..." defines the output format.
  2. %h is the abbreviated hash of the commit
  3. %d commit decorations (e.g. branch heads or tags)
  4. %ad is the commit date
  5. %s is the comment
  6. %an is the name of the author
  7. --graph tells git to display the commit tree in the form of an ASCII graph layout
  8. --date=short keeps the date format short and nice

因为在git中查看log的命令较为复杂,可以考虑设定命令的简短替代:

git config --global alias.hist "log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short"

5. git checkout--在git的多次commit间 切换,使得工作区中的文件切换到特定commit时的内容状态

git hist
* 96a2dc0 2018-09-07 | git how to recommend that separate files can use multiple commits (HEAD -> master) [kprojiang@laptop]
* b0e4635 2018-09-07 | by reading git how to webpage, I draw a tree in d3.js [kprojiang@laptop]
* 5f53731 2018-09-07 | 学到了git init和git init --bare,git clone和git push、git pull。 (origin/master) [kprojiang@laptop]
* e21add0 2018-09-06 | add repoSource.txt, which is the first file to backup in e/kPro_nutStore/testGit [kprojiang@laptop]
git checkout e21add0
Note: checking out 'e21add0'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at e21add0 add repoSource.txt, which is the first file to backup in e/kPro_nutStore/testGit
git branch
* (HEAD detached at e21add0)
master
git hist
* e21add0 2018-09-06 | add repoSource.txt, which is the first file to backup in e/kPro_nutStore/testGit (HEAD) [kprojiang@laptop]

在git checkout到特定的commit后,可以看到git branch下,HEAD不再指向master位置,git hist记录从原来的4条变为1条。

6. git tag

  1. 使用git tag可以查看现在repository的commit中有哪几个tag

  2. 使用git tag v1可以给当前commit状态打标签,骚操作是这样的

    git branch$ git branch

    * master

    git tag v1

    git checkout v1^ # checkout到v1更早的那一个版本

    git tag v1-beta

    git tag

    v1

    v1-beta

    git checkout master

使用上tag 后,可以在git hist中再加上选项,命令为git hist master --all

删掉一个tag的命令,是git tag -d v_Nthetag

7. 在编辑代码和文档的过程中,各阶段上使用Git 做回滚操作的命令

首先附上Git 上文件编辑和Git 仓库的流程图。

  1. 文件编辑操作E后,还没有执行git add 命令,放弃E ———— git checkout file

    如果是 modify(add first line) -> git add file -> modify(add second line) -> git checkout file,那么这次git checkout只会撤销second line。

    就是说,git checkout file 是操作unstaged modification。

  2. 文件编辑操作E后,执行git add 命令A,放弃A ———— git reset HEAD file

  3. git commit 操作C后,视repository的共享状态而论:

    1. 在git log中不留下修改记录

      • clone了远程的repo,后面还要push回去,且

      • 不是要撤销或覆盖远程的repo中的commit

      • 相当于对自己新增的commit进行撤销或覆盖

      • 覆盖的方法

          git commit --amend -m "amendCommitMessage"
      • 撤销的方法

          git revert --hard hashtag
    2. 在git log中留下修改记录

      • 便于push

      • 便于别人clone后再pull更新

      • 命令

          git revert HEAD
    3. 新开一个分支进行修改

       git checkout -b newBranchName hashtag

8. management of repository cloned from some remote repository

对于从远程仓库复制过来的、当前的工作repo,有以下命令可以查看 remote repo的信息:

git remote -v # 可以看到当前工作的repo从哪个origin repo复制过来的,将推送到哪个destination repo
origin E:/kPro_nutStore/testGit (fetch)
origin E:/kPro_nutStore/testGit (push) git remote show origin # 可以看到对origin repo的fetch 和 push的配置
Fetch URL: E:/kPro_nutStore/testGit
Push URL: E:/kPro_nutStore/testGit
HEAD branch: master
Remote branch:
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date) git branch # 可以看到当前工作的repo上的branch信息
* master
remotes/origin/master git branch -a # 对于从remote repo 复制过来的clone repo,也可以看到remote repo 的branch

9. 对于主repo,merge其他branch的新内容

我的应用场景是,将主repo保存在坚果云可以同步的文件夹中。在坚果云中有很多目录,是不同的工作内容,在各自的目录下都用了git init从而成为一个repo。

每天的工作,因为工作内容的不同,是将对应的目录(文件夹)用git clone在坚果云可以同步的位置新建一个目录(文件夹)————这样一天的工作,虽然是branch,还是可以同步一下的,在多个设备间也是可以同步的。

在每天的工作结尾,确认已经完成工作后,就是要将这个目录(文件夹)下的内容更新到主repo中。

  • 这时的操作

    切换到主repo所在路径上,在git bash上用使用如下命令:

      git checkout -b repoNameCustomized master
    Switched to a new branch 'repoWork-master'
    git branch
    master
    repoWillpushback-master
    * repoWork-master
    $ git pull /f/kPro/repoWork master
    remote: Enumerating objects: 6, done.
    remote: Counting objects: 100% (6/6), done.
    remote: Compressing objects: 100% (3/3), done.
    remote: Total 4 (delta 1), reused 0 (delta 0)
    Unpacking objects: 100% (4/4), done.
    From F:/kPro/repoWork
    * branch master -> FETCH_HEAD
    Updating e8eeecd..72c5451
    Fast-forward
    repoSource.txt | 3 +++
    work.txt | 0
    2 files changed, 3 insertions(+)
    create mode 100644 work.txt
    $ git checkout master
    Switched to branch 'master'
    $ git merge repoWork-master
    Updating e8eeecd..72c5451
    Fast-forward
    repoSource.txt | 3 +++
    work.txt | 0
    2 files changed, 3 insertions(+)
    create mode 100644 work.txt
    $ git hist
    * 72c5451 2018-09-08 | 20180908work (HEAD -> master, repoWork-master) [kprojiang@laptop]
    * e8eeecd 2018-09-08 | Merge branch 'repoWillpushback-master' [kProJiang@laptop]
    |\
    | * e789b69 2018-09-08 | another line is added to forContrast, in repoWillpushback (repoWillpushback-master) [kprojiang@laptop]
    * | 7fbee69 2018-09-08 | in repoSource, add a line in forContrastRM.txt [kProJiang@laptop]
    |/
    * cbf5fcd 2018-09-08 | the new commit done in the repoWillpushback [kprojiang@laptop]
    $ git branch -d repoWork

其中,第一行记录中有一个部分是"repoWork-master",表明是从pull 过来的更新。

从第二行到第六行,是另一个pull过程,其中还包含了merge过程中的冲突处理。

git-scm上看到,分支合并的时候,出现了fast-forward提示是如下原因:合并时出现了“Fast forward”的提示。由于当前 master 分支所在的提交对象是要并入的 hotfix 分支的直接上游,Git 只需把 master 分支指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支的话,那么 Git 在合并两者时,只会简单地把指针右移,因为这种单线的历史分支不存在任何需要解决的分歧,所以这种合并过程可以称为快进(Fast forward)。

在fast-forward提示中,因为repoWork已经完成了历史使命,可以删掉了。这时,再次看git hist显示的信息,第一行中已经没有repoWork-master这个内容了(因为是fast-forward)。

  • git pull 的使用方法是这样的:

      $ git pull “远程主机名” “远程分支名”:“本地分支名”

    比如,要取回origin主机的next分支,与本地的master分支合并,需要写成下面这样:

      $ git pull origin next:master

    如果远程分支(next)要与当前分支合并,则冒号后面的部分可以省略。上面命令可以简写为:

      $ git pull origin next

    一般,在pull 一个repo的时候,如果在工作repo修改的这段时间内主repo也有修改,和工作repo 修改的文件列表不重叠,那么是可以直接pull的,不会出现冲突;如果主repo和工作repo修改的文件列表有重叠,那么还需要冲突合并一下。解决冲突的文件后,需要git add 并 git commit一下。

我这么做,就是拿坚果云中的主repo类似remote repository了,在电脑上同时有主repo的内容(原内容,未经今天修改)和今天的工作内容,方便我需要比较前后两者在内容上的差异时同时打开两份互相对应的文件。因为这个考虑,才没有在主repo上直接开branch进行工作。

参考资料

目前在看的是GitHowTo

中文相似的教程文档有Git-scm中文版v2命令解释教程