赞
踩
git是一个免费的开源分布式版本控制系统,它最初是 Linus Torvalds 于2005 年 4 月,为了帮助管理 Linux 内核开发而开发的版本控制软件
版本控制系统(Version Control System, VCS)是一种可以记录一个或多个文件内容变化,以便将来查阅的系统。它有四个发展阶段:
起源
linux有两个工具diff和patch,可以计算两个文件的不同,并还原。这两个工具可以说是VCS的起源。据说1991-2002年之间,即使CVS出现之后,Linus一直使用diff和patch管理着Linux的代码
第一代:本地版本控制系统
1982 年开发的修订控制系统(Revision Control System,RCS) 是第一代的版本控制系统,它由一组 UNIX 命令构成。RCS把diff的集合,通过自己的格式保存到磁盘中,还可以通过这些diff集合,重新回到文件修改的任何历史中的点
第二代:集中式版本控制系统
1986 年开发的并发版本系统(Concurrent Versions System,CVS)是,CVS使用Copy-Modify-Merge(拷贝、修改、合并)变化表支持对文件的同时访问和修改。它明确地将源文件的存储和用户的工作空间独立开来,并使其并行操作,这意味着可以多人同时处理文件
但有一个明显的限制,用户必须在允许提交之前将当前修订合并到他们的工作中,这意味着一个缺点,如果服务器宕机了或者未连上服务器,开发者就不能对项目进行提交了
第二代版本控制系统主要有 CVS、SourceSafe、Subversion、Team Foundation Server、SVK
第三代:分布式版本控制系统
以git为首的第三代分布式VCS,解决了上述问题,每个使用者电脑上就有一个完整的数据仓库,没有网络依然可以使用,在网络具备时,再和服务器进行同步即可
第三代版本控制系统主要有 Git、Bazaar、Mercurial、BitKeeper、Monotone
在程序开发中,使用git的好处有
#在当前目录新建一个Git代码库
git init
#新建一个目录,将其初始化为Git代码库
git init [project-name]
# 下载一个项目和它的整个代码历史
git clone [url]
# 显示当前的Git配置
git config --list
# 编辑Git配置文件
git config -e [--global]
# 设置提交代码时的用户信息
git config [--global] user.name "[name]"
git config [--global] user.email "[email]"
# 设置当前项目用户名
git config --local user.name "name"
# 添加指定文件到暂存区 git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录 git add [dir] # 添加当前目录的所有文件到暂存区 git add . # 添加每个变化前,都会要求确认 # 对于同一个文件的多处变化,可以实现分次提交 git add -p # 删除工作区文件,并且将这次删除放入暂存区 git rm [file1] [file2] ... # 停止追踪指定文件,但该文件会保留在工作区 git rm --cached [file] # 改名文件,并且将这个改名放入暂存区 git mv [file-original] [file-renamed]
# 提交暂存区到仓库区 git commit -m [message] # 提交暂存区的指定文件到仓库区 git commit [file1] [file2] ... -m [message] # 提交工作区自上次commit之后的变化,直接到仓库区 git commit -a # 提交时显示所有diff信息 git commit -v # 使用一次新的commit,替代上一次提交 # 如果代码没有任何新变化,则用来改写上一次commit的提交信息 git commit --amend -m [message] # 重做上一次commit,并包括指定文件的新变化 git commit --amend [file1] [file2] ... # 撤销提交 git reset --soft HEAD^ # 将上一次提交的用户名和邮箱修改 git commit --amend --author="name <email>"
# 列出所有本地分支 git branch # 列出所有远程分支 git branch -r # 列出所有本地分支和远程分支 git branch -a # 新建一个分支,但依然停留在当前分支 git branch [branch-name] # 新建一个分支,并切换到该分支 git checkout -b [branch] # 新建一个分支,指向指定commit git branch [branch] [commit] # 新建一个分支,与指定的远程分支建立追踪关系 git branch --track [branch] [remote-branch] # 切换到指定分支,并更新工作区 git checkout [branch-name] # 切换到上一个分支 git checkout - # 建立追踪关系,在现有分支与指定的远程分支之间 git branch --set-upstream [branch] [remote-branch] # 合并指定分支到当前分支 git merge [branch] # 选择一个commit,合并进当前分支 git cherry-pick [commit] # 删除分支 git branch -d [branch-name] # 删除远程分支 git push origin --delete [branch-name] git branch -dr [remote/branch]
# 列出所有tag git tag # 新建一个tag在当前commit git tag [tag] # 新建一个tag在指定commit git tag [tag] [commit] # 删除本地tag git tag -d [tag] # 删除远程tag git push origin :refs/tags/[tagName] # 查看tag信息 git show [tag] # 提交指定tag git push [remote] [tag] # 提交所有tag git push [remote] --tags # 新建一个分支,指向某个tag git checkout -b [branch] [tag]
# 显示有变更的文件 git status # 显示当前分支的版本历史 git log # 显示commit历史,以及每次commit发生变更的文件 git log --stat # 每个提交在一行内显示 git log --oneline # 格式化输出log # 例子git log --pretty=format:"%h - %an, %ar : %s" # %h:commit缩略哈希 # %an:用户名 # %ar:用户从近到远相对时间 # %s:commit信息 # 详细文档可以见:https://git-scm.com/docs/git-log git log --pretty=format:"<format-string>" # 显示两个commit之间的所有commit git log <commit1>...<commit2> # 显示某个文件的版本历史,包括文件改名 git log --follow [file] git whatchanged [file] # 显示指定文件相关的每一次diff git log -p [file] # 显示过去5次commit git log -5 --pretty --oneline # 显示所有提交过的用户,按提交次数排序 git shortlog -sn # 显示指定文件是什么人在什么时间修改过 git blame [file] # 显示暂存区和工作区的差异 git diff # 显示暂存区和上一个commit的差异 git diff --cached [file] # 显示工作区与当前分支最新commit之间的差异 git diff HEAD # 显示两次提交之间的差异 git diff [commit1]...[commit2] # 显示两个分支之间的差异 git diff [master]..[my-branch] # 显示两次提交之间的改动文件 git diff --numstat [commit1]...[commit2] # 显示今天你写了多少行代码 git diff --shortstat "@{0 day ago}" # 显示某次提交的元数据和内容变化 git show [commit] # 显示某次提交发生变化的文件 git show --name-only [commit] # 显示某次提交时,某个文件的内容 git show [commit]:[filename] # git reflog可以查看删除和reset的commit信息(git log查不到) # git reglog包含所有分支信息,gitlog当前分支信息 git reflog
# 下载远程仓库的所有变动 git fetch [remote] # 显示所有远程仓库 git remote -v # 显示某个远程仓库的信息 git remote show [remote] # 增加一个新的远程仓库,并命名 git remote add [shortname] [url] # 取回远程仓库的变化,并与本地分支合并 git pull [remote] [branch] # 上传本地指定分支到远程仓库 git push [remote] [branch] # 强行推送当前分支到远程仓库,即使有冲突 git push [remote] --force # 推送所有分支到远程仓库 git push [remote] --all
# 恢复暂存区的指定文件到工作区 git checkout [file] # 恢复某个commit的指定文件到暂存区和工作区 git checkout [commit] [file] # 恢复暂存区的所有文件到工作区 git checkout . # 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 git reset [file] # 重置暂存区与工作区,与上一次commit保持一致 git reset --hard # 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 git reset [commit] # 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致 git reset --hard [commit] # 重置当前HEAD为指定commit,但保持暂存区和工作区不变 git reset --keep [commit] # 新建一个commit,用来撤销指定commit # 后者的所有变化都将被前者抵消,并且应用到当前分支 git revert [commit] # 暂时将未提交的变化移除,稍后再移入 git stash git stash pop
Git 冲突是指在合并分支时,git不清楚两个分支都修改的文件哪个版本是正确的,这在多人合作时经常会出现。
GIT会在文件的冲突位置添加以下信息
<<<<<<<<<<
==========
>>>>>>>>>>
本地版本和他人版本冲突内容通过======分隔开来,这需要选择正确的版本,来告诉git这才是对的
合并分支一般使用git merge
或 git rebase
,在实际开发中为了时间线保持一条直线,使用git rebase
比较多
(feature1)$ git rebase master
执行以上操作, git会执行以下操作
在 rebase 的过程中,也许会出现冲突 conflict。在这种情况,git 会停止 rebase 并会让你去解决冲突。在解决完冲突后,用 git add
命令去更新这些内容, git commit --amend
不修改commit信息继续提交,然后使用 git rebase --continue
继续rebase直到合并完成
# 继续下个冲突解决
git rebase --continue
# 取消本次rebase
git rebase --abort
rebase变基,还可以用来修改历史commit信息,合并commit等操作
# 修改刚提交的3个commit
git rebase -i HEAD~3
会开启新窗口,将pick根据需求修改为自己想要的
pick 242f87598 commit-msg1
pick f7656a723 commit-msg2
pick 11f2d0297 commit-msg3
解决冲突时使用e,修改历史commit信息时使用r,合并commit使用s
git的第一个commit
git 初始版本命令 | git当前版本命令 | 含义 |
---|---|---|
init-db | git init | 初始化git仓库 |
update-cache | git add | 添加文件到暂存区 |
write-tree | git write-tree | 使用临时索引中的内容将树对象写入Git仓库 |
commit-tree | git commit | 基于指定的树在Git仓库中创建新的commit对象 |
read-tree | git read-tree | 显示Git仓库中树对象内容 |
show-diff | git diff | 显示暂存区与工作区之间的差异 |
cat-file | git cat-file | 显示存储在Git仓库中的对象内容 |
具体命令作用,可以查看这篇博文:源码解析:Git的第一个提交是什么样的?
最近的git版本为v2.37,git自2005年至今,不断迭代,功能不断完善、也增加好多代码,阅读最新版的源码很困难,所以选择一个简单版本来阅读源码,最好可以编译,更利于理解代码运行
在github找到git 第一个发布版本是v0.99,但是有个文件始终编译不过去,猜测是openssl版本问题,但git并没有说明对应版本,最后找到v1.3这个版本编译成功
cd git-1.3.0
sudo apt-get install libcurl4-openssl-dev
sudo apt-get install expat-devel
sudo apt-get install libexpat1-dev
make
make install
根据git-1.3.0\Documentation\tutorial.txt 可以练习这个版本的命令
mkdir git-tutorial
cd git-tutorial
git-init-db
echo "Hello World" >hello
echo "Silly example" >example
git add .
git commit
git branch expr
不能commit,报错fatal: empty ident,需要更新用户
git-repo-config "user.name" "aabond"
发现这个版本的git add
实际执行的是个shell脚本git-add.sh, 最终调用git-update-index
git-update-index --add $verbose -z --stdin ;;
而git-update-index
的源码在update-index.c, 将文件遍历,根据SHA1算法写入.git/objects,并添加到缓存
int main(int argc, const char **argv) { ... entries = read_cache(); ... if (read_from_stdin) { struct strbuf buf; strbuf_init(&buf); while (1) { char *path_name; read_line(&buf, stdin, line_termination); if (buf.eof) break; if (line_termination && buf.buf[0] == '"') path_name = unquote_c_style(buf.buf, NULL); else path_name = buf.buf; update_one(path_name, prefix, prefix_length); if (path_name != buf.buf) free(path_name); } } ... } static void update_one(const char *path, const char *prefix, int prefix_length) { ... if (add_file_to_cache(p)) die("Unable to process file %s", path); ... } static int add_file_to_cache(const char *path) { ... // 写入objects if (index_path(ce->sha1, path, &st, !info_only)) return -1; ... // 添加缓存 if (add_cache_entry(ce, option)) return error("%s: cannot add to the index - missing --add option?", path); return 0; }
写入.git/objects: 通过zlib对文件压缩,计算sha1
int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1) { int size; unsigned char *compressed; z_stream stream; unsigned char sha1[20]; char *filename; static char tmpfile[PATH_MAX]; unsigned char hdr[50]; int fd, hdrlen; /* Normally if we have it in the pack then we do not bother writing * it out into .git/objects/??/?{38} file. */ filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen); ... }
根据不同的对象类型type生成header
char *write_sha1_file_prepare(void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned char *hdr, int *hdrlen) { SHA_CTX c; /* Generate the header */ *hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1; /* Sha1.. */ SHA1_Init(&c); SHA1_Update(&c, hdr, *hdrlen); SHA1_Update(&c, buf, len); SHA1_Final(sha1, &c); return sha1_file_name(sha1); }
通过sha1_file_name这个函数,可以看到将sha1的头两个字符用/分隔开来,得到文件的路径和名称
char *sha1_file_name(const unsigned char *sha1) { static char *name, *base; if (!base) { const char *sha1_file_directory = get_object_directory(); int len = strlen(sha1_file_directory); base = xmalloc(len + 60); memcpy(base, sha1_file_directory, len); memset(base+len, 0, 60); base[len] = '/'; base[len+3] = '/'; name = base + len + 1; } fill_sha1_path(name, sha1); return base; }
缓存实际存储在.git/index
int read_cache(void)
{
int fd, i;
...
fd = open(get_index_file(), O_RDONLY);
...
}
char *get_index_file(void)
{
if (!git_index_file)
setup_git_env();
return git_index_file;
}
static void setup_git_env(void)
{
...
git_index_file = getenv(INDEX_ENVIRONMENT);
if (!git_index_file) {
git_index_file = xmalloc(strlen(git_dir) + 7);
sprintf(git_index_file, "%s/index", git_dir);
}
...
}
这个版本的git commit
实际执行也是个shell脚本git-commit.sh,会调用git-commit-tree
,源码位于commit-tree.c
int main(int argc, char **argv) { int i; int parents = 0; unsigned char tree_sha1[20]; unsigned char commit_sha1[20]; char comment[1000]; char *buffer; unsigned int size; // 设置用户邮箱 name + '@' + hostname [+ '.' + domainname setup_ident(); setup_git_directory(); ... /* Person/date information */ add_buffer(&buffer, &size, "author %s\n", git_author_info(1)); add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1)); /* And add the comment */ while (fgets(comment, sizeof(comment), stdin) != NULL) add_buffer(&buffer, &size, "%s", comment); if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) { printf("%s\n", sha1_to_hex(commit_sha1)); return 0; } else return 1; }
同上,这个版本的git branch
也是通过调用sh脚本git-branch.sh来实现,下述是创建分支相关源码
...
branchname="$1"
# 验证参数
rev=$(git-rev-parse --verify "$head") || exit
# 检测分支名字合法
git-check-ref-format "heads/$branchname"
...
# 创建.git/refs/heads/$branchname文件,将当前分支的Head指向的commit对象的hash写入文件中
git update-ref "refs/heads/$branchname" $rev
使用.ignore插件生成忽略文件
使用方法:在项目名字上右键,点击New->.ignore file->.ignore file(git),然后选择对应的编程语言、框架
GitToolBox
最直观的感受是能够直接查看每行是谁commit的,还有能够自动fetch remote
使用快捷键ctrl+k快速commit
当完成把本地分支完成的任务推送到gitlab时,有时会发现有冲突,提示
这种情况下,需要在本地解决冲突,再推送到gitlab
点击git rebase, 会提示冲突文件
点击冲突文件,显示冲突部分。开始修改,可选择魔棒工具, x和>>来选择合适部分
最后完成
最后将rebase的commit提交,pull到gitlab, 最终显示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。