mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-02 19:32:23 +00:00
commit
fb6b0e019e
6
examples/.gitignore
vendored
6
examples/.gitignore
vendored
@ -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)
|
||||
|
||||
|
160
examples/blame.c
Normal file
160
examples/blame.c
Normal file
@ -0,0 +1,160 @@
|
||||
#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"
|
||||
|
198
include/git2/blame.h
Normal file
198
include/git2/blame.h
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
|
476
src/blame.c
Normal file
476
src/blame.c
Normal file
@ -0,0 +1,476 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "blame.h"
|
||||
#include "git2/commit.h"
|
||||
#include "git2/revparse.h"
|
||||
#include "git2/revwalk.h"
|
||||
#include "git2/tree.h"
|
||||
#include "git2/diff.h"
|
||||
#include "git2/blob.h"
|
||||
#include "git2/signature.h"
|
||||
#include "util.h"
|
||||
#include "repository.h"
|
||||
#include "blame_git.h"
|
||||
|
||||
|
||||
static int hunk_byfinalline_search_cmp(const void *key, const void *entry)
|
||||
{
|
||||
uint32_t lineno = *(size_t*)key;
|
||||
git_blame_hunk *hunk = (git_blame_hunk*)entry;
|
||||
|
||||
if (lineno < hunk->final_start_line_number)
|
||||
return -1;
|
||||
if (lineno >= hunk->final_start_line_number + hunk->lines_in_hunk)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); }
|
||||
static int hunk_cmp(const void *_a, const void *_b)
|
||||
{
|
||||
git_blame_hunk *a = (git_blame_hunk*)_a,
|
||||
*b = (git_blame_hunk*)_b;
|
||||
|
||||
return a->final_start_line_number - b->final_start_line_number;
|
||||
}
|
||||
|
||||
static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line)
|
||||
{
|
||||
return line >= (size_t)(hunk->final_start_line_number + hunk->lines_in_hunk - 1);
|
||||
}
|
||||
|
||||
static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line)
|
||||
{
|
||||
return line <= hunk->final_start_line_number;
|
||||
}
|
||||
|
||||
static git_blame_hunk* new_hunk(
|
||||
uint16_t start,
|
||||
uint16_t lines,
|
||||
uint16_t orig_start,
|
||||
const char *path)
|
||||
{
|
||||
git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk));
|
||||
if (!hunk) return NULL;
|
||||
|
||||
hunk->lines_in_hunk = lines;
|
||||
hunk->final_start_line_number = start;
|
||||
hunk->orig_start_line_number = orig_start;
|
||||
hunk->orig_path = path ? git__strdup(path) : NULL;
|
||||
|
||||
return hunk;
|
||||
}
|
||||
|
||||
static git_blame_hunk* dup_hunk(git_blame_hunk *hunk)
|
||||
{
|
||||
git_blame_hunk *newhunk = new_hunk(
|
||||
hunk->final_start_line_number,
|
||||
hunk->lines_in_hunk,
|
||||
hunk->orig_start_line_number,
|
||||
hunk->orig_path);
|
||||
git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id);
|
||||
git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id);
|
||||
return newhunk;
|
||||
}
|
||||
|
||||
static void free_hunk(git_blame_hunk *hunk)
|
||||
{
|
||||
git__free((void*)hunk->orig_path);
|
||||
git_signature_free(hunk->final_signature);
|
||||
git_signature_free(hunk->orig_signature);
|
||||
git__free(hunk);
|
||||
}
|
||||
|
||||
/* Starting with the hunk that includes start_line, shift all following hunks'
|
||||
* final_start_line by shift_by lines */
|
||||
static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!git_vector_bsearch2( &i, v, hunk_byfinalline_search_cmp, &start_line)) {
|
||||
for (; i < v->length; i++) {
|
||||
git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
|
||||
hunk->final_start_line_number += shift_by;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
git_blame* git_blame__alloc(
|
||||
git_repository *repo,
|
||||
git_blame_options opts,
|
||||
const char *path)
|
||||
{
|
||||
git_blame *gbr = (git_blame*)git__calloc(1, sizeof(git_blame));
|
||||
if (!gbr) {
|
||||
giterr_set_oom();
|
||||
return NULL;
|
||||
}
|
||||
git_vector_init(&gbr->hunks, 8, hunk_cmp);
|
||||
git_vector_init(&gbr->paths, 8, paths_cmp);
|
||||
gbr->repository = repo;
|
||||
gbr->options = opts;
|
||||
gbr->path = git__strdup(path);
|
||||
git_vector_insert(&gbr->paths, git__strdup(path));
|
||||
return gbr;
|
||||
}
|
||||
|
||||
void git_blame_free(git_blame *blame)
|
||||
{
|
||||
size_t i;
|
||||
git_blame_hunk *hunk;
|
||||
char *path;
|
||||
|
||||
if (!blame) return;
|
||||
|
||||
git_vector_foreach(&blame->hunks, i, hunk)
|
||||
free_hunk(hunk);
|
||||
git_vector_free(&blame->hunks);
|
||||
|
||||
git_vector_foreach(&blame->paths, i, path)
|
||||
git__free(path);
|
||||
git_vector_free(&blame->paths);
|
||||
|
||||
git_array_clear(blame->line_index);
|
||||
|
||||
git__free((void*)blame->path);
|
||||
git_blob_free(blame->final_blob);
|
||||
git__free(blame);
|
||||
}
|
||||
|
||||
uint32_t git_blame_get_hunk_count(git_blame *blame)
|
||||
{
|
||||
assert(blame);
|
||||
return blame->hunks.length;
|
||||
}
|
||||
|
||||
const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index)
|
||||
{
|
||||
assert(blame);
|
||||
return (git_blame_hunk*)git_vector_get(&blame->hunks, index);
|
||||
}
|
||||
|
||||
const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, uint32_t lineno)
|
||||
{
|
||||
size_t i;
|
||||
assert(blame);
|
||||
|
||||
if (!git_vector_bsearch2( &i, &blame->hunks, hunk_byfinalline_search_cmp, &lineno)) {
|
||||
return git_blame_get_hunk_byindex(blame, i);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void normalize_options(
|
||||
git_blame_options *out,
|
||||
const git_blame_options *in,
|
||||
git_repository *repo)
|
||||
{
|
||||
git_blame_options dummy = GIT_BLAME_OPTIONS_INIT;
|
||||
if (!in) in = &dummy;
|
||||
|
||||
memcpy(out, in, sizeof(git_blame_options));
|
||||
|
||||
/* No newest_commit => HEAD */
|
||||
if (git_oid_iszero(&out->newest_commit)) {
|
||||
git_reference_name_to_id(&out->newest_commit, repo, "HEAD");
|
||||
}
|
||||
|
||||
/* min_line 0 really means 1 */
|
||||
if (!out->min_line) out->min_line = 1;
|
||||
/* max_line 0 really means N, but we don't know N yet */
|
||||
|
||||
/* Fix up option implications */
|
||||
if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES)
|
||||
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
|
||||
if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES)
|
||||
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
|
||||
if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
|
||||
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE;
|
||||
}
|
||||
|
||||
static git_blame_hunk *split_hunk_in_vector(
|
||||
git_vector *vec,
|
||||
git_blame_hunk *hunk,
|
||||
size_t rel_line,
|
||||
bool return_new)
|
||||
{
|
||||
size_t new_line_count;
|
||||
git_blame_hunk *nh;
|
||||
|
||||
/* Don't split if already at a boundary */
|
||||
if (rel_line <= 0 ||
|
||||
rel_line >= hunk->lines_in_hunk)
|
||||
{
|
||||
return hunk;
|
||||
}
|
||||
|
||||
new_line_count = hunk->lines_in_hunk - rel_line;
|
||||
nh = new_hunk(hunk->final_start_line_number+rel_line, new_line_count,
|
||||
hunk->orig_start_line_number+rel_line, hunk->orig_path);
|
||||
git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id);
|
||||
git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id);
|
||||
|
||||
/* Adjust hunk that was split */
|
||||
hunk->lines_in_hunk -= new_line_count;
|
||||
git_vector_insert_sorted(vec, nh, NULL);
|
||||
{
|
||||
git_blame_hunk *ret = return_new ? nh : hunk;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a list of char indices for where lines begin
|
||||
* Adapted from core git:
|
||||
* https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789
|
||||
*/
|
||||
static int index_blob_lines(git_blame *blame)
|
||||
{
|
||||
const char *buf = blame->final_buf;
|
||||
git_off_t len = blame->final_buf_size;
|
||||
int num = 0, incomplete = 0, bol = 1;
|
||||
size_t *i;
|
||||
|
||||
if (len && buf[len-1] != '\n')
|
||||
incomplete++; /* incomplete line at the end */
|
||||
while (len--) {
|
||||
if (bol) {
|
||||
i = git_array_alloc(blame->line_index);
|
||||
GITERR_CHECK_ALLOC(i);
|
||||
*i = buf - blame->final_buf;
|
||||
bol = 0;
|
||||
}
|
||||
if (*buf++ == '\n') {
|
||||
num++;
|
||||
bol = 1;
|
||||
}
|
||||
}
|
||||
i = git_array_alloc(blame->line_index);
|
||||
GITERR_CHECK_ALLOC(i);
|
||||
*i = buf - blame->final_buf;
|
||||
blame->num_lines = num + incomplete;
|
||||
return blame->num_lines;
|
||||
}
|
||||
|
||||
static git_blame_hunk* hunk_from_entry(git_blame__entry *e)
|
||||
{
|
||||
git_blame_hunk *h = new_hunk(
|
||||
e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path);
|
||||
git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit));
|
||||
h->final_signature = git_signature_dup(git_commit_author(e->suspect->commit));
|
||||
h->boundary = e->is_boundary ? 1 : 0;
|
||||
return h;
|
||||
}
|
||||
|
||||
static int load_blob(git_blame *blame)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (blame->final_blob) return 0;
|
||||
|
||||
error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
error = git_object_lookup_bypath((git_object**)&blame->final_blob,
|
||||
(git_object*)blame->final, blame->path, GIT_OBJ_BLOB);
|
||||
if (error < 0)
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
return error;
|
||||
}
|
||||
|
||||
static int blame_internal(git_blame *blame)
|
||||
{
|
||||
int error;
|
||||
git_blame__entry *ent = NULL;
|
||||
git_blame__origin *o;
|
||||
|
||||
if ((error = load_blob(blame)) < 0 ||
|
||||
(error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0)
|
||||
goto cleanup;
|
||||
blame->final_buf = git_blob_rawcontent(blame->final_blob);
|
||||
blame->final_buf_size = git_blob_rawsize(blame->final_blob);
|
||||
|
||||
ent = git__calloc(1, sizeof(git_blame__entry));
|
||||
ent->num_lines = index_blob_lines(blame);
|
||||
ent->lno = blame->options.min_line - 1;
|
||||
ent->num_lines = ent->num_lines - blame->options.min_line + 1;
|
||||
if (blame->options.max_line > 0)
|
||||
ent->num_lines = blame->options.max_line - blame->options.min_line + 1;
|
||||
ent->s_lno = ent->lno;
|
||||
ent->suspect = o;
|
||||
|
||||
blame->ent = ent;
|
||||
blame->path = blame->path;
|
||||
|
||||
git_blame__like_git(blame, blame->options.flags);
|
||||
|
||||
cleanup:
|
||||
for (ent = blame->ent; ent; ) {
|
||||
git_blame__entry *e = ent->next;
|
||||
|
||||
git_vector_insert(&blame->hunks, hunk_from_entry(ent));
|
||||
|
||||
git_blame__free_entry(ent);
|
||||
ent = e;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* File blaming
|
||||
******************************************************************************/
|
||||
|
||||
int git_blame_file(
|
||||
git_blame **out,
|
||||
git_repository *repo,
|
||||
const char *path,
|
||||
git_blame_options *options)
|
||||
{
|
||||
int error = -1;
|
||||
git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT;
|
||||
git_blame *blame = NULL;
|
||||
|
||||
assert(out && repo && path);
|
||||
normalize_options(&normOptions, options, repo);
|
||||
|
||||
blame = git_blame__alloc(repo, normOptions, path);
|
||||
GITERR_CHECK_ALLOC(blame);
|
||||
|
||||
if ((error = load_blob(blame)) < 0)
|
||||
goto on_error;
|
||||
|
||||
if ((error = blame_internal(blame)) < 0)
|
||||
goto on_error;
|
||||
|
||||
*out = blame;
|
||||
return 0;
|
||||
|
||||
on_error:
|
||||
git_blame_free(blame);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Buffer blaming
|
||||
*******************************************************************************/
|
||||
|
||||
static bool hunk_is_bufferblame(git_blame_hunk *hunk)
|
||||
{
|
||||
return git_oid_iszero(&hunk->final_commit_id);
|
||||
}
|
||||
|
||||
static int buffer_hunk_cb(
|
||||
const git_diff_delta *delta,
|
||||
const git_diff_hunk *hunk,
|
||||
void *payload)
|
||||
{
|
||||
git_blame *blame = (git_blame*)payload;
|
||||
size_t wedge_line;
|
||||
|
||||
GIT_UNUSED(delta);
|
||||
|
||||
wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start;
|
||||
blame->current_diff_line = wedge_line;
|
||||
|
||||
/* If this hunk doesn't start between existing hunks, split a hunk up so it does */
|
||||
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
|
||||
if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
|
||||
blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
|
||||
wedge_line - blame->current_hunk->orig_start_line_number, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ptrs_equal_cmp(const void *a, const void *b) { return a<b ? -1 : a>b ? 1 : 0; }
|
||||
static int buffer_line_cb(
|
||||
const git_diff_delta *delta,
|
||||
const git_diff_hunk *hunk,
|
||||
const git_diff_line *line,
|
||||
void *payload)
|
||||
{
|
||||
git_blame *blame = (git_blame*)payload;
|
||||
|
||||
GIT_UNUSED(delta);
|
||||
GIT_UNUSED(hunk);
|
||||
GIT_UNUSED(line);
|
||||
|
||||
#ifdef DO_DEBUG
|
||||
{
|
||||
char *str = git__substrdup(content, content_len);
|
||||
git__free(str);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (line->origin == GIT_DIFF_LINE_ADDITION) {
|
||||
if (hunk_is_bufferblame(blame->current_hunk) &&
|
||||
hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
|
||||
/* Append to the current buffer-blame hunk */
|
||||
blame->current_hunk->lines_in_hunk++;
|
||||
shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1);
|
||||
} else {
|
||||
/* Create a new buffer-blame hunk with this line */
|
||||
shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
|
||||
blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path);
|
||||
git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
|
||||
}
|
||||
blame->current_diff_line++;
|
||||
}
|
||||
|
||||
if (line->origin == GIT_DIFF_LINE_DELETION) {
|
||||
/* Trim the line from the current hunk; remove it if it's now empty */
|
||||
size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
|
||||
|
||||
if (--(blame->current_hunk->lines_in_hunk) == 0) {
|
||||
size_t i;
|
||||
shift_base--;
|
||||
if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
|
||||
git_vector_remove(&blame->hunks, i);
|
||||
free_hunk(blame->current_hunk);
|
||||
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, i);
|
||||
}
|
||||
}
|
||||
shift_hunks_by(&blame->hunks, shift_base, -1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_blame_buffer(
|
||||
git_blame **out,
|
||||
git_blame *reference,
|
||||
const char *buffer,
|
||||
size_t buffer_len)
|
||||
{
|
||||
git_blame *blame;
|
||||
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
|
||||
size_t i;
|
||||
git_blame_hunk *hunk;
|
||||
|
||||
diffopts.context_lines = 0;
|
||||
|
||||
assert(out && reference && buffer && buffer_len);
|
||||
|
||||
blame = git_blame__alloc(reference->repository, reference->options, reference->path);
|
||||
|
||||
/* Duplicate all of the hunk structures in the reference blame */
|
||||
git_vector_foreach(&reference->hunks, i, hunk) {
|
||||
git_vector_insert(&blame->hunks, dup_hunk(hunk));
|
||||
}
|
||||
|
||||
/* Diff to the reference blob */
|
||||
git_diff_blob_to_buffer(reference->final_blob, blame->path,
|
||||
buffer, buffer_len, blame->path,
|
||||
&diffopts, NULL, buffer_hunk_cb, buffer_line_cb, blame);
|
||||
|
||||
*out = blame;
|
||||
return 0;
|
||||
}
|
93
src/blame.h
Normal file
93
src/blame.h
Normal file
@ -0,0 +1,93 @@
|
||||
#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
|
622
src/blame_git.c
Normal file
622
src/blame_git.c
Normal file
@ -0,0 +1,622 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "blame_git.h"
|
||||
#include "commit.h"
|
||||
#include "blob.h"
|
||||
#include "xdiff/xinclude.h"
|
||||
|
||||
/*
|
||||
* Origin is refcounted and usually we keep the blob contents to be
|
||||
* reused.
|
||||
*/
|
||||
static git_blame__origin *origin_incref(git_blame__origin *o)
|
||||
{
|
||||
if (o)
|
||||
o->refcnt++;
|
||||
return o;
|
||||
}
|
||||
|
||||
static void origin_decref(git_blame__origin *o)
|
||||
{
|
||||
if (o && --o->refcnt <= 0) {
|
||||
if (o->previous)
|
||||
origin_decref(o->previous);
|
||||
git_blob_free(o->blob);
|
||||
git_commit_free(o->commit);
|
||||
git__free(o);
|
||||
}
|
||||
}
|
||||
|
||||
/* Given a commit and a path in it, create a new origin structure. */
|
||||
static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
|
||||
{
|
||||
int error = 0;
|
||||
git_blame__origin *o;
|
||||
|
||||
o = git__calloc(1, sizeof(*o) + strlen(path) + 1);
|
||||
GITERR_CHECK_ALLOC(o);
|
||||
o->commit = commit;
|
||||
o->refcnt = 1;
|
||||
strcpy(o->path, path);
|
||||
|
||||
if (!(error = git_object_lookup_bypath((git_object**)&o->blob, (git_object*)commit,
|
||||
path, GIT_OBJ_BLOB))) {
|
||||
*out = o;
|
||||
} else {
|
||||
origin_decref(o);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Locate an existing origin or create a new one. */
|
||||
int git_blame__get_origin(
|
||||
git_blame__origin **out,
|
||||
git_blame *blame,
|
||||
git_commit *commit,
|
||||
const char *path)
|
||||
{
|
||||
git_blame__entry *e;
|
||||
|
||||
for (e = blame->ent; e; e = e->next) {
|
||||
if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) {
|
||||
*out = origin_incref(e->suspect);
|
||||
}
|
||||
}
|
||||
return make_origin(out, commit, path);
|
||||
}
|
||||
|
||||
typedef struct blame_chunk_cb_data {
|
||||
git_blame *blame;
|
||||
git_blame__origin *target;
|
||||
git_blame__origin *parent;
|
||||
long tlno;
|
||||
long plno;
|
||||
}blame_chunk_cb_data;
|
||||
|
||||
static bool same_suspect(git_blame__origin *a, git_blame__origin *b)
|
||||
{
|
||||
if (a == b)
|
||||
return true;
|
||||
if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit)))
|
||||
return false;
|
||||
return 0 == strcmp(a->path, b->path);
|
||||
}
|
||||
|
||||
/* find the line number of the last line the target is suspected for */
|
||||
static int find_last_in_target(git_blame *blame, git_blame__origin *target)
|
||||
{
|
||||
git_blame__entry *e;
|
||||
int last_in_target = -1;
|
||||
|
||||
for (e=blame->ent; e; e=e->next) {
|
||||
if (e->guilty || !same_suspect(e->suspect, target))
|
||||
continue;
|
||||
if (last_in_target < e->s_lno + e->num_lines)
|
||||
last_in_target = e->s_lno + e->num_lines;
|
||||
}
|
||||
return last_in_target;
|
||||
}
|
||||
|
||||
/*
|
||||
* It is known that lines between tlno to same came from parent, and e
|
||||
* has an overlap with that range. it also is known that parent's
|
||||
* line plno corresponds to e's line tlno.
|
||||
*
|
||||
* <---- e ----->
|
||||
* <------> (entirely within)
|
||||
* <------------> (extends past)
|
||||
* <------------> (starts before)
|
||||
* <------------------> (entirely encloses)
|
||||
*
|
||||
* Split e into potentially three parts; before this chunk, the chunk
|
||||
* to be blamed for the parent, and after that portion.
|
||||
*/
|
||||
static void split_overlap(git_blame__entry *split, git_blame__entry *e,
|
||||
int tlno, int plno, int same, git_blame__origin *parent)
|
||||
{
|
||||
int chunk_end_lno;
|
||||
|
||||
if (e->s_lno < tlno) {
|
||||
/* there is a pre-chunk part not blamed on the parent */
|
||||
split[0].suspect = origin_incref(e->suspect);
|
||||
split[0].lno = e->lno;
|
||||
split[0].s_lno = e->s_lno;
|
||||
split[0].num_lines = tlno - e->s_lno;
|
||||
split[1].lno = e->lno + tlno - e->s_lno;
|
||||
split[1].s_lno = plno;
|
||||
} else {
|
||||
split[1].lno = e->lno;
|
||||
split[1].s_lno = plno + (e->s_lno - tlno);
|
||||
}
|
||||
|
||||
if (same < e->s_lno + e->num_lines) {
|
||||
/* there is a post-chunk part not blamed on parent */
|
||||
split[2].suspect = origin_incref(e->suspect);
|
||||
split[2].lno = e->lno + (same - e->s_lno);
|
||||
split[2].s_lno = e->s_lno + (same - e->s_lno);
|
||||
split[2].num_lines = e->s_lno + e->num_lines - same;
|
||||
chunk_end_lno = split[2].lno;
|
||||
} else {
|
||||
chunk_end_lno = e->lno + e->num_lines;
|
||||
}
|
||||
split[1].num_lines = chunk_end_lno - split[1].lno;
|
||||
|
||||
/*
|
||||
* if it turns out there is nothing to blame the parent for, forget about
|
||||
* the splitting. !split[1].suspect signals this.
|
||||
*/
|
||||
if (split[1].num_lines < 1)
|
||||
return;
|
||||
split[1].suspect = origin_incref(parent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Link in a new blame entry to the scoreboard. Entries that cover the same
|
||||
* line range have been removed from the scoreboard previously.
|
||||
*/
|
||||
static void add_blame_entry(git_blame *blame, git_blame__entry *e)
|
||||
{
|
||||
git_blame__entry *ent, *prev = NULL;
|
||||
|
||||
origin_incref(e->suspect);
|
||||
|
||||
for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next)
|
||||
prev = ent;
|
||||
|
||||
/* prev, if not NULL, is the last one that is below e */
|
||||
e->prev = prev;
|
||||
if (prev) {
|
||||
e->next = prev->next;
|
||||
prev->next = e;
|
||||
} else {
|
||||
e->next = blame->ent;
|
||||
blame->ent = e;
|
||||
}
|
||||
if (e->next)
|
||||
e->next->prev = e;
|
||||
}
|
||||
|
||||
/*
|
||||
* src typically is on-stack; we want to copy the information in it to
|
||||
* a malloced blame_entry that is already on the linked list of the scoreboard.
|
||||
* The origin of dst loses a refcnt while the origin of src gains one.
|
||||
*/
|
||||
static void dup_entry(git_blame__entry *dst, git_blame__entry *src)
|
||||
{
|
||||
git_blame__entry *p, *n;
|
||||
|
||||
p = dst->prev;
|
||||
n = dst->next;
|
||||
origin_incref(src->suspect);
|
||||
origin_decref(dst->suspect);
|
||||
memcpy(dst, src, sizeof(*src));
|
||||
dst->prev = p;
|
||||
dst->next = n;
|
||||
dst->score = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* split_overlap() divided an existing blame e into up to three parts in split.
|
||||
* Adjust the linked list of blames in the scoreboard to reflect the split.
|
||||
*/
|
||||
static void split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
|
||||
{
|
||||
git_blame__entry *new_entry;
|
||||
|
||||
if (split[0].suspect && split[2].suspect) {
|
||||
/* The first part (reuse storage for the existing entry e */
|
||||
dup_entry(e, &split[0]);
|
||||
|
||||
/* The last part -- me */
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
|
||||
add_blame_entry(blame, new_entry);
|
||||
|
||||
/* ... and the middle part -- parent */
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
|
||||
add_blame_entry(blame, new_entry);
|
||||
} else if (!split[0].suspect && !split[2].suspect) {
|
||||
/*
|
||||
* The parent covers the entire area; reuse storage for e and replace it
|
||||
* with the parent
|
||||
*/
|
||||
dup_entry(e, &split[1]);
|
||||
} else if (split[0].suspect) {
|
||||
/* me and then parent */
|
||||
dup_entry(e, &split[0]);
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
|
||||
add_blame_entry(blame, new_entry);
|
||||
} else {
|
||||
/* parent and then me */
|
||||
dup_entry(e, &split[1]);
|
||||
new_entry = git__malloc(sizeof(*new_entry));
|
||||
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
|
||||
add_blame_entry(blame, new_entry);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After splitting the blame, the origins used by the on-stack blame_entry
|
||||
* should lose one refcnt each.
|
||||
*/
|
||||
static void decref_split(git_blame__entry *split)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<3; i++)
|
||||
origin_decref(split[i].suspect);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper for blame_chunk(). blame_entry e is known to overlap with the patch
|
||||
* hunk; split it and pass blame to the parent.
|
||||
*/
|
||||
static void blame_overlap(
|
||||
git_blame *blame,
|
||||
git_blame__entry *e,
|
||||
int tlno,
|
||||
int plno,
|
||||
int same,
|
||||
git_blame__origin *parent)
|
||||
{
|
||||
git_blame__entry split[3] = {{0}};
|
||||
|
||||
split_overlap(split, e, tlno, plno, same, parent);
|
||||
if (split[1].suspect)
|
||||
split_blame(blame, split, e);
|
||||
decref_split(split);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process one hunk from the patch between the current suspect for blame_entry
|
||||
* e and its parent. Find and split the overlap, and pass blame to the
|
||||
* overlapping part to the parent.
|
||||
*/
|
||||
static void blame_chunk(
|
||||
git_blame *blame,
|
||||
int tlno,
|
||||
int plno,
|
||||
int same,
|
||||
git_blame__origin *target,
|
||||
git_blame__origin *parent)
|
||||
{
|
||||
git_blame__entry *e;
|
||||
|
||||
for (e = blame->ent; e; e = e->next) {
|
||||
if (e->guilty || !same_suspect(e->suspect, target))
|
||||
continue;
|
||||
if (same <= e->s_lno)
|
||||
continue;
|
||||
if (tlno < e->s_lno + e->num_lines) {
|
||||
blame_overlap(blame, e, tlno, plno, same, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int my_emit(
|
||||
xdfenv_t *xe,
|
||||
xdchange_t *xscr,
|
||||
xdemitcb_t *ecb,
|
||||
xdemitconf_t const *xecfg)
|
||||
{
|
||||
xdchange_t *xch = xscr;
|
||||
GIT_UNUSED(xe);
|
||||
GIT_UNUSED(xecfg);
|
||||
while (xch) {
|
||||
blame_chunk_cb_data *d = ecb->priv;
|
||||
blame_chunk(d->blame, d->tlno, d->plno, xch->i2, d->target, d->parent);
|
||||
d->plno = xch->i1 + xch->chg1;
|
||||
d->tlno = xch->i2 + xch->chg2;
|
||||
xch = xch->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
|
||||
{
|
||||
const int blk = 1024;
|
||||
long trimmed = 0, recovered = 0;
|
||||
char *ap = a->ptr + a->size;
|
||||
char *bp = b->ptr + b->size;
|
||||
long smaller = (a->size < b->size) ? a->size : b->size;
|
||||
|
||||
if (ctx)
|
||||
return;
|
||||
|
||||
while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
|
||||
trimmed += blk;
|
||||
ap -= blk;
|
||||
bp -= blk;
|
||||
}
|
||||
|
||||
while (recovered < trimmed)
|
||||
if (ap[recovered++] == '\n')
|
||||
break;
|
||||
a->size -= trimmed - recovered;
|
||||
b->size -= trimmed - recovered;
|
||||
}
|
||||
|
||||
static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
|
||||
{
|
||||
xpparam_t xpp = {0};
|
||||
xdemitconf_t xecfg = {0};
|
||||
xdemitcb_t ecb = {0};
|
||||
|
||||
xecfg.emit_func = (void(*)(void))my_emit;
|
||||
ecb.priv = cb_data;
|
||||
|
||||
trim_common_tail(&file_a, &file_b, 0);
|
||||
return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
|
||||
}
|
||||
|
||||
static void fill_origin_blob(git_blame__origin *o, mmfile_t *file)
|
||||
{
|
||||
memset(file, 0, sizeof(*file));
|
||||
if (o->blob) {
|
||||
file->ptr = (char*)git_blob_rawcontent(o->blob);
|
||||
file->size = (size_t)git_blob_rawsize(o->blob);
|
||||
}
|
||||
}
|
||||
|
||||
static int pass_blame_to_parent(
|
||||
git_blame *blame,
|
||||
git_blame__origin *target,
|
||||
git_blame__origin *parent)
|
||||
{
|
||||
int last_in_target;
|
||||
mmfile_t file_p, file_o;
|
||||
blame_chunk_cb_data d = { blame, target, parent, 0, 0 };
|
||||
|
||||
last_in_target = find_last_in_target(blame, target);
|
||||
if (last_in_target < 0)
|
||||
return 1; /* nothing remains for this target */
|
||||
|
||||
fill_origin_blob(parent, &file_p);
|
||||
fill_origin_blob(target, &file_o);
|
||||
|
||||
diff_hunks(file_p, file_o, &d);
|
||||
/* The reset (i.e. anything after tlno) are the same as the parent */
|
||||
blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int paths_on_dup(void **old, void *new)
|
||||
{
|
||||
GIT_UNUSED(old);
|
||||
git__free(new);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static git_blame__origin* find_origin(
|
||||
git_blame *blame,
|
||||
git_commit *parent,
|
||||
git_blame__origin *origin)
|
||||
{
|
||||
git_blame__origin *porigin = NULL;
|
||||
git_diff *difflist = NULL;
|
||||
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
|
||||
git_tree *otree=NULL, *ptree=NULL;
|
||||
|
||||
/* Get the trees from this commit and its parent */
|
||||
if (0 != git_commit_tree(&otree, origin->commit) ||
|
||||
0 != git_commit_tree(&ptree, parent))
|
||||
goto cleanup;
|
||||
|
||||
/* Configure the diff */
|
||||
diffopts.context_lines = 0;
|
||||
diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK;
|
||||
|
||||
/* Check to see if files we're interested have changed */
|
||||
diffopts.pathspec.count = blame->paths.length;
|
||||
diffopts.pathspec.strings = (char**)blame->paths.contents;
|
||||
if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
|
||||
goto cleanup;
|
||||
|
||||
if (!git_diff_num_deltas(difflist)) {
|
||||
/* No changes; copy data */
|
||||
git_blame__get_origin(&porigin, blame, parent, origin->path);
|
||||
} else {
|
||||
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
|
||||
int i;
|
||||
|
||||
/* Generate a full diff between the two trees */
|
||||
git_diff_free(difflist);
|
||||
diffopts.pathspec.count = 0;
|
||||
if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
|
||||
goto cleanup;
|
||||
|
||||
/* Let diff find renames */
|
||||
findopts.flags = GIT_DIFF_FIND_RENAMES;
|
||||
if (0 != git_diff_find_similar(difflist, &findopts))
|
||||
goto cleanup;
|
||||
|
||||
/* Find one that matches */
|
||||
for (i=0; i<(int)git_diff_num_deltas(difflist); i++) {
|
||||
const git_diff_delta *delta = git_diff_get_delta(difflist, i);
|
||||
|
||||
if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path))
|
||||
{
|
||||
git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path),
|
||||
paths_on_dup);
|
||||
make_origin(&porigin, parent, delta->old_file.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_diff_free(difflist);
|
||||
git_tree_free(otree);
|
||||
git_tree_free(ptree);
|
||||
return porigin;
|
||||
}
|
||||
|
||||
/*
|
||||
* The blobs of origin and porigin exactly match, so everything origin is
|
||||
* suspected for can be blamed on the parent.
|
||||
*/
|
||||
static void pass_whole_blame(git_blame *blame,
|
||||
git_blame__origin *origin, git_blame__origin *porigin)
|
||||
{
|
||||
git_blame__entry *e;
|
||||
|
||||
if (!porigin->blob)
|
||||
git_object_lookup((git_object**)&porigin->blob, blame->repository,
|
||||
git_blob_id(origin->blob), GIT_OBJ_BLOB);
|
||||
for (e=blame->ent; e; e=e->next) {
|
||||
if (!same_suspect(e->suspect, origin))
|
||||
continue;
|
||||
origin_incref(porigin);
|
||||
origin_decref(e->suspect);
|
||||
e->suspect = porigin;
|
||||
}
|
||||
}
|
||||
|
||||
static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
|
||||
{
|
||||
git_commit *commit = origin->commit;
|
||||
int i, num_parents;
|
||||
git_blame__origin *sg_buf[16];
|
||||
git_blame__origin *porigin, **sg_origin = sg_buf;
|
||||
|
||||
GIT_UNUSED(opt);
|
||||
|
||||
num_parents = git_commit_parentcount(commit);
|
||||
if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
|
||||
/* Stop at oldest specified commit */
|
||||
num_parents = 0;
|
||||
if (!num_parents) {
|
||||
git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit));
|
||||
goto finish;
|
||||
}
|
||||
else if (num_parents < (int)ARRAY_SIZE(sg_buf))
|
||||
memset(sg_buf, 0, sizeof(sg_buf));
|
||||
else
|
||||
sg_origin = git__calloc(num_parents, sizeof(*sg_origin));
|
||||
|
||||
for (i=0; i<num_parents; i++) {
|
||||
git_commit *p;
|
||||
int j, same;
|
||||
|
||||
if (sg_origin[i])
|
||||
continue;
|
||||
|
||||
git_commit_parent(&p, origin->commit, i);
|
||||
porigin = find_origin(blame, p, origin);
|
||||
|
||||
if (!porigin)
|
||||
continue;
|
||||
if (porigin->blob && origin->blob &&
|
||||
!git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
|
||||
pass_whole_blame(blame, origin, porigin);
|
||||
origin_decref(porigin);
|
||||
goto finish;
|
||||
}
|
||||
for (j = same = 0; j<i; j++)
|
||||
if (sg_origin[j] &&
|
||||
!git_oid_cmp(git_blob_id(sg_origin[j]->blob), git_blob_id(porigin->blob))) {
|
||||
same = 1;
|
||||
break;
|
||||
}
|
||||
if (!same)
|
||||
sg_origin[i] = porigin;
|
||||
else
|
||||
origin_decref(porigin);
|
||||
}
|
||||
|
||||
/* Standard blame */
|
||||
for (i=0; i<num_parents; i++) {
|
||||
git_blame__origin *porigin = sg_origin[i];
|
||||
if (!porigin)
|
||||
continue;
|
||||
if (!origin->previous) {
|
||||
origin_incref(porigin);
|
||||
origin->previous = porigin;
|
||||
}
|
||||
if (pass_blame_to_parent(blame, origin, porigin))
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* TODO: optionally find moves in parents' files */
|
||||
|
||||
/* TODO: optionally find copies in parents' files */
|
||||
|
||||
finish:
|
||||
for (i=0; i<num_parents; i++)
|
||||
if (sg_origin[i])
|
||||
origin_decref(sg_origin[i]);
|
||||
if (sg_origin != sg_buf)
|
||||
git__free(sg_origin);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If two blame entries that are next to each other came from
|
||||
* contiguous lines in the same origin (i.e. <commit, path> pair),
|
||||
* merge them together.
|
||||
*/
|
||||
static void coalesce(git_blame *blame)
|
||||
{
|
||||
git_blame__entry *ent, *next;
|
||||
|
||||
for (ent=blame->ent; ent && (next = ent->next); ent = next) {
|
||||
if (same_suspect(ent->suspect, next->suspect) &&
|
||||
ent->guilty == next->guilty &&
|
||||
ent->s_lno + ent->num_lines == next->s_lno)
|
||||
{
|
||||
ent->num_lines += next->num_lines;
|
||||
ent->next = next->next;
|
||||
if (ent->next)
|
||||
ent->next->prev = ent;
|
||||
origin_decref(next->suspect);
|
||||
git__free(next);
|
||||
ent->score = 0;
|
||||
next = ent; /* again */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void git_blame__like_git(git_blame *blame, uint32_t opt)
|
||||
{
|
||||
while (true) {
|
||||
git_blame__entry *ent;
|
||||
git_blame__origin *suspect = NULL;
|
||||
|
||||
/* Find a suspect to break down */
|
||||
for (ent = blame->ent; !suspect && ent; ent = ent->next)
|
||||
if (!ent->guilty)
|
||||
suspect = ent->suspect;
|
||||
if (!suspect)
|
||||
return; /* all done */
|
||||
|
||||
/* We'll use this suspect later in the loop, so hold on to it for now. */
|
||||
origin_incref(suspect);
|
||||
pass_blame(blame, suspect, opt);
|
||||
|
||||
/* Take responsibility for the remaining entries */
|
||||
for (ent = blame->ent; ent; ent = ent->next) {
|
||||
if (same_suspect(ent->suspect, suspect)) {
|
||||
ent->guilty = true;
|
||||
ent->is_boundary = !git_oid_cmp(
|
||||
git_commit_id(suspect->commit),
|
||||
&blame->options.oldest_commit);
|
||||
}
|
||||
}
|
||||
origin_decref(suspect);
|
||||
}
|
||||
|
||||
coalesce(blame);
|
||||
}
|
||||
|
||||
void git_blame__free_entry(git_blame__entry *ent)
|
||||
{
|
||||
if (!ent) return;
|
||||
origin_decref(ent->suspect);
|
||||
git__free(ent);
|
||||
}
|
20
src/blame_git.h
Normal file
20
src/blame_git.h
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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
|
35
src/object.c
35
src/object.c
@ -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;
|
||||
}
|
||||
|
64
tests-clar/blame/blame_helpers.c
Normal file
64
tests-clar/blame/blame_helpers.c
Normal file
@ -0,0 +1,64 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
|
16
tests-clar/blame/blame_helpers.h
Normal file
16
tests-clar/blame/blame_helpers.h
Normal file
@ -0,0 +1,16 @@
|
||||
#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);
|
||||
|
||||
|
130
tests-clar/blame/buffer.c
Normal file
130
tests-clar/blame/buffer.c
Normal file
@ -0,0 +1,130 @@
|
||||
#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");
|
||||
}
|
56
tests-clar/blame/getters.c
Normal file
56
tests-clar/blame/getters.c
Normal file
@ -0,0 +1,56 @@
|
||||
#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);
|
||||
}
|
71
tests-clar/blame/harder.c
Normal file
71
tests-clar/blame/harder.c
Normal file
@ -0,0 +1,71 @@
|
||||
#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;
|
||||
}
|
305
tests-clar/blame/simple.c
Normal file
305
tests-clar/blame/simple.c
Normal file
@ -0,0 +1,305 @@
|
||||
#include "blame_helpers.h"
|
||||
|
||||
static git_repository *g_repo;
|
||||
static git_blame *g_blame;
|
||||
|
||||
void test_blame_simple__initialize(void)
|
||||
{
|
||||
g_repo = NULL;
|
||||
g_blame = NULL;
|
||||
}
|
||||
|
||||
void test_blame_simple__cleanup(void)
|
||||
{
|
||||
git_blame_free(g_blame);
|
||||
git_repository_free(g_repo);
|
||||
}
|
||||
|
||||
/*
|
||||
* $ git blame -s branch_file.txt
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* c47800c7 1 (Scott Chacon 2010-05-25 11:58:14 -0700 1
|
||||
* a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2
|
||||
*/
|
||||
void test_blame_simple__trivial_testrepo(void)
|
||||
{
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo/.gitted")));
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", NULL));
|
||||
|
||||
cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame));
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "c47800c7", "branch_file.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf3", "branch_file.txt");
|
||||
}
|
||||
|
||||
/*
|
||||
* $ git blame -n b.txt
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1
|
||||
* da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
|
||||
* da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
|
||||
* da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
|
||||
* ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
|
||||
* 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
|
||||
* 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7
|
||||
* 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8
|
||||
* 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9
|
||||
* 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10
|
||||
* aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11
|
||||
* aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12
|
||||
* aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13
|
||||
* aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14
|
||||
* aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15
|
||||
*/
|
||||
void test_blame_simple__trivial_blamerepo(void)
|
||||
{
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", NULL));
|
||||
|
||||
cl_assert_equal_i(4, git_blame_get_hunk_count(g_blame));
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 2, 6, 5, 0, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 3, 11, 5, 0, "aa06ecca", "b.txt");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* $ git blame -n 359fc2d -- include/git2.h
|
||||
* orig line no final line no
|
||||
* commit orig path V author timestamp V
|
||||
* d12299fe src/git.h 1 (Vicent Martí 2010-12-03 22:22:10 +0200 1
|
||||
* 359fc2d2 include/git2.h 2 (Edward Thomson 2013-01-08 17:07:25 -0600 2
|
||||
* d12299fe src/git.h 5 (Vicent Martí 2010-12-03 22:22:10 +0200 3
|
||||
* bb742ede include/git2.h 4 (Vicent Martí 2011-09-19 01:54:32 +0300 4
|
||||
* bb742ede include/git2.h 5 (Vicent Martí 2011-09-19 01:54:32 +0300 5
|
||||
* d12299fe src/git.h 24 (Vicent Martí 2010-12-03 22:22:10 +0200 6
|
||||
* d12299fe src/git.h 25 (Vicent Martí 2010-12-03 22:22:10 +0200 7
|
||||
* d12299fe src/git.h 26 (Vicent Martí 2010-12-03 22:22:10 +0200 8
|
||||
* d12299fe src/git.h 27 (Vicent Martí 2010-12-03 22:22:10 +0200 9
|
||||
* d12299fe src/git.h 28 (Vicent Martí 2010-12-03 22:22:10 +0200 10
|
||||
* 96fab093 include/git2.h 11 (Sven Strickroth 2011-10-09 18:37:41 +0200 11
|
||||
* 9d1dcca2 src/git2.h 33 (Vicent Martí 2011-02-07 10:35:58 +0200 12
|
||||
* 44908fe7 src/git2.h 29 (Vicent Martí 2010-12-06 23:03:16 +0200 13
|
||||
* a15c550d include/git2.h 14 (Vicent Martí 2011-11-16 14:09:44 +0100 14
|
||||
* 44908fe7 src/git2.h 30 (Vicent Martí 2010-12-06 23:03:16 +0200 15
|
||||
* d12299fe src/git.h 32 (Vicent Martí 2010-12-03 22:22:10 +0200 16
|
||||
* 44908fe7 src/git2.h 33 (Vicent Martí 2010-12-06 23:03:16 +0200 17
|
||||
* d12299fe src/git.h 34 (Vicent Martí 2010-12-03 22:22:10 +0200 18
|
||||
* 44908fe7 src/git2.h 35 (Vicent Martí 2010-12-06 23:03:16 +0200 19
|
||||
* 638c2ca4 src/git2.h 36 (Vicent Martí 2010-12-18 02:10:25 +0200 20
|
||||
* 44908fe7 src/git2.h 36 (Vicent Martí 2010-12-06 23:03:16 +0200 21
|
||||
* d12299fe src/git.h 37 (Vicent Martí 2010-12-03 22:22:10 +0200 22
|
||||
* 44908fe7 src/git2.h 38 (Vicent Martí 2010-12-06 23:03:16 +0200 23
|
||||
* 44908fe7 src/git2.h 39 (Vicent Martí 2010-12-06 23:03:16 +0200 24
|
||||
* bf787bd8 include/git2.h 25 (Carlos Martín Nieto 2012-04-08 18:56:50 +0200 25
|
||||
* 0984c876 include/git2.h 26 (Scott J. Goldman 2012-11-28 18:27:43 -0800 26
|
||||
* 2f8a8ab2 src/git2.h 41 (Vicent Martí 2011-01-29 01:56:25 +0200 27
|
||||
* 27df4275 include/git2.h 47 (Michael Schubert 2011-06-28 14:13:12 +0200 28
|
||||
* a346992f include/git2.h 28 (Ben Straub 2012-05-10 09:47:14 -0700 29
|
||||
* d12299fe src/git.h 40 (Vicent Martí 2010-12-03 22:22:10 +0200 30
|
||||
* 44908fe7 src/git2.h 41 (Vicent Martí 2010-12-06 23:03:16 +0200 31
|
||||
* 44908fe7 src/git2.h 42 (Vicent Martí 2010-12-06 23:03:16 +0200 32
|
||||
* 44908fe7 src/git2.h 43 (Vicent Martí 2010-12-06 23:03:16 +0200 33
|
||||
* 44908fe7 src/git2.h 44 (Vicent Martí 2010-12-06 23:03:16 +0200 34
|
||||
* 44908fe7 src/git2.h 45 (Vicent Martí 2010-12-06 23:03:16 +0200 35
|
||||
* 65b09b1d include/git2.h 33 (Russell Belfer 2012-02-02 18:03:43 -0800 36
|
||||
* d12299fe src/git.h 46 (Vicent Martí 2010-12-03 22:22:10 +0200 37
|
||||
* 44908fe7 src/git2.h 47 (Vicent Martí 2010-12-06 23:03:16 +0200 38
|
||||
* 5d4cd003 include/git2.h 55 (Carlos Martín Nieto 2011-03-28 17:02:45 +0200 39
|
||||
* 41fb1ca0 include/git2.h 39 (Philip Kelley 2012-10-29 13:41:14 -0400 40
|
||||
* 2dc31040 include/git2.h 56 (Carlos Martín Nieto 2011-06-20 18:58:57 +0200 41
|
||||
* 764df57e include/git2.h 40 (Ben Straub 2012-06-15 13:14:43 -0700 42
|
||||
* 5280f4e6 include/git2.h 41 (Ben Straub 2012-07-31 19:39:06 -0700 43
|
||||
* 613d5eb9 include/git2.h 43 (Philip Kelley 2012-11-28 11:42:37 -0500 44
|
||||
* d12299fe src/git.h 48 (Vicent Martí 2010-12-03 22:22:10 +0200 45
|
||||
* 111ee3fe include/git2.h 41 (Vicent Martí 2012-07-11 14:37:26 +0200 46
|
||||
* f004c4a8 include/git2.h 44 (Russell Belfer 2012-08-21 17:26:39 -0700 47
|
||||
* 111ee3fe include/git2.h 42 (Vicent Martí 2012-07-11 14:37:26 +0200 48
|
||||
* 9c82357b include/git2.h 58 (Carlos Martín Nieto 2011-06-17 18:13:14 +0200 49
|
||||
* d6258deb include/git2.h 61 (Carlos Martín Nieto 2011-06-25 15:10:09 +0200 50
|
||||
* b311e313 include/git2.h 63 (Julien Miotte 2011-07-27 18:31:13 +0200 51
|
||||
* 3412391d include/git2.h 63 (Carlos Martín Nieto 2011-07-07 11:47:31 +0200 52
|
||||
* bfc9ca59 include/git2.h 43 (Russell Belfer 2012-03-28 16:45:36 -0700 53
|
||||
* bf477ed4 include/git2.h 44 (Michael Schubert 2012-02-15 00:33:38 +0100 54
|
||||
* edebceff include/git2.h 46 (nulltoken 2012-05-01 13:57:45 +0200 55
|
||||
* 743a4b3b include/git2.h 48 (nulltoken 2012-06-15 22:24:59 +0200 56
|
||||
* 0a32dca5 include/git2.h 54 (Michael Schubert 2012-08-19 22:26:32 +0200 57
|
||||
* 590fb68b include/git2.h 55 (nulltoken 2012-10-04 13:47:45 +0200 58
|
||||
* bf477ed4 include/git2.h 45 (Michael Schubert 2012-02-15 00:33:38 +0100 59
|
||||
* d12299fe src/git.h 49 (Vicent Martí 2010-12-03 22:22:10 +0200 60
|
||||
*/
|
||||
void test_blame_simple__trivial_libgit2(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
git_object *obj;
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("../..")));
|
||||
|
||||
/* This test can't work on a shallow clone */
|
||||
if (git_repository_is_shallow(g_repo))
|
||||
return;
|
||||
|
||||
cl_git_pass(git_revparse_single(&obj, g_repo, "359fc2d"));
|
||||
git_oid_cpy(&opts.newest_commit, git_object_id(obj));
|
||||
git_object_free(obj);
|
||||
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "include/git2.h", &opts));
|
||||
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "359fc2d2", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 2, 3, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 3, 4, 2, 0, "bb742ede", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 4, 6, 5, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 5, 11, 1, 0, "96fab093", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 6, 12, 1, 0, "9d1dcca2", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 7, 13, 1, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 8, 14, 1, 0, "a15c550d", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 9, 15, 1, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 10, 16, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 11, 17, 1, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 12, 18, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 13, 19, 1, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 14, 20, 1, 0, "638c2ca4", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 15, 21, 1, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 16, 22, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 17, 23, 2, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 18, 25, 1, 0, "bf787bd8", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 19, 26, 1, 0, "0984c876", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 20, 27, 1, 0, "2f8a8ab2", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 21, 28, 1, 0, "27df4275", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 22, 29, 1, 0, "a346992f", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 23, 30, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 24, 31, 5, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 25, 36, 1, 0, "65b09b1d", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 26, 37, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 27, 38, 1, 0, "44908fe7", "src/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 28, 39, 1, 0, "5d4cd003", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 29, 40, 1, 0, "41fb1ca0", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 30, 41, 1, 0, "2dc31040", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 31, 42, 1, 0, "764df57e", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 32, 43, 1, 0, "5280f4e6", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 33, 44, 1, 0, "613d5eb9", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 34, 45, 1, 0, "d12299fe", "src/git.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 35, 46, 1, 0, "111ee3fe", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 36, 47, 1, 0, "f004c4a8", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 37, 48, 1, 0, "111ee3fe", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 38, 49, 1, 0, "9c82357b", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 39, 50, 1, 0, "d6258deb", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 40, 51, 1, 0, "b311e313", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 41, 52, 1, 0, "3412391d", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 42, 53, 1, 0, "bfc9ca59", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 43, 54, 1, 0, "bf477ed4", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 44, 55, 1, 0, "edebceff", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 45, 56, 1, 0, "743a4b3b", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 46, 57, 1, 0, "0a32dca5", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 47, 58, 1, 0, "590fb68b", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 48, 59, 1, 0, "bf477ed4", "include/git2.h");
|
||||
check_blame_hunk_index(g_repo, g_blame, 49, 60, 1, 0, "d12299fe", "src/git.h");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* $ git blame -n b.txt -L 8
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* 63d671eb 8 (Ben Straub 2013-02-12 15:13:04 -0800 8
|
||||
* 63d671eb 9 (Ben Straub 2013-02-12 15:13:04 -0800 9
|
||||
* 63d671eb 10 (Ben Straub 2013-02-12 15:13:04 -0800 10
|
||||
* aa06ecca 6 (Ben Straub 2013-02-12 15:14:46 -0800 11
|
||||
* aa06ecca 7 (Ben Straub 2013-02-12 15:14:46 -0800 12
|
||||
* aa06ecca 8 (Ben Straub 2013-02-12 15:14:46 -0800 13
|
||||
* aa06ecca 9 (Ben Straub 2013-02-12 15:14:46 -0800 14
|
||||
* aa06ecca 10 (Ben Straub 2013-02-12 15:14:46 -0800 15
|
||||
*/
|
||||
void test_blame_simple__can_restrict_lines_min(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
|
||||
|
||||
opts.min_line = 8;
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
|
||||
cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame));
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 8, 3, 0, "63d671eb", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 11, 5, 0, "aa06ecca", "b.txt");
|
||||
}
|
||||
|
||||
/*
|
||||
* $ git blame -n b.txt -L ,6
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* da237394 1 (Ben Straub 2013-02-12 15:11:30 -0800 1
|
||||
* da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
|
||||
* da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
|
||||
* da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
|
||||
* ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
|
||||
* 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
|
||||
*/
|
||||
void test_blame_simple__can_restrict_lines_max(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
|
||||
|
||||
opts.max_line = 6;
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
|
||||
cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame));
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 1, 4, 0, "da237394", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 2, 6, 1, 0, "63d671eb", "b.txt");
|
||||
}
|
||||
|
||||
/*
|
||||
* $ git blame -n b.txt -L 2,7
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* da237394 2 (Ben Straub 2013-02-12 15:11:30 -0800 2
|
||||
* da237394 3 (Ben Straub 2013-02-12 15:11:30 -0800 3
|
||||
* da237394 4 (Ben Straub 2013-02-12 15:11:30 -0800 4
|
||||
* ^b99f7ac 1 (Ben Straub 2013-02-12 15:10:12 -0800 5
|
||||
* 63d671eb 6 (Ben Straub 2013-02-12 15:13:04 -0800 6
|
||||
* 63d671eb 7 (Ben Straub 2013-02-12 15:13:04 -0800 7
|
||||
*/
|
||||
void test_blame_simple__can_restrict_lines_both(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("blametest.git")));
|
||||
|
||||
opts.min_line = 2;
|
||||
opts.max_line = 7;
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "b.txt", &opts));
|
||||
cl_assert_equal_i(3, git_blame_get_hunk_count(g_blame));
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 2, 3, 0, "da237394", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 5, 1, 1, "b99f7ac0", "b.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 2, 6, 2, 0, "63d671eb", "b.txt");
|
||||
}
|
||||
|
||||
/*
|
||||
* $ git blame -n branch_file.txt be3563a..HEAD
|
||||
* orig line no final line no
|
||||
* commit V author timestamp V
|
||||
* ^be3563a 1 (Scott Chacon 2010-05-25 11:58:27 -0700 1) hi
|
||||
* a65fedf3 2 (Scott Chacon 2011-08-09 19:33:46 -0700 2) bye!
|
||||
*/
|
||||
void test_blame_simple__can_restrict_to_newish_commits(void)
|
||||
{
|
||||
git_blame_options opts = GIT_BLAME_OPTIONS_INIT;
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
|
||||
|
||||
{
|
||||
git_object *obj;
|
||||
cl_git_pass(git_revparse_single(&obj, g_repo, "be3563a"));
|
||||
git_oid_cpy(&opts.oldest_commit, git_object_id(obj));
|
||||
git_object_free(obj);
|
||||
}
|
||||
|
||||
cl_git_pass(git_blame_file(&g_blame, g_repo, "branch_file.txt", &opts));
|
||||
|
||||
cl_assert_equal_i(2, git_blame_get_hunk_count(g_blame));
|
||||
check_blame_hunk_index(g_repo, g_blame, 0, 1, 1, 1, "be3563a", "branch_file.txt");
|
||||
check_blame_hunk_index(g_repo, g_blame, 1, 2, 1, 0, "a65fedf", "branch_file.txt");
|
||||
}
|
83
tests-clar/object/lookupbypath.c
Normal file
83
tests-clar/object/lookupbypath.c
Normal file
@ -0,0 +1,83 @@
|
||||
#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);
|
||||
}
|
||||
|
1
tests-clar/resources/blametest.git/HEAD
Normal file
1
tests-clar/resources/blametest.git/HEAD
Normal file
@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
5
tests-clar/resources/blametest.git/config
Normal file
5
tests-clar/resources/blametest.git/config
Normal file
@ -0,0 +1,5 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
ignorecase = true
|
1
tests-clar/resources/blametest.git/description
Normal file
1
tests-clar/resources/blametest.git/description
Normal file
@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
x•<>A E]sŠÙu¥Ê@bŒ±GðÀPë¢Azk<<3C>ù’—¼Ÿ×y~60–I³“1S´’=“-6÷FfC.†ìcp½õе,
$b
|
||||
¶8MF
|
||||
ᨹO!1eïÈ<C3AF>ò]TqkÓZáV¸··çô¾>žmÚÒ)¯ó49d<39>ì-Ñ#ª<>îmüg©áw©ºTK@Î
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
x•ЏKВ0DYзЮuІг6I%„P9'ИЗЎ]фЈђЮџ"NАМоI#Ѕ‰л<O4б©€YPи(O.SЫзФЪЬ yЙЙ
FV›/ІTH^іеѕC¬“ЖД<D096>ЙvЎ–Ј3мrъЖ+їЧq-0ИПZьаЮчЧTЗ=\в:ЯЂШ %tОАў:ибVеї•zь.5CуЄЉA@
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
x•ŽK
|
||||
1]ç½›•ÒIg2Yˆˆâ <A>3‹L vîoÀ¸{õR-eÐΤ1ƒ#ŽóBÆ0<C386>©}¶s˜9xãí‹R6d1’S¡ËZÜx‡§´Ð#œãçúÞdíñ”j¹€&‡‹F§
Ñ#ªAGKø?KݧÇôgõ2r
|
@ -0,0 +1,3 @@
|
||||
x•<>AnÃ0{Ö+tË))J´AÛ<!/ eºÉÁqàÊÿ¯<C3BF>¢Èu±³Àl[æùÞc*òÖW÷èÅ26t-DS¨V5C2<43>ÀŠN#§Lá©«?zdy@7Jc*àY+
ÌnÖ¼bݧЅô¿¯
|
||||
ì)·¬#gJjBH^
|
||||
7•dšMâtë·e<C2B7>_þˆ×¾êfñd?ß÷~Ûì½-ó9"1B<12>Pžî.Ý_£Âåï‚Ãç!ü&íO
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
x+)JMU03c040031QHÔ+©(a˜Ñyíihyâª>3ö<í^¹G¥nÕ@$<24>HÉ\;ÍMê<4D>oã¶œ<C2B6>úѬ‹£Æ¤
|
@ -0,0 +1,4 @@
|
||||
x•<>K
|
||||
Â0@]÷³ëJ™|'"¢ÞÀ$“ÔvÑVbzžÀíƒïÉ:ÏSðÐj)<29>Ñif£Ñ‘äDNSÆdOÌbs§Ñ[ìÞ±–¥Ab(
|
||||
¦Y;“ƒfç¬(‚˜„ƒ‰®‹[×
|
||||
·²À³Õ¸%8§Ïõ5µqK'Yç(ã‘zF8b@ìvº·µòŸÕÝKý£ÿc–?S
|
Binary file not shown.
Binary file not shown.
1
tests-clar/resources/blametest.git/refs/heads/master
Normal file
1
tests-clar/resources/blametest.git/refs/heads/master
Normal file
@ -0,0 +1 @@
|
||||
bc7c5ac2bafe828a68e9d1d460343718d6fbe136
|
Loading…
Reference in New Issue
Block a user