---

layout: post
title: "Git pro"
category: Reading Notes

tags: ["读文章", "git"]

{% include JB/setup %}

基本命令

忽略某些文件

$ cat .gitignore

*.[oa] # 忽略所有.a结尾的文件

!lib.a # 但lib.a除外

/TODO # 仅仅忽略项目跟目录下的TODO文件,不包括subdir/TODO

移除文件

要从git中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用git rm命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中。

如果只是简单地从工作目录中手工删除文件,运行git status时就会在“Changed but not updated”部分看到;
然后在运行git rm记录此次移除文件的操作。

最后提交的时候,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项-f,以防误删除文件后丢失修改的内容。

比如一些大型日志文件或者一堆.a编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在.gitignore文件中不上,用--cached选项即可

$ git rm --cached readme.txt

移动文件

$ git mv file_from file_to

其实,运行git mv就相当于运行了下面三条命令:

$ mv README.txt README
$ git rm README.txt
$ git add README

查看提交历史

$ git log
    默认不用任何参数的话,git log会按提交时间列出所有的更新,最近的更新排在最上面。每次更新都有一个SHA-1效验和、作者的名字和电子邮件地址、提交时间,最后缩进一个段落显示提交说明

git log有许多选项可以帮助你搜寻感兴趣的提交

$ git log -p -2
    我们用-p选项展开显示每次提交的内容差异,用-2则仅显示最近的两次更新
--stat 仅显示简要的增改行数统计
--pretty选项,可以指定使用完全不同于默认格式的方式展示提交历史

$ git log --pretty=oneline
    将每个提交放在一行显示

使用format可以订制要显示的记录格式,这样的输出便于后期编程提取分析

$ git log --pretty=format:"%h - %an, %ar : %s"

常用的格式占位符写法及其代表的意义

选项   说明
%H  提交对象(commit)的完整哈希字串
%h  提交对象的简短哈希字串
%T  树对象(tree)的完整哈希字串
%t  树对象的简短哈希字串
%P  父对象(parent)的完整哈希字串
%p  父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 -date= 选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s  提交说明

下面的命令列出所有最近两周内的提交:

$ git log --since=2.weeks

例子:

如果要查看 Git 仓库中,2008 年 10 月期间,Junio Hamano 提交的但未合并的测试脚本(位于项目的 t/ 目录下的文件),可以用下面的查询命令:

$ git log --pretty="%h:%s" --author=gitster --since="2008-10-01"    --before="2008-11-01" --no-merges -- t/

撤销操作

$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend 

上面的三条命令最终得到一个提交,第二个提交命令修正了第一个的提交内容

取消已经暂存的文件

如果用git add *全加到了暂存区域,可以使用

$ git reset HEAD <filename>

来取消暂存文件

$ git checkout -- benchmarks.rb

添加远程仓库

$ git remote
origin
$ git remote add pb git://github.com/paulboone/ticgit.git
$ git remote -v
origin  git://github.com/schacon/ticgit.git
pb  git://github.com/paulboone/ticgit.git

然后使用

$ git fetch pb

推送数据到远程仓库

$ git push origin master

查看远程仓库信息

$ git remote show origin
* remote origin
URL:git://github.com/schacon/ticgit.git
Remote branch merged with 'git pull' while on branch master
    master
Tracked remote branches
    master
    ticgit

远程仓库的删除和重命名

$ git remote rename pb paul
$ git remote
origin
paul

Git分支

Git保存的不是文件差异或者变化量,而只是一系列文件快照

在Git中提交时,会保存一个提交(commit)对象,它包含一个指向暂存内容快照的指针,作者和相关附属信息,以及一定数量(也可能没有)指向改提交对象直接祖先的指针:第一次提交是没有直接祖先的,普通提交有一个祖先,有两个或多个分支合并所产生的提交则有多个祖先。

为了直观起见,我们假设在工作目录中有三个文件,准备将他们暂存后提交。暂存操作会对每一个文件计算效验和(即SHA-1哈希字串),然后把当前的文件快照保存到Git仓库中,并将效验和加入暂存区域:

$ git add README test.rb LICENSE2
$ git commit -m 'initial commit of my project'

当使用git commit新建一个提交对象前,Git会先计算每个子目录的效验和,然后在Git仓库中将这些目录保存为树(tree)对象。之后Git创建的提交对象,除了包含相关提交信息以外,还包含着指向这个树对象(项目根目录)的指针,如此它就可以在将来需要的时候,重现此次快照的内容了。

现在Git仓库中有五个对象:三个表示文件快照内容的blob对象一个记录着目录树内容及其中各个文件对应blob对象索引的tree对象;以及一个包含指向tree对象(根目录)的索引和其他提交信息元数据的commit对象。仓库中的每个对象保存的数据和相互关系看来应该是这样:

一次提交后仓库里的数据

做些修改再次提交,那么这次的提交对象会包含一个指向上次提交对象的指针。两次提交后,仓库历史会变成如下样子:

多次提交后的Git对象数据

Git中的分支,其实本质上仅仅是个指向commit对象的可变指针。Git会使用master作为分支的默认名字。在若干次提交后,你其实已经有了一个指向最后一次提交对象的master分支,它在每次提交的时候都会自动向前移动。

新建一个Git分支,就是创建一个新的分支指针

$ git branch testing

这会在当前commit对象上新建一个分支指针:
多个分支指向提交数据的历史

Git是如何知道你当前在哪个分支上工作的呢?它保存着一个名为HEAD的特别指针。在Git中,它是一个指向你正在工作中的本地分支的指针。运行git branch命令,仅仅是建立了一个新的分支,但不会自动切换到这个分支中去,所以在这个例子中,我们仍然在master分支里工作

HEAD 指向当前所在的分支

要切换到其他分支,可以执行git checkout命令。我们现在转换到新建的testing分支:

$ git checkout testing

这样HEAD就指向了testing分支

HEAD 在你转换分支时指向新的分支

这样的实现方式会给我们带来什么好处呢?好吧,现在不妨再提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'

每次提交后 HEAD 随着分支一起向前移动

现在 testing 分支向前移动了一格,而 master 分支仍然指向原先 git checkout 时所在的 commit 对象

$ vim test.rb
$ git commit -a -m 'made other changes'

分叉了的分支历史

由于Git中的分支实际上仅是一个包含所指对象效验和(40个字符长度SHA-1字串)的文件,所以创建和销毁一个分支就变得非常廉价。说白了,新建一个分支就是向一个文件写入41个字节(外加一个换行符)那么简单。

这和大多数版本控制系统形成了鲜明对比,他们管理分支大多采取备份所有项目文件到特定目录的方式,所以根据项目文件数量和大小不同,可能花费的时间也会有相当大的差别,快则几秒,慢则数分钟。而Git的实现与项目复杂度无关,它永远可以在几毫秒的时间内完成分支的创建和切换。同时,因为每次提交时都记录了祖先信息,所以以后要合并分支时,寻找恰当的合并基础的工作其实已经完成了一大半,实现起来非常容易。

基本的分支与合并

基本分支

首先,我们正在工作,并且已经提交了几次更新:
一部分简短的提交历史

现在,你决定要修补问题追踪系统上的#53问题,运行:

$ git checkout -b iss53
Switched to a new branch "iss53"

相当于下面这两条命令

$ git branch iss53
$ git checkout iss53

然后修正iss53并提交

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'

iss53 分支随工作进展向前推进

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

$ git checkout master
Switched to branch "master"

接下来进行紧急修补。我们创建了一个紧急修补分支(hotfix)来开展工作

$ git checkout -b 'hotfix'
Switched to a new branch "hotfix"

$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix]: created 3a0874c: "fixed the broken email address"
1 files changed, 0 insertions(+), 1 deletions(-)

 hotfix 分支是从 master分支所在点分化出来的

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

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast forward
README |    1 -
1 files changed, 0 insertions(+), 1 deletions(-)

注意:在合并时出现了“fast forward”提示。由于当前master分支所在的commit是要并入hotfix分支的直接上游,Git只需要把指针直接右移。换句话说,如果顺着一个分支走下去可以到达另一个分支,那么Git在合并两者时,只会简单地把指针前移,因为没有什么分歧需要解决,所以这个过程叫做快进(fast forward)

现在的目录变为当前master分支指向的commit所对应的快照,可以发布了。

合并之后,master 分支和hotfix分支指向同一位置

在那个超级重要的修补发布以后,你想要回到被打扰之前的工作。因为现在 hotfix 分支和 master 指向相同的提交,现在没什么用了,可以先删掉它。使用 git branch 的 -d 选项表示删除:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

基本合并

在完成问题#53的工作之后,可以合并到master分支,实际操作同前面合并hotfix分支差不多,只需检出想要更新的分支(master),并运行git merge命令制订来源:

$ git checkout master
$ git merge iss53
Merge made by recursive.
README |    1 +
1 files changed, 1 insertions(+), 0 deletions(-)

请注意,这次合并的实现,并不同于之前 hotfix 的并入方式。这一次,你的开发历史是从更早的地方开始分叉的。由于当前 master 分支所指向的 commit (C4)并非想要并入分支(iss53)的直接祖先,Git 不得不进行一些处理。就此例而言,Git 会用两个分支的末端(C4 和 C5)和它们的共同祖先(C2)进行一次简单的三方合并计算

Git为分支合并自动识别出最佳的同源合并点

Git并没有简单地把分支指针右移,而是对三方合并的结果做了一个新的快照,并自动创建一个指向它的commit(c6)。我们把这个特殊的commit叫做合并提交(merge commit),因为它的祖先不止一个。

Git 自动创建了一个包含了合并结果的 commit 对象

合并的冲突

有时候合并操作并不会如此顺利。如果你修改了两个待合并分支里同一个文件的同一部分,Git就无法干净地把两者合到一起。

如果你在解决问题#53的过程中修改了hotfix中修改的部分,将得到类似下面的结果:

$ 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查阅:

[master*]$ git status
index.html: needs merge
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)\#
#
#       unmerged:   index.html
#

======= 隔开的上半部分,是 HEAD(即 master 分支,在运行 merge 命令时检出的分支)中的内容,下半部分是在 iss53 分支中的内容。解决冲突的办法无非是二者选其一或者由你亲自整合到一起。

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
    please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

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

分布式工作流程

许多使用Git的开发者都喜欢以这样方式来工作,比如仅在master分支中保留完全稳定的代码,即已经发布或即将发布的代码。与此同时,他们还有一个名为develop或next的平行分支,专门用于后续的开发,或仅用于稳定性测试。这样,在确保这些已完成的特征分支能够通过所有测试,并且不会引入更多错误之后,就可以并到主干分支中,等待下一次的发布。

稳定分支总是比较老旧

稳定分支的指针总是在提交历史中落后一大截,而前沿分支总是比较靠前。

特征分支

在任何规模的项目中都可以使用特征(topic)分支。一个特征分支是指一个短期的,用来实现单一特征或与其相关工作的分支。

例子:

起先我们在 master 工作到 C1,然后开始一个新分支 iss91 尝试修复 91 号缺陷,提交到 C6 的时候,又冒出一个新的解决问题的想法,于是从之前 C4 的地方又分出一个分支 iss91v2,干到 C8 的时候,又回到主干中提交了 C9 和 C10,再回到 iss91v2 继续工作,提交 C11,接着,又冒出个不太确定的想法,从 master 的最新提交 C10 处开了个新的分支 dumbidea 做些试验。

拥有多个特性分支的提交历史

假定两件事情:我们最终决定使用第二个解决方案,即 iss91v2 中的办法;另外,我们把 dumbidea 分支拿给同事们看了以后,发现它竟然是个天才之作。所以接下来,我们抛弃原来的 iss91 分支(即丢弃 C5 和 C6),直接在主干中并入另外两个分支。

合并了 dumbidea 和 iss91v2 以后的历史

远程分支

远程分支(remote branch)是对远程仓库状态的索引。它们是一些无法移动的本地分支;只有在进行Git的网络活动时才会更新。远程分支就像是书签,提醒着你上次连接远程仓库时上面各分支的位置。

我们用(远程仓库名)/(分支名)这样的形式表示远程分支。

gitpro

2015/10/25

合成模式

  1. 抽象构件(component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。合成对象通常把它所包含的子对象当成类型为component的对象。在安全式的合成模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝构件对象给出。
  2. 树叶构件(leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
  3. 树枝构件(composite)角色:代表参加组合的有下级子对象的对象。树枝构件类给出所有的管理子对象的方法,如add(),remove()以及getChild()

    // 抽象构件角色类
    public interface Component {
        /**
         * 输出组建自身的名称
         */
        public void printStruct(String preStr);
    }
    
    // 树枝构件角色类
    public class Composite implements Component {
        /**
         * 用来存储组合对象中包含的子组件对象
         */
        private List<Component> childComponents = new ArrayList<Component>();
        /**
         * 组合对象的名字
         */
        private String name;
        /**
         * 构造方法,传入组合对象的名字
         * @param name    组合对象的名字
         */
        public Composite(String name){
            this.name = name;
        }
        /**
         * 聚集管理方法,增加一个子构件对象
         * @param child 子构件对象
         */
        public void addChild(Component child){
            childComponents.add(child);
        }
        /**
         * 聚集管理方法,删除一个子构件对象
         * @param index 子构件对象的下标
         */
        public void removeChild(int index){
            childComponents.remove(index);
        }
        /**
         * 聚集管理方法,返回所有子构件对象
         */
        public List<Component> getChild(){
            return childComponents;
        }
        /**
         * 输出对象的自身结构
         * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进
         */
        @Override
        public void printStruct(String preStr) {
            // 先把自己输出
            System.out.println(preStr + "+" + this.name);
            //如果还包含有子组件,那么就输出这些子组件对象
            if(this.childComponents != null){
                //添加两个空格,表示向后缩进两个空格
                preStr += "  ";
                //输出当前对象的子对象
                for(Component c : childComponents){
                    //递归输出每个子对象
                    c.printStruct(preStr);
                }
            }
    
        }
    
    }
    
    // 树叶构件角色类
    public class Leaf implements Component {
        /**
         * 叶子对象的名字
         */
        private String name;
        /**
         * 构造方法,传入叶子对象的名称
         * @param name 叶子对象的名字
         */
        public Leaf(String name){
            this.name = name;
        }
        /**
         * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
         * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进
         */
        @Override
        public void printStruct(String preStr) {
            // TODO Auto-generated method stub
            System.out.println(preStr + "-" + name);
        }
    
    }
    
    // 客户端类
    public class Client {
        public static void main(String[]args){
            Composite root = new Composite("服装");
            Composite c1 = new Composite("男装");
            Composite c2 = new Composite("女装");
    
            Leaf leaf1 = new Leaf("衬衫");
            Leaf leaf2 = new Leaf("夹克");
            Leaf leaf3 = new Leaf("裙子");
            Leaf leaf4 = new Leaf("套装");
    
            root.addChild(c1);
            root.addChild(c2);
            c1.addChild(leaf1);
            c1.addChild(leaf2);
            c2.addChild(leaf3);
            c2.addChild(leaf4);
    
            root.printStruct("");
        }
    }
    
2015/10/25

---

layout: post
title: "Refactoring Java Using a Functional Interface and Generics"
category: Reading Notes

tags: ["读文章", "Java", "代码重构"]

{% include JB/setup %}

Original Code

public V get(final K key)
{
  Session s;
  try {
      s = oGrid.getSession();
      ObjectMap map = s.getMap(cacheName);
      return (V) map.get(key);
  }
  catch (ObjectGridException oge)
  {
      throw new RuntimeException("Error performing cache operation", oge);
  }
  finally
  {
      if (s != null)
          s.close();         
  }    
  return null;
}      

public void put(final K key, final V value)
{
  Session s;
  try {
      s = oGrid.getSession();
      ObjectMap map = s.getMap(cacheName);
      map.upsert(key, value);
  }
  catch (ObjectGridException oge)
  {
      throw new RuntimeException("Error performing cache operation", oge);
  }
  finally
  {
      if (s != null)
          s.close();             
  }            
}

public Map<K, V> getAll(Set<? extends K> keys)
{
  final List<V> valueList = new ArrayList<V>();
  final List<K> keyList = new ArrayList<K>();
  keyList.addAll(keys);

  Session s;
  try {
      s = oGrid.getSession();
      ObjectMap map = s.getMap(cacheName);
      valueList = map.getAll(keyList);
  }
  catch (ObjectGridException oge)
  {
      throw new RuntimeException("Error performing cache operation", oge);
  }
  finally
  {
      if (s != null)
          s.close();             
  }

  Map<K, V> map = new HashMap<K, V>();
  for(int i = 0; i < keyList.size(); i++) {
      map.put(keyList.get(i), valueList.get(i));
  }
  return map;
}

The problem:this block of code is duplicate

Session s;
try {
  s = oGrid.getSession();
  ObjectMap map = s.getMap(cacheName);
  // Some small bit of business logic goes here
}
catch (ObjectGridException oge)
{
  throw new RuntimeException("Error performing cache operation", oge);
}
finally
{
  if (s != null)
      s.close();             
}

The refactored code:

interface Executable<T> {
  public T execute(ObjectMap map) throws ObjectGridException;
}

private <T> T executeWithMap(Executable<T> ex)
{
  Session;
  try {
      s = oGrid.getSession();
      ObjectMap map = s.getMap(cacheName);
      // Execute our business logic
      return ex.execute(map);
  }
  catch (ObjectGridException oge)
  {
      throw new RuntimeException("Error performing cache operation", oge);
  }
  finally
  {
      if (s != null)
          s.close();             
  }
}

public V get(final K key)
{
  return executeWithMap(new Executable<V>() {
      public V execute(ObjectMap map) throws ObjectGridException
      {
          return (V) map.get(key);
      }
  });              
}      

public void put(final K key, final V value)
{
  executeWithMap(new Executable<Void>() {
      public Void execute(ObjectMap map) throws ObjectGridException
      {
          map.upsert(key, value);
          return null;
      }
  });              
}

Reference:

http://www.michaelbrameld.com/blog/2013/11/02/refacoring-java-generic-functional-interface/

2015/10/25

---

2015/10/25

---

layout: post
title: "Java基础"
category: Reading Notes

tags: ["读文章", “Java”]

{% include JB/setup %}

  • 做基本运算(如+、-、/、<<、>>、>>>、~、等),当两个操作数的类型是byte,short,char,int之一的时候,结果的类型是int;
  • 做整数基本运算时,当一个操作数是long,结果是long
  • 不加任何修饰的整数字面值默认就是int类型。

    short s1 = 1; //OK
    
    short s2 = s1 + 1; //ERROR
    

上面的第一句1整形字面值,它的类型是int,为什么可以直接赋值给short呢?因为字面值都是常量,编译器能很容易的检测出它到底在不在short所能表示的值的范围内。当写成short s1 = 32768的时候,编译就知道short容不下32768了,就会报错。

而对于第二句,s1是short类型,1是int类型,结果是int类型,自然不能自动赋值给short类型的s2了,因为有潜在的高位有效值被截断的风险。可能会有人想,上面s1已经赋值了一个字面值,对于下面的s2,编译器应该也可以计算出它的值啊。如果s1是final的,确实会这样,但s1是变量,编译器是无法预测它会不会在运行期改变的,即使它可能不会改变。

short s = 1;
s = s + 1; //error
short s = 1;
s += 1; //ok

第二段代码编译后的字节码:
0:   iconst_1
1:   istore_1
2:   iload_1
3:   iconst_1
4:   iadd
5:   i2s
6:   istore_1
  • 指令0,1做了short s = 1操作,
  • 指令2,3,4做了s+1的操作,
  • 第5条是关键,做了一个强制转换,将int转换为short,
  • 第6条将强制转换的结果存回变量s

综上可以看到(其中type为byte,short,char,int之一,value可为变量可为常量,类型可为byte,short,char,int,float,double,long):

type s = value;
s += ovalue;//ok

逻辑上等价于(说逻辑上是因为type为int的时候并不存在强制转换,但结果是相同的):

type s = value;
s = (type)(s + ovalue);
2015/10/25