78144666 发表于 2018-9-18 12:36:02

Git基本概念及操作(3)

  如果使用传统的如CC开发的话,刚开始进行GIT开发可能不是太适应。这个主要是有些概念不一样。比喻在CC中,我们一般是围绕一个主分支进行开发,对一个文件来说,在主分支上会生成不同的版本。同样,我们在每一个版本下面创立新的次分支,在次分支上也会生出很多版本。最后合到主分支,产生下一个版本。那么在GIT中是如何实现这些关联呢?GIT中同样有分支、版本概念。但是没有Configspec概念。tag概念同LABEL概念类似。当然这些概念都同GIT中是如何管理文件版本相关的。首先我们来看GIT是如何将文件对象化管理的,前面我们说GIT同其它版本管理系统不一样是GIT每个版本都不是保存变更,而是全保存。那么如果全保存的话,显然会带来相当大的硬盘开销,其弊端非常明显,那么GIT是怎么消除这个弊端的呢?GIT利用了HASH算法,我们知道在目前已知道的算法中,HASH(SHA-1)算法产生的唯一性还是非常强的。也就是说虽然在工作区域是一个普通文件,但是在仓库中保存的是一个HASH值,由这个HASH值来表示文件,自然空间节省很多。GIT中将这个HASH值称之为对象。这些对象通常是由提交版本、子目录、文件的HASH值组成。对每一个对象通常按类型、大小和内容进行管理。其中最主要的是类型分为三种:
  1)blob 这种类型对象是用来存储文件数据,GIT中表现为一个HASH值,如下图所示:

  在以前GIT版本中会有一个单独的目录保存这些文件生成的HASH值通常是.git/objects/fc现在不在有这个fc目录,直接取生成的HASH的前两个值作目录。如下所示,注意在未提交之前,目录是不诞生对象。
  # git add .
  # ls -latr .git/objects/
  total 28
  drwxr-xr-x. 2 root root 4096 Apr 28 13:34 pack
  drwxr-xr-x. 2 root root 4096 Apr 28 13:34 info
  drwxr-xr-x. 2 root root 4096 Apr 28 13:35 f8
  drwxr-xr-x. 2 root root 4096 Apr 28 13:35 d2
  drwxr-xr-x. 2 root root 4096 Apr 28 13:35 52
  drwxr-xr-x. 7 root root 4096 Apr 28 13:35 ..
  drwxr-xr-x. 7 root root 4096 Apr 28 13:35 .
  # git hash-object README
  52c897fedf9f8728e953a149b7be3b5829a07b1c
  # git hash-object main.c
  f8b643afbf2c84dc03b777743d3e53a22045cf49
  # git hash-object testdir/test.c
  d22409509cd2f0a420e0cb6355008a9676656961
  # git show d224
  int test()
  {
  return 0;
  }
  # git show f8b6
  int main()
  {
  return 0;
  }
  # git show 52c8
  new readme
  # git cat-file -t 52c8
  blob
  # git cat-file -t f8b6
  blob
  # git cat-file -t d224
  blob
  2)tree 是一个simple 对象,它的内容就是一个指针表,指针指向的就是这个目录下的所有文件及子目录对象。也就是说这个对象记录了当前目录的元信息。如下图所示:

  正如前面所说,tree也是一串HASH值,这串HASH存储的是元信息。用来表示目录层次关系的。这和一般的SCM系统中是不一样,一般的SCM中目录同文件是一样的保存为版本对象的。
  # git commit -a -m "init commit"
   init commit
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  3 files changed, 9 insertions(+)
  create mode 100644 README
  create mode 100644 main.c
  create mode 100644 testdir/test.c
  # ls -latr .git/objects/
  total 40
  drwxr-xr-x.2 root root 4096 Apr 28 13:34 pack
  drwxr-xr-x.2 root root 4096 Apr 28 13:34 info
  drwxr-xr-x.2 root root 4096 Apr 28 13:35 f8
  drwxr-xr-x.2 root root 4096 Apr 28 13:35 d2
  drwxr-xr-x.2 root root 4096 Apr 28 13:35 52
  drwxr-xr-x.2 root root 4096 Apr 28 14:12 bc
  drwxr-xr-x.2 root root 4096 Apr 28 14:12 a8
  drwxr-xr-x.2 root root 4096 Apr 28 14:12 4c
  drwxr-xr-x. 10 root root 4096 Apr 28 14:12 .
  drwxr-xr-x.8 root root 4096 Apr 28 14:12 ..
  # ls -latr .git/objects/4c/fc5245433ac0b9277892ccb6e7bd4347aa01ec
  -r--r--r--. 1 root root 133 Apr 28 14:12 .git/objects/4c/fc5245433ac0b9277892ccb6e7bd4347aa01ec
  # ls -latr .git/objects/a8/8383ae64b09e71666adc12f4bf4760857f8c55
  -r--r--r--. 1 root root 115 Apr 28 14:12 .git/objects/a8/8383ae64b09e71666adc12f4bf4760857f8c55
  # ls -latr .git/objects/bc/a9ca7434467d22451fc370375dbab1a8930433
  -r--r--r--. 1 root root 51 Apr 28 14:12 .git/objects/bc/a9ca7434467d22451fc370375dbab1a8930433
  # git cat-file -t bca9
  tree
  # git cat-file -t a883
  tree
  # git cat-file -t 4cfc
  commit
  # git ls-tree bcag
  fatal: Not a valid object name bcag
  # git ls-tree bca9
  100644 blob d22409509cd2f0a420e0cb6355008a9676656961    test.c
  # git ls-tree a883
  100644 blob 52c897fedf9f8728e953a149b7be3b5829a07b1c    README
  100644 blob f8b643afbf2c84dc03b777743d3e53a22045cf49    main.c
  040000 tree bca9ca7434467d22451fc370375dbab1a8930433    testdir
  #
  3)commit 也是一种对象,但是这种对象存储是在提交这个点上的元信息,包括提交人、上次提交的版本对象指针及本次提交包含的树节点指针。如下图所示:

  看到这里可能还不是很明白。那我们将面连接起来如下所示:

  从这个图我们应该能够简单的明白了,在GIT中版本这种对象是在最高层,不像一般SCM中,对一个文件来说会记录不同的版本。而在GIT中不是这样的,它是对当下这个项目提交一个版本就进行一次快照。通俗的说就是一个版本包含当前快照下所有对象指针。这样从版本管理的角度来说,要管理的对象的是版本。不再是文件或者目录。这些东西统统可以缩成一个快照。如下图所示:

  在实际上可以使用如下命令查看:
  # git show -s --pretty=raw 4cfc
  commit 4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
  tree a88383ae64b09e71666adc12f4bf4760857f8c55
  author ROOT root1335593557 +0800
  committer ROOT root1335593557 +0800
  init commit
  #
  通过上面的讲述我相信在Commit、Tree和Blob对象之三者的关系有了一个简单的认识,有人总结成了如下一张图,可以说是GIT实现的核心。

  从这张图我们也就明白了前面所说了Commit对象作为GIT管理的核心。GIT的Commit对象它不是说针对单个文件,它是针对的整个项目的。另外还有一种对象,TAG对象。TAG对象是用来标记特定的Commit的,如下图所示,这里可能还不能够清楚解析,后面学习了分支之后会比较清楚的了解。

  前面讲述了四种GIT对象,GIT对象是不可改变的(除了一些撤消操作之外),当然这里面的TAG对象有时候也可以称之为References引用,引用是可以移动,引用都是基本版本Commit对象的。因此从另外一个角度来说TAG也可以称之为引用。但实际上我们打了一个TAG之后是不能移动,他的特性与对象又一样。常用的引用主要分为分支、头、及远程分支。如下图所示:

  从上图可以看出,HEAD,BRACH,REMOTE,TAG最下层都是Commit对象,也就是GIT管理的粒度是以Commit来决定。Commit相当于一个快照。我们可以从git目录查看到这种组织方式。
  # cat .git/HEAD
  ref: refs/heads/master
  # ls -l .git/refs/
  total 8
  drwxr-xr-x. 2 root root 4096 Apr 28 14:12 heads
  drwxr-xr-x. 2 root root 4096 Apr 28 13:34 tags
  # ls -l .git/refs/tags/
  total 0
  # ls -l .git/refs/heads/
  total 4
  -rw-r--r--. 1 root root 41 Apr 28 14:12 master
  # cat .git/refs/heads/master
  4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
  # git cat-file -t 4cfc
  commit
  #
  另外GIT也使用了优化算法,就是前面两个版本之间如果相同的对象没有发生改变的话,会直接采用链接的方式。如下图所示:

  这样就可以省去很多对象的产生。前面我们对Reference对象没有特别强调,只是强调他们是可以随着分支移动。这一点与很多SCM系统不同,比喻说CC,分支拉出来就固定,分支上生长出版本。而GIT不同,GIT上的分支它是随着版本移动的。我们来研究如下:
  1)分支 GIT中分支本质上是个指向Commit 对象的可变分支。默认情况下Git使用master作为分支的默认名字,每次一提交,master指针都会自动向前移动到下一个版本的commit对象上。当然你也可创建一个分支,使用命令git branch testing.如下图所示:

  可以通过仓库显示如下:
  # git branch testing
  # git branch
  * master
  testing
  # cat .git/HEAD
  ref: refs/heads/master
  # cat .git/refs/heads/master
  4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
  # cat .git/refs/heads/testing
  4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
  #
  这时我们可以看master和testing都指向4cfc Commit对象,同时master前面带有*号,HEAD中指向master,表示当前工作分支还在master上,如果想要在testing上工作,就必须修改HEAD指针。使用git checkout testing就可以切换了。
  # git checkout testing
  Switched to branch 'testing'
  # cat .git/HEAD
  ref: refs/heads/testing
  #
  这时HEAD指针指向Testing分支了。

  因为在GIT中创建和删除分支基本上不需要耗费计算机资源,因为分支在GIT表示的就是一个指向版本的指针(这个文件中保存的是版本对像的SHA值)。所以GIT是非常鼓励多建分支。那么很明显分支太多,也会带来版本合并的麻烦,所以一般来说建议尝试一个新功能在一个分支上开发完毕后,进行测试完成后就合并到主分支上再删除原来分支。下面我们围绕一个开发过程来进行分支的创建和合并。

  对应的版本图如下:

  对应的操作记录如下:
  # git checkout -b iss53
  Switched to a new branch 'iss53'
  # vi main.c
  # git commit -a -m 'add new variable to calculate the value on branch '
   add new variable to calculate the value on branch
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  1 file changed, 5 insertions(+), 1 deletion(-)
  # git checkout master
  Switched to branch 'master'
  # git checkout -b 'hotfix'
  Switched to a new branch 'hotfix'
  # git branch
  * hotfix
  iss53
  master
  testing
  # cd testdir/
  # vi test.c
  # git commit -a -m 'fixed the issue"
  # cd ..
  > ls
  > ^C
  # cd ..
  # ls
  main.cREADMEtestdir
  # git commit -a -m 'fixed the issue"
  > ^C
  # git commit -a -m 'fixed the issue'
   fixed the issue
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  1 file changed, 4 insertions(+), 1 deletion(-)
  # git checkout master
  Switched to branch 'master'
  # git merge hotfix
  Updating 4cfc524..85412f9
  Fast-forward
  testdir/test.c |    5 ++++-
  1 file changed, 4 insertions(+), 1 deletion(-)
  # git branch -d hotfix
  Deleted branch hotfix (was 85412f9).
  # git branch
  iss53
  * master
  testing
  # vi testdir/test.c
  # git checkout master
  Already on 'master'
  # git merge iss53
  Merge made by the 'recursive' strategy.
  main.c |    6 +++++-
  1 file changed, 5 insertions(+), 1 deletion(-)
  # git merge iss53
  Already up-to-date.
  #
  通过GIT进行合并,因为是基本文本方式合并,可能不与CC中图形化直观,但是一定要注意两点,一点是DIFF工具可改,二点是MERGE前的工作区应该是干净的。当提示一些CONFLIC时需要手工打这些文件进行修改。
  # git mergetool
  No files need merging
  # git checkout testing
  Switched to branch 'testing'
  # ls
  main.cREADMEtestdir
  # vi main.c
  # git commit -a -m "test comments"
   test comments
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  1 file changed, 1 insertion(+)
  # git checkout master
  Switched to branch 'master'
  # git mergetool
  No files need merging
  # git merge testing
  Auto-merging main.c
  CONFLICT (content): Merge conflict in main.c
  Automatic merge failed; fix conflicts and then commit the result.
  # git status
  # On branch master
  # Unmerged paths:
  #   (use "git add/rm ..." as appropriate to mark resolution)
  #
  #    both modified:      main.c
  #
  no changes added to commit (use "git add" and/or "git commit -a")
  # git merge testing
  error: 'merge' is not possible because you have unmerged files.
  hint: Fix them up in the work tree,
  hint: and then use 'git add/rm ' as
  hint: appropriate to mark resolution and make a commit,
  hint: or use 'git commit -a'.
  fatal: Exiting because of an unresolved conflict.
  # git status
  # On branch master
  # Unmerged paths:
  #   (use "git add/rm ..." as appropriate to mark resolution)
  #
  #    both modified:      main.c
  #
  no changes added to commit (use "git add" and/or "git commit -a")
  # git commit -a -m "commit stat"
   commit stat
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  # git st
  # On branch master
  nothing to commit (working directory clean)
  前面讲了一些通用的分支操作,通过创建分支、合并分支并最终形成一个稳定的版本的过程,这个过程也是我们常见的SCM的管理职责,目前SCM人员需要制定主分支、开发分支、特性分支,通过管控这些分支来保证整个产品的质量的稳定。如下图所示:

  上图中不同的开发分支,代表了产品成熟的不同的周期。通过维护这些分支的生命周期来维护产品的生命周期。这里还需要强调的我们前面所进行的操作都是向有操作,也就是MERGE操作,实际上还有一种操作叫REBASE操作,rebase比较复杂,轻易不要使用,因为rebase会将当前已提交的版本历史给删除掉了,这样的话,一旦别人根据这些开发的话会产生丢失版本情况。如下图所示:

  对应的操作记录如下:
  # git branch experiment
  # git branch
  experiment
  * master
  # git checkout -b experiment
  fatal: A branch named 'experiment' already exists.
  # git checkout -B experiment
  Switched to and reset branch 'experiment'
  # vi main.c
  # git commit -a -m 'commit on experiment'
   commit on experiment
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  1 file changed, 2 insertions(+), 3 deletions(-)
  # git checkout master
  Switched to branch 'master'
  # vi main.c
  # git commit -a -m 'commit on master'
   commit on master
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  1 file changed, 1 insertion(+), 2 deletions(-)
  # gitk
  # vi main.c
  # git commit -a -m 'commit on master more'
   commit on master more
  Committer: ROOT root
  Your name and email address were configured automatically based
  on your username and hostname. Please check that they are accurate.
  You can suppress this message by setting them explicitly:
  git config --global user.name "Your Name"
  git config --global user.email you@example.com

  After doing this, you may fix the>  git commit --amend --reset-author
  1 file changed, 1 insertion(+)
  # git checkout experiment
  Switched to branch 'experiment'
  # git rebase master
  It seems that there is already a rebase-apply directory, and
  I wonder if you are in the middle of another rebase.If that is the
  case, please try
  git rebase (--continue | --abort | --skip)
  If that is not the case, please
  rm -fr /work/bongos/mygit/.git/rebase-apply
  and run me again.I am stopping in case you still have something
  valuable there.
  # rm -fr /work/bongos/mygit/.git/rebase-apply
  # git rebase master
  First, rewinding head to replay your work on top of it...
  Applying: commit on experiment
  Using index info to reconstruct a base tree...
  Falling back to patching base and 3-way merge...
  Auto-merging main.c
  CONFLICT (content): Merge conflict in main.c
  Failed to merge in the changes.
  Patch failed at 0001 commit on experiment
  When you have resolved this problem run "git rebase --continue".
  If you would prefer to skip this patch, instead run "git rebase --skip".
  To check out the original branch and stop rebasing run "git rebase --abort".
  # vi main.c
  # git rebase --continue
  main.c: needs merge
  You must edit all merge conflicts and then
  mark them as resolved using git add
  # git add .
  # git rebase --continue
  Applying: commit on experiment
  # git st
  # On branch experiment
  nothing to commit (working directory clean)
  # gitk
  # git branch
  * experiment
  master
  #

页: [1]
查看完整版本: Git基本概念及操作(3)