Skip to content

Commit 0889118

Browse files
committed
Merge branch 'ds/rebase-update-refs' into seen
source: <[email protected]> * ds/rebase-update-refs: rebase: add rebase.updateRefs config option rebase: add --update-refs option branch: add branch_checked_out() helper log-tree: create for_each_decoration()
2 parents 13eafed + 05882cd commit 0889118

File tree

10 files changed

+262
-43
lines changed

10 files changed

+262
-43
lines changed

Documentation/config/rebase.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ rebase.autoStash::
2121
`--autostash` options of linkgit:git-rebase[1].
2222
Defaults to false.
2323

24+
rebase.updateRefs::
25+
If set to true enable `--update-refs` option by default.
26+
2427
rebase.missingCommitsCheck::
2528
If set to "warn", git rebase -i will print a warning if some
2629
commits are removed (e.g. a line was deleted), however the

Documentation/git-rebase.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,17 @@ provided. Otherwise an explicit `--no-reschedule-failed-exec` at the
609609
start would be overridden by the presence of
610610
`rebase.rescheduleFailedExec=true` configuration.
611611

612+
--update-refs::
613+
--no-update-refs::
614+
Automatically force-update any branches that point to commits that
615+
are being rebased. Any branches that are checked out in a worktree
616+
or point to a `squash! ...` or `fixup! ...` commit are not updated
617+
in this way.
618+
+
619+
If the `--update-refs` option is enabled by default using the
620+
configuration variable `rebase.updateRefs`, this option can be
621+
used to override and disable this setting.
622+
612623
INCOMPATIBLE OPTIONS
613624
--------------------
614625

branch.c

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,19 @@ int validate_branchname(const char *name, struct strbuf *ref)
369369
return ref_exists(ref->buf);
370370
}
371371

372+
int branch_checked_out(const char *refname, char **path)
373+
{
374+
struct worktree **worktrees = get_worktrees();
375+
const struct worktree *wt = find_shared_symref(worktrees, "HEAD", refname);
376+
int result = wt && !wt->is_bare;
377+
378+
if (result && path)
379+
*path = xstrdup(wt->path);
380+
381+
free_worktrees(worktrees);
382+
return result;
383+
}
384+
372385
/*
373386
* Check if a branch 'name' can be created as a new branch; die otherwise.
374387
* 'force' can be used when it is OK for the named branch already exists.
@@ -377,23 +390,18 @@ int validate_branchname(const char *name, struct strbuf *ref)
377390
*/
378391
int validate_new_branchname(const char *name, struct strbuf *ref, int force)
379392
{
380-
struct worktree **worktrees;
381-
const struct worktree *wt;
382-
393+
char *path;
383394
if (!validate_branchname(name, ref))
384395
return 0;
385396

386397
if (!force)
387398
die(_("a branch named '%s' already exists"),
388399
ref->buf + strlen("refs/heads/"));
389400

390-
worktrees = get_worktrees();
391-
wt = find_shared_symref(worktrees, "HEAD", ref->buf);
392-
if (wt && !wt->is_bare)
401+
if (branch_checked_out(ref->buf, &path))
393402
die(_("cannot force update the branch '%s' "
394403
"checked out at '%s'"),
395-
ref->buf + strlen("refs/heads/"), wt->path);
396-
free_worktrees(worktrees);
404+
ref->buf + strlen("refs/heads/"), path);
397405

398406
return 1;
399407
}

branch.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ void create_branches_recursively(struct repository *r, const char *name,
101101
const char *tracking_name, int force,
102102
int reflog, int quiet, enum branch_track track,
103103
int dry_run);
104+
105+
/*
106+
* Returns true if the branch at 'refname' is checked out at any
107+
* non-bare worktree. The path of the worktree is stored in the
108+
* given 'path', if provided.
109+
*/
110+
int branch_checked_out(const char *refname, char **path);
111+
104112
/*
105113
* Check if 'name' can be a valid name for a branch; die otherwise.
106114
* Return 1 if the named branch already exists; return 0 otherwise.

builtin/rebase.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ struct rebase_options {
102102
int reschedule_failed_exec;
103103
int reapply_cherry_picks;
104104
int fork_point;
105+
int update_refs;
105106
};
106107

107108
#define REBASE_OPTIONS_INIT { \
@@ -298,6 +299,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
298299
ret = complete_action(the_repository, &replay, flags,
299300
shortrevisions, opts->onto_name, opts->onto,
300301
&opts->orig_head, &commands, opts->autosquash,
302+
opts->update_refs,
301303
&todo_list);
302304
}
303305

@@ -800,6 +802,11 @@ static int rebase_config(const char *var, const char *value, void *data)
800802
return 0;
801803
}
802804

805+
if (!strcmp(var, "rebase.updaterefs")) {
806+
opts->update_refs = git_config_bool(var, value);
807+
return 0;
808+
}
809+
803810
if (!strcmp(var, "rebase.reschedulefailedexec")) {
804811
opts->reschedule_failed_exec = git_config_bool(var, value);
805812
return 0;
@@ -1124,6 +1131,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
11241131
OPT_BOOL(0, "autosquash", &options.autosquash,
11251132
N_("move commits that begin with "
11261133
"squash!/fixup! under -i")),
1134+
OPT_BOOL(0, "update-refs", &options.update_refs,
1135+
N_("update local refs that point to commits "
1136+
"that are being rebased")),
11271137
{ OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
11281138
N_("GPG-sign commits"),
11291139
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },

log-tree.c

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,54 @@ static void show_name(struct strbuf *sb, const struct name_decoration *decoratio
282282
strbuf_addstr(sb, decoration->name);
283283
}
284284

285+
struct format_decorations_context {
286+
struct strbuf *sb;
287+
int use_color;
288+
const char *prefix;
289+
const char *separator;
290+
const char *suffix;
291+
const char *color_commit;
292+
const char *color_reset;
293+
const struct name_decoration *current_and_HEAD;
294+
};
295+
296+
static int append_decoration(const struct name_decoration *d,
297+
void *data)
298+
{
299+
struct format_decorations_context *ctx = data;
300+
/*
301+
* When both current and HEAD are there, only
302+
* show HEAD->current where HEAD would have
303+
* appeared, skipping the entry for current.
304+
*/
305+
if (d != ctx->current_and_HEAD) {
306+
strbuf_addstr(ctx->sb, ctx->color_commit);
307+
strbuf_addstr(ctx->sb, ctx->prefix);
308+
strbuf_addstr(ctx->sb, ctx->color_reset);
309+
strbuf_addstr(ctx->sb, decorate_get_color(ctx->use_color, d->type));
310+
if (d->type == DECORATION_REF_TAG)
311+
strbuf_addstr(ctx->sb, "tag: ");
312+
313+
show_name(ctx->sb, d);
314+
315+
if (ctx->current_and_HEAD &&
316+
d->type == DECORATION_REF_HEAD) {
317+
strbuf_addstr(ctx->sb, " -> ");
318+
strbuf_addstr(ctx->sb, ctx->color_reset);
319+
strbuf_addstr(ctx->sb,
320+
decorate_get_color(
321+
ctx->use_color,
322+
ctx->current_and_HEAD->type));
323+
show_name(ctx->sb, ctx->current_and_HEAD);
324+
}
325+
strbuf_addstr(ctx->sb, ctx->color_reset);
326+
327+
ctx->prefix = ctx->separator;
328+
}
329+
330+
return 0;
331+
}
332+
285333
/*
286334
* The caller makes sure there is no funny color before calling.
287335
* format_decorations_extended makes sure the same after return.
@@ -294,49 +342,42 @@ void format_decorations_extended(struct strbuf *sb,
294342
const char *suffix)
295343
{
296344
const struct name_decoration *decoration;
297-
const struct name_decoration *current_and_HEAD;
298-
const char *color_commit =
299-
diff_get_color(use_color, DIFF_COMMIT);
300-
const char *color_reset =
301-
decorate_get_color(use_color, DECORATION_NONE);
345+
struct format_decorations_context ctx = {
346+
.sb = sb,
347+
.use_color = use_color,
348+
.prefix = prefix,
349+
.separator = separator,
350+
.suffix = suffix,
351+
.color_commit = diff_get_color(use_color, DIFF_COMMIT),
352+
.color_reset = decorate_get_color(use_color, DECORATION_NONE),
353+
};
302354

303355
decoration = get_name_decoration(&commit->object);
304356
if (!decoration)
305357
return;
306358

307-
current_and_HEAD = current_pointed_by_HEAD(decoration);
308-
while (decoration) {
309-
/*
310-
* When both current and HEAD are there, only
311-
* show HEAD->current where HEAD would have
312-
* appeared, skipping the entry for current.
313-
*/
314-
if (decoration != current_and_HEAD) {
315-
strbuf_addstr(sb, color_commit);
316-
strbuf_addstr(sb, prefix);
317-
strbuf_addstr(sb, color_reset);
318-
strbuf_addstr(sb, decorate_get_color(use_color, decoration->type));
319-
if (decoration->type == DECORATION_REF_TAG)
320-
strbuf_addstr(sb, "tag: ");
321-
322-
show_name(sb, decoration);
323-
324-
if (current_and_HEAD &&
325-
decoration->type == DECORATION_REF_HEAD) {
326-
strbuf_addstr(sb, " -> ");
327-
strbuf_addstr(sb, color_reset);
328-
strbuf_addstr(sb, decorate_get_color(use_color, current_and_HEAD->type));
329-
show_name(sb, current_and_HEAD);
330-
}
331-
strbuf_addstr(sb, color_reset);
359+
ctx.current_and_HEAD = current_pointed_by_HEAD(decoration);
332360

333-
prefix = separator;
334-
}
361+
for_each_decoration(commit, append_decoration, &ctx);
362+
363+
strbuf_addstr(sb, ctx.color_commit);
364+
strbuf_addstr(sb, ctx.suffix);
365+
strbuf_addstr(sb, ctx.color_reset);
366+
}
367+
368+
int for_each_decoration(const struct commit *c, decoration_fn fn, void *data)
369+
{
370+
const struct name_decoration *decoration;
371+
372+
decoration = get_name_decoration(&c->object);
373+
while (decoration) {
374+
int res;
375+
if ((res = fn(decoration, data)))
376+
return res;
335377
decoration = decoration->next;
336378
}
337-
strbuf_addstr(sb, color_commit);
338-
strbuf_addstr(sb, suffix);
339-
strbuf_addstr(sb, color_reset);
379+
380+
return 0;
340381
}
341382

342383
void show_decorations(struct rev_info *opt, struct commit *commit)

log-tree.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *);
3535
void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *);
3636
void fmt_output_email_subject(struct strbuf *, struct rev_info *);
3737

38+
typedef int decoration_fn(const struct name_decoration *d,
39+
void *data);
40+
int for_each_decoration(const struct commit *c, decoration_fn fn, void *data);
41+
3842
#endif

sequencer.c

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "commit-reach.h"
3636
#include "rebase-interactive.h"
3737
#include "reset.h"
38+
#include "branch.h"
39+
#include "log-tree.h"
3840

3941
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
4042

@@ -5638,10 +5640,104 @@ static int skip_unnecessary_picks(struct repository *r,
56385640
return 0;
56395641
}
56405642

5643+
struct todo_add_branch_context {
5644+
struct todo_list new_list;
5645+
struct strbuf *buf;
5646+
struct commit *commit;
5647+
};
5648+
5649+
static int add_branch_for_decoration(const struct name_decoration *d, void *data)
5650+
{
5651+
struct todo_add_branch_context *ctx = data;
5652+
size_t base_offset = ctx->buf->len;
5653+
int i = ctx->new_list.nr;
5654+
struct todo_item *item;
5655+
char *path;
5656+
5657+
ALLOC_GROW(ctx->new_list.items,
5658+
ctx->new_list.nr + 1,
5659+
ctx->new_list.alloc);
5660+
item = &ctx->new_list.items[i];
5661+
5662+
/* If the branch is checked out, then leave a comment instead. */
5663+
if (branch_checked_out(d->name, &path)) {
5664+
item->command = TODO_COMMENT;
5665+
strbuf_addf(ctx->buf, "# Ref %s checked out at '%s'\n",
5666+
d->name, path);
5667+
free(path);
5668+
} else {
5669+
item->command = TODO_EXEC;
5670+
strbuf_addf(ctx->buf, "git update-ref %s HEAD %s\n",
5671+
d->name, oid_to_hex(&ctx->commit->object.oid));
5672+
}
5673+
5674+
item->commit = NULL;
5675+
item->offset_in_buf = base_offset;
5676+
item->arg_offset = base_offset;
5677+
item->arg_len = ctx->buf->len - base_offset;
5678+
ctx->new_list.nr++;
5679+
5680+
return 0;
5681+
}
5682+
5683+
/*
5684+
* For each 'pick' command, find out if the commit has a decoration in
5685+
* refs/heads/. If so, then add a 'git branch -f' exec command after
5686+
* that 'pick' (plus any following 'squash' or 'fixup' commands).
5687+
*/
5688+
static int todo_list_add_branch_commands(struct todo_list *todo_list)
5689+
{
5690+
int i;
5691+
static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP;
5692+
static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP;
5693+
static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP;
5694+
struct decoration_filter decoration_filter = {
5695+
.include_ref_pattern = &decorate_refs_include,
5696+
.exclude_ref_pattern = &decorate_refs_exclude,
5697+
.exclude_ref_config_pattern = &decorate_refs_exclude_config
5698+
};
5699+
struct todo_add_branch_context ctx = {
5700+
.new_list = TODO_LIST_INIT,
5701+
.buf = &todo_list->buf,
5702+
};
5703+
5704+
string_list_append(&decorate_refs_include, "refs/heads/");
5705+
load_ref_decorations(&decoration_filter, 0);
5706+
5707+
for (i = 0; i < todo_list->nr; ) {
5708+
struct todo_item *item = &todo_list->items[i];
5709+
5710+
do {
5711+
/* insert ith item into new list */
5712+
ALLOC_GROW(ctx.new_list.items,
5713+
ctx.new_list.nr + 1,
5714+
ctx.new_list.alloc);
5715+
5716+
memcpy(&ctx.new_list.items[ctx.new_list.nr++],
5717+
&todo_list->items[i],
5718+
sizeof(struct todo_item));
5719+
5720+
i++;
5721+
} while (i < todo_list->nr &&
5722+
todo_list->items[i].command != TODO_PICK);
5723+
5724+
ctx.commit = item->commit;
5725+
for_each_decoration(item->commit, add_branch_for_decoration, &ctx);
5726+
}
5727+
5728+
free(todo_list->items);
5729+
todo_list->items = ctx.new_list.items;
5730+
todo_list->nr = ctx.new_list.nr;
5731+
todo_list->alloc = ctx.new_list.alloc;
5732+
5733+
return 0;
5734+
}
5735+
56415736
int complete_action(struct repository *r, struct replay_opts *opts, unsigned flags,
56425737
const char *shortrevisions, const char *onto_name,
56435738
struct commit *onto, const struct object_id *orig_head,
56445739
struct string_list *commands, unsigned autosquash,
5740+
unsigned keep_decorations,
56455741
struct todo_list *todo_list)
56465742
{
56475743
char shortonto[GIT_MAX_HEXSZ + 1];
@@ -5663,6 +5759,9 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla
56635759
if (autosquash && todo_list_rearrange_squash(todo_list))
56645760
return -1;
56655761

5762+
if (keep_decorations && todo_list_add_branch_commands(todo_list))
5763+
return -1;
5764+
56665765
if (commands->nr)
56675766
todo_list_add_exec_commands(todo_list, commands);
56685767

0 commit comments

Comments
 (0)