Commit fb6b0e01 by Russell Belfer

Merge pull request #1317 from libgit2/blame

Blame Canada
parents a605bbd9 7dcb1c45
......@@ -2,4 +2,10 @@ general
showindex
diff
rev-list
blame
cat-file
init
log
rev-parse
status
*.dSYM
......@@ -3,7 +3,7 @@
CC = gcc
CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
LFLAGS = -L../build -lgit2 -lz
APPS = general showindex diff rev-list cat-file status log rev-parse init
APPS = general showindex diff rev-list cat-file status log rev-parse init blame
all: $(APPS)
......
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
static void check(int error, const char *msg)
{
if (error) {
fprintf(stderr, "%s (%d)\n", msg, error);
exit(error);
}
}
static void usage(const char *msg, const char *arg)
{
if (msg && arg)
fprintf(stderr, "%s: %s\n", msg, arg);
else if (msg)
fprintf(stderr, "%s\n", msg);
fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
fprintf(stderr, "\n");
fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
fprintf(stderr, " -M find line moves within and across files\n");
fprintf(stderr, " -C find line copies within and across files\n");
fprintf(stderr, "\n");
exit(1);
}
int main(int argc, char *argv[])
{
int i, line, break_on_null_hunk;
const char *path = NULL, *a;
const char *rawdata, *commitspec=NULL, *bare_args[3] = {0};
char spec[1024] = {0};
git_repository *repo = NULL;
git_revspec revspec = {0};
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
git_blame *blame = NULL;
git_blob *blob;
git_threads_init();
if (argc < 2) usage(NULL, NULL);
for (i=1; i<argc; i++) {
a = argv[i];
if (a[0] != '-') {
int i=0;
while (bare_args[i] && i < 3) ++i;
if (i >= 3)
usage("Invalid argument set", NULL);
bare_args[i] = a;
}
else if (!strcmp(a, "--"))
continue;
else if (!strcasecmp(a, "-M"))
opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
else if (!strcasecmp(a, "-C"))
opts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
else if (!strcasecmp(a, "-L")) {
i++; a = argv[i];
if (i >= argc) check(-1, "Not enough arguments to -L");
check(sscanf(a, "%d,%d", &opts.min_line, &opts.max_line)-2, "-L format error");
}
else {
/* commit range */
if (commitspec) check(-1, "Only one commit spec allowed");
commitspec = a;
}
}
/* Handle the bare arguments */
if (!bare_args[0]) usage("Please specify a path", NULL);
path = bare_args[0];
if (bare_args[1]) {
/* <commitspec> <path> */
path = bare_args[1];
commitspec = bare_args[0];
}
if (bare_args[2]) {
/* <oldcommit> <newcommit> <path> */
path = bare_args[2];
sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
commitspec = spec;
}
/* Open the repo */
check(git_repository_open_ext(&repo, ".", 0, NULL), "Couldn't open repository");
/* Parse the end points */
if (commitspec) {
check(git_revparse(&revspec, repo, commitspec), "Couldn't parse commit spec");
if (revspec.flags & GIT_REVPARSE_SINGLE) {
git_oid_cpy(&opts.newest_commit, git_object_id(revspec.from));
git_object_free(revspec.from);
} else {
git_oid_cpy(&opts.oldest_commit, git_object_id(revspec.from));
git_oid_cpy(&opts.newest_commit, git_object_id(revspec.to));
git_object_free(revspec.from);
git_object_free(revspec.to);
}
}
/* Run the blame */
check(git_blame_file(&blame, repo, path, &opts), "Blame error");
/* Get the raw data for output */
if (git_oid_iszero(&opts.newest_commit))
strcpy(spec, "HEAD");
else
git_oid_tostr(spec, sizeof(spec), &opts.newest_commit);
strcat(spec, ":");
strcat(spec, path);
{
git_object *obj;
check(git_revparse_single(&obj, repo, spec), "Object lookup error");
check(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error");
git_object_free(obj);
}
rawdata = git_blob_rawcontent(blob);
/* Produce the output */
line = 1;
i = 0;
break_on_null_hunk = 0;
while (i < git_blob_rawsize(blob)) {
const char *eol = strchr(rawdata+i, '\n');
char oid[10] = {0};
const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
if (break_on_null_hunk && !hunk) break;
if (hunk) {
break_on_null_hunk = 1;
char sig[128] = {0};
git_oid_tostr(oid, 10, &hunk->final_commit_id);
snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
printf("%s ( %-30s %3d) %.*s\n",
oid,
sig,
line,
(int)(eol-rawdata-i),
rawdata+i);
}
i = eol - rawdata + 1;
line++;
}
/* Cleanup */
git_blob_free(blob);
git_blame_free(blame);
git_repository_free(repo);
git_threads_shutdown();
}
......@@ -10,6 +10,7 @@
#include "git2/attr.h"
#include "git2/blob.h"
#include "git2/blame.h"
#include "git2/branch.h"
#include "git2/buffer.h"
#include "git2/checkout.h"
......
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_git_blame_h__
#define INCLUDE_git_blame_h__
#include "common.h"
#include "oid.h"
/**
* @file git2/blame.h
* @brief Git blame routines
* @defgroup git_blame Git blame routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Flags for indicating option behavior for git_blame APIs.
*/
typedef enum {
/** Normal blame, the default */
GIT_BLAME_NORMAL = 0,
/** Track lines that have moved within a file (like `git blame -M`).
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_SAME_FILE = (1<<0),
/** Track lines that have moved across files in the same commit (like `git blame -C`).
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1<<1),
/** Track lines that have been copied from another file that exists in the
* same commit (like `git blame -CC`). Implies SAME_FILE.
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1<<2),
/** Track lines that have been copied from another file that exists in *any*
* commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES.
* NOT IMPLEMENTED. */
GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1<<3),
} git_blame_flag_t;
/**
* Blame options structure
*
* Use zeros to indicate default settings. It's easiest to use the
* `GIT_BLAME_OPTIONS_INIT` macro:
* git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
*
* - `flags` is a combination of the `git_blame_flag_t` values above.
* - `min_match_characters` is the lower bound on the number of alphanumeric
* characters that must be detected as moving/copying within a file for it to
* associate those lines with the parent commit. The default value is 20.
* This value only takes effect if any of the `GIT_BLAME_TRACK_COPIES_*`
* flags are specified.
* - `newest_commit` is the id of the newest commit to consider. The default
* is HEAD.
* - `oldest_commit` is the id of the oldest commit to consider. The default
* is the first commit encountered with a NULL parent.
* - `min_line` is the first line in the file to blame. The default is 1 (line
* numbers start with 1).
* - `max_line` is the last line in the file to blame. The default is the last
* line of the file.
*/
typedef struct git_blame_options {
unsigned int version;
uint32_t flags;
uint16_t min_match_characters;
git_oid newest_commit;
git_oid oldest_commit;
uint32_t min_line;
uint32_t max_line;
} git_blame_options;
#define GIT_BLAME_OPTIONS_VERSION 1
#define GIT_BLAME_OPTIONS_INIT {GIT_BLAME_OPTIONS_VERSION}
/**
* Structure that represents a blame hunk.
*
* - `lines_in_hunk` is the number of lines in this hunk
* - `final_commit_id` is the OID of the commit where this line was last
* changed.
* - `final_start_line_number` is the 1-based line number where this hunk
* begins, in the final version of the file
* - `orig_commit_id` is the OID of the commit where this hunk was found. This
* will usually be the same as `final_commit_id`, except when
* `GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES` has been specified.
* - `orig_path` is the path to the file where this hunk originated, as of the
* commit specified by `orig_commit_id`.
* - `orig_start_line_number` is the 1-based line number where this hunk begins
* in the file named by `orig_path` in the commit specified by
* `orig_commit_id`.
* - `boundary` is 1 iff the hunk has been tracked to a boundary commit (the
* root, or the commit specified in git_blame_options.oldest_commit)
*/
typedef struct git_blame_hunk {
uint16_t lines_in_hunk;
git_oid final_commit_id;
uint16_t final_start_line_number;
git_signature *final_signature;
git_oid orig_commit_id;
const char *orig_path;
uint16_t orig_start_line_number;
git_signature *orig_signature;
char boundary;
} git_blame_hunk;
/* Opaque structure to hold blame results */
typedef struct git_blame git_blame;
/**
* Gets the number of hunks that exist in the blame structure.
*/
GIT_EXTERN(uint32_t) git_blame_get_hunk_count(git_blame *blame);
/**
* Gets the blame hunk at the given index.
*
* @param blame the blame structure to query
* @param index index of the hunk to retrieve
* @return the hunk at the given index, or NULL on error
*/
GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byindex(
git_blame *blame,
uint32_t index);
/**
* Gets the hunk that relates to the given line number in the newest commit.
*
* @param blame the blame structure to query
* @param lineno the (1-based) line number to find a hunk for
* @return the hunk that contains the given line, or NULL on error
*/
GIT_EXTERN(const git_blame_hunk*) git_blame_get_hunk_byline(
git_blame *blame,
uint32_t lineno);
/**
* Get the blame for a single file.
*
* @param out pointer that will receive the blame object
* @param repo repository whose history is to be walked
* @param path path to file to consider
* @param options options for the blame operation. If NULL, this is treated as
* though GIT_BLAME_OPTIONS_INIT were passed.
* @return 0 on success, or an error code. (use giterr_last for information
* about the error.)
*/
GIT_EXTERN(int) git_blame_file(
git_blame **out,
git_repository *repo,
const char *path,
git_blame_options *options);
/**
* Get blame data for a file that has been modified in memory. The `reference`
* parameter is a pre-calculated blame for the in-odb history of the file. This
* means that once a file blame is completed (which can be expensive), updating
* the buffer blame is very fast.
*
* Lines that differ between the buffer and the committed version are marked as
* having a zero OID for their final_commit_id.
*
* @param out pointer that will receive the resulting blame data
* @param reference cached blame from the history of the file (usually the output
* from git_blame_file)
* @param buffer the (possibly) modified contents of the file
* @param buffer_len number of valid bytes in the buffer
* @return 0 on success, or an error code. (use giterr_last for information
* about the error)
*/
GIT_EXTERN(int) git_blame_buffer(
git_blame **out,
git_blame *reference,
const char *buffer,
size_t buffer_len);
/**
* Free memory allocated by git_blame_file or git_blame_buffer.
*
* @param blame the blame structure to free
*/
GIT_EXTERN(void) git_blame_free(git_blame *blame);
/** @} */
GIT_END_DECL
#endif
......@@ -78,6 +78,23 @@ GIT_EXTERN(int) git_object_lookup_prefix(
size_t len,
git_otype type);
/**
* Lookup an object that represents a tree entry.
*
* @param out buffer that receives a pointer to the object (which must be freed
* by the caller)
* @param treeish root object that can be peeled to a tree
* @param path relative path from the root object to the desired object
* @param type type of object desired
* @return 0 on success, or an error code
*/
GIT_EXTERN(int) git_object_lookup_bypath(
git_object **out,
const git_object *treeish,
const char *path,
git_otype type);
/**
* Get the id (SHA1) of a repository object
*
......
This diff is collapsed. Click to expand it.
#ifndef INCLUDE_blame_h__
#define INCLUDE_blame_h__
#include "git2/blame.h"
#include "common.h"
#include "vector.h"
#include "diff.h"
#include "array.h"
#include "git2/oid.h"
/*
* One blob in a commit that is being suspected
*/
typedef struct git_blame__origin {
int refcnt;
struct git_blame__origin *previous;
git_commit *commit;
git_blob *blob;
char path[GIT_FLEX_ARRAY];
} git_blame__origin;
/*
* Each group of lines is described by a git_blame__entry; it can be split
* as we pass blame to the parents. They form a linked list in the
* scoreboard structure, sorted by the target line number.
*/
typedef struct git_blame__entry {
struct git_blame__entry *prev;
struct git_blame__entry *next;
/* the first line of this group in the final image;
* internally all line numbers are 0 based.
*/
int lno;
/* how many lines this group has */
int num_lines;
/* the commit that introduced this group into the final image */
git_blame__origin *suspect;
/* true if the suspect is truly guilty; false while we have not
* checked if the group came from one of its parents.
*/
bool guilty;
/* true if the entry has been scanned for copies in the current parent
*/
bool scanned;
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
int s_lno;
/* how significant this entry is -- cached to avoid
* scanning the lines over and over.
*/
unsigned score;
/* Whether this entry has been tracked to a boundary commit.
*/
bool is_boundary;
} git_blame__entry;
struct git_blame {
const char *path;
git_repository *repository;
git_blame_options options;
git_vector hunks;
git_vector paths;
git_blob *final_blob;
git_array_t(size_t) line_index;
size_t current_diff_line;
git_blame_hunk *current_hunk;
/* Scoreboard fields */
git_commit *final;
git_blame__entry *ent;
int num_lines;
const char *final_buf;
git_off_t final_buf_size;
};
git_blame *git_blame__alloc(
git_repository *repo,
git_blame_options opts,
const char *path);
#endif
This diff is collapsed. Click to expand it.
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_blame_git__
#define INCLUDE_blame_git__
#include "blame.h"
int git_blame__get_origin(
git_blame__origin **out,
git_blame *sb,
git_commit *commit,
const char *path);
void git_blame__free_entry(git_blame__entry *ent);
void git_blame__like_git(git_blame *sb, uint32_t flags);
#endif
......@@ -364,3 +364,38 @@ int git_object_dup(git_object **dest, git_object *source)
*dest = source;
return 0;
}
int git_object_lookup_bypath(
git_object **out,
const git_object *treeish,
const char *path,
git_otype type)
{
int error = -1;
git_tree *tree = NULL;
git_tree_entry *entry = NULL;
assert(out && treeish && path);
if ((error = git_object_peel((git_object**)&tree, treeish, GIT_OBJ_TREE) < 0) ||
(error = git_tree_entry_bypath(&entry, tree, path)) < 0)
{
goto cleanup;
}
if (type != GIT_OBJ_ANY && git_tree_entry_type(entry) != type)
{
giterr_set(GITERR_OBJECT,
"object at path '%s' is not of the asked-for type %d",
path, type);
error = GIT_EINVALIDSPEC;
goto cleanup;
}
error = git_tree_entry_to_object(out, git_object_owner(treeish), entry);
cleanup:
git_tree_entry_free(entry);
git_tree_free(tree);
return error;
}
#include "blame_helpers.h"
void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...)
{
va_list arglist;
printf("Hunk %zd (line %d +%d): ", idx,
hunk->final_start_line_number, hunk->lines_in_hunk-1);
va_start(arglist, fmt);
vprintf(fmt, arglist);
va_end(arglist);
printf("\n");
}
void check_blame_hunk_index(git_repository *repo, git_blame *blame, int idx,
int start_line, int len, char boundary, const char *commit_id, const char *orig_path)
{
char expected[41] = {0}, actual[41] = {0};
const git_blame_hunk *hunk = git_blame_get_hunk_byindex(blame, idx);
cl_assert(hunk);
if (!strncmp(commit_id, "0000", 4)) {
strcpy(expected, "0000000000000000000000000000000000000000");
} else {
git_object *obj;
cl_git_pass(git_revparse_single(&obj, repo, commit_id));
git_oid_fmt(expected, git_object_id(obj));
git_object_free(obj);
}
if (hunk->final_start_line_number != start_line) {
hunk_message(idx, hunk, "mismatched start line number: expected %d, got %d",
start_line, hunk->final_start_line_number);
}
cl_assert_equal_i(hunk->final_start_line_number, start_line);
if (hunk->lines_in_hunk != len) {
hunk_message(idx, hunk, "mismatched line count: expected %d, got %d",
len, hunk->lines_in_hunk);
}
cl_assert_equal_i(hunk->lines_in_hunk, len);
git_oid_fmt(actual, &hunk->final_commit_id);
if (strcmp(expected, actual)) {
hunk_message(idx, hunk, "has mismatched original id (got %s, expected %s)\n",
actual, expected);
}
cl_assert_equal_s(actual, expected);
if (strcmp(hunk->orig_path, orig_path)) {
hunk_message(idx, hunk, "has mismatched original path (got '%s', expected '%s')\n",
hunk->orig_path, orig_path);
}
cl_assert_equal_s(hunk->orig_path, orig_path);
if (hunk->boundary != boundary) {
hunk_message(idx, hunk, "doesn't match boundary flag (got %d, expected %d)\n",
hunk->boundary, boundary);
}
cl_assert_equal_i(boundary, hunk->boundary);
}
#include "clar_libgit2.h"
#include "blame.h"
void hunk_message(size_t idx, const git_blame_hunk *hunk, const char *fmt, ...);
void check_blame_hunk_index(
git_repository *repo,
git_blame *blame,
int idx,
int start_line,
int len,
char boundary,
const char *commit_id,
const char *orig_path);
#include "blame_helpers.h"
git_repository *g_repo;
git_blame *g_fileblame, *g_bufferblame;
void test_blame_buffer__initialize(void)
{
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
cl_git_pass(git_blame_file(&g_fileblame, g_repo, "b.txt", NULL));
g_bufferblame = NULL;
}
void test_blame_buffer__cleanup(void)
{
git_blame_free(g_fileblame);
git_blame_free(g_bufferblame);
git_repository_free(g_repo);
}
void test_blame_buffer__added_line(void)
{
const char *buffer = "\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
\n\
abcdefg\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
cl_assert_equal_i(5, git_blame_get_hunk_count(g_bufferblame));
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "000000", "b.txt");
}
void test_blame_buffer__deleted_line(void)
{
const char *buffer = "\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 3, 0, "63d671eb", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 3, 9, 1, 0, "63d671eb", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 4, 10, 5, 0, "aa06ecca", "b.txt");
}
void test_blame_buffer__add_splits_hunk(void)
{
const char *buffer = "\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
abc\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 2, 0, "63d671eb", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 3, 8, 1, 0, "00000000", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 4, 9, 3, 0, "63d671eb", "b.txt");
}
void test_blame_buffer__delete_crosses_hunk_boundary(void)
{
const char *buffer = "\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 2, 0, "aa06ecca", "b.txt");
}
void test_blame_buffer__replace_line(void)
{
const char *buffer = "\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n\
\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
abc\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n\
\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n\n";
cl_git_pass(git_blame_buffer(&g_bufferblame, g_fileblame, buffer, strlen(buffer)));
check_blame_hunk_index(g_repo, g_bufferblame, 2, 6, 1, 0, "63d671eb", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 3, 7, 1, 0, "00000000", "b.txt");
check_blame_hunk_index(g_repo, g_bufferblame, 4, 8, 3, 0, "63d671eb", "b.txt");
}
#include "clar_libgit2.h"
#include "blame.h"
git_blame *g_blame;
void test_blame_getters__initialize(void)
{
size_t i;
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
git_blame_hunk hunks[] = {
{ 3, {{0}}, 1, NULL, {{0}}, "a", 0},
{ 3, {{0}}, 4, NULL, {{0}}, "b", 0},
{ 3, {{0}}, 7, NULL, {{0}}, "c", 0},
{ 3, {{0}}, 10, NULL, {{0}}, "d", 0},
{ 3, {{0}}, 13, NULL, {{0}}, "e", 0},
};
g_blame = git_blame__alloc(NULL, opts, "");
for (i=0; i<5; i++) {
git_blame_hunk *h = git__calloc(1, sizeof(git_blame_hunk));
h->final_start_line_number = hunks[i].final_start_line_number;
h->orig_path = git__strdup(hunks[i].orig_path);
h->lines_in_hunk = hunks[i].lines_in_hunk;
git_vector_insert(&g_blame->hunks, h);
}
}
void test_blame_getters__cleanup(void)
{
git_blame_free(g_blame);
}
void test_blame_getters__byindex(void)
{
const git_blame_hunk *h = git_blame_get_hunk_byindex(g_blame, 2);
cl_assert(h);
cl_assert_equal_s(h->orig_path, "c");
h = git_blame_get_hunk_byindex(g_blame, 95);
cl_assert_equal_p(h, NULL);
}
void test_blame_getters__byline(void)
{
const git_blame_hunk *h = git_blame_get_hunk_byline(g_blame, 5);
cl_assert(h);
cl_assert_equal_s(h->orig_path, "b");
h = git_blame_get_hunk_byline(g_blame, 95);
cl_assert_equal_p(h, NULL);
}
#include "clar_libgit2.h"
#include "blame.h"
/**
* The test repo has a history that looks like this:
*
* * (A) bc7c5ac
* |\
* | * (B) aa06ecc
* * | (C) 63d671e
* |/
* * (D) da23739
* * (E) b99f7ac
*
*/
static git_repository *g_repo = NULL;
void test_blame_harder__initialize(void)
{
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
}
void test_blame_harder__cleanup(void)
{
git_repository_free(g_repo);
g_repo = NULL;
}
void test_blame_harder__m(void)
{
/* TODO */
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
opts.flags = GIT_BLAME_TRACK_COPIES_SAME_FILE;
}
void test_blame_harder__c(void)
{
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
/* Attribute the first hunk in b.txt to (E), since it was cut/pasted from
* a.txt in (D).
*/
opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
}
void test_blame_harder__cc(void)
{
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
/* Attribute the second hunk in b.txt to (E), since it was copy/pasted from
* a.txt in (C).
*/
opts.flags = GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
}
void test_blame_harder__ccc(void)
{
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
/* Attribute the third hunk in b.txt to (E). This hunk was deleted from
* a.txt in (D), but reintroduced in (B).
*/
opts.flags = GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES;
}
#include "clar_libgit2.h"
#include "repository.h"
static git_repository *g_repo;
static git_tree *g_root_tree;
static git_commit *g_head_commit;
static git_object *g_expectedobject,
*g_actualobject;
void test_object_lookupbypath__initialize(void)
{
git_reference *head;
git_tree_entry *tree_entry;
cl_git_pass(git_repository_open(&g_repo, cl_fixture("attr/.gitted")));
cl_git_pass(git_repository_head(&head, g_repo));
cl_git_pass(git_reference_peel((git_object**)&g_head_commit, head, GIT_OBJ_COMMIT));
cl_git_pass(git_commit_tree(&g_root_tree, g_head_commit));
cl_git_pass(git_tree_entry_bypath(&tree_entry, g_root_tree, "subdir/subdir_test2.txt"));
cl_git_pass(git_object_lookup(&g_expectedobject, g_repo, git_tree_entry_id(tree_entry),
GIT_OBJ_ANY));
git_tree_entry_free(tree_entry);
git_reference_free(head);
g_actualobject = NULL;
}
void test_object_lookupbypath__cleanup(void)
{
git_object_free(g_actualobject);
git_object_free(g_expectedobject);
git_tree_free(g_root_tree);
git_commit_free(g_head_commit);
g_expectedobject = NULL;
git_repository_free(g_repo);
g_repo = NULL;
}
void test_object_lookupbypath__errors(void)
{
cl_assert_equal_i(GIT_EINVALIDSPEC,
git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
"subdir/subdir_test2.txt", GIT_OBJ_TREE)); // It's not a tree
cl_assert_equal_i(GIT_ENOTFOUND,
git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
"file/doesnt/exist", GIT_OBJ_ANY));
}
void test_object_lookupbypath__from_root_tree(void)
{
cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_root_tree,
"subdir/subdir_test2.txt", GIT_OBJ_BLOB));
cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
git_object_id(g_actualobject)));
}
void test_object_lookupbypath__from_head_commit(void)
{
cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)g_head_commit,
"subdir/subdir_test2.txt", GIT_OBJ_BLOB));
cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
git_object_id(g_actualobject)));
}
void test_object_lookupbypath__from_subdir_tree(void)
{
git_tree_entry *entry = NULL;
git_tree *tree = NULL;
cl_git_pass(git_tree_entry_bypath(&entry, g_root_tree, "subdir"));
cl_git_pass(git_tree_lookup(&tree, g_repo, git_tree_entry_id(entry)));
cl_git_pass(git_object_lookup_bypath(&g_actualobject, (git_object*)tree,
"subdir_test2.txt", GIT_OBJ_BLOB));
cl_assert_equal_i(0, git_oid_cmp(git_object_id(g_expectedobject),
git_object_id(g_actualobject)));
git_tree_entry_free(entry);
git_tree_free(tree);
}
[core]
repositoryformatversion = 0
filemode = true
bare = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
bc7c5ac2bafe828a68e9d1d460343718d6fbe136
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment