Merge branch 'js/range-diff' into es/format-patch-rangediff
* js/range-diff: (21 commits) range-diff: use dim/bold cues to improve dual color mode range-diff: make --dual-color the default mode range-diff: left-pad patch numbers completion: support `git range-diff` range-diff: populate the man page range-diff --dual-color: skip white-space warnings range-diff: offer to dual-color the diffs diff: add an internal option to dual-color diffs of diffs color: add the meta color GIT_COLOR_REVERSE range-diff: use color for the commit pairs range-diff: add tests range-diff: do not show "function names" in hunk headers range-diff: adjust the output of the commit pairs range-diff: suppress the diff headers range-diff: indent the diffs just like tbdiff range-diff: right-trim commit messages range-diff: also show the diff between patches range-diff: improve the order of the shown commits range-diff: first rudimentary implementation Introduce `range-diff` to compare iterations of a topic branch ...
This commit is contained in:
commit
fe8f41fb2a
1
.gitignore
vendored
1
.gitignore
vendored
@ -113,6 +113,7 @@
|
||||
/git-pull
|
||||
/git-push
|
||||
/git-quiltimport
|
||||
/git-range-diff
|
||||
/git-read-tree
|
||||
/git-rebase
|
||||
/git-rebase--am
|
||||
|
@ -1193,8 +1193,10 @@ color.diff.<slot>::
|
||||
(highlighting whitespace errors), `oldMoved` (deleted lines),
|
||||
`newMoved` (added lines), `oldMovedDimmed`, `oldMovedAlternative`,
|
||||
`oldMovedAlternativeDimmed`, `newMovedDimmed`, `newMovedAlternative`
|
||||
and `newMovedAlternativeDimmed` (See the '<mode>'
|
||||
setting of '--color-moved' in linkgit:git-diff[1] for details).
|
||||
`newMovedAlternativeDimmed` (See the '<mode>'
|
||||
setting of '--color-moved' in linkgit:git-diff[1] for details),
|
||||
`contextDimmed`, `oldDimmed`, `newDimmed`, `contextBold`,
|
||||
`oldBold`, and `newBold` (see linkgit:git-range-diff[1] for details).
|
||||
|
||||
color.decorate.<slot>::
|
||||
Use customized color for 'git log --decorate' output. `<slot>` is one
|
||||
|
252
Documentation/git-range-diff.txt
Normal file
252
Documentation/git-range-diff.txt
Normal file
@ -0,0 +1,252 @@
|
||||
git-range-diff(1)
|
||||
=================
|
||||
|
||||
NAME
|
||||
----
|
||||
git-range-diff - Compare two commit ranges (e.g. two versions of a branch)
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>]
|
||||
[--no-dual-color] [--creation-factor=<factor>]
|
||||
( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> )
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
This command shows the differences between two versions of a patch
|
||||
series, or more generally, two commit ranges (ignoring merge commits).
|
||||
|
||||
To that end, it first finds pairs of commits from both commit ranges
|
||||
that correspond with each other. Two commits are said to correspond when
|
||||
the diff between their patches (i.e. the author information, the commit
|
||||
message and the commit diff) is reasonably small compared to the
|
||||
patches' size. See ``Algorithm`` below for details.
|
||||
|
||||
Finally, the list of matching commits is shown in the order of the
|
||||
second commit range, with unmatched commits being inserted just after
|
||||
all of their ancestors have been shown.
|
||||
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
--no-dual-color::
|
||||
When the commit diffs differ, `git range-diff` recreates the
|
||||
original diffs' coloring, and adds outer -/+ diff markers with
|
||||
the *background* being red/green to make it easier to see e.g.
|
||||
when there was a change in what exact lines were added.
|
||||
+
|
||||
Additionally, the commit diff lines that are only present in the first commit
|
||||
range are shown "dimmed" (this can be overridden using the `color.diff.<slot>`
|
||||
config setting where `<slot>` is one of `contextDimmed`, `oldDimmed` and
|
||||
`newDimmed`), and the commit diff lines that are only present in the second
|
||||
commit range are shown in bold (which can be overridden using the config
|
||||
settings `color.diff.<slot>` with `<slot>` being one of `contextBold`,
|
||||
`oldBold` or `newBold`).
|
||||
+
|
||||
This is known to `range-diff` as "dual coloring". Use `--no-dual-color`
|
||||
to revert to color all lines according to the outer diff markers
|
||||
(and completely ignore the inner diff when it comes to color).
|
||||
|
||||
--creation-factor=<percent>::
|
||||
Set the creation/deletion cost fudge factor to `<percent>`.
|
||||
Defaults to 60. Try a larger value if `git range-diff` erroneously
|
||||
considers a large change a total rewrite (deletion of one commit
|
||||
and addition of another), and a smaller one in the reverse case.
|
||||
See the ``Algorithm`` section below for an explanation why this is
|
||||
needed.
|
||||
|
||||
<range1> <range2>::
|
||||
Compare the commits specified by the two ranges, where
|
||||
`<range1>` is considered an older version of `<range2>`.
|
||||
|
||||
<rev1>...<rev2>::
|
||||
Equivalent to passing `<rev2>..<rev1>` and `<rev1>..<rev2>`.
|
||||
|
||||
<base> <rev1> <rev2>::
|
||||
Equivalent to passing `<base>..<rev1>` and `<base>..<rev2>`.
|
||||
Note that `<base>` does not need to be the exact branch point
|
||||
of the branches. Example: after rebasing a branch `my-topic`,
|
||||
`git range-diff my-topic@{u} my-topic@{1} my-topic` would
|
||||
show the differences introduced by the rebase.
|
||||
|
||||
`git range-diff` also accepts the regular diff options (see
|
||||
linkgit:git-diff[1]), most notably the `--color=[<when>]` and
|
||||
`--no-color` options. These options are used when generating the "diff
|
||||
between patches", i.e. to compare the author, commit message and diff of
|
||||
corresponding old/new commits. There is currently no means to tweak the
|
||||
diff options passed to `git log` when generating those patches.
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
This command uses the `diff.color.*` and `pager.range-diff` settings
|
||||
(the latter is on by default).
|
||||
See linkgit:git-config[1].
|
||||
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
When a rebase required merge conflicts to be resolved, compare the changes
|
||||
introduced by the rebase directly afterwards using:
|
||||
|
||||
------------
|
||||
$ git range-diff @{u} @{1} @
|
||||
------------
|
||||
|
||||
|
||||
A typical output of `git range-diff` would look like this:
|
||||
|
||||
------------
|
||||
-: ------- > 1: 0ddba11 Prepare for the inevitable!
|
||||
1: c0debee = 2: cab005e Add a helpful message at the start
|
||||
2: f00dbal ! 3: decafe1 Describe a bug
|
||||
@@ -1,3 +1,3 @@
|
||||
Author: A U Thor <author@example.com>
|
||||
|
||||
-TODO: Describe a bug
|
||||
+Describe a bug
|
||||
@@ -324,5 +324,6
|
||||
This is expected.
|
||||
|
||||
-+What is unexpected is that it will also crash.
|
||||
++Unexpectedly, it also crashes. This is a bug, and the jury is
|
||||
++still out there how to fix it best. See ticket #314 for details.
|
||||
|
||||
Contact
|
||||
3: bedead < -: ------- TO-UNDO
|
||||
------------
|
||||
|
||||
In this example, there are 3 old and 3 new commits, where the developer
|
||||
removed the 3rd, added a new one before the first two, and modified the
|
||||
commit message of the 2nd commit as well its diff.
|
||||
|
||||
When the output goes to a terminal, it is color-coded by default, just
|
||||
like regular `git diff`'s output. In addition, the first line (adding a
|
||||
commit) is green, the last line (deleting a commit) is red, the second
|
||||
line (with a perfect match) is yellow like the commit header of `git
|
||||
show`'s output, and the third line colors the old commit red, the new
|
||||
one green and the rest like `git show`'s commit header.
|
||||
|
||||
A naive color-coded diff of diffs is actually a bit hard to read,
|
||||
though, as it colors the entire lines red or green. The line that added
|
||||
"What is unexpected" in the old commit, for example, is completely red,
|
||||
even if the intent of the old commit was to add something.
|
||||
|
||||
To help with that, `range` uses the `--dual-color` mode by default. In
|
||||
this mode, the diff of diffs will retain the original diff colors, and
|
||||
prefix the lines with -/+ markers that have their *background* red or
|
||||
green, to make it more obvious that they describe how the diff itself
|
||||
changed.
|
||||
|
||||
|
||||
Algorithm
|
||||
---------
|
||||
|
||||
The general idea is this: we generate a cost matrix between the commits
|
||||
in both commit ranges, then solve the least-cost assignment.
|
||||
|
||||
The cost matrix is populated thusly: for each pair of commits, both
|
||||
diffs are generated and the "diff of diffs" is generated, with 3 context
|
||||
lines, then the number of lines in that diff is used as cost.
|
||||
|
||||
To avoid false positives (e.g. when a patch has been removed, and an
|
||||
unrelated patch has been added between two iterations of the same patch
|
||||
series), the cost matrix is extended to allow for that, by adding
|
||||
fixed-cost entries for wholesale deletes/adds.
|
||||
|
||||
Example: Let commits `1--2` be the first iteration of a patch series and
|
||||
`A--C` the second iteration. Let's assume that `A` is a cherry-pick of
|
||||
`2,` and `C` is a cherry-pick of `1` but with a small modification (say,
|
||||
a fixed typo). Visualize the commits as a bipartite graph:
|
||||
|
||||
------------
|
||||
1 A
|
||||
|
||||
2 B
|
||||
|
||||
C
|
||||
------------
|
||||
|
||||
We are looking for a "best" explanation of the new series in terms of
|
||||
the old one. We can represent an "explanation" as an edge in the graph:
|
||||
|
||||
|
||||
------------
|
||||
1 A
|
||||
/
|
||||
2 --------' B
|
||||
|
||||
C
|
||||
------------
|
||||
|
||||
This explanation comes for "free" because there was no change. Similarly
|
||||
`C` could be explained using `1`, but that comes at some cost c>0
|
||||
because of the modification:
|
||||
|
||||
------------
|
||||
1 ----. A
|
||||
| /
|
||||
2 ----+---' B
|
||||
|
|
||||
`----- C
|
||||
c>0
|
||||
------------
|
||||
|
||||
In mathematical terms, what we are looking for is some sort of a minimum
|
||||
cost bipartite matching; `1` is matched to `C` at some cost, etc. The
|
||||
underlying graph is in fact a complete bipartite graph; the cost we
|
||||
associate with every edge is the size of the diff between the two
|
||||
commits' patches. To explain also new commits, we introduce dummy nodes
|
||||
on both sides:
|
||||
|
||||
------------
|
||||
1 ----. A
|
||||
| /
|
||||
2 ----+---' B
|
||||
|
|
||||
o `----- C
|
||||
c>0
|
||||
o o
|
||||
|
||||
o o
|
||||
------------
|
||||
|
||||
The cost of an edge `o--C` is the size of `C`'s diff, modified by a
|
||||
fudge factor that should be smaller than 100%. The cost of an edge
|
||||
`o--o` is free. The fudge factor is necessary because even if `1` and
|
||||
`C` have nothing in common, they may still share a few empty lines and
|
||||
such, possibly making the assignment `1--C`, `o--o` slightly cheaper
|
||||
than `1--o`, `o--C` even if `1` and `C` have nothing in common. With the
|
||||
fudge factor we require a much larger common part to consider patches as
|
||||
corresponding.
|
||||
|
||||
The overall time needed to compute this algorithm is the time needed to
|
||||
compute n+m commit diffs and then n*m diffs of patches, plus the time
|
||||
needed to compute the least-cost assigment between n and m diffs. Git
|
||||
uses an implementation of the Jonker-Volgenant algorithm to solve the
|
||||
assignment problem, which has cubic runtime complexity. The matching
|
||||
found in this case will look like this:
|
||||
|
||||
------------
|
||||
1 ----. A
|
||||
| /
|
||||
2 ----+---' B
|
||||
.--+-----'
|
||||
o -' `----- C
|
||||
c>0
|
||||
o ---------- o
|
||||
|
||||
o ---------- o
|
||||
------------
|
||||
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:git-log[1]
|
||||
|
||||
GIT
|
||||
---
|
||||
Part of the linkgit:git[1] suite
|
3
Makefile
3
Makefile
@ -870,6 +870,7 @@ LIB_OBJS += gpg-interface.o
|
||||
LIB_OBJS += graph.o
|
||||
LIB_OBJS += grep.o
|
||||
LIB_OBJS += hashmap.o
|
||||
LIB_OBJS += linear-assignment.o
|
||||
LIB_OBJS += help.o
|
||||
LIB_OBJS += hex.o
|
||||
LIB_OBJS += ident.o
|
||||
@ -924,6 +925,7 @@ LIB_OBJS += progress.o
|
||||
LIB_OBJS += prompt.o
|
||||
LIB_OBJS += protocol.o
|
||||
LIB_OBJS += quote.o
|
||||
LIB_OBJS += range-diff.o
|
||||
LIB_OBJS += reachable.o
|
||||
LIB_OBJS += read-cache.o
|
||||
LIB_OBJS += reflog-walk.o
|
||||
@ -1062,6 +1064,7 @@ BUILTIN_OBJS += builtin/prune-packed.o
|
||||
BUILTIN_OBJS += builtin/prune.o
|
||||
BUILTIN_OBJS += builtin/pull.o
|
||||
BUILTIN_OBJS += builtin/push.o
|
||||
BUILTIN_OBJS += builtin/range-diff.o
|
||||
BUILTIN_OBJS += builtin/read-tree.o
|
||||
BUILTIN_OBJS += builtin/rebase--helper.o
|
||||
BUILTIN_OBJS += builtin/receive-pack.o
|
||||
|
@ -201,6 +201,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_pull(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_push(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_range_diff(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
|
||||
|
116
builtin/range-diff.c
Normal file
116
builtin/range-diff.c
Normal file
@ -0,0 +1,116 @@
|
||||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "parse-options.h"
|
||||
#include "range-diff.h"
|
||||
#include "config.h"
|
||||
|
||||
static const char * const builtin_range_diff_usage[] = {
|
||||
N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"),
|
||||
N_("git range-diff [<options>] <old-tip>...<new-tip>"),
|
||||
N_("git range-diff [<options>] <base> <old-tip> <new-tip>"),
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
int cmd_range_diff(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int creation_factor = 60;
|
||||
struct diff_options diffopt = { NULL };
|
||||
int simple_color = -1;
|
||||
struct option options[] = {
|
||||
OPT_INTEGER(0, "creation-factor", &creation_factor,
|
||||
N_("Percentage by which creation is weighted")),
|
||||
OPT_BOOL(0, "no-dual-color", &simple_color,
|
||||
N_("color both diff and diff-between-diffs")),
|
||||
OPT_END()
|
||||
};
|
||||
int i, j, res = 0;
|
||||
struct strbuf four_spaces = STRBUF_INIT;
|
||||
struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
|
||||
|
||||
git_config(git_diff_ui_config, NULL);
|
||||
|
||||
diff_setup(&diffopt);
|
||||
diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||
diffopt.flags.suppress_diff_headers = 1;
|
||||
diffopt.output_prefix = output_prefix_cb;
|
||||
strbuf_addstr(&four_spaces, " ");
|
||||
diffopt.output_prefix_data = &four_spaces;
|
||||
|
||||
argc = parse_options(argc, argv, NULL, options,
|
||||
builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
|
||||
PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
|
||||
|
||||
for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
|
||||
int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
|
||||
|
||||
if (!c)
|
||||
argv[j++] = argv[i++];
|
||||
else
|
||||
i += c;
|
||||
}
|
||||
while (i < argc)
|
||||
argv[j++] = argv[i++];
|
||||
argc = j;
|
||||
diff_setup_done(&diffopt);
|
||||
|
||||
/* Make sure that there are no unparsed options */
|
||||
argc = parse_options(argc, argv, NULL,
|
||||
options + ARRAY_SIZE(options) - 1, /* OPT_END */
|
||||
builtin_range_diff_usage, 0);
|
||||
|
||||
if (simple_color < 1) {
|
||||
if (!simple_color)
|
||||
/* force color when --dual-color was used */
|
||||
diffopt.use_color = 1;
|
||||
diffopt.flags.dual_color_diffed_diffs = 1;
|
||||
}
|
||||
|
||||
if (argc == 2) {
|
||||
if (!strstr(argv[0], ".."))
|
||||
die(_("no .. in range: '%s'"), argv[0]);
|
||||
strbuf_addstr(&range1, argv[0]);
|
||||
|
||||
if (!strstr(argv[1], ".."))
|
||||
die(_("no .. in range: '%s'"), argv[1]);
|
||||
strbuf_addstr(&range2, argv[1]);
|
||||
} else if (argc == 3) {
|
||||
strbuf_addf(&range1, "%s..%s", argv[0], argv[1]);
|
||||
strbuf_addf(&range2, "%s..%s", argv[0], argv[2]);
|
||||
} else if (argc == 1) {
|
||||
const char *b = strstr(argv[0], "..."), *a = argv[0];
|
||||
int a_len;
|
||||
|
||||
if (!b) {
|
||||
error(_("single arg format must be symmetric range"));
|
||||
usage_with_options(builtin_range_diff_usage, options);
|
||||
}
|
||||
|
||||
a_len = (int)(b - a);
|
||||
if (!a_len) {
|
||||
a = "HEAD";
|
||||
a_len = strlen(a);
|
||||
}
|
||||
b += 3;
|
||||
if (!*b)
|
||||
b = "HEAD";
|
||||
strbuf_addf(&range1, "%s..%.*s", b, a_len, a);
|
||||
strbuf_addf(&range2, "%.*s..%s", a_len, a, b);
|
||||
} else {
|
||||
error(_("need two commit ranges"));
|
||||
usage_with_options(builtin_range_diff_usage, options);
|
||||
}
|
||||
|
||||
res = show_range_diff(range1.buf, range2.buf, creation_factor,
|
||||
&diffopt);
|
||||
|
||||
strbuf_release(&range1);
|
||||
strbuf_release(&range2);
|
||||
strbuf_release(&four_spaces);
|
||||
|
||||
return res;
|
||||
}
|
7
color.h
7
color.h
@ -36,6 +36,12 @@ struct strbuf;
|
||||
#define GIT_COLOR_BOLD_BLUE "\033[1;34m"
|
||||
#define GIT_COLOR_BOLD_MAGENTA "\033[1;35m"
|
||||
#define GIT_COLOR_BOLD_CYAN "\033[1;36m"
|
||||
#define GIT_COLOR_FAINT_RED "\033[2;31m"
|
||||
#define GIT_COLOR_FAINT_GREEN "\033[2;32m"
|
||||
#define GIT_COLOR_FAINT_YELLOW "\033[2;33m"
|
||||
#define GIT_COLOR_FAINT_BLUE "\033[2;34m"
|
||||
#define GIT_COLOR_FAINT_MAGENTA "\033[2;35m"
|
||||
#define GIT_COLOR_FAINT_CYAN "\033[2;36m"
|
||||
#define GIT_COLOR_BG_RED "\033[41m"
|
||||
#define GIT_COLOR_BG_GREEN "\033[42m"
|
||||
#define GIT_COLOR_BG_YELLOW "\033[43m"
|
||||
@ -44,6 +50,7 @@ struct strbuf;
|
||||
#define GIT_COLOR_BG_CYAN "\033[46m"
|
||||
#define GIT_COLOR_FAINT "\033[2m"
|
||||
#define GIT_COLOR_FAINT_ITALIC "\033[2;3m"
|
||||
#define GIT_COLOR_REVERSE "\033[7m"
|
||||
|
||||
/* A special value meaning "no color selected" */
|
||||
#define GIT_COLOR_NIL "NIL"
|
||||
|
@ -139,6 +139,7 @@ git-prune-packed plumbingmanipulators
|
||||
git-pull mainporcelain remote
|
||||
git-push mainporcelain remote
|
||||
git-quiltimport foreignscminterface
|
||||
git-range-diff mainporcelain
|
||||
git-read-tree plumbingmanipulators
|
||||
git-rebase mainporcelain history
|
||||
git-receive-pack synchelpers
|
||||
|
@ -1976,6 +1976,20 @@ _git_push ()
|
||||
__git_complete_remote_or_refspec
|
||||
}
|
||||
|
||||
_git_range_diff ()
|
||||
{
|
||||
case "$cur" in
|
||||
--*)
|
||||
__gitcomp "
|
||||
--creation-factor= --no-dual-color
|
||||
$__git_diff_common_options
|
||||
"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
__git_complete_revlist
|
||||
}
|
||||
|
||||
_git_rebase ()
|
||||
{
|
||||
__git_find_repo_path
|
||||
|
105
diff.c
105
diff.c
@ -70,6 +70,12 @@ static char diff_colors[][COLOR_MAXLEN] = {
|
||||
GIT_COLOR_BOLD_YELLOW, /* NEW_MOVED ALTERNATIVE */
|
||||
GIT_COLOR_FAINT, /* NEW_MOVED_DIM */
|
||||
GIT_COLOR_FAINT_ITALIC, /* NEW_MOVED_ALTERNATIVE_DIM */
|
||||
GIT_COLOR_FAINT, /* CONTEXT_DIM */
|
||||
GIT_COLOR_FAINT_RED, /* OLD_DIM */
|
||||
GIT_COLOR_FAINT_GREEN, /* NEW_DIM */
|
||||
GIT_COLOR_BOLD, /* CONTEXT_BOLD */
|
||||
GIT_COLOR_BOLD_RED, /* OLD_BOLD */
|
||||
GIT_COLOR_BOLD_GREEN, /* NEW_BOLD */
|
||||
};
|
||||
|
||||
static const char *color_diff_slots[] = {
|
||||
@ -89,6 +95,12 @@ static const char *color_diff_slots[] = {
|
||||
[DIFF_FILE_NEW_MOVED_ALT] = "newMovedAlternative",
|
||||
[DIFF_FILE_NEW_MOVED_DIM] = "newMovedDimmed",
|
||||
[DIFF_FILE_NEW_MOVED_ALT_DIM] = "newMovedAlternativeDimmed",
|
||||
[DIFF_CONTEXT_DIM] = "contextDimmed",
|
||||
[DIFF_FILE_OLD_DIM] = "oldDimmed",
|
||||
[DIFF_FILE_NEW_DIM] = "newDimmed",
|
||||
[DIFF_CONTEXT_BOLD] = "contextBold",
|
||||
[DIFF_FILE_OLD_BOLD] = "oldBold",
|
||||
[DIFF_FILE_NEW_BOLD] = "newBold",
|
||||
};
|
||||
|
||||
static NORETURN void die_want_option(const char *option_name)
|
||||
@ -609,14 +621,18 @@ static void check_blank_at_eof(mmfile_t *mf1, mmfile_t *mf2,
|
||||
ecbdata->blank_at_eof_in_postimage = (at - l2) + 1;
|
||||
}
|
||||
|
||||
static void emit_line_0(struct diff_options *o, const char *set, const char *reset,
|
||||
static void emit_line_0(struct diff_options *o,
|
||||
const char *set, unsigned reverse, const char *reset,
|
||||
int first, const char *line, int len)
|
||||
{
|
||||
int has_trailing_newline, has_trailing_carriage_return;
|
||||
int nofirst;
|
||||
FILE *file = o->file;
|
||||
|
||||
fputs(diff_line_prefix(o), file);
|
||||
if (first)
|
||||
fputs(diff_line_prefix(o), file);
|
||||
else if (!len)
|
||||
return;
|
||||
|
||||
if (len == 0) {
|
||||
has_trailing_newline = (first == '\n');
|
||||
@ -634,8 +650,10 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
|
||||
}
|
||||
|
||||
if (len || !nofirst) {
|
||||
if (reverse && want_color(o->use_color))
|
||||
fputs(GIT_COLOR_REVERSE, file);
|
||||
fputs(set, file);
|
||||
if (!nofirst)
|
||||
if (first && !nofirst)
|
||||
fputc(first, file);
|
||||
fwrite(line, len, 1, file);
|
||||
fputs(reset, file);
|
||||
@ -649,7 +667,7 @@ static void emit_line_0(struct diff_options *o, const char *set, const char *res
|
||||
static void emit_line(struct diff_options *o, const char *set, const char *reset,
|
||||
const char *line, int len)
|
||||
{
|
||||
emit_line_0(o, set, reset, line[0], line+1, len-1);
|
||||
emit_line_0(o, set, 0, reset, line[0], line+1, len-1);
|
||||
}
|
||||
|
||||
enum diff_symbol {
|
||||
@ -1168,7 +1186,8 @@ static void dim_moved_lines(struct diff_options *o)
|
||||
|
||||
static void emit_line_ws_markup(struct diff_options *o,
|
||||
const char *set, const char *reset,
|
||||
const char *line, int len, char sign,
|
||||
const char *line, int len,
|
||||
const char *set_sign, char sign,
|
||||
unsigned ws_rule, int blank_at_eof)
|
||||
{
|
||||
const char *ws = NULL;
|
||||
@ -1179,14 +1198,20 @@ static void emit_line_ws_markup(struct diff_options *o,
|
||||
ws = NULL;
|
||||
}
|
||||
|
||||
if (!ws)
|
||||
emit_line_0(o, set, reset, sign, line, len);
|
||||
else if (blank_at_eof)
|
||||
if (!ws && !set_sign)
|
||||
emit_line_0(o, set, 0, reset, sign, line, len);
|
||||
else if (!ws) {
|
||||
/* Emit just the prefix, then the rest. */
|
||||
emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
|
||||
sign, "", 0);
|
||||
emit_line_0(o, set, 0, reset, 0, line, len);
|
||||
} else if (blank_at_eof)
|
||||
/* Blank line at EOF - paint '+' as well */
|
||||
emit_line_0(o, ws, reset, sign, line, len);
|
||||
emit_line_0(o, ws, 0, reset, sign, line, len);
|
||||
else {
|
||||
/* Emit just the prefix, then the rest. */
|
||||
emit_line_0(o, set, reset, sign, "", 0);
|
||||
emit_line_0(o, set_sign ? set_sign : set, !!set_sign, reset,
|
||||
sign, "", 0);
|
||||
ws_check_emit(line, len, ws_rule,
|
||||
o->file, set, reset, ws);
|
||||
}
|
||||
@ -1196,7 +1221,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
|
||||
struct emitted_diff_symbol *eds)
|
||||
{
|
||||
static const char *nneof = " No newline at end of file\n";
|
||||
const char *context, *reset, *set, *meta, *fraginfo;
|
||||
const char *context, *reset, *set, *set_sign, *meta, *fraginfo;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
enum diff_symbol s = eds->s;
|
||||
@ -1209,7 +1234,7 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
|
||||
context = diff_get_color_opt(o, DIFF_CONTEXT);
|
||||
reset = diff_get_color_opt(o, DIFF_RESET);
|
||||
putc('\n', o->file);
|
||||
emit_line_0(o, context, reset, '\\',
|
||||
emit_line_0(o, context, 0, reset, '\\',
|
||||
nneof, strlen(nneof));
|
||||
break;
|
||||
case DIFF_SYMBOL_SUBMODULE_HEADER:
|
||||
@ -1236,7 +1261,18 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
|
||||
case DIFF_SYMBOL_CONTEXT:
|
||||
set = diff_get_color_opt(o, DIFF_CONTEXT);
|
||||
reset = diff_get_color_opt(o, DIFF_RESET);
|
||||
emit_line_ws_markup(o, set, reset, line, len, ' ',
|
||||
set_sign = NULL;
|
||||
if (o->flags.dual_color_diffed_diffs) {
|
||||
char c = !len ? 0 : line[0];
|
||||
|
||||
if (c == '+')
|
||||
set = diff_get_color_opt(o, DIFF_FILE_NEW);
|
||||
else if (c == '@')
|
||||
set = diff_get_color_opt(o, DIFF_FRAGINFO);
|
||||
else if (c == '-')
|
||||
set = diff_get_color_opt(o, DIFF_FILE_OLD);
|
||||
}
|
||||
emit_line_ws_markup(o, set, reset, line, len, set_sign, ' ',
|
||||
flags & (DIFF_SYMBOL_CONTENT_WS_MASK), 0);
|
||||
break;
|
||||
case DIFF_SYMBOL_PLUS:
|
||||
@ -1263,7 +1299,23 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
|
||||
set = diff_get_color_opt(o, DIFF_FILE_NEW);
|
||||
}
|
||||
reset = diff_get_color_opt(o, DIFF_RESET);
|
||||
emit_line_ws_markup(o, set, reset, line, len, '+',
|
||||
if (!o->flags.dual_color_diffed_diffs)
|
||||
set_sign = NULL;
|
||||
else {
|
||||
char c = !len ? 0 : line[0];
|
||||
|
||||
set_sign = set;
|
||||
if (c == '-')
|
||||
set = diff_get_color_opt(o, DIFF_FILE_OLD_BOLD);
|
||||
else if (c == '@')
|
||||
set = diff_get_color_opt(o, DIFF_FRAGINFO);
|
||||
else if (c == '+')
|
||||
set = diff_get_color_opt(o, DIFF_FILE_NEW_BOLD);
|
||||
else
|
||||
set = diff_get_color_opt(o, DIFF_CONTEXT_BOLD);
|
||||
flags &= ~DIFF_SYMBOL_CONTENT_WS_MASK;
|
||||
}
|
||||
emit_line_ws_markup(o, set, reset, line, len, set_sign, '+',
|
||||
flags & DIFF_SYMBOL_CONTENT_WS_MASK,
|
||||
flags & DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF);
|
||||
break;
|
||||
@ -1291,7 +1343,22 @@ static void emit_diff_symbol_from_struct(struct diff_options *o,
|
||||
set = diff_get_color_opt(o, DIFF_FILE_OLD);
|
||||
}
|
||||
reset = diff_get_color_opt(o, DIFF_RESET);
|
||||
emit_line_ws_markup(o, set, reset, line, len, '-',
|
||||
if (!o->flags.dual_color_diffed_diffs)
|
||||
set_sign = NULL;
|
||||
else {
|
||||
char c = !len ? 0 : line[0];
|
||||
|
||||
set_sign = set;
|
||||
if (c == '+')
|
||||
set = diff_get_color_opt(o, DIFF_FILE_NEW_DIM);
|
||||
else if (c == '@')
|
||||
set = diff_get_color_opt(o, DIFF_FRAGINFO);
|
||||
else if (c == '-')
|
||||
set = diff_get_color_opt(o, DIFF_FILE_OLD_DIM);
|
||||
else
|
||||
set = diff_get_color_opt(o, DIFF_CONTEXT_DIM);
|
||||
}
|
||||
emit_line_ws_markup(o, set, reset, line, len, set_sign, '-',
|
||||
flags & DIFF_SYMBOL_CONTENT_WS_MASK, 0);
|
||||
break;
|
||||
case DIFF_SYMBOL_WORDS_PORCELAIN:
|
||||
@ -1482,6 +1549,7 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
|
||||
const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
|
||||
const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
|
||||
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
|
||||
const char *reverse = ecbdata->color_diff ? GIT_COLOR_REVERSE : "";
|
||||
static const char atat[2] = { '@', '@' };
|
||||
const char *cp, *ep;
|
||||
struct strbuf msgbuf = STRBUF_INIT;
|
||||
@ -1502,6 +1570,8 @@ static void emit_hunk_header(struct emit_callback *ecbdata,
|
||||
ep += 2; /* skip over @@ */
|
||||
|
||||
/* The hunk header in fraginfo color */
|
||||
if (ecbdata->opt->flags.dual_color_diffed_diffs)
|
||||
strbuf_addstr(&msgbuf, reverse);
|
||||
strbuf_addstr(&msgbuf, frag);
|
||||
strbuf_add(&msgbuf, line, ep - line);
|
||||
strbuf_addstr(&msgbuf, reset);
|
||||
@ -3395,13 +3465,16 @@ static void builtin_diff(const char *name_a,
|
||||
memset(&xpp, 0, sizeof(xpp));
|
||||
memset(&xecfg, 0, sizeof(xecfg));
|
||||
memset(&ecbdata, 0, sizeof(ecbdata));
|
||||
if (o->flags.suppress_diff_headers)
|
||||
lbl[0] = NULL;
|
||||
ecbdata.label_path = lbl;
|
||||
ecbdata.color_diff = want_color(o->use_color);
|
||||
ecbdata.ws_rule = whitespace_rule(name_b);
|
||||
if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
|
||||
check_blank_at_eof(&mf1, &mf2, &ecbdata);
|
||||
ecbdata.opt = o;
|
||||
ecbdata.header = header.len ? &header : NULL;
|
||||
if (header.len && !o->flags.suppress_diff_headers)
|
||||
ecbdata.header = &header;
|
||||
xpp.flags = o->xdl_opts;
|
||||
xpp.anchors = o->anchors;
|
||||
xpp.anchors_nr = o->anchors_nr;
|
||||
|
10
diff.h
10
diff.h
@ -94,6 +94,8 @@ struct diff_flags {
|
||||
unsigned funccontext:1;
|
||||
unsigned default_follow_renames:1;
|
||||
unsigned stat_with_summary:1;
|
||||
unsigned suppress_diff_headers:1;
|
||||
unsigned dual_color_diffed_diffs:1;
|
||||
};
|
||||
|
||||
static inline void diff_flags_or(struct diff_flags *a,
|
||||
@ -246,7 +248,13 @@ enum color_diff {
|
||||
DIFF_FILE_NEW_MOVED = 13,
|
||||
DIFF_FILE_NEW_MOVED_ALT = 14,
|
||||
DIFF_FILE_NEW_MOVED_DIM = 15,
|
||||
DIFF_FILE_NEW_MOVED_ALT_DIM = 16
|
||||
DIFF_FILE_NEW_MOVED_ALT_DIM = 16,
|
||||
DIFF_CONTEXT_DIM = 17,
|
||||
DIFF_FILE_OLD_DIM = 18,
|
||||
DIFF_FILE_NEW_DIM = 19,
|
||||
DIFF_CONTEXT_BOLD = 20,
|
||||
DIFF_FILE_OLD_BOLD = 21,
|
||||
DIFF_FILE_NEW_BOLD = 22,
|
||||
};
|
||||
const char *diff_get_color(int diff_use_color, enum color_diff ix);
|
||||
#define diff_get_color_opt(o, ix) \
|
||||
|
1
git.c
1
git.c
@ -520,6 +520,7 @@ static struct cmd_struct commands[] = {
|
||||
{ "prune-packed", cmd_prune_packed, RUN_SETUP },
|
||||
{ "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "push", cmd_push, RUN_SETUP },
|
||||
{ "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER },
|
||||
{ "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX},
|
||||
{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "receive-pack", cmd_receive_pack },
|
||||
|
201
linear-assignment.c
Normal file
201
linear-assignment.c
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Based on: Jonker, R., & Volgenant, A. (1987). <i>A shortest augmenting path
|
||||
* algorithm for dense and sparse linear assignment problems</i>. Computing,
|
||||
* 38(4), 325-340.
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "linear-assignment.h"
|
||||
|
||||
#define COST(column, row) cost[(column) + column_count * (row)]
|
||||
|
||||
/*
|
||||
* The parameter `cost` is the cost matrix: the cost to assign column j to row
|
||||
* i is `cost[j + column_count * i].
|
||||
*/
|
||||
void compute_assignment(int column_count, int row_count, int *cost,
|
||||
int *column2row, int *row2column)
|
||||
{
|
||||
int *v, *d;
|
||||
int *free_row, free_count = 0, saved_free_count, *pred, *col;
|
||||
int i, j, phase;
|
||||
|
||||
memset(column2row, -1, sizeof(int) * column_count);
|
||||
memset(row2column, -1, sizeof(int) * row_count);
|
||||
ALLOC_ARRAY(v, column_count);
|
||||
|
||||
/* column reduction */
|
||||
for (j = column_count - 1; j >= 0; j--) {
|
||||
int i1 = 0;
|
||||
|
||||
for (i = 1; i < row_count; i++)
|
||||
if (COST(j, i1) > COST(j, i))
|
||||
i1 = i;
|
||||
v[j] = COST(j, i1);
|
||||
if (row2column[i1] == -1) {
|
||||
/* row i1 unassigned */
|
||||
row2column[i1] = j;
|
||||
column2row[j] = i1;
|
||||
} else {
|
||||
if (row2column[i1] >= 0)
|
||||
row2column[i1] = -2 - row2column[i1];
|
||||
column2row[j] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* reduction transfer */
|
||||
ALLOC_ARRAY(free_row, row_count);
|
||||
for (i = 0; i < row_count; i++) {
|
||||
int j1 = row2column[i];
|
||||
if (j1 == -1)
|
||||
free_row[free_count++] = i;
|
||||
else if (j1 < -1)
|
||||
row2column[i] = -2 - j1;
|
||||
else {
|
||||
int min = COST(!j1, i) - v[!j1];
|
||||
for (j = 1; j < column_count; j++)
|
||||
if (j != j1 && min > COST(j, i) - v[j])
|
||||
min = COST(j, i) - v[j];
|
||||
v[j1] -= min;
|
||||
}
|
||||
}
|
||||
|
||||
if (free_count ==
|
||||
(column_count < row_count ? row_count - column_count : 0)) {
|
||||
free(v);
|
||||
free(free_row);
|
||||
return;
|
||||
}
|
||||
|
||||
/* augmenting row reduction */
|
||||
for (phase = 0; phase < 2; phase++) {
|
||||
int k = 0;
|
||||
|
||||
saved_free_count = free_count;
|
||||
free_count = 0;
|
||||
while (k < saved_free_count) {
|
||||
int u1, u2;
|
||||
int j1 = 0, j2, i0;
|
||||
|
||||
i = free_row[k++];
|
||||
u1 = COST(j1, i) - v[j1];
|
||||
j2 = -1;
|
||||
u2 = INT_MAX;
|
||||
for (j = 1; j < column_count; j++) {
|
||||
int c = COST(j, i) - v[j];
|
||||
if (u2 > c) {
|
||||
if (u1 < c) {
|
||||
u2 = c;
|
||||
j2 = j;
|
||||
} else {
|
||||
u2 = u1;
|
||||
u1 = c;
|
||||
j2 = j1;
|
||||
j1 = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (j2 < 0) {
|
||||
j2 = j1;
|
||||
u2 = u1;
|
||||
}
|
||||
|
||||
i0 = column2row[j1];
|
||||
if (u1 < u2)
|
||||
v[j1] -= u2 - u1;
|
||||
else if (i0 >= 0) {
|
||||
j1 = j2;
|
||||
i0 = column2row[j1];
|
||||
}
|
||||
|
||||
if (i0 >= 0) {
|
||||
if (u1 < u2)
|
||||
free_row[--k] = i0;
|
||||
else
|
||||
free_row[free_count++] = i0;
|
||||
}
|
||||
row2column[i] = j1;
|
||||
column2row[j1] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* augmentation */
|
||||
saved_free_count = free_count;
|
||||
ALLOC_ARRAY(d, column_count);
|
||||
ALLOC_ARRAY(pred, column_count);
|
||||
ALLOC_ARRAY(col, column_count);
|
||||
for (free_count = 0; free_count < saved_free_count; free_count++) {
|
||||
int i1 = free_row[free_count], low = 0, up = 0, last, k;
|
||||
int min, c, u1;
|
||||
|
||||
for (j = 0; j < column_count; j++) {
|
||||
d[j] = COST(j, i1) - v[j];
|
||||
pred[j] = i1;
|
||||
col[j] = j;
|
||||
}
|
||||
|
||||
j = -1;
|
||||
do {
|
||||
last = low;
|
||||
min = d[col[up++]];
|
||||
for (k = up; k < column_count; k++) {
|
||||
j = col[k];
|
||||
c = d[j];
|
||||
if (c <= min) {
|
||||
if (c < min) {
|
||||
up = low;
|
||||
min = c;
|
||||
}
|
||||
col[k] = col[up];
|
||||
col[up++] = j;
|
||||
}
|
||||
}
|
||||
for (k = low; k < up; k++)
|
||||
if (column2row[col[k]] == -1)
|
||||
goto update;
|
||||
|
||||
/* scan a row */
|
||||
do {
|
||||
int j1 = col[low++];
|
||||
|
||||
i = column2row[j1];
|
||||
u1 = COST(j1, i) - v[j1] - min;
|
||||
for (k = up; k < column_count; k++) {
|
||||
j = col[k];
|
||||
c = COST(j, i) - v[j] - u1;
|
||||
if (c < d[j]) {
|
||||
d[j] = c;
|
||||
pred[j] = i;
|
||||
if (c == min) {
|
||||
if (column2row[j] == -1)
|
||||
goto update;
|
||||
col[k] = col[up];
|
||||
col[up++] = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (low != up);
|
||||
} while (low == up);
|
||||
|
||||
update:
|
||||
/* updating of the column pieces */
|
||||
for (k = 0; k < last; k++) {
|
||||
int j1 = col[k];
|
||||
v[j1] += d[j1] - min;
|
||||
}
|
||||
|
||||
/* augmentation */
|
||||
do {
|
||||
if (j < 0)
|
||||
BUG("negative j: %d", j);
|
||||
i = pred[j];
|
||||
column2row[j] = i;
|
||||
SWAP(j, row2column[i]);
|
||||
} while (i1 != i);
|
||||
}
|
||||
|
||||
free(col);
|
||||
free(pred);
|
||||
free(d);
|
||||
free(v);
|
||||
free(free_row);
|
||||
}
|
22
linear-assignment.h
Normal file
22
linear-assignment.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef LINEAR_ASSIGNMENT_H
|
||||
#define LINEAR_ASSIGNMENT_H
|
||||
|
||||
/*
|
||||
* Compute an assignment of columns -> rows (and vice versa) such that every
|
||||
* column is assigned to at most one row (and vice versa) minimizing the
|
||||
* overall cost.
|
||||
*
|
||||
* The parameter `cost` is the cost matrix: the cost to assign column j to row
|
||||
* i is `cost[j + column_count * i].
|
||||
*
|
||||
* The arrays column2row and row2column will be populated with the respective
|
||||
* assignments (-1 for unassigned, which can happen only if column_count !=
|
||||
* row_count).
|
||||
*/
|
||||
void compute_assignment(int column_count, int row_count, int *cost,
|
||||
int *column2row, int *row2column);
|
||||
|
||||
/* The maximal cost in the cost matrix (to prevent integer overflows). */
|
||||
#define COST_MAX (1<<16)
|
||||
|
||||
#endif
|
435
range-diff.c
Normal file
435
range-diff.c
Normal file
@ -0,0 +1,435 @@
|
||||
#include "cache.h"
|
||||
#include "range-diff.h"
|
||||
#include "string-list.h"
|
||||
#include "run-command.h"
|
||||
#include "argv-array.h"
|
||||
#include "hashmap.h"
|
||||
#include "xdiff-interface.h"
|
||||
#include "linear-assignment.h"
|
||||
#include "diffcore.h"
|
||||
#include "commit.h"
|
||||
#include "pretty.h"
|
||||
#include "userdiff.h"
|
||||
|
||||
struct patch_util {
|
||||
/* For the search for an exact match */
|
||||
struct hashmap_entry e;
|
||||
const char *diff, *patch;
|
||||
|
||||
int i, shown;
|
||||
int diffsize;
|
||||
size_t diff_offset;
|
||||
/* the index of the matching item in the other branch, or -1 */
|
||||
int matching;
|
||||
struct object_id oid;
|
||||
};
|
||||
|
||||
/*
|
||||
* Reads the patches into a string list, with the `util` field being populated
|
||||
* as struct object_id (will need to be free()d).
|
||||
*/
|
||||
static int read_patches(const char *range, struct string_list *list)
|
||||
{
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
FILE *in;
|
||||
struct strbuf buf = STRBUF_INIT, line = STRBUF_INIT;
|
||||
struct patch_util *util = NULL;
|
||||
int in_header = 1;
|
||||
|
||||
argv_array_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
|
||||
"--reverse", "--date-order", "--decorate=no",
|
||||
"--no-abbrev-commit", range,
|
||||
NULL);
|
||||
cp.out = -1;
|
||||
cp.no_stdin = 1;
|
||||
cp.git_cmd = 1;
|
||||
|
||||
if (start_command(&cp))
|
||||
return error_errno(_("could not start `log`"));
|
||||
in = fdopen(cp.out, "r");
|
||||
if (!in) {
|
||||
error_errno(_("could not read `log` output"));
|
||||
finish_command(&cp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (strbuf_getline(&line, in) != EOF) {
|
||||
const char *p;
|
||||
|
||||
if (skip_prefix(line.buf, "commit ", &p)) {
|
||||
if (util) {
|
||||
string_list_append(list, buf.buf)->util = util;
|
||||
strbuf_reset(&buf);
|
||||
}
|
||||
util = xcalloc(sizeof(*util), 1);
|
||||
if (get_oid(p, &util->oid)) {
|
||||
error(_("could not parse commit '%s'"), p);
|
||||
free(util);
|
||||
string_list_clear(list, 1);
|
||||
strbuf_release(&buf);
|
||||
strbuf_release(&line);
|
||||
fclose(in);
|
||||
finish_command(&cp);
|
||||
return -1;
|
||||
}
|
||||
util->matching = -1;
|
||||
in_header = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (starts_with(line.buf, "diff --git")) {
|
||||
in_header = 0;
|
||||
strbuf_addch(&buf, '\n');
|
||||
if (!util->diff_offset)
|
||||
util->diff_offset = buf.len;
|
||||
strbuf_addbuf(&buf, &line);
|
||||
} else if (in_header) {
|
||||
if (starts_with(line.buf, "Author: ")) {
|
||||
strbuf_addbuf(&buf, &line);
|
||||
strbuf_addstr(&buf, "\n\n");
|
||||
} else if (starts_with(line.buf, " ")) {
|
||||
strbuf_rtrim(&line);
|
||||
strbuf_addbuf(&buf, &line);
|
||||
strbuf_addch(&buf, '\n');
|
||||
}
|
||||
continue;
|
||||
} else if (starts_with(line.buf, "@@ "))
|
||||
strbuf_addstr(&buf, "@@");
|
||||
else if (!line.buf[0] || starts_with(line.buf, "index "))
|
||||
/*
|
||||
* A completely blank (not ' \n', which is context)
|
||||
* line is not valid in a diff. We skip it
|
||||
* silently, because this neatly handles the blank
|
||||
* separator line between commits in git-log
|
||||
* output.
|
||||
*
|
||||
* We also want to ignore the diff's `index` lines
|
||||
* because they contain exact blob hashes in which
|
||||
* we are not interested.
|
||||
*/
|
||||
continue;
|
||||
else
|
||||
strbuf_addbuf(&buf, &line);
|
||||
|
||||
strbuf_addch(&buf, '\n');
|
||||
util->diffsize++;
|
||||
}
|
||||
fclose(in);
|
||||
strbuf_release(&line);
|
||||
|
||||
if (util)
|
||||
string_list_append(list, buf.buf)->util = util;
|
||||
strbuf_release(&buf);
|
||||
|
||||
if (finish_command(&cp))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int patch_util_cmp(const void *dummy, const struct patch_util *a,
|
||||
const struct patch_util *b, const char *keydata)
|
||||
{
|
||||
return strcmp(a->diff, keydata ? keydata : b->diff);
|
||||
}
|
||||
|
||||
static void find_exact_matches(struct string_list *a, struct string_list *b)
|
||||
{
|
||||
struct hashmap map;
|
||||
int i;
|
||||
|
||||
hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
|
||||
|
||||
/* First, add the patches of a to a hash map */
|
||||
for (i = 0; i < a->nr; i++) {
|
||||
struct patch_util *util = a->items[i].util;
|
||||
|
||||
util->i = i;
|
||||
util->patch = a->items[i].string;
|
||||
util->diff = util->patch + util->diff_offset;
|
||||
hashmap_entry_init(util, strhash(util->diff));
|
||||
hashmap_add(&map, util);
|
||||
}
|
||||
|
||||
/* Now try to find exact matches in b */
|
||||
for (i = 0; i < b->nr; i++) {
|
||||
struct patch_util *util = b->items[i].util, *other;
|
||||
|
||||
util->i = i;
|
||||
util->patch = b->items[i].string;
|
||||
util->diff = util->patch + util->diff_offset;
|
||||
hashmap_entry_init(util, strhash(util->diff));
|
||||
other = hashmap_remove(&map, util, NULL);
|
||||
if (other) {
|
||||
if (other->matching >= 0)
|
||||
BUG("already assigned!");
|
||||
|
||||
other->matching = i;
|
||||
util->matching = other->i;
|
||||
}
|
||||
}
|
||||
|
||||
hashmap_free(&map, 0);
|
||||
}
|
||||
|
||||
static void diffsize_consume(void *data, char *line, unsigned long len)
|
||||
{
|
||||
(*(int *)data)++;
|
||||
}
|
||||
|
||||
static int diffsize(const char *a, const char *b)
|
||||
{
|
||||
xpparam_t pp = { 0 };
|
||||
xdemitconf_t cfg = { 0 };
|
||||
mmfile_t mf1, mf2;
|
||||
int count = 0;
|
||||
|
||||
mf1.ptr = (char *)a;
|
||||
mf1.size = strlen(a);
|
||||
mf2.ptr = (char *)b;
|
||||
mf2.size = strlen(b);
|
||||
|
||||
cfg.ctxlen = 3;
|
||||
if (!xdi_diff_outf(&mf1, &mf2, diffsize_consume, &count, &pp, &cfg))
|
||||
return count;
|
||||
|
||||
error(_("failed to generate diff"));
|
||||
return COST_MAX;
|
||||
}
|
||||
|
||||
static void get_correspondences(struct string_list *a, struct string_list *b,
|
||||
int creation_factor)
|
||||
{
|
||||
int n = a->nr + b->nr;
|
||||
int *cost, c, *a2b, *b2a;
|
||||
int i, j;
|
||||
|
||||
ALLOC_ARRAY(cost, st_mult(n, n));
|
||||
ALLOC_ARRAY(a2b, n);
|
||||
ALLOC_ARRAY(b2a, n);
|
||||
|
||||
for (i = 0; i < a->nr; i++) {
|
||||
struct patch_util *a_util = a->items[i].util;
|
||||
|
||||
for (j = 0; j < b->nr; j++) {
|
||||
struct patch_util *b_util = b->items[j].util;
|
||||
|
||||
if (a_util->matching == j)
|
||||
c = 0;
|
||||
else if (a_util->matching < 0 && b_util->matching < 0)
|
||||
c = diffsize(a_util->diff, b_util->diff);
|
||||
else
|
||||
c = COST_MAX;
|
||||
cost[i + n * j] = c;
|
||||
}
|
||||
|
||||
c = a_util->matching < 0 ?
|
||||
a_util->diffsize * creation_factor / 100 : COST_MAX;
|
||||
for (j = b->nr; j < n; j++)
|
||||
cost[i + n * j] = c;
|
||||
}
|
||||
|
||||
for (j = 0; j < b->nr; j++) {
|
||||
struct patch_util *util = b->items[j].util;
|
||||
|
||||
c = util->matching < 0 ?
|
||||
util->diffsize * creation_factor / 100 : COST_MAX;
|
||||
for (i = a->nr; i < n; i++)
|
||||
cost[i + n * j] = c;
|
||||
}
|
||||
|
||||
for (i = a->nr; i < n; i++)
|
||||
for (j = b->nr; j < n; j++)
|
||||
cost[i + n * j] = 0;
|
||||
|
||||
compute_assignment(n, n, cost, a2b, b2a);
|
||||
|
||||
for (i = 0; i < a->nr; i++)
|
||||
if (a2b[i] >= 0 && a2b[i] < b->nr) {
|
||||
struct patch_util *a_util = a->items[i].util;
|
||||
struct patch_util *b_util = b->items[a2b[i]].util;
|
||||
|
||||
a_util->matching = a2b[i];
|
||||
b_util->matching = i;
|
||||
}
|
||||
|
||||
free(cost);
|
||||
free(a2b);
|
||||
free(b2a);
|
||||
}
|
||||
|
||||
static void output_pair_header(struct diff_options *diffopt,
|
||||
int patch_no_width,
|
||||
struct strbuf *buf,
|
||||
struct strbuf *dashes,
|
||||
struct patch_util *a_util,
|
||||
struct patch_util *b_util)
|
||||
{
|
||||
struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
|
||||
struct commit *commit;
|
||||
char status;
|
||||
const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
|
||||
const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
|
||||
const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
|
||||
const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
|
||||
const char *color;
|
||||
|
||||
if (!dashes->len)
|
||||
strbuf_addchars(dashes, '-',
|
||||
strlen(find_unique_abbrev(oid,
|
||||
DEFAULT_ABBREV)));
|
||||
|
||||
if (!b_util) {
|
||||
color = color_old;
|
||||
status = '<';
|
||||
} else if (!a_util) {
|
||||
color = color_new;
|
||||
status = '>';
|
||||
} else if (strcmp(a_util->patch, b_util->patch)) {
|
||||
color = color_commit;
|
||||
status = '!';
|
||||
} else {
|
||||
color = color_commit;
|
||||
status = '=';
|
||||
}
|
||||
|
||||
strbuf_reset(buf);
|
||||
strbuf_addstr(buf, status == '!' ? color_old : color);
|
||||
if (!a_util)
|
||||
strbuf_addf(buf, "%*s: %s ", patch_no_width, "-", dashes->buf);
|
||||
else
|
||||
strbuf_addf(buf, "%*d: %s ", patch_no_width, a_util->i + 1,
|
||||
find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
|
||||
|
||||
if (status == '!')
|
||||
strbuf_addf(buf, "%s%s", color_reset, color);
|
||||
strbuf_addch(buf, status);
|
||||
if (status == '!')
|
||||
strbuf_addf(buf, "%s%s", color_reset, color_new);
|
||||
|
||||
if (!b_util)
|
||||
strbuf_addf(buf, " %*s: %s", patch_no_width, "-", dashes->buf);
|
||||
else
|
||||
strbuf_addf(buf, " %*d: %s", patch_no_width, b_util->i + 1,
|
||||
find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
|
||||
|
||||
commit = lookup_commit_reference(the_repository, oid);
|
||||
if (commit) {
|
||||
if (status == '!')
|
||||
strbuf_addf(buf, "%s%s", color_reset, color);
|
||||
|
||||
strbuf_addch(buf, ' ');
|
||||
pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
|
||||
}
|
||||
strbuf_addf(buf, "%s\n", color_reset);
|
||||
|
||||
fwrite(buf->buf, buf->len, 1, stdout);
|
||||
}
|
||||
|
||||
static struct userdiff_driver no_func_name = {
|
||||
.funcname = { "$^", 0 }
|
||||
};
|
||||
|
||||
static struct diff_filespec *get_filespec(const char *name, const char *p)
|
||||
{
|
||||
struct diff_filespec *spec = alloc_filespec(name);
|
||||
|
||||
fill_filespec(spec, &null_oid, 0, 0644);
|
||||
spec->data = (char *)p;
|
||||
spec->size = strlen(p);
|
||||
spec->should_munmap = 0;
|
||||
spec->is_stdin = 1;
|
||||
spec->driver = &no_func_name;
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
static void patch_diff(const char *a, const char *b,
|
||||
struct diff_options *diffopt)
|
||||
{
|
||||
diff_queue(&diff_queued_diff,
|
||||
get_filespec("a", a), get_filespec("b", b));
|
||||
|
||||
diffcore_std(diffopt);
|
||||
diff_flush(diffopt);
|
||||
}
|
||||
|
||||
static void output(struct string_list *a, struct string_list *b,
|
||||
struct diff_options *diffopt)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
|
||||
int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));
|
||||
int i = 0, j = 0;
|
||||
|
||||
/*
|
||||
* We assume the user is really more interested in the second argument
|
||||
* ("newer" version). To that end, we print the output in the order of
|
||||
* the RHS (the `b` parameter). To put the LHS (the `a` parameter)
|
||||
* commits that are no longer in the RHS into a good place, we place
|
||||
* them once we have shown all of their predecessors in the LHS.
|
||||
*/
|
||||
|
||||
while (i < a->nr || j < b->nr) {
|
||||
struct patch_util *a_util, *b_util;
|
||||
a_util = i < a->nr ? a->items[i].util : NULL;
|
||||
b_util = j < b->nr ? b->items[j].util : NULL;
|
||||
|
||||
/* Skip all the already-shown commits from the LHS. */
|
||||
while (i < a->nr && a_util->shown)
|
||||
a_util = ++i < a->nr ? a->items[i].util : NULL;
|
||||
|
||||
/* Show unmatched LHS commit whose predecessors were shown. */
|
||||
if (i < a->nr && a_util->matching < 0) {
|
||||
output_pair_header(diffopt, patch_no_width,
|
||||
&buf, &dashes, a_util, NULL);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Show unmatched RHS commits. */
|
||||
while (j < b->nr && b_util->matching < 0) {
|
||||
output_pair_header(diffopt, patch_no_width,
|
||||
&buf, &dashes, NULL, b_util);
|
||||
b_util = ++j < b->nr ? b->items[j].util : NULL;
|
||||
}
|
||||
|
||||
/* Show matching LHS/RHS pair. */
|
||||
if (j < b->nr) {
|
||||
a_util = a->items[b_util->matching].util;
|
||||
output_pair_header(diffopt, patch_no_width,
|
||||
&buf, &dashes, a_util, b_util);
|
||||
if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
|
||||
patch_diff(a->items[b_util->matching].string,
|
||||
b->items[j].string, diffopt);
|
||||
a_util->shown = 1;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
strbuf_release(&buf);
|
||||
strbuf_release(&dashes);
|
||||
}
|
||||
|
||||
int show_range_diff(const char *range1, const char *range2,
|
||||
int creation_factor, struct diff_options *diffopt)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
struct string_list branch1 = STRING_LIST_INIT_DUP;
|
||||
struct string_list branch2 = STRING_LIST_INIT_DUP;
|
||||
|
||||
if (read_patches(range1, &branch1))
|
||||
res = error(_("could not parse log for '%s'"), range1);
|
||||
if (!res && read_patches(range2, &branch2))
|
||||
res = error(_("could not parse log for '%s'"), range2);
|
||||
|
||||
if (!res) {
|
||||
find_exact_matches(&branch1, &branch2);
|
||||
get_correspondences(&branch1, &branch2, creation_factor);
|
||||
output(&branch1, &branch2, diffopt);
|
||||
}
|
||||
|
||||
string_list_clear(&branch1, 1);
|
||||
string_list_clear(&branch2, 1);
|
||||
|
||||
return res;
|
||||
}
|
9
range-diff.h
Normal file
9
range-diff.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef RANGE_DIFF_H
|
||||
#define RANGE_DIFF_H
|
||||
|
||||
#include "diff.h"
|
||||
|
||||
int show_range_diff(const char *range1, const char *range2,
|
||||
int creation_factor, struct diff_options *diffopt);
|
||||
|
||||
#endif
|
1
t/.gitattributes
vendored
1
t/.gitattributes
vendored
@ -1,6 +1,7 @@
|
||||
t[0-9][0-9][0-9][0-9]/* -whitespace
|
||||
/diff-lib/* eol=lf
|
||||
/t0110/url-* binary
|
||||
/t3206/* eol=lf
|
||||
/t3900/*.txt eol=lf
|
||||
/t3901/*.txt eol=lf
|
||||
/t4034/*/* eol=lf
|
||||
|
145
t/t3206-range-diff.sh
Executable file
145
t/t3206-range-diff.sh
Executable file
@ -0,0 +1,145 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='range-diff tests'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
# Note that because of the range-diff's heuristics, test_commit does more
|
||||
# harm than good. We need some real history.
|
||||
|
||||
test_expect_success 'setup' '
|
||||
git fast-import < "$TEST_DIRECTORY"/t3206/history.export
|
||||
'
|
||||
|
||||
test_expect_success 'simple A..B A..C (unmodified)' '
|
||||
git range-diff --no-color master..topic master..unmodified \
|
||||
>actual &&
|
||||
cat >expected <<-EOF &&
|
||||
1: 4de457d = 1: 35b9b25 s/5/A/
|
||||
2: fccce22 = 2: de345ab s/4/A/
|
||||
3: 147e64e = 3: 9af6654 s/11/B/
|
||||
4: a63e992 = 4: 2901f77 s/12/B/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'simple B...C (unmodified)' '
|
||||
git range-diff --no-color topic...unmodified >actual &&
|
||||
# same "expected" as above
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'simple A B C (unmodified)' '
|
||||
git range-diff --no-color master topic unmodified >actual &&
|
||||
# same "expected" as above
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'trivial reordering' '
|
||||
git range-diff --no-color master topic reordered >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
1: 4de457d = 1: aca177a s/5/A/
|
||||
3: 147e64e = 2: 14ad629 s/11/B/
|
||||
4: a63e992 = 3: ee58208 s/12/B/
|
||||
2: fccce22 = 4: 307b27a s/4/A/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'removed a commit' '
|
||||
git range-diff --no-color master topic removed >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
1: 4de457d = 1: 7657159 s/5/A/
|
||||
2: fccce22 < -: ------- s/4/A/
|
||||
3: 147e64e = 2: 43d84d3 s/11/B/
|
||||
4: a63e992 = 3: a740396 s/12/B/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'added a commit' '
|
||||
git range-diff --no-color master topic added >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
1: 4de457d = 1: 2716022 s/5/A/
|
||||
2: fccce22 = 2: b62accd s/4/A/
|
||||
-: ------- > 3: df46cfa s/6/A/
|
||||
3: 147e64e = 4: 3e64548 s/11/B/
|
||||
4: a63e992 = 5: 12b4063 s/12/B/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'new base, A B C' '
|
||||
git range-diff --no-color master topic rebased >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
1: 4de457d = 1: cc9c443 s/5/A/
|
||||
2: fccce22 = 2: c5d9641 s/4/A/
|
||||
3: 147e64e = 3: 28cc2b6 s/11/B/
|
||||
4: a63e992 = 4: 5628ab7 s/12/B/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'new base, B...C' '
|
||||
# this syntax includes the commits from master!
|
||||
git range-diff --no-color topic...rebased >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
-: ------- > 1: a31b12e unrelated
|
||||
1: 4de457d = 2: cc9c443 s/5/A/
|
||||
2: fccce22 = 3: c5d9641 s/4/A/
|
||||
3: 147e64e = 4: 28cc2b6 s/11/B/
|
||||
4: a63e992 = 5: 5628ab7 s/12/B/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'changed commit' '
|
||||
git range-diff --no-color topic...changed >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
1: 4de457d = 1: a4b3333 s/5/A/
|
||||
2: fccce22 = 2: f51d370 s/4/A/
|
||||
3: 147e64e ! 3: 0559556 s/11/B/
|
||||
@@ -10,7 +10,7 @@
|
||||
9
|
||||
10
|
||||
-11
|
||||
-+B
|
||||
++BB
|
||||
12
|
||||
13
|
||||
14
|
||||
4: a63e992 ! 4: d966c5c s/12/B/
|
||||
@@ -8,7 +8,7 @@
|
||||
@@
|
||||
9
|
||||
10
|
||||
- B
|
||||
+ BB
|
||||
-12
|
||||
+B
|
||||
13
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_expect_success 'changed message' '
|
||||
git range-diff --no-color topic...changed-message >actual &&
|
||||
sed s/Z/\ /g >expected <<-EOF &&
|
||||
1: 4de457d = 1: f686024 s/5/A/
|
||||
2: fccce22 ! 2: 4ab067d s/4/A/
|
||||
@@ -2,6 +2,8 @@
|
||||
Z
|
||||
Z s/4/A/
|
||||
Z
|
||||
+ Also a silly comment here!
|
||||
+
|
||||
Zdiff --git a/file b/file
|
||||
Z--- a/file
|
||||
Z+++ b/file
|
||||
3: 147e64e = 3: b9cb956 s/11/B/
|
||||
4: a63e992 = 4: 8add5f1 s/12/B/
|
||||
EOF
|
||||
test_cmp expected actual
|
||||
'
|
||||
|
||||
test_done
|
604
t/t3206/history.export
Normal file
604
t/t3206/history.export
Normal file
@ -0,0 +1,604 @@
|
||||
blob
|
||||
mark :1
|
||||
data 51
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
reset refs/heads/removed
|
||||
commit refs/heads/removed
|
||||
mark :2
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200
|
||||
data 8
|
||||
initial
|
||||
M 100644 :1 file
|
||||
|
||||
blob
|
||||
mark :3
|
||||
data 51
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/topic
|
||||
mark :4
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
blob
|
||||
mark :5
|
||||
data 51
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/topic
|
||||
mark :6
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
data 7
|
||||
s/4/A/
|
||||
from :4
|
||||
M 100644 :5 file
|
||||
|
||||
blob
|
||||
mark :7
|
||||
data 50
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
B
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/topic
|
||||
mark :8
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :6
|
||||
M 100644 :7 file
|
||||
|
||||
blob
|
||||
mark :9
|
||||
data 49
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
B
|
||||
B
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/topic
|
||||
mark :10
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :8
|
||||
M 100644 :9 file
|
||||
|
||||
blob
|
||||
mark :11
|
||||
data 10
|
||||
unrelated
|
||||
|
||||
commit refs/heads/master
|
||||
mark :12
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200
|
||||
data 10
|
||||
unrelated
|
||||
from :2
|
||||
M 100644 :11 otherfile
|
||||
|
||||
commit refs/heads/rebased
|
||||
mark :13
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :12
|
||||
M 100644 :3 file
|
||||
|
||||
commit refs/heads/rebased
|
||||
mark :14
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
|
||||
data 7
|
||||
s/4/A/
|
||||
from :13
|
||||
M 100644 :5 file
|
||||
|
||||
commit refs/heads/rebased
|
||||
mark :15
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :14
|
||||
M 100644 :7 file
|
||||
|
||||
commit refs/heads/rebased
|
||||
mark :16
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :15
|
||||
M 100644 :9 file
|
||||
|
||||
commit refs/heads/added
|
||||
mark :17
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
commit refs/heads/added
|
||||
mark :18
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
|
||||
data 7
|
||||
s/4/A/
|
||||
from :17
|
||||
M 100644 :5 file
|
||||
|
||||
blob
|
||||
mark :19
|
||||
data 51
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
A
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/added
|
||||
mark :20
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
|
||||
data 7
|
||||
s/6/A/
|
||||
from :18
|
||||
M 100644 :19 file
|
||||
|
||||
blob
|
||||
mark :21
|
||||
data 50
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
A
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
B
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/added
|
||||
mark :22
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :20
|
||||
M 100644 :21 file
|
||||
|
||||
blob
|
||||
mark :23
|
||||
data 49
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
A
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
B
|
||||
B
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/added
|
||||
mark :24
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :22
|
||||
M 100644 :23 file
|
||||
|
||||
commit refs/heads/reordered
|
||||
mark :25
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
blob
|
||||
mark :26
|
||||
data 50
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
B
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/reordered
|
||||
mark :27
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :25
|
||||
M 100644 :26 file
|
||||
|
||||
blob
|
||||
mark :28
|
||||
data 49
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
B
|
||||
B
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/reordered
|
||||
mark :29
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :27
|
||||
M 100644 :28 file
|
||||
|
||||
commit refs/heads/reordered
|
||||
mark :30
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200
|
||||
data 7
|
||||
s/4/A/
|
||||
from :29
|
||||
M 100644 :9 file
|
||||
|
||||
commit refs/heads/changed
|
||||
mark :31
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
commit refs/heads/changed
|
||||
mark :32
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
|
||||
data 7
|
||||
s/4/A/
|
||||
from :31
|
||||
M 100644 :5 file
|
||||
|
||||
blob
|
||||
mark :33
|
||||
data 51
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
BB
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/changed
|
||||
mark :34
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :32
|
||||
M 100644 :33 file
|
||||
|
||||
blob
|
||||
mark :35
|
||||
data 50
|
||||
1
|
||||
2
|
||||
3
|
||||
A
|
||||
A
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
BB
|
||||
B
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
|
||||
commit refs/heads/changed
|
||||
mark :36
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :34
|
||||
M 100644 :35 file
|
||||
|
||||
commit refs/heads/changed-message
|
||||
mark :37
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
commit refs/heads/changed-message
|
||||
mark :38
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200
|
||||
data 35
|
||||
s/4/A/
|
||||
|
||||
Also a silly comment here!
|
||||
from :37
|
||||
M 100644 :5 file
|
||||
|
||||
commit refs/heads/changed-message
|
||||
mark :39
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :38
|
||||
M 100644 :7 file
|
||||
|
||||
commit refs/heads/changed-message
|
||||
mark :40
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :39
|
||||
M 100644 :9 file
|
||||
|
||||
commit refs/heads/unmodified
|
||||
mark :41
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
commit refs/heads/unmodified
|
||||
mark :42
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200
|
||||
data 7
|
||||
s/4/A/
|
||||
from :41
|
||||
M 100644 :5 file
|
||||
|
||||
commit refs/heads/unmodified
|
||||
mark :43
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :42
|
||||
M 100644 :7 file
|
||||
|
||||
commit refs/heads/unmodified
|
||||
mark :44
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :43
|
||||
M 100644 :9 file
|
||||
|
||||
commit refs/heads/removed
|
||||
mark :45
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
|
||||
data 7
|
||||
s/5/A/
|
||||
from :2
|
||||
M 100644 :3 file
|
||||
|
||||
commit refs/heads/removed
|
||||
mark :46
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
|
||||
data 8
|
||||
s/11/B/
|
||||
from :45
|
||||
M 100644 :26 file
|
||||
|
||||
commit refs/heads/removed
|
||||
mark :47
|
||||
author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200
|
||||
committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200
|
||||
data 8
|
||||
s/12/B/
|
||||
from :46
|
||||
M 100644 :28 file
|
||||
|
||||
reset refs/heads/removed
|
||||
from :47
|
||||
|
Loading…
Reference in New Issue
Block a user