Skip to content

Commit e6f0834

Browse files
committed
diff_tree: Add a rust helper around git-diff-tree
1 parent f98834d commit e6f0834

File tree

2 files changed

+226
-33
lines changed

2 files changed

+226
-33
lines changed

helper/cinnabar-helper.c

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,28 @@ static void do_rev_list(struct string_list *args)
390390
rev_list_finish(revs);
391391
}
392392

393-
static void strbuf_diff_tree(struct diff_queue_struct *q,
394-
struct diff_options *opt, void *data)
393+
struct diff_tree_file {
394+
struct object_id *oid;
395+
char *path;
396+
unsigned short mode;
397+
};
398+
399+
struct diff_tree_item {
400+
struct diff_tree_file a;
401+
struct diff_tree_file b;
402+
unsigned short int score;
403+
char status;
404+
};
405+
406+
struct diff_tree_ctx {
407+
void (*cb)(void *, struct diff_tree_item *);
408+
void *context;
409+
};
410+
411+
static void diff_tree_cb(struct diff_queue_struct *q,
412+
struct diff_options *opt, void *data)
395413
{
396-
struct strbuf *buf = data;
414+
struct diff_tree_ctx *ctx = data;
397415
int i;
398416

399417
for (i = 0; i < q->nr; i++) {
@@ -402,45 +420,30 @@ static void strbuf_diff_tree(struct diff_queue_struct *q,
402420
die("internal diff status error");
403421
if (p->status == DIFF_STATUS_UNKNOWN)
404422
continue;
405-
strbuf_addf(buf, "%06o %06o %s %s %c",
406-
p->one->mode,
407-
p->two->mode,
408-
oid_to_hex(&p->one->oid),
409-
oid_to_hex(&p->two->oid),
410-
p->status);
411-
if (p->score)
412-
strbuf_addf(buf, "%03d",
413-
(int)(p->score * 100 / MAX_SCORE));
414-
strbuf_addch(buf, '\t');
415-
if (p->status == DIFF_STATUS_COPIED ||
416-
p->status == DIFF_STATUS_RENAMED) {
417-
strbuf_addstr(buf, p->one->path);
418-
strbuf_addch(buf, '\0');
419-
strbuf_addstr(buf, p->two->path);
420-
} else {
421-
strbuf_addstr(buf, p->one->mode ? p->one->path
422-
: p->two->path);
423-
}
424-
strbuf_addch(buf, '\0');
423+
struct diff_tree_item item = {
424+
{ &p->one->oid, p->one->path, p->one->mode },
425+
{ &p->two->oid, p->two->path, p->two->mode },
426+
p->score,
427+
p->status,
428+
};
429+
ctx->cb(ctx->context, &item);
425430
}
426431
}
427432

428-
static void do_diff_tree(struct string_list *args)
433+
void diff_tree_(int argc, const char **argv, void (*cb)(void *, struct diff_tree_item *), void *context)
429434
{
435+
struct diff_tree_ctx ctx = { cb, context };
430436
struct rev_info revs;
431-
struct strbuf buf = STRBUF_INIT;
432-
const char **argv = string_list_to_argv(args);
433437

434438
init_revisions(&revs, NULL);
435439
revs.diff = 1;
436440
// Note: we do a pass through, but don't make much effort to actually
437441
// support all the options properly.
438-
setup_revisions(args->nr + 1, argv, &revs, NULL);
442+
setup_revisions(argc, argv, &revs, NULL);
439443
revs.diffopt.output_format = DIFF_FORMAT_CALLBACK;
440-
revs.diffopt.format_callback = strbuf_diff_tree;
441-
revs.diffopt.format_callback_data = &buf;
444+
revs.diffopt.format_callback = diff_tree_cb;
445+
revs.diffopt.format_callback_data = &ctx;
442446
revs.diffopt.flags.recursive = 1;
443-
free(argv);
444447

445448
if (revs.pending.nr != 2)
446449
die("diff-tree needs two revs");
@@ -449,9 +452,44 @@ static void do_diff_tree(struct string_list *args)
449452
&revs.pending.objects[1].item->oid,
450453
"", &revs.diffopt);
451454
log_tree_diff_flush(&revs);
455+
rev_info_release(&revs);
456+
}
457+
458+
static void strbuf_diff_tree(void *ctx, struct diff_tree_item *item)
459+
{
460+
struct strbuf *buf = ctx;
461+
strbuf_addf(buf, "%06o %06o %s %s %c",
462+
item->a.mode,
463+
item->b.mode,
464+
oid_to_hex(item->a.oid),
465+
oid_to_hex(item->b.oid),
466+
item->status);
467+
if (item->score)
468+
strbuf_addf(buf, "%03d",
469+
(int)(item->score * 100 / MAX_SCORE));
470+
strbuf_addch(buf, '\t');
471+
if (item->status == DIFF_STATUS_COPIED ||
472+
item->status == DIFF_STATUS_RENAMED) {
473+
strbuf_addstr(buf, item->a.path);
474+
strbuf_addch(buf, '\0');
475+
strbuf_addstr(buf, item->b.path);
476+
} else {
477+
strbuf_addstr(buf, item->a.mode ? item->a.path
478+
: item->b.path);
479+
}
480+
strbuf_addch(buf, '\0');
481+
}
482+
483+
static void do_diff_tree(struct string_list *args)
484+
{
485+
struct strbuf buf = STRBUF_INIT;
486+
const char **argv = string_list_to_argv(args);
487+
488+
diff_tree_(args->nr + 1, argv, strbuf_diff_tree, &buf);
489+
free(argv);
490+
452491
send_buffer(&buf);
453492
strbuf_release(&buf);
454-
rev_info_release(&revs);
455493
}
456494

457495
void ensure_notes(struct notes_tree *notes)

helper/libgit.rs

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use std::convert::TryInto;
66
use std::ffi::{c_void, CStr, CString, OsStr};
77
use std::io::{self, Write};
8-
use std::os::raw::{c_char, c_int, c_long, c_uint, c_ulong};
8+
use std::os::raw::{c_char, c_int, c_long, c_uint, c_ulong, c_ushort};
99
#[cfg(unix)]
1010
use std::os::unix::ffi::OsStrExt;
1111

@@ -15,7 +15,7 @@ use derive_more::{Deref, Display};
1515
use getset::Getters;
1616

1717
use crate::oid::{GitObjectId, ObjectId};
18-
use crate::util::{FromBytes, SliceExt};
18+
use crate::util::{BorrowKey, FromBytes, SliceExt};
1919

2020
const GIT_MAX_RAWSZ: usize = 32;
2121

@@ -456,3 +456,158 @@ impl Iterator for RevList {
456456
}
457457
}
458458
}
459+
460+
const DIFF_STATUS_ADDED: c_char = b'A' as c_char;
461+
const DIFF_STATUS_COPIED: c_char = b'C' as c_char;
462+
const DIFF_STATUS_DELETED: c_char = b'D' as c_char;
463+
const DIFF_STATUS_MODIFIED: c_char = b'M' as c_char;
464+
const DIFF_STATUS_TYPE_CHANGED: c_char = b'T' as c_char;
465+
const DIFF_STATUS_RENAMED: c_char = b'R' as c_char;
466+
467+
#[allow(non_camel_case_types)]
468+
#[repr(C)]
469+
struct diff_tree_file {
470+
oid: *const object_id,
471+
path: *const c_char,
472+
mode: c_ushort,
473+
}
474+
475+
#[allow(non_camel_case_types)]
476+
#[repr(C)]
477+
struct diff_tree_item {
478+
a: diff_tree_file,
479+
b: diff_tree_file,
480+
score: c_ushort,
481+
status: c_char,
482+
}
483+
484+
extern "C" {
485+
fn diff_tree_(
486+
argc: c_int,
487+
argv: *const *const c_char,
488+
cb: unsafe extern "C" fn(*mut c_void, *const diff_tree_item),
489+
context: *const c_void,
490+
);
491+
}
492+
493+
pub struct FileMode(u16);
494+
495+
pub enum DiffTreeItem {
496+
Added {
497+
path: Box<[u8]>,
498+
mode: FileMode,
499+
oid: BlobId,
500+
},
501+
Deleted {
502+
path: Box<[u8]>,
503+
mode: FileMode,
504+
oid: BlobId,
505+
},
506+
Modified {
507+
path: Box<[u8]>,
508+
from_mode: FileMode,
509+
from_oid: BlobId,
510+
to_mode: FileMode,
511+
to_oid: BlobId,
512+
},
513+
Renamed {
514+
to_path: Box<[u8]>,
515+
to_mode: FileMode,
516+
to_oid: BlobId,
517+
from_path: Box<[u8]>,
518+
from_mode: FileMode,
519+
from_oid: BlobId,
520+
},
521+
Copied {
522+
to_path: Box<[u8]>,
523+
to_mode: FileMode,
524+
to_oid: BlobId,
525+
from_path: Box<[u8]>,
526+
from_mode: FileMode,
527+
from_oid: BlobId,
528+
},
529+
}
530+
531+
impl BorrowKey for DiffTreeItem {
532+
type Key = Box<[u8]>;
533+
fn borrow_key(&self) -> &Self::Key {
534+
match self {
535+
DiffTreeItem::Added { path, .. } => path,
536+
DiffTreeItem::Deleted { path, .. } => path,
537+
DiffTreeItem::Modified { path, .. } => path,
538+
DiffTreeItem::Renamed { to_path, .. } => to_path,
539+
DiffTreeItem::Copied { to_path, .. } => to_path,
540+
}
541+
}
542+
}
543+
544+
unsafe extern "C" fn diff_tree_fill(diff_tree: *mut c_void, item: *const diff_tree_item) {
545+
let diff_tree = (diff_tree as *mut Vec<DiffTreeItem>).as_mut().unwrap();
546+
let item = item.as_ref().unwrap();
547+
let item = match item.status {
548+
DIFF_STATUS_MODIFIED | DIFF_STATUS_TYPE_CHANGED => DiffTreeItem::Modified {
549+
path: {
550+
let a_path: Box<[u8]> = CStr::from_ptr(item.a.path).to_bytes().into();
551+
let b_path = CStr::from_ptr(item.b.path).to_bytes();
552+
assert_eq!(a_path.as_bstr(), b_path.as_bstr());
553+
a_path
554+
},
555+
from_oid: BlobId::from(GitObjectId::from(item.a.oid.as_ref().unwrap().clone())),
556+
from_mode: FileMode(item.a.mode),
557+
to_oid: BlobId::from(GitObjectId::from(item.b.oid.as_ref().unwrap().clone())),
558+
to_mode: FileMode(item.b.mode),
559+
},
560+
DIFF_STATUS_ADDED => DiffTreeItem::Added {
561+
path: CStr::from_ptr(item.b.path).to_bytes().into(),
562+
oid: BlobId::from(GitObjectId::from(item.b.oid.as_ref().unwrap().clone())),
563+
mode: FileMode(item.b.mode),
564+
},
565+
DIFF_STATUS_DELETED => DiffTreeItem::Deleted {
566+
path: CStr::from_ptr(item.a.path).to_bytes().into(),
567+
oid: BlobId::from(GitObjectId::from(item.a.oid.as_ref().unwrap().clone())),
568+
mode: FileMode(item.a.mode),
569+
},
570+
DIFF_STATUS_RENAMED => DiffTreeItem::Renamed {
571+
to_path: CStr::from_ptr(item.b.path).to_bytes().into(),
572+
to_oid: BlobId::from(GitObjectId::from(item.b.oid.as_ref().unwrap().clone())),
573+
to_mode: FileMode(item.b.mode),
574+
from_path: CStr::from_ptr(item.a.path).to_bytes().into(),
575+
from_oid: BlobId::from(GitObjectId::from(item.a.oid.as_ref().unwrap().clone())),
576+
from_mode: FileMode(item.a.mode),
577+
},
578+
DIFF_STATUS_COPIED => DiffTreeItem::Copied {
579+
to_path: CStr::from_ptr(item.b.path).to_bytes().into(),
580+
to_oid: BlobId::from(GitObjectId::from(item.b.oid.as_ref().unwrap().clone())),
581+
to_mode: FileMode(item.b.mode),
582+
from_path: CStr::from_ptr(item.a.path).to_bytes().into(),
583+
from_oid: BlobId::from(GitObjectId::from(item.a.oid.as_ref().unwrap().clone())),
584+
from_mode: FileMode(item.a.mode),
585+
},
586+
c => panic!("Unknown diff state: {}", c),
587+
};
588+
diff_tree.push(item);
589+
}
590+
591+
pub fn diff_tree(a: &CommitId, b: &CommitId) -> impl Iterator<Item = DiffTreeItem> {
592+
let a = CString::new(format!("{}", a)).unwrap();
593+
let b = CString::new(format!("{}", b)).unwrap();
594+
let args = [
595+
cstr!(""),
596+
&a,
597+
&b,
598+
cstr!("--ignore-submodules=dirty"),
599+
cstr!("--"),
600+
];
601+
let mut argv: Vec<_> = args.iter().map(|a| a.as_ptr()).collect();
602+
argv.push(std::ptr::null());
603+
let mut result = Vec::<DiffTreeItem>::new();
604+
unsafe {
605+
diff_tree_(
606+
args.len().try_into().unwrap(),
607+
&argv[0],
608+
diff_tree_fill,
609+
&mut result as *mut _ as *mut c_void,
610+
);
611+
}
612+
result.into_iter()
613+
}

0 commit comments

Comments
 (0)