前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[程序员最佳实践学习] git commit 如何写 ? git 分支如何使用? bean copy 最佳实践?

[程序员最佳实践学习] git commit 如何写 ? git 分支如何使用? bean copy 最佳实践?

作者头像
一个会写诗的程序员
发布2019-05-10 18:23:56
1.2K0
发布2019-05-10 18:23:56
举报

git commit 如何写 ?

changelog规约: https://github.com/conventional-changelog/ commit代码检查: https://commitlint.js.org/#/concepts-shareable-config

要精心构思,不可随意为之.

一个提交信息可以表明一个开发者是不是一个好的合作者。

如果你对如何写好 git 提交信息没有仔细想过,那你很可能没有怎么使用过 git log 和相关工具。这里有一个恶性循环:因为提交的历史信息组织混乱而且前后矛盾,那后面的人也就不愿意花时间去使用和维护它。 又因为没有人去使用和维护它,提交的信息就会一直组织混乱和前后矛盾。

但一个好的日志是一个优美和有用的东西,一旦日志处理的好,那么git blame、revert、rebase、log、shortlog 和其它子命令都将发挥它们的作用。查看别人的提交和 pull 请求是值得的,而且可以随时独立完成。理解几个月或者几年前发生的代码变动不仅变得可能,而且高效。

一个项目的长期成功依赖于(除了其它方面)它的可维护性,一个维护者最有力的工具就是项目的日志。所以非常值得花时间学习如何正确地维护它。刚开始可能很麻烦,但很快会成为习惯,最终会成为人们自豪和生产力的源泉。

七条很棒的 git 提交信息规则

1 用一个空行隔开标题和正文 2 限制标题字数在 50 个字符内 3 用大写字母写标题行 4 不要用句号结束标题行 5 在标题行使用祈使语气 6 正文在 72 个字符处换行 7 使用正文解释是什么和为什么,而不是如何做

Commit message 的格式

每次提交,Commit message 都包括三个部分:Header,Body 和 Footer。

代码语言:javascript
复制
<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>

其中,Header 是必需的,Body 和 Footer 可以省略。

不管是哪一个部分,任何一行都不得超过72个字符(或100个字符)。这是为了避免自动换行影响美观。

Header

Header部分只有一行,包括三个字段:

type(必需)、scope(可选)和subject(必需)。

(1)type

type用于说明 commit 的类别,只允许使用下面7个标识。

代码语言:javascript
复制
feat:新功能(feature)
fix:修补bug
docs:文档(documentation)
style: 格式(不影响代码运行的变动)
refactor:重构(即不是新增功能,也不是修改bug的代码变动)
test:增加测试
chore:构建过程或辅助工具的变动
如果type为feat和fix,则该 commit 将肯定出现在 Change log 之中。其他情况(docs、chore、style、refactor、test)由你决定,要不要放入 Change log,建议是不要。

(2)scope

scope用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。

(3)subject

subject是 commit 目的的简短描述,不超过50个字符。

以动词开头,使用第一人称现在时,比如change,而不是changed或changes 第一个字母小写 结尾不加句号(.)

git 分支如何使用?

Git 分支 - 分支的新建与合并

分支的新建与合并

现在让我们来看一个简单的分支与合并的例子,实际工作中大体也会用到这样的工作流程:

  1. 开发某个网站。
  2. 为实现某个新的需求,创建一个分支。
  3. 在这个分支上开展工作。

假设此时,你突然接到一个电话说有个很严重的问题需要紧急修补,那么可以按照下面的方式处理:

  1. 返回到原先已经发布到生产服务器上的分支。
  2. 为这次紧急修补建立一个新分支,并在其中修复问题。
  3. 通过测试后,回到生产服务器所在的分支,将修补分支合并进来,然后再推送到生产服务器上。
  4. 切换到之前实现新需求的分支,继续工作。

分支的新建与切换

首先,我们假设你正在项目中愉快地工作,并且已经提交了几次更新(见图 3-10)。

image

图 3-10. 一个简短的提交历史

现在,你决定要修补问题追踪系统上的 #53 问题。顺带说明下,Git 并不同任何特定的问题追踪系统打交道。这里为了说明要解决的问题,才把新建的分支取名为 iss53。要新建并切换到该分支,运行 git checkout 并加上 -b 参数:

代码语言:javascript
复制
$ git checkout -b iss53
Switched to a new branch 'iss53'

这相当于执行下面这两条命令:

代码语言:javascript
复制
$ git branch iss53
$ git checkout iss53

图 3-11 示意该命令的执行结果。

image

图 3-11. 创建了一个新分支的指针

接着你开始尝试修复问题,在提交了若干次更新后,iss53 分支的指针也会随着向前推进,因为它就是当前分支(换句话说,当前的 HEAD 指针正指向 iss53,见图 3-12):

代码语言:javascript
复制
$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

image

图 3-12. iss53 分支随工作进展向前推进

现在你就接到了那个网站问题的紧急电话,需要马上修补。有了 Git ,我们就不需要同时发布这个补丁和 iss53 里作出的修改,也不需要在创建和发布该补丁到服务器之前花费大力气来复原这些修改。唯一需要的仅仅是切换回 master 分支。

不过在此之前,留心你的暂存区或者工作目录里,那些还没有提交的修改,它会和你即将检出的分支产生冲突从而阻止 Git 为你切换分支。切换分支的时候最好保持一个清洁的工作区域。稍后会介绍几个绕过这种问题的办法(分别叫做 stashing 和 commit amending)。目前已经提交了所有的修改,所以接下来可以正常转换到 master 分支:

代码语言:javascript
复制
$ git checkout master
Switched to branch 'master'

此时工作目录中的内容和你在解决问题 #53 之前一模一样,你可以集中精力进行紧急修补。这一点值得牢记:Git 会把工作目录的内容恢复为检出某分支时它所指向的那个提交对象的快照。它会自动添加、删除和修改文件以确保目录的内容和你当时提交时完全一样。

接下来,你得进行紧急修补。我们创建一个紧急修补分支 hotfix 来开展工作,直到搞定(见图 3-13):

代码语言:javascript
复制
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 3a0874c] fixed the broken email address
 1 files changed, 1 deletion(-)

image

图 3-13. hotfix 分支是从 master 分支所在点分化出来的

有必要作些测试,确保修补是成功的,然后回到 master 分支并把它合并进来,然后发布到生产服务器。用 git merge 命令来进行合并:

代码语言:javascript
复制
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 README | 1 -
 1 file changed, 1 deletion(-)

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

现在最新的修改已经在当前 master 分支所指向的提交对象中了,可以部署到生产服务器上去了(见图 3-14)。

image

图 3-14. 合并之后,master 分支和 hotfix 分支指向同一位置。

在那个超级重要的修补发布以后,你想要回到被打扰之前的工作。由于当前 hotfix 分支和 master 都指向相同的提交对象,所以 hotfix 已经完成了历史使命,可以删掉了。使用 git branch-d 选项执行删除操作:

代码语言:javascript
复制
$ git branch -d hotfix
Deleted branch hotfix (was 3a0874c).

现在回到之前未完成的 #53 问题修复分支上继续工作(图 3-15):

代码语言:javascript
复制
$ git checkout iss53
Switched to branch 'iss53'
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
 1 file changed, 1 insertion(+)

image

图 3-15. iss53 分支可以不受影响继续推进。

值得注意的是之前 hotfix 分支的修改内容尚未包含到 iss53 中来。如果需要纳入此次修补,可以用 git merge master 把 master 分支合并到 iss53;或者等 iss53 完成之后,再将 iss53 分支中的更新并入 master

分支的合并

在问题 #53 相关的工作完成之后,可以合并回 master 分支。实际操作同前面合并 hotfix 分支差不多,只需回到 master 分支,运行 git merge 命令指定要合并进来的分支:

代码语言:javascript
复制
$ git checkout master
$ git merge iss53
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 1 +
 1 file changed, 1 insertion(+)

请注意,这次合并操作的底层实现,并不同于之前 hotfix 的并入方式。因为这次你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的提交对象(C4)并不是 iss53 分支的直接祖先,Git 不得不进行一些额外处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)以及它们的共同祖先(C2)进行一次简单的三方合并计算。图 3-16 用红框标出了 Git 用于合并的三个提交对象:

image

图 3-16. Git 为分支合并自动识别出最佳的同源合并点。

这次,Git 没有简单地把分支指针右移,而是对三方合并后的结果重新做一个新的快照,并自动创建一个指向它的提交对象(C6)(见图 3-17)。这个提交对象比较特殊,它有两个祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁决哪个共同祖先才是最佳合并基础;这和 CVS 或 Subversion(1.5 以后的版本)不同,它们需要开发者手工指定合并基础。所以此特性让 Git 的合并操作比其他系统都要简单不少。

image

图 3-17. Git 自动创建了一个包含了合并结果的提交对象。

既然之前的工作成果已经合并到 master 了,那么 iss53 也就没用了。你可以就此删除它,并在问题追踪系统里关闭该问题。

代码语言:javascript
复制
$ git branch -d iss53

遇到冲突时的分支合并

有时候合并操作并不会如此顺利。如果在不同的分支中都修改了同一个文件的同一部分,Git 就无法干净地把两者合到一起(译注:逻辑上说,这种问题只能由人来裁决。)。如果你在解决问题 #53 的过程中修改了 hotfix 中修改的部分,将得到类似下面的结果:

代码语言:javascript
复制
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git 作了合并,但没有提交,它会停下来等你解决冲突。要看看哪些文件在合并时发生冲突,可以用 git status 查阅:

代码语言:javascript
复制
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

任何包含未解决冲突的文件都会以未合并(unmerged)的状态列出。Git 会在有冲突的文件里加入标准的冲突解决标记,可以通过它们来手工定位并解决这些冲突。可以看到此文件包含类似下面这样的部分:

代码语言:javascript
复制
<<<<<<< HEAD
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
  please contact us at support@github.com
</div>
>>>>>>> iss53

可以看到 ======= 隔开的上半部分,是 HEAD(即 master 分支,在运行 merge 命令时所切换到的分支)中的内容,下半部分是在 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。比如你可以通过把这段内容替换为下面这样来解决:

代码语言:javascript
复制
<div id="footer">
please contact us at email.support@github.com
</div>

这个解决方案各采纳了两个分支中的一部分内容,而且我还删除了 <<<<<<<=======>>>>>>> 这些行。在解决了所有文件里的所有冲突后,运行 git add 将把它们标记为已解决状态(译注:实际上就是来一次快照保存到暂存区域。)。因为一旦暂存,就表示冲突已经解决。如果你想用一个有图形界面的工具来解决这些问题,不妨运行 git mergetool,它会调用一个可视化的合并工具并引导你解决所有冲突:

代码语言:javascript
复制
$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

如果不想用默认的合并工具(Git 为我默认选择了 opendiff,因为我在 Mac 上运行了该命令),你可以在上方"merge tool candidates"里找到可用的合并工具列表,输入你想用的工具名。我们将在第七章讨论怎样改变环境中的默认值。

退出合并工具以后,Git 会询问你合并是否成功。如果回答是,它会为你把相关文件暂存起来,以表明状态为已解决。

再运行一次 git status 来确认所有冲突都已解决:

代码语言:javascript
复制
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   index.html

如果觉得满意了,并且确认所有冲突都已解决,也就是进入了暂存区,就可以用 git commit 来完成这次合并提交。提交的记录差不多是这样:

代码语言:javascript
复制
Merge branch 'iss53'

Conflicts:
  index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#       .git/MERGE_HEAD
# and try again.
#

如果想给将来看这次合并的人一些方便,可以修改该信息,提供更多合并细节。比如你都作了哪些改动,以及这么做的原因。有时候裁决冲突的理由并不直接或明显,有必要略加注解。

https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell

Git Branching - Branches in a Nutshell

Nearly every VCS has some form of branching support. Branching means you diverge from the main line of development and continue to do work without messing with that main line. In many VCS tools, this is a somewhat expensive process, often requiring you to create a new copy of your source code directory, which can take a long time for large projects.

Some people refer to Git’s branching model as its “killer feature,” and it certainly sets Git apart in the VCS community. Why is it so special? The way Git branches is incredibly lightweight, making branching operations nearly instantaneous, and switching back and forth between branches generally just as fast. Unlike many other VCSs, Git encourages workflows that branch and merge often, even multiple times in a day. Understanding and mastering this feature gives you a powerful and unique tool and can entirely change the way that you develop.

Branches in a Nutshell

To really understand the way Git does branching, we need to take a step back and examine how Git stores its data.

As you may remember from Getting Started, Git doesn’t store data as a series of changesets or differences, but instead as a series of snapshots.

When you make a commit, Git stores a commit object that contains a pointer to the snapshot of the content you staged. This object also contains the author’s name and email address, the message that you typed, and pointers to the commit or commits that directly came before this commit (its parent or parents): zero parents for the initial commit, one parent for a normal commit, and multiple parents for a commit that results from a merge of two or more branches.

To visualize this, let’s assume that you have a directory containing three files, and you stage them all and commit. Staging the files computes a checksum for each one (the SHA-1 hash we mentioned in Getting Started), stores that version of the file in the Git repository (Git refers to them as blobs), and adds that checksum to the staging area:

代码语言:javascript
复制
$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

When you create the commit by running git commit, Git checksums each subdirectory (in this case, just the root project directory) and stores them as a tree object in the Git repository. Git then creates a commit object that has the metadata and a pointer to the root project tree so it can re-create that snapshot when needed.

Your Git repository now contains five objects: three blobs (each representing the contents of one of the three files), one tree that lists the contents of the directory and specifies which file names are stored as which blobs, and one commit with the pointer to that root tree and all the commit metadata.

A commit and its tree.

Figure 9. A commit and its tree

If you make some changes and commit again, the next commit stores a pointer to the commit that came immediately before it.

Commits and their parents.

Figure 10. Commits and their parents

A branch in Git is simply a lightweight movable pointer to one of these commits. The default branch name in Git is master. As you start making commits, you’re given a master branch that points to the last commit you made. Every time you commit, the master branch pointer moves forward automatically.

Note : The “master” branch in Git is not a special branch. It is exactly like any other branch. The only reason nearly every repository has one is that the git init command creates it by default and most people don’t bother to change it.

A branch and its commit history.

Figure 11. A branch and its commit history

Creating a New Branch

What happens when you create a new branch? Well, doing so creates a new pointer for you to move around. Let’s say you want to create a new branch called testing. You do this with the git branch command:

代码语言:javascript
复制
$ git branch testing

This creates a new pointer to the same commit you’re currently on.

Two branches pointing into the same series of commits.

Figure 12. Two branches pointing into the same series of commits

How does Git know what branch you’re currently on? It keeps a special pointer called HEAD. Note that this is a lot different than the concept of HEAD in other VCSs you may be used to, such as Subversion or CVS. In Git, this is a pointer to the local branch you’re currently on. In this case, you’re still on master. The git branch command only created a new branch — it didn’t switch to that branch.

HEAD pointing to a branch.

Figure 13. HEAD pointing to a branch

You can easily see this by running a simple git log command that shows you where the branch pointers are pointing. This option is called --decorate.

代码语言:javascript
复制
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project

You can see the “master” and “testing” branches that are right there next to the f30ab commit.

Switching Branches

To switch to an existing branch, you run the git checkout command. Let’s switch to the new testing branch:

代码语言:javascript
复制
$ git checkout testing

This moves HEAD to point to the testing branch.

image.png

Figure 14. HEAD points to the current branch

What is the significance of that? Well, let’s do another commit:

代码语言:javascript
复制
$ vim test.rb
$ git commit -a -m 'made a change'

image.png

Figure 15. The HEAD branch moves forward when a commit is made

This is interesting, because now your testing branch has moved forward, but your master branch still points to the commit you were on when you ran git checkout to switch branches. Let’s switch back to the master branch:

代码语言:javascript
复制
$ git checkout master

HEAD moves when you checkout.

Figure 16. HEAD moves when you checkout

That command did two things. It moved the HEAD pointer back to point to the master branch, and it reverted the files in your working directory back to the snapshot that master points to. This also means the changes you make from this point forward will diverge from an older version of the project. It essentially rewinds the work you’ve done in your testing branch so you can go in a different direction.

Switching branches changes files in your working directory

It’s important to note that when you switch branches in Git, files in your working directory will change. If you switch to an older branch, your working directory will be reverted to look like it did the last time you committed on that branch. If Git cannot do it cleanly, it will not let you switch at all.

Let’s make a few changes and commit again:

代码语言:javascript
复制
$ vim test.rb
$ git commit -a -m 'made other changes'

Now your project history has diverged (see Divergent history). You created and switched to a branch, did some work on it, and then switched back to your main branch and did other work. Both of those changes are isolated in separate branches: you can switch back and forth between the branches and merge them together when you’re ready. And you did all that with simple branch, checkout, and commit commands.

Divergent history.

Figure 17. Divergent history

You can also see this easily with the git log command. If you run git log --oneline --decorate --graph --all it will print out the history of your commits, showing where your branch pointers are and how your history has diverged.

代码语言:javascript
复制
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

Because a branch in Git is actually a simple file that contains the 40 character SHA-1 checksum of the commit it points to, branches are cheap to create and destroy. Creating a new branch is as quick and simple as writing 41 bytes to a file (40 characters and a newline).

This is in sharp contrast to the way most older VCS tools branch, which involves copying all of the project’s files into a second directory. This can take several seconds or even minutes, depending on the size of the project, whereas in Git the process is always instantaneous. Also, because we’re recording the parents when we commit, finding a proper merge base for merging is automatically done for us and is generally very easy to do. These features help encourage developers to create and use branches often.

Let’s see why you should do so.

Creating a new branch and switching to it at the same time

It’s typical to create a new branch and want to switch to that new branch at the same time — this can be done in one operation with git checkout -b <newbranchname>.

bean copy 最佳实践?

java bean属性拷贝工具类比对(性能+功能) 业务系统中经常需要两个对象进行属性的拷贝,不能否认逐个的对象拷贝是最快速最安全的做法,但是当数据对象的属性字段数量超过程序员的容忍的程度比如通常超过5个属性的时候,代码因此变得臃肿不堪,使用一些方便的对象拷贝工具类将是很好的选择。

目前流行的较为公用认可的工具类:

Apache的两个版本:(反射机制)

代码语言:javascript
复制
org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)

org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

Spring版本:(反射机制)

代码语言:javascript
复制
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)

新版本的spring也集成了cglib版:

代码语言:javascript
复制
org.springframework.cglib.beans.BeanCopier

cglib版本:(使用动态代理,效率高)

代码语言:javascript
复制
net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)

dozer版本(xml配置映射,性能最低下):

代码语言:javascript
复制
org.dozer.DozerBeanMapper.map(Object source, Class<T> destinationClass)

就API而言,使用哪个工具类无所谓,我们通常关心两点,功能完善性以及性能。

参考文档:

Java Bean Copy框架性能对比: https://yq.aliyun.com/articles/392185

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.04.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • git commit 如何写 ?
    • 七条很棒的 git 提交信息规则
      • Commit message 的格式
        • Header
    • git 分支如何使用?
    • Git 分支 - 分支的新建与合并
      • 分支的新建与合并
        • 分支的新建与切换
        • 分支的合并
        • 遇到冲突时的分支合并
    • Git Branching - Branches in a Nutshell
      • Branches in a Nutshell
        • Creating a New Branch
        • Switching Branches
        • Apache的两个版本:(反射机制)
        • Spring版本:(反射机制)
        • cglib版本:(使用动态代理,效率高)
        • dozer版本(xml配置映射,性能最低下):
    • bean copy 最佳实践?
    • 参考文档:
    相关产品与服务
    腾讯云代码分析
    腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档