git "тривиальная" стратегия слияния, которая напрямую фиксирует конфликты слияния - PullRequest
0 голосов
/ 08 ноября 2019

Предположим, у меня есть ветвь b, отслеживающая локальную ветвь master.

Я пытаюсь написать сценарий для выбора всех коммитов b как единого блока сверхулюбого дерева, на которое в данный момент master указывает.

Поскольку это часть неинтерактивного скрипта, важно, чтобы вишня всегда удалась и никогда не возвращалась к интерактивному вводу.

Есть ли стратегия слияния или какая-то комбинация флагов, которые можно использовать, чтобы направлять git для непосредственного совершения конфликта слияния?

Я согласен с внесением поправок взафиксируйте факт, чтобы удалить конфликт слияния.


Суть этого в основном состоит в том, чтобы научиться писать сценарии git и лишь частично автоматизировать часть моего текущего рабочего процесса. Я знаю, что постоянно собирать вишню - это не The Git Way, и что я выбрасываю историю местного развития. Использование множества локальных веток, которые все отслеживают друг друга, также, по всей вероятности, не является Git Way.

Для целей этого вопроса, пожалуйста, рассмотрите историю в локальном хранилище, которая опрятна и аккуратна, когдарассматриваемый из внешнего мира как более важный, чем точная местная история .


Итак, вот пример ситуации, которую я пытаюсь разрешить.

создать каталог песочницы

$ mkdir -p /tmp/gitdir

перейти к каталогу песочницы

$ cd /tmp/gitdir

создать репозиторий git и ветку master

$ git init

записать файл, добавить в 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

дорожка b из 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 и добавьте коммит.

$ 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)

Итак,у нас есть пара вариантов, как исправить эту ситуацию.

В большинстве случаев я хочу сделать в такой ветке изменения сразу после всех изменений. изменения в другом.

В подобной ситуации rebase является правильным в большинстве случаев, но иногда тянет за собой устаревшие коммиты. То, что я действительно пытаюсь сделать, это переместить содержимое ветки, которая рассматривается как patch или delta вперед на графике.


Приложение I

Вот моя попытка написать скрипт для автоматизации выбора вишни содержимого ветви поверх отслеживаемой ветви.

В настоящее время проблема заключается в том, что подпроцесс git cherry-pickиногда отказывается из-за конфликта слияния, я хочу, чтобы он просто фиксировал конфликтующие файлы.

Пожалуйста, рассмотрите этот скрипт как доказательство работы токена. Обратная связь по самому сценарию, хотя и приветствуется, не является основной задачей вопроса. Сценарий в основном здесь, чтобы быть конкретным «доказательством» того, что я пытаюсь сделать и почему.

#!/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();

1 Ответ

2 голосов
/ 08 ноября 2019

Есть ли стратегия слияния или какая-то комбинация флагов, которые можно использовать, чтобы направлять git напрямую совершать конфликт слияния?

Нет. Вы можете написать один, но это берет на себя очень большой кусок ответственности. Вы можете затем делегировать почти всю эту ответственность, но это будет несколько сложно.

Опция rebase -s использует git cherry-pick для вызова механизма слияния, в том числе с опциейпредоставления -s <em>strategy</em>. Другими словами, вы можете использовать git rebase -s resolve вместо git rebase -s recursive. 1 Это, в свою очередь, означает, что вы можете написать свою собственную стратегию и поместить ее в свою $PATH какимя исполняемого файла, например, git-merge-gregory. Запуск git rebase -s gregory будет вызывать вашу программу git-merge-gregory при каждом выделении вишни.

К сожалению, нет документации о том, как на самом деле вызывается git-merge-<em>strategy</em>. В старом скрипте git stash можно было увидеть, как вызывать его напрямую. Поэтому нам нужно взглянуть на старый Git, такой как 2.6.0, чтобы найти этих строк в git-stash.sh. Я не буду цитировать большинство из них, но есть некоторые магические export s, чтобы установить переменные окружения на label commits, за которыми следует этот бит:

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

Итак, ваш git-merge-gregory должна быть исполняемой программой, которая принимает как минимум идентификатор хеша базового коммита или дерева слияния $b_base, двойную черту, идентификатор хеша "своего" коммита или дерева$c_tree и идентификатор хэша текущего коммита или дерева $w_tree. (Конечно, вам также могут быть переданы дополнительные -X аргументы, которые пользователь передал команде git rebase.)

Теперь ваша программа должна завершить все слияние и завершиться снулевой статус, чтобы указать, что слияние было успешным, или оставить беспорядок слияния в индексе и рабочем дереве и выйти из ненулевого значения, чтобы указать, что пользователь должен очистить после вас.

К счастью, что вы можете сделатьв этот момент чит: вызовите git-merge-recursive со всеми этими аргументами и проверьте его статус выхода. Если он вышел из нуля, все готово. Если он вышел из нуля, ваша программа может попытаться вычистить оставшуюся неразбериху git-merge-recursive, используя любой код, который вам нравится. Это, вероятно, путь для вашего эксперимента.


1 Этот конкретный пример не имеет смысла, поскольку rebase напрямую вызывает git-merge-<em>strategy</em>, предоставляя ему ровно один хэш-идентификатор фиксации базы слияния. Разница между -s resolve и -s recursive появляется только тогда, когда git merge вызывает стратегию с более чем одной базой слияния. Таким образом, эти два ведут себя одинаково во всех случаях повторного выбора типа «вишня».


Суть в основном состоит в том, чтобы научиться писать сценарии git ...

Вероятно, это неправильная задача для этого. Большинство сценариев Git включают в себя запуск git rev-parse с различными параметрами и / или запуск git rev-list с различными параметрами, получение из них хеш-идентификаторов, а затем выполнение других команд Git для этих хеш-идентификаторов. Обычно все это имеет дело с индексом простыми способами. Слияние является большим и сложным, с множеством угловых случаев и специальной обработкой индекса Git, где индекс расширяется, чтобы содержать до трех копий каждого файла вместо одной копии каждого файла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...