假设我有一个分支b
跟踪本地分支master
。
我正在尝试编写一个脚本,以将b
的所有提交作为一个单元在当前master
指向的任何树的顶部上进行选择。
因为这是非交互式脚本的一部分,所以重要的一点是,选择成功必须始终成功,而绝不能退回到交互式输入。
是否存在可以用于指导git直接提交合并冲突的合并策略或标志的某种组合?
我可以在删除合并冲突之后修改提交。
主要目的是学习如何编写git脚本,而仅部分实现自动化当前工作流程的一部分。我知道,不断挑剔不是The Git Way,而是我抛弃了本地开发历史。使用大量相互跟踪的本地分支机构也不是全部可行的方法。
出于这个问题的目的,请考虑一下本地存储库中的历史记录,从外部世界来看,它们整洁有序比准确的本地历史记录更重要>。
所以,这是我要解决的情况的一个示例。
创建沙箱目录
$ mkdir -p /tmp/gitdir
导航到沙箱目录
$ cd /tmp/gitdir
创建git repo和master分支
$ git init
写入文件,添加到git中,提交。
$ 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
向分支c
添加2次提交
$ 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 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 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;
}
# 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();