Git基本概念及操作(3)

2023-01-31 03:01:01 git 操作 基本概念

如果使用传统的如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值,如下图所示:

image

在以前GIT版本中会有一个单独的目录保存这些文件生成的HASH值通常是.git/objects/fc现在不在有这个fc目录,直接取生成的HASH的前两个值作目录。如下所示,注意在未提交之前,目录是不诞生对象。

[root@wrlinux3 mygit]# git add .
[root@wrlinux3 mygit]# 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 .
[root@wrlinux3 mygit]# git hash-object README
52c897fedf9f8728e953a149b7be3b5829a07b1c
[root@wrlinux3 mygit]# git hash-object main.c
f8b643afbf2c84dc03b777743D3e53a22045cf49
[root@wrlinux3 mygit]# git hash-object testdir/test.c
d22409509cd2f0a420e0cb6355008a9676656961

[root@wrlinux3 mygit]# git show d224
int test()
{
return 0;
}
[root@wrlinux3 mygit]# git show f8b6
int main()
{
  return 0;
}
[root@wrlinux3 mygit]# git show 52c8
new readme

[root@wrlinux3 mygit]# git cat-file -t 52c8
blob
[root@wrlinux3 mygit]# git cat-file -t f8b6
blob
[root@wrlinux3 mygit]# git cat-file -t d224
blob

2)tree 是一个simple 对象,它的内容就是一个指针表,指针指向的就是这个目录下的所有文件及子目录对象。也就是说这个对象记录了当前目录的元信息。如下图所示:

image

正如前面所说,tree也是一串HASH值,这串HASH存储的是元信息。用来表示目录层次关系的。这和一般的SCM系统中是不一样,一般的SCM中目录同文件是一样的保存为版本对象的。

[root@wrlinux3 mygit]# git commit -a -m "init commit"
[master (root-commit) 4cfc524] init commit
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    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
[root@wrlinux3 mygit]# 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 ..
[root@wrlinux3 mygit]# ls -latr .git/objects/4c/fc5245433ac0b9277892ccb6e7bd4347aa01ec
-r--r--r--. 1 root root 133 Apr 28 14:12 .git/objects/4c/fc5245433ac0b9277892ccb6e7bd4347aa01ec
[root@wrlinux3 mygit]# ls -latr .git/objects/a8/8383ae64b09e71666adc12f4bf4760857f8c55
-r--r--r--. 1 root root 115 Apr 28 14:12 .git/objects/a8/8383ae64b09e71666adc12f4bf4760857f8c55
[root@wrlinux3 mygit]# ls -latr .git/objects/bc/a9ca7434467d22451fc370375dbab1a8930433
-r--r--r--. 1 root root 51 Apr 28 14:12 .git/objects/bc/a9ca7434467d22451fc370375dbab1a8930433
[root@wrlinux3 mygit]# git cat-file -t bca9
tree
[root@wrlinux3 mygit]# git cat-file -t a883
tree
[root@wrlinux3 mygit]# git cat-file -t 4cfc
commit
[root@wrlinux3 mygit]# git ls-tree bcag
fatal: Not a valid object name bcag
[root@wrlinux3 mygit]# git ls-tree bca9
100644 blob d22409509cd2f0a420e0cb6355008a9676656961    test.c
[root@wrlinux3 mygit]# git ls-tree a883
100644 blob 52c897fedf9f8728e953a149b7be3b5829a07b1c    README
100644 blob f8b643afbf2c84dc03b777743d3e53a22045cf49    main.c
040000 tree bca9ca7434467d22451fc370375dbab1a8930433    testdir
[root@wrlinux3 mygit]#

3)commit 也是一种对象,但是这种对象存储是在提交这个点上的元信息,包括提交人、上次提交的版本对象指针及本次提交包含的树节点指针。如下图所示:

image

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

image

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

image

在实际上可以使用如下命令查看:

[root@wrlinux3 mygit]# git show -s --pretty=raw 4cfc
commit 4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
tree a88383ae64b09e71666adc12f4bf4760857f8c55
author ROOT root <root@wrlinux3.nsn-nsn.net> 1335593557 +0800
committer ROOT root <root@wrlinux3.nsn-nsn.net> 1335593557 +0800

    init commit
[root@wrlinux3 mygit]#

通过上面的讲述我相信在Commit、Tree和Blob对象之三者的关系有了一个简单的认识,有人总结成了如下一张图,可以说是GIT实现的核心。

image

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

image

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

image

从上图可以看出,HEAD,BRACH,REMOTE,TAG最下层都是Commit对象,也就是GIT管理的粒度是以Commit来决定。Commit相当于一个快照。我们可以从git目录查看到这种组织方式。

[root@wrlinux3 mygit]# cat .git/HEAD
ref: refs/heads/master
[root@wrlinux3 mygit]# 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
[root@wrlinux3 mygit]# ls -l .git/refs/tags/
total 0
[root@wrlinux3 mygit]# ls -l .git/refs/heads/
total 4
-rw-r--r--. 1 root root 41 Apr 28 14:12 master
[root@wrlinux3 mygit]# cat .git/refs/heads/master
4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
[root@wrlinux3 mygit]# git cat-file -t 4cfc
commit
[root@wrlinux3 mygit]#

另外GIT也使用了优化算法,就是前面两个版本之间如果相同的对象没有发生改变的话,会直接采用链接的方式。如下图所示:

image

这样就可以省去很多对象的产生。前面我们对Reference对象没有特别强调,只是强调他们是可以随着分支移动。这一点与很多SCM系统不同,比喻说CC,分支拉出来就固定,分支上生长出版本。而GIT不同,GIT上的分支它是随着版本移动的。我们来研究如下:

1)分支 GIT中分支本质上是个指向Commit 对象的可变分支。默认情况下Git使用master作为分支的默认名字,每次一提交,master指针都会自动向前移动到下一个版本的commit对象上。当然你也可创建一个分支,使用命令git branch testing.如下图所示:

image

可以通过仓库显示如下:

[root@wrlinux3 mygit]# git branch testing
[root@wrlinux3 mygit]# git branch
* master
  testing

[root@wrlinux3 mygit]# cat .git/HEAD
ref: refs/heads/master

[root@wrlinux3 mygit]# cat .git/refs/heads/master
4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
[root@wrlinux3 mygit]# cat .git/refs/heads/testing
4cfc5245433ac0b9277892ccb6e7bd4347aa01ec
[root@wrlinux3 mygit]#

这时我们可以看master和testing都指向4cfc Commit对象,同时master前面带有*号,HEAD中指向master,表示当前工作分支还在master上,如果想要在testing上工作,就必须修改HEAD指针。使用git checkout testing就可以切换了。

[root@wrlinux3 mygit]# git checkout testing
Switched to branch 'testing'
[root@wrlinux3 mygit]# cat .git/HEAD
ref: refs/heads/testing
[root@wrlinux3 mygit]#

这时HEAD指针指向Testing分支了。

image

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

image

对应的版本图如下:

image

对应的操作记录如下:

[root@wrlinux3 mygit]# git checkout -b iss53
Switched to a new branch 'iss53'
[root@wrlinux3 mygit]# vi main.c
[root@wrlinux3 mygit]# git commit -a -m 'add new variable to calculate the value on branch [iss53]'
[iss53 2f1f64e] add new variable to calculate the value on branch [iss53]
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

1 file changed, 5 insertions(+), 1 deletion(-)
[root@wrlinux3 mygit]# git checkout master
Switched to branch 'master'
[root@wrlinux3 mygit]# git checkout -b 'hotfix'
Switched to a new branch 'hotfix'
[root@wrlinux3 mygit]# git branch
* hotfix
  iss53
  master
  testing
[root@wrlinux3 mygit]# cd testdir/
[root@wrlinux3 testdir]# vi test.c
[root@wrlinux3 testdir]# git commit -a -m 'fixed the issue"
[root@wrlinux3 testdir]# cd ..
> ls
> ^C
[root@wrlinux3 testdir]# cd ..
[root@wrlinux3 mygit]# ls
main.c  README  testdir
[root@wrlinux3 mygit]# git commit -a -m 'fixed the issue"
> ^C
[root@wrlinux3 mygit]# git commit -a -m 'fixed the issue'
[hotfix 85412f9] fixed the issue
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

1 file changed, 4 insertions(+), 1 deletion(-)
[root@wrlinux3 mygit]# git checkout master
Switched to branch 'master'
[root@wrlinux3 mygit]# git merge hotfix
Updating 4cfc524..85412f9
Fast-forward
testdir/test.c |    5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
[root@wrlinux3 mygit]# git branch -d hotfix
Deleted branch hotfix (was 85412f9).
[root@wrlinux3 mygit]# git branch
  iss53
* master
  testing
[root@wrlinux3 mygit]# vi testdir/test.c
[root@wrlinux3 mygit]# git checkout master
Already on 'master'
[root@wrlinux3 mygit]# git merge iss53
Merge made by the 'recursive' strategy.
main.c |    6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
[root@wrlinux3 mygit]# git merge iss53
Already up-to-date.
[root@wrlinux3 mygit]#

通过GIT进行合并,因为是基本文本方式合并,可能不与CC中图形化直观,但是一定要注意两点,一点是DIFF工具可改,二点是MERGE前的工作区应该是干净的。当提示一些CONFLIC时需要手工打这些文件进行修改。

[root@wrlinux3 mygit]# git mergetool
No files need merging
[root@wrlinux3 mygit]# git checkout testing
Switched to branch 'testing'
[root@wrlinux3 mygit]# ls
main.c  README  testdir
[root@wrlinux3 mygit]# vi main.c
[root@wrlinux3 mygit]# git commit -a -m "test comments"
[testing d2569cb] test comments
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

1 file changed, 1 insertion(+)
[root@wrlinux3 mygit]# git checkout master
Switched to branch 'master'
[root@wrlinux3 mygit]# git mergetool
No files need merging
[root@wrlinux3 mygit]# git merge testing
Auto-merging main.c
CONFLICT (content): Merge conflict in main.c
Automatic merge failed; fix conflicts and then commit the result.
[root@wrlinux3 mygit]# git status
# On branch master
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#    both modified:      main.c
#
no changes added to commit (use "git add" and/or "git commit -a")
[root@wrlinux3 mygit]# 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 <file>' as
hint: appropriate to mark resolution and make a commit,
hint: or use 'git commit -a'.
fatal: Exiting because of an unresolved conflict.
[root@wrlinux3 mygit]# git status
# On branch master
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#    both modified:      main.c
#
no changes added to commit (use "git add" and/or "git commit -a")
[root@wrlinux3 mygit]# git commit -a -m "commit stat"
[master 5e453fa] commit stat
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

[root@wrlinux3 mygit]# git st
# On branch master
nothing to commit (working directory clean)

前面讲了一些通用的分支操作,通过创建分支、合并分支并最终形成一个稳定的版本的过程,这个过程也是我们常见的SCM的管理职责,目前SCM人员需要制定主分支、开发分支、特性分支,通过管控这些分支来保证整个产品的质量的稳定。如下图所示:

image

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

image

对应的操作记录如下:

[root@wrlinux3 mygit]# git branch experiment
[root@wrlinux3 mygit]# git branch
  experiment
* master
[root@wrlinux3 mygit]# git checkout -b experiment
fatal: A branch named 'experiment' already exists.
[root@wrlinux3 mygit]# git checkout -B experiment
Switched to and reset branch 'experiment'
[root@wrlinux3 mygit]# vi main.c
[root@wrlinux3 mygit]# git commit -a -m 'commit on experiment'
[experiment 774b7ee] commit on experiment
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

1 file changed, 2 insertions(+), 3 deletions(-)
[root@wrlinux3 mygit]# git checkout master
Switched to branch 'master'
[root@wrlinux3 mygit]# vi main.c
[root@wrlinux3 mygit]# git commit -a -m 'commit on master'
[master cbe0f99] commit on master
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

1 file changed, 1 insertion(+), 2 deletions(-)
[root@wrlinux3 mygit]# gitk
[root@wrlinux3 mygit]# vi main.c
[root@wrlinux3 mygit]# git commit -a -m 'commit on master more'
[master 8f20d8c] commit on master more
Committer: ROOT root <root@wrlinux3.nsn-nsn.net>
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 identity used for this commit with:

    git commit --amend --reset-author

1 file changed, 1 insertion(+)
[root@wrlinux3 mygit]# git checkout experiment
Switched to branch 'experiment'
[root@wrlinux3 mygit]# 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.
[root@wrlinux3 mygit]# rm -fr /work/bongos/mygit/.git/rebase-apply
[root@wrlinux3 mygit]# 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".

[root@wrlinux3 mygit]# vi main.c
[root@wrlinux3 mygit]# git rebase --continue
main.c: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add
[root@wrlinux3 mygit]# git add .
[root@wrlinux3 mygit]# git rebase --continue
Applying: commit on experiment
[root@wrlinux3 mygit]# git st
# On branch experiment
nothing to commit (working directory clean)
[root@wrlinux3 mygit]# gitk
[root@wrlinux3 mygit]# git branch
* experiment
  master
[root@wrlinux3 mygit]#

相关文章