3
4
#include "git-compat-util.h"
5
#include "commit-reach.h"
6
+#include "commit-slab.h"
7
#include "config.h"
8
#include "diff.h"
9
#include "diffcore.h"
1090
return !opt->loginfo;
1091
}
1092
1093
+/* Per-commit path storage for --follow across merges */
1094
+define_commit_slab(follow_pathspec_slab, char *);
1095
+
1096
+static const char *pathspec_single_path(const struct pathspec *ps)
1097
+{
1098
+ if (ps->nr != 1)
1099
+ return NULL;
1100
+ return ps->items[0].match;
1101
+}
1102
+
1103
+static void set_pathspec_to_single_path(struct pathspec *ps, const char *path)
1104
+{
1105
+ const char *paths[2] = { path, NULL };
1106
+
1107
+ clear_pathspec(ps);
1108
+ parse_pathspec(ps,
1109
+ PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
1110
+ PATHSPEC_LITERAL_PATH, "", paths);
1111
+}
1112
+
1113
+static void remember_follow_pathspec(struct rev_info *opt,
1114
+ struct commit *c, const char *path)
1115
+{
1116
+ char **slot;
1117
+
1118
+ if (!path)
1119
+ return;
1120
+ if (!opt->follow_pathspec_slab) {
1121
+ opt->follow_pathspec_slab = xmalloc(sizeof(*opt->follow_pathspec_slab));
1122
+ init_follow_pathspec_slab(opt->follow_pathspec_slab);
1123
+ }
1124
+ slot = follow_pathspec_slab_at(opt->follow_pathspec_slab, c);
1125
+ if (*slot && !strcmp(*slot, path))
1126
+ return;
1127
+ free(*slot);
1128
+ *slot = xstrdup(path);
1129
+}
1130
+
1131
+static const char *recall_follow_pathspec(struct rev_info *opt,
1132
+ struct commit *c)
1133
+{
1134
+ char **slot;
1135
+
1136
+ if (!opt->follow_pathspec_slab)
1137
+ return NULL;
1138
+ slot = follow_pathspec_slab_peek(opt->follow_pathspec_slab, c);
1139
+ return slot ? *slot : NULL;
1140
+}
1141
+
1142
+static void free_follow_pathspec_slot(char **slot)
1143
+{
1144
+ FREE_AND_NULL(*slot);
1145
+}
1146
+
1147
+void release_follow_pathspec_slab(struct rev_info *opt)
1148
+{
1149
+ if (!opt->follow_pathspec_slab)
1150
+ return;
1151
+ deep_clear_follow_pathspec_slab(opt->follow_pathspec_slab,
1152
+ free_follow_pathspec_slot);
1153
+ FREE_AND_NULL(opt->follow_pathspec_slab);
1154
+}
1155
+
1156
+/* Compute a path to follow in parent, if there is one */
1157
+static void propagate_follow_pathspec_to_parent(struct rev_info *opt,
1158
+ struct commit *commit,
1159
+ struct commit *parent)
1160
+{
1161
+ struct diff_options diff_opts;
1162
+ const char *path;
1163
+
1164
+ parse_commit_or_die(parent);
1165
+ repo_diff_setup(opt->diffopt.repo, &diff_opts);
1166
+ copy_pathspec(&diff_opts.pathspec, &opt->diffopt.pathspec);
1167
+ diff_opts.flags.recursive = 1;
1168
+ diff_opts.flags.follow_renames = 1;
1169
+ diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
1170
+ diff_setup_done(&diff_opts);
1171
+ diff_tree_oid(get_commit_tree_oid(parent),
1172
+ get_commit_tree_oid(commit),
1173
+ "", &diff_opts);
1174
+
1175
+ path = pathspec_single_path(&diff_opts.pathspec);
1176
+ if (path)
1177
+ remember_follow_pathspec(opt, parent, path);
1178
+
1179
+ diff_queue_clear(&diff_queued_diff);
1180
+ diff_free(&diff_opts);
1181
+}
1182
+
1183
/*
1184
* Show the diff of a commit.
1185
*
1276
opt->loginfo = &log;
1277
opt->diffopt.no_free = 1;
1278
1279
+ /* Any recorded path for this commit? If so, restore it */
1280
+ if (opt->diffopt.flags.follow_renames) {
1281
+ const char *stored = recall_follow_pathspec(opt, commit);
1282
+ if (stored) {
1283
+ const char *current = pathspec_single_path(&opt->diffopt.pathspec);
1284
+ if (!current || strcmp(current, stored))
1285
+ set_pathspec_to_single_path(&opt->diffopt.pathspec, stored);
1286
+ }
1287
+ }
1288
+
1289
if (opt->track_linear && !opt->linear && !opt->reverse_output_stage)
1290
fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar);
1291
shown = log_tree_diff(opt, commit, &log);
1298
fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar);
1299
if (shown)
1300
show_diff_of_diff(opt);
1301
+
1302
+ /* Record what path each parent of this commit should use */
1303
+ if (opt->diffopt.flags.follow_renames) {
1304
+ struct commit_list *parents = get_saved_parents(opt, commit);
1305
+ if (parents && parents->next) {
1306
+ struct commit_list *p;
1307
+ for (p = parents; p; p = p->next)
1308
+ propagate_follow_pathspec_to_parent(opt, commit,
1309
+ p->item);
1310
+ } else if (parents) {
1311
+ remember_follow_pathspec(opt, parents->item,
1312
+ pathspec_single_path(&opt->diffopt.pathspec));
1313
+ }
1314
+ }
1315
+
1316
opt->loginfo = NULL;
1317
maybe_flush_or_die(opt->diffopt.file, "stdout");
1318
opt->diffopt.no_free = no_free;