git“琐碎"直接提交合并冲突的合并策略 [英] git "trivial" merge strategy that directly commits merge conflicts

查看:95
本文介绍了git“琐碎"直接提交合并冲突的合并策略的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一个分支b跟踪本地分支master.

Suppose I have a branch b tracking a local branch master.

我正在尝试编写一个脚本,以将b的所有提交作为单个单元在当前master指向的任何树的顶部上进行选择.

I'm trying to write a script to cherry-pick all the commits of b as a single unit on top of whatever tree master is currently pointing to.

因为这是非交互式脚本的一部分,所以重要的一点是,选择成功必须始终成功,而绝不能退回到交互式输入.

是否存在可以用于指导git直接提交合并冲突的合并策略或标志的某种组合?

我可以在删除合并冲突后修改提交.

I'm okay with amending the commit after the fact to remove the merge conflict.

主要目的是学习如何编写git脚本,而仅部分实现自动化当前工作流程的一部分.我知道,不断挑剔不是The Git Way,而是我抛弃了当地的发展历史.使用大量相互跟踪的本地分支机构也绝不是Git Way.

The point of this is mostly to learn how to script git, and only partially to automate part of my current workflow. I'm aware that constantly cherry-picking is not The Git Way and that I'm throwing away local development history. Using tons of local branches that all track each other is also not The Git Way in all likelihood.

出于这个问题的目的,请考虑一下本地存储库中的历史记录,从外部世界来看,它们整洁有序比准确的本地历史记录更重要>.

For the purposes of this question, please consider a history in the local repository that is neat and tidy when viewed from the outside world as more important than an accurate local history.

所以,这是我要解决的情况的一个示例.

So, here's an example of the situation I'm trying to resolve.

创建沙箱目录

$ mkdir -p /tmp/gitdir

导航到沙箱目录

$ cd /tmp/gitdir

创建git repo和master分支

create git repo and master branch

$ git init

写入文件,添加到git中,提交.

write file, add to git, commit.

$ echo master > foo.txt`
$ git add foo.txt`
$ git commit -m 'user commit 1'`
[master (root-commit) e9bcb91] user commit 1
1 file changed, 1 insertion(+)
create mode 100644 foo.txt

创建新分支b

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

更改foo.txt的内容并提交

$ echo b1 > foo.txt
$ git add -u
$ git commit -m 'user commit 2'

设置b来跟踪母版

$ git branch -u master

创建分支c

$ git checkout -b c

c

$ git branch -u b

添加2次提交到分支c

$ echo c1 > foo.txt
$ git add -u
$ git commit -m 'user commit 3'
[c 04da4ab] user commit 3
1 file changed, 1 insertion(+), 1 deletion(-)
$ echo c2 > foo.txt
$ git add -u > foo.txt
$ git commit -m 'user commit 4'
[c 17df476] user commit 4
1 file changed, 1 insertion(+), 1 deletion(-)

返回到b,并添加一个提交.

go back to b, and add a commit.

$ git checkout b
Switched to branch 'b'
Your branch is ahead of 'master' by 1 commit.
  (use "git push" to publish your local commits)

$ echo b2 > foo.txt
$ git add -u
$ git commit -m 'user commit 5'
[b 30f68fa] user commit 5
 1 file changed, 1 insertion(+), 1 deletion(-)

返回分支c.

$ git checkout c
Switched to branch 'c'
Your branch and 'b' have diverged,
and have 2 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

因此,关于如何解决这种情况,我们有两种选择.

So, we have a couple of choices about how to repair this situation.

在这种情况下,大多数时候我想做的就是将更改移至另一个分支中的所有更改之后.

Most of the time what I want to do in a situation like this is move the changes in one branch directly after all the changes in another.

在这种情况下,rebase在大多数情况下是正确的,但有时会拖入过时的提交.我真正想做的是将分支的内容向前移动,在图中将其视为 patch delta .

In a situation like this, rebase is correct most of the time, but sometimes pulls in obsolete commits. What I'm really trying to do is move the contents of the branch, considered as a patch or delta forward in the graph.

这是我尝试编写一个脚本来自动在正在跟踪的分支之上自动挑选分支的内容.

Here's my attempt to write a script to automate cherry-picking the contents of a branch on top of the branch it is tracking.

当前的问题是git cherry-pick子进程有时由于合并冲突而放弃,我希望它仅提交冲突的文件.

The current problem with it is that the git cherry-pick subprocess sometimes gives up because of a merge conflict, I want it to just commit the conflicted files.

请考虑将此脚本作为某种工作量证明.尽管对脚本本身的反馈很感激,但这并不是问题的重点.该脚本在这里主要是我要做什么以及为什么的具体证据".

#!/usr/bin/env perl

use strict;
use warnings;
use Carp;
use Data::Dumper;

use vars qw[*CHERRY_PICK_SINK];

BEGIN {
    $Carp::Verbose = 1;
}

# accepts: command string default command interpreter
# returns: lines of output with $/ stripped, error status
sub capture_lines {
    local ${^CHILD_ERROR_NATIVE};
    my ($cmd) = @_;
    croak if ref $cmd;
    my @o = `$cmd`;
    chomp foreach @o;
    return [@o], ${^CHILD_ERROR_NATIVE};
}

# accepts: ()
# returns: UUID, error
sub get_uuid {
    my $err;
    my $cmd = q[python -c 'import uuid; print(str(uuid.uuid4()))'];
    my $lines;
    ($lines, $err) = capture_lines($cmd);
    return undef, $err if $err;
    if (@$lines <= 0) {
        return [undef, 'empty output'];
    }
    my $line = $lines->[0];
    return $line, undef;
}

# accepts: ()
# returns: aref of hashes for current branch, error status
sub current_branch_hashes {
    my $cmd = q[git log --format="%H" '@{upstream}..HEAD'];
    my ($name, $err) = capture_lines($cmd);
    return $name, $err;
}

# accepts: ()
# returns: name of current branch
sub current_branch_name {
    my $cmd = q[git rev-parse --abbrev-ref --symbolic-full-name HEAD];
    my ($lines, $err) = capture_lines($cmd);
    my $name = $lines->[0];
    return $name, $err;
}

# accepts: ()
# returns: name of upstream, error status
sub current_branch_upstream_name {
    my $cmd = q[git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}'];
    my ($lines, $err) = capture_lines($cmd);
    my $name = $lines->[0];
    return $name, $err;
}

# accepts: committish (be careful)
# returns: hash, error code
sub rev_parse {
    my ($name) = @_;
    croak if ref $name;
    my $name_quoted = quotemeta($name);
    my $cmd = "git rev-parse ${name_quoted}";
    my ($lines, $err) = capture_lines($cmd);
    return $lines->[0], $err;
}

# accepts: branch_name, committish
# returns: error code
sub assign_branch {
    my ($key, $value) = @_;
    croak if ref $key;
    croak if ref $value;
    my $key_quoted = quotemeta($key);
    my $value_quoted = quotemeta($value);
    my $cmd = "git branch -f $key_quoted $value_quoted";
    my (undef, $err) = capture_lines($cmd);
    return $err;
}

# accepts: branch_name
# returns: error code
sub delete_branch {
    my ($key) = @_;
    croak if ref $key;
    my $key_quoted = quotemeta($key);
    my $cmd = "git branch -D ${key_quoted}";
    my $err;
    (undef, $err) = capture_lines($cmd);
    return $err;
}

# accepts: label1, label2
# returns: error status
# note: swaps the where the branch labels point to
sub swap_branch_labels {
    my ($label1, $label2) = @_;
    croak if ref $label1;
    croak if ref $label2;
    my ($hash1, $hash2, $err);
    ($hash1, $err) = rev_parse($label1);
    return $err if $err;
    ($hash2, $err) = rev_parse($label2);
    return $err if $err;
    $err = assign_branch($label1, $hash2);
    return $err if $err;
    $err = assign_branch($label2, $hash1);
    return $err if $err;
}

# accepts: committish
# returns: error status
sub checkout_old {
    my ($name) = @_;
    my $name_quoted = quotemeta($name);
    my $cmd = "git checkout ${name_quoted}";
    (undef, my $err) = capture_lines($cmd);
    return $err;
}

# accepts: name
# returns: error status
sub checkout_new {
    my ($name) = @_;
    my $name_quoted = quotemeta($name);
    my $cmd = "git checkout -b ${name_quoted}";
    (undef, my $err) = capture_lines($cmd);
    return $err;
}

# accepts: aref of commit hashes
# returns: exit status
sub cherrypick_aref {
    local *CHERRY_PICK_SINK;
    local ${^CHILD_ERROR_NATIVE};
    my ($hashes) = @_;
    my $cmd = 'git cherry-pick --stdin';
    open CHERRY_PICK_SINK, '|-', $cmd;
    for my $item (@$hashes) {
        chomp($item);
        print CHERRY_PICK_SINK "$item\n";
    }
    close CHERRY_PICK_SINK;
    return ${^CHILD_ERROR_NATIVE};
}

# accepts: ()
# returns: error
sub cherrypick_self {
    my ($hashes, $err) = current_branch_hashes();
    return "current_branch_hashes: $err" if $err;
    return "cherrypick_self: empty hashes" unless @$hashes >= 1;
    my $current_branch;
    ($current_branch, $err) = current_branch_name();
    return "current_branch_name: $err" if $err;
    my $temp_branch;
    ($temp_branch, $err) = get_uuid();
    return "get_uuid: $err" if $err;
    my $upstream;
    ($upstream, $err) = current_branch_upstream_name();
    return "current_branch_upstream_name: $err" if $err;
    $err = checkout_old($upstream);
    return "checkout_old: $err" if $err;
    $err = checkout_new($temp_branch);
    return "checkout_new: $err" if $err;
    $err = cherrypick_aref($hashes);
    return "cherry-pick: $err" if $err;
    $err = swap_branch_labels($temp_branch, $current_branch);
    return "swap branch labels: $err" if $err;
    $err = delete_branch($temp_branch);
    return "delete branch: $err" if $err;
}

cherrypick_self();

推荐答案

是否存在可以用于指导git直接提交合并冲突的合并策略或标志的某种组合?

Is there a merge strategy or some combination of flags that can be used to direct git to commit a merge conflict directly?

不.您可以可以写一个,但这承担了很大的责任.您可以可以然后委派几乎所有的责任,但这会有些棘手.

No. You can write one, but this is taking on a very big chunk of responsibility. You can then delegate almost all of that responsibility, but it's going to be somewhat tricky.

rebase的-s选项使用git cherry-pick来调用合并机制,包括提供-s strategy的选项.换句话说,您可以使用git rebase -s resolve代替git rebase -s recursive. 1 这又意味着您可以编写自己的 own 策略,并将其放入$PATH作为名为例如git-merge-gregory的可执行文件.然后,运行git rebase -s gregory会在每次提交时都调用您的git-merge-gregory程序.

A rebase's -s option uses git cherry-pick to invoke the merge machinery, including with the option of providing -s strategy. In other words, you can use git rebase -s resolve instead of git rebase -s recursive.1 This in turn means you can write your own strategy, and place it in your $PATH as an executable named, e.g., git-merge-gregory. Running git rebase -s gregory would then invoke your git-merge-gregory program on each commit to be cherry-picked.

不幸的是,关于git-merge-strategy实际调用方式的文档数量为零.在旧的git stash脚本中,可以看到如何直接调用它.因此,我们应该查看旧的Git,例如2.6.0,以找到

Unfortunately, there is zero documentation on how git-merge-strategy is actually invoked. In the old git stash script, it was possible to see how to invoke it directly. So we should look at an old Git, such as 2.6.0, to find these lines in its git-stash.sh. I won't quote most of them but there are some magic exports to set environment variables to label the commits, followed by this bit:

if git merge-recursive $b_tree -- $c_tree $w_tree
then
    # No conflict

因此,您的git-merge-gregory应该是一个可执行程序,该程序至少要 合并基础commit-or-tree $b_base的哈希ID,双破折号, 他们的"提交或树$c_tree,以及当前提交或树$w_tree的哈希ID. (当然,可能还会向用户传递其他传递给git rebase命令的-X自变量.)

So your git-merge-gregory should be an executable program that takes at least the hash ID of the merge base commit-or-tree $b_base, a double-dash, the hash ID of the "theirs" commit-or-tree $c_tree, and the hash ID of the current commit-or-tree $w_tree. (You might also be passed additional -X arguments that the user passed to the git rebase command, of course.)

您的程序现在必须完成整个合并,并以零状态退出以表明合并成功,或者在索引和工作树中留下合并混乱,并以非零退出指示用户应在您之后进行清理.

Your program must now complete the entire merge and exit with a zero status to indicate that the merge was successful, or leave a merge-mess behind in the index and work-tree and exit nonzero to indicate that the user should clean up after you.

幸运的是,这时您可以作弊:使用所有这些参数调用git-merge-recursive并检查退出状态.如果退出零,则操作完成.如果退出非零值,则可以让您的程序尝试使用喜欢的任何代码清理留下的混乱git-merge-recursive.这可能是您进行实验的方法.

Fortunately, what you can do at this point is cheat: invoke git-merge-recursive with all these arguments and inspect its exit status. If it exited zero, you're done. If it exited nonzero, you can have your program attempt to clean up the mess git-merge-recursive left behind, using whatever code you like. That's probably the way to go for your experiment.

1 此特定示例毫无意义,因为rebase直接调用git-merge-strategy,并为其提供一个准确的合并基础提交哈希ID. -s resolve-s recursive之间的区别仅在git merge调用具有多个合并基数的策略时出现.因此,这两个实例在所有重定基础的樱桃选择案例中的行为都完全相同.

1This particular example is pointless, because rebase invokes git-merge-strategy directly, providing it exactly one merge base commit hash ID. The difference between -s resolve and -s recursive emerges only when git merge invokes the strategy with more than one merge base. So these two behave exactly the same in all rebase cherry-pick cases.

重点是学习如何编写git ...

The point of this is mostly to learn how to script git ...

这可能是错误的任务.大多数Git脚本涉及使用各种选项运行git rev-parse和/或使用各种选项运行git rev-list,从它们中获取哈希ID,然后在这些哈希ID上运行其他Git管道命令.通常,所有这些都以简单的方式处理索引.合并既困难又困难,有很多极端情况,并且需要对Git索引进行特殊处理,在这种情况下,索引被扩展以容纳每个文件最多三个副本,而不仅仅是每个文件一个副本.

This is probably the wrong task for that. Most Git scripts involve running git rev-parse with various options and/or running git rev-list with various options, getting hash IDs from them, then running other Git plumbing commands on those hash IDs. Usually this all deals with the index in simple ways. Merge is big and hard, with a lot of corner cases and special handling of Git's index, where the index gets expanded to hold up to three copies of each file, instead of just one copy of each file.

这篇关于git“琐碎"直接提交合并冲突的合并策略的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆