zoukankan      html  css  js  c++  java
  • git如何merge私有remote branch及如何计算树节点最小功能祖先

    一、背景说明

    对于某些git项目,可能只有少数几个人(假设为Maintainer——M)有何如权限,其它人的合入需要这些有权限的开发者(假设为Developer——D)合入,当然,这通常的流程都是某个程序员fork自己的分支,合入,然后有M合入主线分支。这个流程是假设M和P都经过公共的远端服务器进行合并。
    git作为一种分布式系统,它的设计本身是去中心化的,所以M和P都需要经过中心服务器这种某事应该是可以避免的,可不可以让一个P创建自己分支之后,不把这个分支提交到主线上而让M进行merge呢?当然是可以的,比方说自己生成一个补丁,或者整个打个包来发送给M,但是这些方法都是相当于使用了git之外的第三封进程来实现的,那么git内部有没有内置的解决方案呢?
    整个时候就可以使用git的remote功能了。
    总结起来说,也就是系统通过git的内置remote功能来实现一个对中心服务器无感知的合入操作:
    1、开发者D从主线fork自己的分支进行本地开发和commit
    2、开发者D将自己的网络地址告诉维护者M
    3、维护者M在本地添加D的远端地址,执行fetch + merge
    4、维护者M向主线合入开发者D的修改内容
    可以看到在整个过程中,开发者D对于主线只有一个读取操作,而且使用了git内置的remote功能来实现了流程,D的提交记录(日志内容)在M的合入也是没有丢失的。

    二、模拟一下这个流程

    为了简单起见,这里没有使用远端地址,所有操作都是使用本地文件系统。这里要注意的一点是,开发者tsecer始终没有向服务器执行过push或者merge,整个过程由维护者Maint把tsecer设置为远端库并更新

    1、管理员创建一个git仓库

    Admin@Repo: mkdir git.merge.remote
    Admin@Repo: cd git.merge.remote
    Admin@Repo: git init --bare
    已初始化空的 Git 仓库于 /home/harry/git.merge.remote/
    Admin@Repo:

    2、Maint提交修改

    Maint@harry:git clone /home/harry/git.merge.remote .
    正克隆到 '.'...
    warning: 您似乎克隆了一个空仓库。
    完成。
    Maint@harry:pwd
    /home/harry/git.merge.local/Maint
    Maint@harry:echo "Maint add" > readme
    Maint@harry:git add readme
    Maint@harry:git commit -m "form maint"
    [master(根提交) 2dc1633] form maint
    1 file changed, 1 insertion(+)
    create mode 100644 readme
    Maint@harry:git push
    枚举对象: 3, 完成.
    对象计数中: 100% (3/3), 完成.
    写入对象中: 100% (3/3), 212 字节 | 212.00 KiB/s, 完成.
    总共 3(差异 0),复用 0(差异 0),包复用 0
    To /home/harry/git.merge.remote
    * [new branch] master -> master
    Maint@harry:git log
    commit 2dc163344ab4d56e2b9d8a8a1d7872727f456e08 (HEAD -> master, origin/master)
    Author: Maint <Maint@harry.com>
    Date: Thu Jul 23 20:29:20 2020 +0800

    form maint
    Maint@harry:

    3、Dev fork并本地修改

    tsecer@harry: git clone /home/harry/git.merge.remote .
    正克隆到 '.'...
    完成。
    tsecer@harry: echo "from tsecer" >> readme
    tsecer@harry: git add readme
    tsecer@harry: git commit -m "append from tsecer"
    [master acccced] append from tsecer
    1 file changed, 1 insertion(+)
    tsecer@harry:

    4、Maint设置tsecer为remote并合并

    Maint@harry:git remote add tsecer /home/harry/git.merge.local/Dev
    Maint@harry:git fetch tsecer
    remote: 枚举对象: 5, 完成.
    remote: 对象计数中: 100% (5/5), 完成.
    remote: 总共 3(差异 0),复用 0(差异 0),包复用 0
    展开对象中: 100% (3/3), 236 字节 | 236.00 KiB/s, 完成.
    来自 /home/harry/git.merge.local/Dev
    * [新分支] master -> tsecer/master
    Maint@harry:git merge remotes/tsecer/master
    更新 2dc1633..acccced
    Fast-forward
    readme | 1 +
    1 file changed, 1 insertion(+)
    Maint@harry:git log
    commit acccced0299c2dfd116dbf66e3437a026bb46ee7 (HEAD -> master, tsecer/master)
    Author: tsecer <tsecer@harry.com>
    Date: Thu Jul 23 20:32:27 2020 +0800

    append from tsecer

    commit 2dc163344ab4d56e2b9d8a8a1d7872727f456e08 (origin/master)
    Author: Maint <Maint@harry.com>
    Date: Thu Jul 23 20:29:20 2020 +0800

    form maint
    Maint@harry:

    三、为什么Maint merge的时候要指定为remotes/tsecer/master

    git自带文档中对于版本号的说明 git-masterDocumentation evisions.txt
    '<refname>', e.g. 'master', 'heads/master', 'refs/heads/master':: A symbolic ref name. E.g. 'master' typically means the commit
    object referenced by 'refs/heads/master'. If you
    happen to have both 'heads/master' and 'tags/master', you can explicitly say 'heads/master' to tell Git which one you mean.
    When ambiguous, a '<refname>' is disambiguated by taking the
    first match in the following rules:
    . If '$GIT_DIR/<refname>' exists, that is what you mean (this is usually useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD`, `MERGE_HEAD`
    and `CHERRY_PICK_HEAD`);
    . otherwise, 'refs/<refname>' if it exists;
    . otherwise, 'refs/tags/<refname>' if it exists;
    . otherwise, 'refs/heads/<refname>' if it exists;
    . otherwise, 'refs/remotes/<refname>' if it exists;
    . otherwise, 'refs/remotes/<refname>/HEAD' if it exists.
    git代码中对应的处理内容
    git-master efs.c
    static const char *ref_rev_parse_rules[] = {
    "%.*s",
    "refs/%.*s",
    "refs/tags/%.*s",
    "refs/heads/%.*s",
    "refs/remotes/%.*s",
    "refs/remotes/%.*s/HEAD",
    NULL
    };
    从这里可以看到,设置的远端并不符合整个模式,所以要指定完成路径,当然从这个地方看,remotes这个前缀是可以省略的,也就是
    git merge tsecer/master
    也是可以的啦。

    四、git最小公共祖先节点

    虽然tsecer的版本库在远端,但是毕竟是同祖同源的一个版本库,也就是说两个版本库都是有相同的祖先结点的。在执行merge的时候首先要识别出这个公共节点,从而保证两棵树是可以进行合并的。当然这个只是一个合理性检测,也就是说如果不满足这个条件不是说不能合并,而只是说合并的时候不合常理。如果是两个完全不同的版本库,那么可以在git merge的时候通过--allow-unrelated-histories选项来抑制这个检测。
    那么如何确定最小祖先节点呢?这个问题如果是通用的问题就比较麻烦,但是好在git的每个提交日志都是由时间戳的,这样的话可以使用这个内置的时间戳提供的hint进行排序现场归并排序。大致来说就是维护一个优先级队列,按照时间戳进行排序,把两个分支的父节点放入优先级队列,每次从其中找到一个时间戳最新的节点,递归这个取父节点进入优先级队列过程(类似于广度优先搜索),直到取到某个节点身上同时设置了PARENT1和PARENT2两个标志位,这个节点就是公共祖先结点,整个过程解说。
    git-mastercommit-reach.c
    /* all input commits in one and twos[] must have been parsed! */
    static struct commit_list *paint_down_to_common(struct repository *r,
    struct commit *one, int n,
    struct commit **twos,
    int min_generation)
    {
    ……
    while (queue_has_nonstale(&queue)) {
    struct commit *commit = prio_queue_get(&queue);
    struct commit_list *parents;
    int flags;

    if (min_generation && commit->generation > last_gen)
    BUG("bad generation skip %8x > %8x at %s",
    commit->generation, last_gen,
    oid_to_hex(&commit->object.oid));
    last_gen = commit->generation;

    if (commit->generation < min_generation)
    break;

    flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
    ……
    }

    五、如果是通用二叉树呢

    1、在力扣上这种问题的间接做法


    它的实现思路是,它们的最小公共父节点,一定是唯一一个同时在左右子节点分别包含p和q的节点

    class Solution {
    public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (!root || root == p || root == q) return root;
    TreeNode *left = lowestCommonAncestor(root->left, p, q);
    TreeNode *right = lowestCommonAncestor(root->right, p, q);
    if (left && right) return root;
    return left ? left : right;
    }
    };

    2、可能更直观的方法

    如果假设二叉树层数并不是很深,那么可以分别找到从当前节点到根节点的完成路径,然后从两个路径反向查找,第一个分叉的地方就是它们的公共父节点。当然需要假设每个节点是有父节点指针的。
    这种方法对于git的提交树来说显然不太合适,因为他git的“树”其实很多情况下是单链的,而merge的时候可能存在不止一个父节点,从而使整个问题更加复杂。

  • 相关阅读:
    Android 亮度调节
    Android异步回调中的UI同步性问题
    Java总结篇系列:Java 反射
    Node入门教程(4)第三章:第一个 Nodejs 程序
    Node入门教程(3)第二章: Node 安装
    Node入门教程(2)第一章:NodeJS 概述
    Node入门教程(1)目录
    前端面试题:JS中的let和var的区别
    IT学习逆袭的新模式,全栈实习生,不8000就业不还实习费
    11-移动端开发教程-zepto.js入门教程
  • 原文地址:https://www.cnblogs.com/tsecer/p/13368371.html
Copyright © 2011-2022 走看看