$NMBGIT .= '/.git' if (-d $NMBGIT.'/.git');
-my $TAGPREFIX = $ENV{NMBPREFIX} || 'notmuch::';
-
-# magic hash for git
-my $EMPTYBLOB = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';
+my $TAGPREFIX = defined($ENV{NMBPREFIX}) ? $ENV{NMBPREFIX} : 'notmuch::';
# for encoding
my %command = (
archive => \&do_archive,
checkout => \&do_checkout,
+ clone => \&do_clone,
commit => \&do_commit,
fetch => \&do_fetch,
help => \&do_help,
status => \&do_status,
);
+# Convert prefix into form suitable for literal matching against
+# notmuch dump --format=batch-tag output.
+my $ENCPREFIX = encode_for_fs ($TAGPREFIX);
+$ENCPREFIX =~ s/:/%3a/g;
+
my $subcommand = shift || usage ();
if (!exists $command{$subcommand}) {
usage ();
}
+# magic hash for git
+my $EMPTYBLOB = git (qw{hash-object -t blob /dev/null});
+
&{$command{$subcommand}}(@ARGV);
sub git_pipe {
system ('git', "--git-dir=$NMBGIT", 'archive', 'HEAD');
}
+sub do_clone {
+ my $repository = shift;
+
+ my $tempwork = tempdir ('/tmp/nmbug-clone.XXXXXX', CLEANUP => 1);
+ system ('git', 'clone', '--no-checkout', '--separate-git-dir', $NMBGIT,
+ $repository, $tempwork) == 0
+ or die "'git clone' exited with nonzero value\n";
+ git ('config', '--unset', 'core.worktree');
+}
sub is_committed {
my $status = shift;
my $index = $NMBGIT.'/nmbug.index';
- my $query = join ' ', map ("tag:$_", get_tags ($TAGPREFIX));
+ my $query = join ' ', map ("tag:\"$_\"", get_tags ($TAGPREFIX));
- my $fh = spawn ('-|', qw/notmuch dump --/, $query)
+ my $fh = spawn ('-|', qw/notmuch dump --format=batch-tag --/, $query)
or die "notmuch dump: $!";
git ('read-tree', '--empty');
or die 'git update-index';
while (<$fh>) {
- m/ ( [^ ]* ) \s+ \( ([^\)]* ) \) /x || die 'syntax error in dump';
- my ($id,$rest) = ($1,$2);
- #strip prefixes before writing
- my @tags = grep { s/^$TAGPREFIX//; } split (' ', $rest);
+ chomp();
+ my ($rest,$id) = split(/ -- id:/);
+
+ if ($id =~ s/^"(.*)"\s*$/$1/) {
+ # xapian quoted string, dequote.
+ $id =~ s/""/"/g;
+ }
+
+ #strip prefixes from tags before writing
+ my @tags = grep { s/^[+]$ENCPREFIX//; } split (' ', $rest);
index_tags_for_msg ($git,$id, 'A', @tags);
}
unless (close $git) {
die "'git update-index --index-info' exited with nonzero value\n";
}
unless (close $fh) {
- die "'notmuch dump -- $query' exited with nonzero value\n";
+ die "'notmuch dump --format=batch-tag -- $query' exited with nonzero value\n";
}
return $index;
}
+# update the git index to either create or delete an empty file.
+# Neither argument should be encoded/escaped.
sub index_tags_for_msg {
my $fh = shift;
my $msgid = shift;
do_sync (action => 'checkout');
}
+sub quote_for_xapian {
+ my $str = shift;
+ $str =~ s/"/""/g;
+ return '"' . $str . '"';
+}
+
+sub pair_to_batch_line {
+ my ($action, $pair) = @_;
+
+ # the tag should already be suitably encoded
+
+ return $action . $ENCPREFIX . $pair->{tag} .
+ ' -- id:' . quote_for_xapian ($pair->{id})."\n";
+}
sub do_sync {
$D_action = '-';
}
- foreach my $pair (@{$status->{added}}) {
+ my $notmuch = spawn ({}, '|-', qw/notmuch tag --batch/)
+ or die 'notmuch tag --batch';
- notmuch ('tag', $A_action.$TAGPREFIX.$pair->{tag},
- 'id:'.$pair->{id});
+ foreach my $pair (@{$status->{added}}) {
+ print $notmuch pair_to_batch_line ($A_action, $pair);
}
foreach my $pair (@{$status->{deleted}}) {
- notmuch ('tag', $D_action.$TAGPREFIX.$pair->{tag},
- 'id:'.$pair->{id});
+ print $notmuch pair_to_batch_line ($D_action, $pair);
}
+ unless (close $notmuch) {
+ die "'notmuch tag --batch' exited with nonzero value\n";
+ }
}
sub do_pull {
my $remote = shift || 'origin';
+ my $branch = shift || 'master';
git ( 'fetch', $remote);
- do_merge ();
+ do_merge ("$remote/$branch");
}
sub do_merge {
+ my $commit = shift || '@{upstream}';
+
insist_committed ();
my $tempwork = tempdir ('/tmp/nmbug-merge.XXXXXX', CLEANUP => 1);
git ( { GIT_WORK_TREE => $tempwork }, 'checkout', '-f', 'HEAD');
- git ( { GIT_WORK_TREE => $tempwork }, 'merge', 'FETCH_HEAD');
+ git ( { GIT_WORK_TREE => $tempwork }, 'merge', $commit);
do_checkout ();
}
sub is_unmerged {
+ my $commit = shift || '@{upstream}';
- return 0 if (! -f $NMBGIT.'/FETCH_HEAD');
-
- my $fetch_head = git ('rev-parse', 'FETCH_HEAD');
- my $base = git ( 'merge-base', 'HEAD', 'FETCH_HEAD');
+ my $fetch_head = git ('rev-parse', $commit);
+ my $base = git ( 'merge-base', 'HEAD', $commit);
return ($base ne $fetch_head);
sub diff_refs {
my $filter = shift;
my $ref1 = shift || 'HEAD';
- my $ref2 = shift || 'FETCH_HEAD';
+ my $ref2 = shift || '@{upstream}';
my $fh= git_pipe ( 'diff', "--diff-filter=$filter", '--name-only',
$ref1, $ref2);
push local nmbug git state to remote repo
-=item B<pull> [remote]
+=item B<pull> [remote] [branch]
pull (merge) remote repo changes to notmuch. B<pull> is equivalent to
-B<fetch> followed by B<merge>.
+B<fetch> followed by B<merge>. The default remote is C<origin>, and
+the default branch is C<master>.
=back
=over 8
+=item B<clone> repository
+
+Create a local nmbug repository from a remote source. This wraps
+C<git clone>, adding some options to avoid creating a working tree
+while preserving remote-tracking branches and upstreams.
+
=item B<checkout>
Update the notmuch database from git. This is mainly useful to discard
=item B<log> [parameters]
A simple wrapper for git log. After running C<nmbug fetch>, you can
-inspect the changes with C<nmbug log HEAD..FETCH_HEAD>
+inspect the changes with C<nmbug log HEAD..@{upstream}>
-=item B<merge>
+=item B<merge> [commit]
-Merge changes from FETCH_HEAD into HEAD, and load the result into
-notmuch.
+Merge changes from C<commit> into HEAD, and load the result into
+notmuch. The default commit is C<@{upstream}>.
=item B<status>