static const char apply_usage[] =
"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
+static enum whitespace_eol {
+ nowarn_whitespace,
+ warn_on_whitespace,
+ error_on_whitespace,
+ strip_whitespace,
+} new_whitespace = warn_on_whitespace;
+static int whitespace_error = 0;
+static int squelch_whitespace_errors = 5;
+static int applied_after_stripping = 0;
+static const char *patch_input_file = NULL;
+
+static void parse_whitespace_option(const char *option)
+{
+ if (!option) {
+ new_whitespace = warn_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "warn")) {
+ new_whitespace = warn_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "nowarn")) {
+ new_whitespace = nowarn_whitespace;
+ return;
+ }
+ if (!strcmp(option, "error")) {
+ new_whitespace = error_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "error-all")) {
+ new_whitespace = error_on_whitespace;
+ squelch_whitespace_errors = 0;
+ return;
+ }
+ if (!strcmp(option, "strip")) {
+ new_whitespace = strip_whitespace;
+ return;
+ }
+ die("unrecognized whitespace option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+ if (!whitespace_option && !apply_default_whitespace) {
+ new_whitespace = (apply
+ ? warn_on_whitespace
+ : nowarn_whitespace);
+ }
+}
+
/*
* For "diff-stat" like behaviour, we keep track of the biggest change
* we've seen, and the longest filename. That allows us to do simple
oldlines--;
break;
case '+':
+ /*
+ * We know len is at least two, since we have a '+' and
+ * we checked that the last character was a '\n' above.
+ * That is, an addition of an empty line would check
+ * the '+' here. Sneaky...
+ */
+ if ((new_whitespace != nowarn_whitespace) &&
+ isspace(line[len-2])) {
+ whitespace_error++;
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors <
+ whitespace_error)
+ ;
+ else {
+ fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
+ patch_input_file,
+ linenr, len-2, line+1);
+ }
+ }
added++;
newlines--;
break;
unsigned long alloc;
};
+static int apply_line(char *output, const char *patch, int plen)
+{
+ /* plen is number of bytes to be copied from patch,
+ * starting at patch+1 (patch[0] is '+'). Typically
+ * patch[plen] is '\n'.
+ */
+ int add_nl_to_tail = 0;
+ if ((new_whitespace == strip_whitespace) &&
+ 1 < plen && isspace(patch[plen-1])) {
+ if (patch[plen] == '\n')
+ add_nl_to_tail = 1;
+ plen--;
+ while (0 < plen && isspace(patch[plen]))
+ plen--;
+ applied_after_stripping++;
+ }
+ memcpy(output, patch + 1, plen);
+ if (add_nl_to_tail)
+ output[plen++] = '\n';
+ return plen;
+}
+
static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
{
char *buf = desc->buffer;
break;
/* Fall-through for ' ' */
case '+':
- if (*patch != '+' || !no_add) {
- memcpy(new + newsize, patch + 1, plen);
- newsize += plen;
- }
+ if (*patch != '+' || !no_add)
+ newsize += apply_line(new + newsize, patch,
+ plen);
break;
case '@': case '\\':
/* Ignore it, we already handled it */
return 1;
}
-static int apply_patch(int fd)
+static int apply_patch(int fd, const char *filename)
{
int newfd;
unsigned long offset, size;
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
+ patch_input_file = filename;
if (!buffer)
return -1;
offset = 0;
}
newfd = -1;
+ if (whitespace_error && (new_whitespace == error_on_whitespace))
+ apply = 0;
+
write_index = check_index && apply;
if (write_index)
newfd = hold_index_file_for_update(&cache_file, get_index_file());
return 0;
}
+static int git_apply_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "apply.whitespace")) {
+ apply_default_whitespace = strdup(value);
+ return 0;
+ }
+ return git_default_config(var, value);
+}
+
+
int main(int argc, char **argv)
{
int i;
int read_stdin = 1;
+ const char *whitespace_option = NULL;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0);
+ apply_patch(0, "<stdin>");
read_stdin = 0;
continue;
}
line_termination = 0;
continue;
}
+ if (!strncmp(arg, "--whitespace=", 13)) {
+ whitespace_option = arg + 13;
+ parse_whitespace_option(arg + 13);
+ continue;
+ }
if (check_index && prefix_length < 0) {
prefix = setup_git_directory();
prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_default_config);
+ git_config(git_apply_config);
+ if (!whitespace_option && apply_default_whitespace)
+ parse_whitespace_option(apply_default_whitespace);
}
if (0 < prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
if (fd < 0)
usage(apply_usage);
read_stdin = 0;
- apply_patch(fd);
+ set_default_whitespace_mode(whitespace_option);
+ apply_patch(fd, arg);
close(fd);
}
+ set_default_whitespace_mode(whitespace_option);
if (read_stdin)
- apply_patch(0);
+ apply_patch(0, "<stdin>");
+ if (whitespace_error) {
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors < whitespace_error) {
+ int squelched =
+ whitespace_error - squelch_whitespace_errors;
+ fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+ squelched,
+ squelched == 1 ? "" : "s");
+ }
+ if (new_whitespace == error_on_whitespace)
+ die("%d line%s add%s trailing whitespaces.",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ if (applied_after_stripping)
+ fprintf(stderr, "warning: %d line%s applied after"
+ " stripping trailing whitespaces.\n",
+ applied_after_stripping,
+ applied_after_stripping == 1 ? "" : "s");
+ else if (whitespace_error)
+ fprintf(stderr, "warning: %d line%s add%s trailing"
+ " whitespaces.\n",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ }
return 0;
}
extern int commit_index_file(struct cache_file *);
extern void rollback_index_file(struct cache_file *);
+/* Environment bits from configuration mechanism */
extern int trust_executable_bit;
extern int assume_unchanged;
extern int only_use_symrefs;
extern int diff_rename_limit_default;
extern int shared_repository;
+extern const char *apply_default_whitespace;
#define GIT_REPO_VERSION 0
extern int repository_format_version;
scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrollwin.set_shadow_type(gtk.SHADOW_IN)
vbox.pack_start(scrollwin, expand=True, fill=True)
scrollwin.show()
self.treeview.show()
cell = CellRendererGraph()
- # Set the default width to 265
- # This make sure that we have nice display with large tag names
- cell.set_property("width", 265)
column = gtk.TreeViewColumn()
column.set_resizable(True)
column.pack_start(cell, expand=True)
*/
#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
#include "delta.h"
-#include "zlib.h"
/* block size: min = 16, max = 64k, power of 2 */
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define GR_PRIME 0x9e370001
-#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
-
-static unsigned int hashbits(unsigned int size)
-{
- unsigned int val = 1, bits = 0;
- while (val < size && bits < 32) {
- val <<= 1;
- bits++;
- }
- return bits ? bits: 1;
-}
-
-typedef struct s_chanode {
- struct s_chanode *next;
- int icurr;
-} chanode_t;
-
-typedef struct s_chastore {
- int isize, nsize;
- chanode_t *ancur;
-} chastore_t;
-
-static void cha_init(chastore_t *cha, int isize, int icount)
-{
- cha->isize = isize;
- cha->nsize = icount * isize;
- cha->ancur = NULL;
-}
-
-static void *cha_alloc(chastore_t *cha)
-{
- chanode_t *ancur;
- void *data;
+#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
- ancur = cha->ancur;
- if (!ancur || ancur->icurr == cha->nsize) {
- ancur = malloc(sizeof(chanode_t) + cha->nsize);
- if (!ancur)
- return NULL;
- ancur->icurr = 0;
- ancur->next = cha->ancur;
- cha->ancur = ancur;
- }
-
- data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
- ancur->icurr += cha->isize;
- return data;
-}
-
-static void cha_free(chastore_t *cha)
-{
- chanode_t *cur = cha->ancur;
- while (cur) {
- chanode_t *tmp = cur;
- cur = cur->next;
- free(tmp);
- }
-}
-
-typedef struct s_bdrecord {
- struct s_bdrecord *next;
- unsigned int fp;
+struct index {
const unsigned char *ptr;
-} bdrecord_t;
-
-typedef struct s_bdfile {
- chastore_t cha;
- unsigned int fphbits;
- bdrecord_t **fphash;
-} bdfile_t;
+ unsigned int val;
+ struct index *next;
+};
-static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
+static struct index ** delta_index(const unsigned char *buf,
+ unsigned long bufsize,
+ unsigned int *hash_shift)
{
- unsigned int fphbits;
- int i, hsize;
- const unsigned char *data, *top;
- bdrecord_t *brec;
- bdrecord_t **fphash;
-
- fphbits = hashbits(bufsize / BLK_SIZE + 1);
- hsize = 1 << fphbits;
- fphash = malloc(hsize * sizeof(bdrecord_t *));
- if (!fphash)
- return -1;
- for (i = 0; i < hsize; i++)
- fphash[i] = NULL;
- cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
-
- top = buf + bufsize;
- data = buf + (bufsize / BLK_SIZE) * BLK_SIZE;
- if (data == top)
+ unsigned int hsize, hshift, entries, blksize, i;
+ const unsigned char *data;
+ struct index *entry, **hash;
+ void *mem;
+
+ /* determine index hash size */
+ entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+ hsize = entries / 4;
+ for (i = 4; (1 << i) < hsize && i < 16; i++);
+ hsize = 1 << i;
+ hshift = 32 - i;
+ *hash_shift = hshift;
+
+ /* allocate lookup index */
+ mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
+ if (!mem)
+ return NULL;
+ hash = mem;
+ entry = mem + hsize * sizeof(*hash);
+ memset(hash, 0, hsize * sizeof(*hash));
+
+ /* then populate it */
+ data = buf + entries * BLK_SIZE - BLK_SIZE;
+ blksize = bufsize - (data - buf);
+ while (data >= buf) {
+ unsigned int val = adler32(0, data, blksize);
+ i = HASH(val, hshift);
+ entry->ptr = data;
+ entry->val = val;
+ entry->next = hash[i];
+ hash[i] = entry++;
+ blksize = BLK_SIZE;
data -= BLK_SIZE;
+ }
- for ( ; data >= buf; data -= BLK_SIZE) {
- brec = cha_alloc(&bdf->cha);
- if (!brec) {
- cha_free(&bdf->cha);
- free(fphash);
- return -1;
- }
- brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
- brec->ptr = data;
- i = HASH(brec->fp, fphbits);
- brec->next = fphash[i];
- fphash[i] = brec;
- }
-
- bdf->fphbits = fphbits;
- bdf->fphash = fphash;
-
- return 0;
-}
-
-static void delta_cleanup(bdfile_t *bdf)
-{
- free(bdf->fphash);
- cha_free(&bdf->cha);
+ return hash;
}
+/* provide the size of the copy opcode given the block offset and size */
#define COPYOP_SIZE(o, s) \
(!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
!!(s & 0xff) + !!(s & 0xff00) + 1)
+/* the maximum size for any opcode */
+#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
+
void *diff_delta(void *from_buf, unsigned long from_size,
void *to_buf, unsigned long to_size,
unsigned long *delta_size,
unsigned long max_size)
{
- int i, outpos, outsize, inscnt, csize, msize, moff;
- unsigned int fp;
- const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2;
- unsigned char *out, *orig;
- bdrecord_t *brec;
- bdfile_t bdf;
+ unsigned int i, outpos, outsize, inscnt, hash_shift;
+ const unsigned char *ref_data, *ref_top, *data, *top;
+ unsigned char *out;
+ struct index *entry, **hash;
- if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
+ if (!from_size || !to_size)
+ return NULL;
+ hash = delta_index(from_buf, from_size, &hash_shift);
+ if (!hash)
return NULL;
-
+
outpos = 0;
outsize = 8192;
+ if (max_size && outsize >= max_size)
+ outsize = max_size + MAX_OP_SIZE + 1;
out = malloc(outsize);
if (!out) {
- delta_cleanup(&bdf);
+ free(hash);
return NULL;
}
}
inscnt = 0;
- moff = 0;
- while (data < top) {
- msize = 0;
- fp = adler32(0, data, MIN(top - data, BLK_SIZE));
- i = HASH(fp, bdf.fphbits);
- for (brec = bdf.fphash[i]; brec; brec = brec->next) {
- if (brec->fp == fp) {
- csize = ref_top - brec->ptr;
- if (csize > top - data)
- csize = top - data;
- for (ptr1 = brec->ptr, ptr2 = data;
- csize && *ptr1 == *ptr2;
- csize--, ptr1++, ptr2++);
- csize = ptr1 - brec->ptr;
- if (csize > msize) {
- moff = brec->ptr - ref_data;
- msize = csize;
- if (msize >= 0x10000) {
- msize = 0x10000;
- break;
- }
+ while (data < top) {
+ unsigned int moff = 0, msize = 0;
+ unsigned int blksize = MIN(top - data, BLK_SIZE);
+ unsigned int val = adler32(0, data, blksize);
+ i = HASH(val, hash_shift);
+ for (entry = hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = ref_top - ref;
+ if (entry->val != val)
+ continue;
+ if (ref_size > top - src)
+ ref_size = top - src;
+ while (ref_size && *src++ == *ref) {
+ ref++;
+ ref_size--;
+ }
+ ref_size = ref - entry->ptr;
+ if (ref_size > msize) {
+ /* this is our best match so far */
+ moff = entry->ptr - ref_data;
+ msize = ref_size;
+ if (msize >= 0x10000) {
+ msize = 0x10000;
+ break;
}
}
}
inscnt = 0;
}
} else {
+ unsigned char *op;
+
if (inscnt) {
out[outpos - inscnt - 1] = inscnt;
inscnt = 0;
}
data += msize;
- orig = out + outpos++;
+ op = out + outpos++;
i = 0x80;
if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
msize >>= 8;
if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
- *orig = i;
- }
-
- if (max_size && outpos > max_size) {
- free(out);
- delta_cleanup(&bdf);
- return NULL;
+ *op = i;
}
- /* next time around the largest possible output is 1 + 4 + 3 */
- if (outpos > outsize - 8) {
+ if (outpos >= outsize - MAX_OP_SIZE) {
void *tmp = out;
outsize = outsize * 3 / 2;
- out = realloc(out, outsize);
+ if (max_size && outsize >= max_size)
+ outsize = max_size + MAX_OP_SIZE + 1;
+ if (max_size && outpos > max_size)
+ out = NULL;
+ else
+ out = realloc(out, outsize);
if (!out) {
free(tmp);
- delta_cleanup(&bdf);
+ free(hash);
return NULL;
}
}
if (inscnt)
out[outpos - inscnt - 1] = inscnt;
- delta_cleanup(&bdf);
+ free(hash);
*delta_size = outpos;
return out;
}
int repository_format_version = 0;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
int shared_repository = 0;
+const char *apply_default_whitespace = NULL;
static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
*git_graft_file;
}
prec=4
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary=
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws=
while case "$#" in 0) break;; esac
do
--sk|--ski|--skip)
skip=t; shift ;;
+ --whitespace=*)
+ ws=$1; shift ;;
+
--)
shift; break ;;
-*)
exit 1
}
- # -b, -s, -u and -k flags are kept for the resuming session after
- # a patch failure.
+ # -b, -s, -u, -k and --whitespace flags are kept for the
+ # resuming session after a patch failure.
# -3 and -i can and must be given when resuming.
echo "$binary" >"$dotest/binary"
+ echo " $ws" >"$dotest/whitespace"
echo "$sign" >"$dotest/sign"
echo "$utf8" >"$dotest/utf8"
echo "$keep" >"$dotest/keep"
then
keep=-k
fi
+ws=`cat "$dotest/whitespace"`
if test "$(cat "$dotest/sign")" = t
then
SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
case "$resolved" in
'')
- git-apply $binary --index "$dotest/patch"
+ git-apply $binary --index $ws "$dotest/patch"
apply_status=$?
;;
t)
my $rc = GetOptions( "long|l" => \$longrev,
"help|h" => \$help,
"rename|r" => \$rename,
- "rev-file|S" => \$rev_file);
+ "rev-file|S=s" => \$rev_file);
if (!$rc or $help) {
usage();
}
my $revlist;
if ($rev_file) {
- open($revlist, '<' . $rev_file);
+ open($revlist, '<' . $rev_file)
+ or die "Failed to open $rev_file : $!";
} else {
$revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
or die "Failed to exec git-rev-list: $!";
}
$ri++;
+ } elsif (m/^\\/) {
+ ;
+ # Skip \No newline at end of file.
+ # But this can be internationalized, so only look
+ # for an initial \
+
} else {
if (substr($_,1) ne get_line($slines,$ri) ) {
die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n",
exit(1);
}
-my $GIT_DIR = `git rev-parse --git-dir`;
-exit 1 if $?; # rev-parse would have given "not a git dir" message.
-chomp($GIT_DIR);
-
our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
getopts("hnfkv") || usage;
usage() if $opt_h;
@ARGV >= 1 or usage;
+my $GIT_DIR = `git rev-parse --git-dir`;
+exit 1 if $?; # rev-parse would have given "not a git dir" message.
+chomp($GIT_DIR);
+
my (@srcArgs, @dstArgs, @srcs, @dsts);
my ($src, $dst, $base, $dstDir);
+# remove any trailing slash in arguments
+for (@ARGV) { s/\/*$//; }
+
my $argCount = scalar @ARGV;
if (-d $ARGV[$argCount-1]) {
$dstDir = $ARGV[$argCount-1];
- # remove any trailing slash
- $dstDir =~ s/\/$//;
@srcArgs = @ARGV[0..$argCount-2];
-
+
foreach $src (@srcArgs) {
$base = $src;
$base =~ s/^.*\///;
}
}
else {
- if ($argCount != 2) {
+ if ($argCount < 2) {
+ print "Error: need at least two arguments\n";
+ exit(1);
+ }
+ if ($argCount > 2) {
print "Error: moving to directory '"
. $ARGV[$argCount-1]
- . "' not possible; not exisiting\n";
+ . "' not possible; not existing\n";
exit(1);
}
@srcArgs = ($ARGV[0]);
$dstDir = "";
}
+# normalize paths, needed to compare against versioned files and update-index
+# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+for (@srcArgs, @dstArgs) {
+ s|^\./||;
+ s|/\./|/| while (m|/\./|);
+ s|//+|/|g;
+ # Also "a/b/../c" ==> "a/c"
+ 1 while (s,(^|/)[^/]+/\.\./,$1,);
+}
+
my (@allfiles,@srcfiles,@dstfiles);
my $safesrc;
my (%overwritten, %srcForDst);
static int pack_revindex_ix(struct packed_git *p)
{
- unsigned long ui = (unsigned long)(long)p;
+ unsigned long ui = (unsigned long)p;
int i;
ui = ui ^ (ui >> 16); /* defeat structure alignment */
--- /dev/null
+#!/bin/sh
+
+test_description='git-annotate'
+. ./test-lib.sh
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'echo "1A quick brown fox jumps over the" >file &&
+ echo "lazy dog" >>file &&
+ git add file
+ GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+ 'check all lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
+
+test_expect_success \
+ 'Setup new lines blamed on B' \
+ 'echo "2A quick brown fox jumps over the" >>file &&
+ echo "lazy dog" >> file &&
+ GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
+
+test_expect_success \
+ 'Two lines blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]'
+
+test_expect_success \
+ 'merge-setup part 1' \
+ 'git checkout -b branch1 master &&
+ echo "3A slow green fox jumps into the" >> file &&
+ echo "well." >> file &&
+ GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+ 'Two lines blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]'
+
+test_expect_success \
+ 'Two lines blamed on B1' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
+
+test_expect_success \
+ 'merge-setup part 2' \
+ 'git checkout -b branch2 master &&
+ sed -i -e "s/2A quick brown/4A quick brown lazy dog/" file &&
+ GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+ 'One line blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
+
+test_expect_success \
+ 'One line blamed on B2' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+
+
+test_expect_success \
+ 'merge-setup part 3' \
+ 'git pull . branch1'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+ 'One line blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
+
+test_expect_success \
+ 'Two lines blamed on B1' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
+
+test_expect_success \
+ 'One line blamed on B2' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+
+test_done