Skip to content

Commit 0a1bc12

Browse files
pcloudsgitster
authored andcommitted
receive-pack: allow pushes that update .git/shallow
The basic 8 steps to update .git/shallow does not fully apply here because the user may choose to accept just a few refs (while fetch always accepts all refs). The steps are modified a bit. 1-6. same as before. After calling assign_shallow_commits_to_refs at step 6, each shallow commit has a bitmap that marks all refs that require it. 7. mark all "ours" shallow commits that are reachable from any refs. We will need to do the original step 7 on them later. 8. go over all shallow commit bitmaps, mark refs that require new shallow commits. 9. setup a strict temporary shallow file to plug all the holes, even if it may cut some of our history short. This file is used by all hooks. The hooks could use --shallow-file=$GIT_DIR/shallow to overcome this and reach everything in current repo. 10. go over the new refs one by one. For each ref, do the reachability test if it needs a shallow commit on the list from step 7. Remove it if it's reachable from our refs. Gather all required shallow commits, run check_everything_connected() with the new ref, then install them to .git/shallow. This mode is disabled by default and can be turned on with receive.shallowupdate Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 614db3e commit 0a1bc12

File tree

5 files changed

+201
-13
lines changed

5 files changed

+201
-13
lines changed

Documentation/config.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,6 +2026,10 @@ receive.updateserverinfo::
20262026
If set to true, git-receive-pack will run git-update-server-info
20272027
after receiving data from git-push and updating refs.
20282028

2029+
receive.shallowupdate::
2030+
If set to true, .git/shallow can be updated when new refs
2031+
require new shallow roots. Otherwise those refs are rejected.
2032+
20292033
remote.pushdefault::
20302034
The remote to push to by default. Overrides
20312035
`branch.<name>.remote` for all branches, and is overridden by

builtin/receive-pack.c

Lines changed: 150 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ static int fix_thin = 1;
4444
static const char *head_name;
4545
static void *head_name_to_free;
4646
static int sent_capabilities;
47+
static int shallow_update;
4748
static const char *alt_shallow_file;
4849

4950
static enum deny_action parse_deny_action(const char *var, const char *value)
@@ -123,6 +124,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
123124
return 0;
124125
}
125126

127+
if (strcmp(var, "receive.shallowupdate") == 0) {
128+
shallow_update = git_config_bool(var, value);
129+
return 0;
130+
}
131+
126132
return git_default_config(var, value, cb);
127133
}
128134

@@ -423,7 +429,46 @@ static void refuse_unconfigured_deny_delete_current(void)
423429
rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
424430
}
425431

426-
static const char *update(struct command *cmd)
432+
static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]);
433+
static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
434+
{
435+
static struct lock_file shallow_lock;
436+
struct sha1_array extra = SHA1_ARRAY_INIT;
437+
const char *alt_file;
438+
uint32_t mask = 1 << (cmd->index % 32);
439+
int i;
440+
441+
trace_printf_key("GIT_TRACE_SHALLOW",
442+
"shallow: update_shallow_ref %s\n", cmd->ref_name);
443+
for (i = 0; i < si->shallow->nr; i++)
444+
if (si->used_shallow[i] &&
445+
(si->used_shallow[i][cmd->index / 32] & mask) &&
446+
!delayed_reachability_test(si, i))
447+
sha1_array_append(&extra, si->shallow->sha1[i]);
448+
449+
setup_alternate_shallow(&shallow_lock, &alt_file, &extra);
450+
if (check_shallow_connected(command_singleton_iterator,
451+
0, cmd, alt_file)) {
452+
rollback_lock_file(&shallow_lock);
453+
sha1_array_clear(&extra);
454+
return -1;
455+
}
456+
457+
commit_lock_file(&shallow_lock);
458+
459+
/*
460+
* Make sure setup_alternate_shallow() for the next ref does
461+
* not lose these new roots..
462+
*/
463+
for (i = 0; i < extra.nr; i++)
464+
register_shallow(extra.sha1[i]);
465+
466+
si->shallow_ref[cmd->index] = 0;
467+
sha1_array_clear(&extra);
468+
return 0;
469+
}
470+
471+
static const char *update(struct command *cmd, struct shallow_info *si)
427472
{
428473
const char *name = cmd->ref_name;
429474
struct strbuf namespaced_name_buf = STRBUF_INIT;
@@ -531,6 +576,10 @@ static const char *update(struct command *cmd)
531576
return NULL; /* good */
532577
}
533578
else {
579+
if (shallow_update && si->shallow_ref[cmd->index] &&
580+
update_shallow_ref(cmd, si))
581+
return "shallow error";
582+
534583
lock = lock_any_ref_for_update(namespaced_name, old_sha1,
535584
0, NULL);
536585
if (!lock) {
@@ -671,31 +720,43 @@ static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
671720
return 0;
672721
}
673722

674-
static void set_connectivity_errors(struct command *commands)
723+
static void set_connectivity_errors(struct command *commands,
724+
struct shallow_info *si)
675725
{
676726
struct command *cmd;
677727

678728
for (cmd = commands; cmd; cmd = cmd->next) {
679729
struct command *singleton = cmd;
730+
if (shallow_update && si->shallow_ref[cmd->index])
731+
/* to be checked in update_shallow_ref() */
732+
continue;
680733
if (!check_everything_connected(command_singleton_iterator,
681734
0, &singleton))
682735
continue;
683736
cmd->error_string = "missing necessary objects";
684737
}
685738
}
686739

740+
struct iterate_data {
741+
struct command *cmds;
742+
struct shallow_info *si;
743+
};
744+
687745
static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
688746
{
689-
struct command **cmd_list = cb_data;
747+
struct iterate_data *data = cb_data;
748+
struct command **cmd_list = &data->cmds;
690749
struct command *cmd = *cmd_list;
691750

692-
while (cmd) {
751+
for (; cmd; cmd = cmd->next) {
752+
if (shallow_update && data->si->shallow_ref[cmd->index])
753+
/* to be checked in update_shallow_ref() */
754+
continue;
693755
if (!is_null_sha1(cmd->new_sha1) && !cmd->skip_update) {
694756
hashcpy(sha1, cmd->new_sha1);
695757
*cmd_list = cmd->next;
696758
return 0;
697759
}
698-
cmd = cmd->next;
699760
}
700761
*cmd_list = NULL;
701762
return -1; /* end of list */
@@ -715,21 +776,25 @@ static void reject_updates_to_hidden(struct command *commands)
715776
}
716777
}
717778

718-
static void execute_commands(struct command *commands, const char *unpacker_error)
779+
static void execute_commands(struct command *commands,
780+
const char *unpacker_error,
781+
struct shallow_info *si)
719782
{
783+
int checked_connectivity;
720784
struct command *cmd;
721785
unsigned char sha1[20];
786+
struct iterate_data data;
722787

723788
if (unpacker_error) {
724789
for (cmd = commands; cmd; cmd = cmd->next)
725790
cmd->error_string = "unpacker error";
726791
return;
727792
}
728793

729-
cmd = commands;
730-
if (check_everything_connected(iterate_receive_command_list,
731-
0, &cmd))
732-
set_connectivity_errors(commands);
794+
data.cmds = commands;
795+
data.si = si;
796+
if (check_everything_connected(iterate_receive_command_list, 0, &data))
797+
set_connectivity_errors(commands, si);
733798

734799
reject_updates_to_hidden(commands);
735800

@@ -746,14 +811,30 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
746811
free(head_name_to_free);
747812
head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
748813

814+
checked_connectivity = 1;
749815
for (cmd = commands; cmd; cmd = cmd->next) {
750816
if (cmd->error_string)
751817
continue;
752818

753819
if (cmd->skip_update)
754820
continue;
755821

756-
cmd->error_string = update(cmd);
822+
cmd->error_string = update(cmd, si);
823+
if (shallow_update && !cmd->error_string &&
824+
si->shallow_ref[cmd->index]) {
825+
error("BUG: connectivity check has not been run on ref %s",
826+
cmd->ref_name);
827+
checked_connectivity = 0;
828+
}
829+
}
830+
831+
if (shallow_update) {
832+
if (!checked_connectivity)
833+
error("BUG: run 'git fsck' for safety.\n"
834+
"If there are errors, try to remove "
835+
"the reported refs above");
836+
if (alt_shallow_file && *alt_shallow_file)
837+
unlink(alt_shallow_file);
757838
}
758839
}
759840

@@ -924,6 +1005,53 @@ static const char *unpack_with_sideband(struct shallow_info *si)
9241005
return ret;
9251006
}
9261007

1008+
static void prepare_shallow_update(struct command *commands,
1009+
struct shallow_info *si)
1010+
{
1011+
int i, j, k, bitmap_size = (si->ref->nr + 31) / 32;
1012+
1013+
si->used_shallow = xmalloc(sizeof(*si->used_shallow) *
1014+
si->shallow->nr);
1015+
assign_shallow_commits_to_refs(si, si->used_shallow, NULL);
1016+
1017+
si->need_reachability_test =
1018+
xcalloc(si->shallow->nr, sizeof(*si->need_reachability_test));
1019+
si->reachable =
1020+
xcalloc(si->shallow->nr, sizeof(*si->reachable));
1021+
si->shallow_ref = xcalloc(si->ref->nr, sizeof(*si->shallow_ref));
1022+
1023+
for (i = 0; i < si->nr_ours; i++)
1024+
si->need_reachability_test[si->ours[i]] = 1;
1025+
1026+
for (i = 0; i < si->shallow->nr; i++) {
1027+
if (!si->used_shallow[i])
1028+
continue;
1029+
for (j = 0; j < bitmap_size; j++) {
1030+
if (!si->used_shallow[i][j])
1031+
continue;
1032+
si->need_reachability_test[i]++;
1033+
for (k = 0; k < 32; k++)
1034+
if (si->used_shallow[i][j] & (1 << k))
1035+
si->shallow_ref[j * 32 + k]++;
1036+
}
1037+
1038+
/*
1039+
* true for those associated with some refs and belong
1040+
* in "ours" list aka "step 7 not done yet"
1041+
*/
1042+
si->need_reachability_test[i] =
1043+
si->need_reachability_test[i] > 1;
1044+
}
1045+
1046+
/*
1047+
* keep hooks happy by forcing a temporary shallow file via
1048+
* env variable because we can't add --shallow-file to every
1049+
* command. check_everything_connected() will be done with
1050+
* true .git/shallow though.
1051+
*/
1052+
setenv(GIT_SHALLOW_FILE_ENVIRONMENT, alt_shallow_file, 1);
1053+
}
1054+
9271055
static void update_shallow_info(struct command *commands,
9281056
struct shallow_info *si,
9291057
struct sha1_array *ref)
@@ -932,8 +1060,10 @@ static void update_shallow_info(struct command *commands,
9321060
int *ref_status;
9331061
remove_nonexistent_theirs_shallow(si);
9341062
/* XXX remove_nonexistent_ours_in_pack() */
935-
if (!si->nr_ours && !si->nr_theirs)
1063+
if (!si->nr_ours && !si->nr_theirs) {
1064+
shallow_update = 0;
9361065
return;
1066+
}
9371067

9381068
for (cmd = commands; cmd; cmd = cmd->next) {
9391069
if (is_null_sha1(cmd->new_sha1))
@@ -943,6 +1073,11 @@ static void update_shallow_info(struct command *commands,
9431073
}
9441074
si->ref = ref;
9451075

1076+
if (shallow_update) {
1077+
prepare_shallow_update(commands, si);
1078+
return;
1079+
}
1080+
9461081
ref_status = xmalloc(sizeof(*ref_status) * ref->nr);
9471082
assign_shallow_commits_to_refs(si, NULL, ref_status);
9481083
for (cmd = commands; cmd; cmd = cmd->next) {
@@ -1064,11 +1199,13 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
10641199
const char *unpack_status = NULL;
10651200

10661201
prepare_shallow_info(&si, &shallow);
1202+
if (!si.nr_ours && !si.nr_theirs)
1203+
shallow_update = 0;
10671204
if (!delete_only(commands)) {
10681205
unpack_status = unpack_with_sideband(&si);
10691206
update_shallow_info(commands, &si, &ref);
10701207
}
1071-
execute_commands(commands, unpack_status);
1208+
execute_commands(commands, unpack_status, &si);
10721209
if (pack_lockfile)
10731210
unlink_or_warn(pack_lockfile);
10741211
if (report_status)

commit.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ struct shallow_info {
216216
int *ours, nr_ours;
217217
int *theirs, nr_theirs;
218218
struct sha1_array *ref;
219+
220+
/* for receive-pack */
221+
uint32_t **used_shallow;
222+
int *need_reachability_test;
223+
int *reachable;
224+
int *shallow_ref;
225+
struct commit **commits;
226+
int nr_commits;
219227
};
220228

221229
extern void prepare_shallow_info(struct shallow_info *, struct sha1_array *);
@@ -226,6 +234,7 @@ extern void remove_nonexistent_ours_in_pack(struct shallow_info *,
226234
extern void assign_shallow_commits_to_refs(struct shallow_info *info,
227235
uint32_t **used,
228236
int *ref_status);
237+
extern int delayed_reachability_test(struct shallow_info *si, int c);
229238

230239
int is_descendant_of(struct commit *, struct commit_list *);
231240
int in_merge_bases(struct commit *, struct commit *);

shallow.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,3 +617,26 @@ static void post_assign_shallow(struct shallow_info *info,
617617

618618
free(ca.commits);
619619
}
620+
621+
/* (Delayed) step 7, reachability test at commit level */
622+
int delayed_reachability_test(struct shallow_info *si, int c)
623+
{
624+
if (si->need_reachability_test[c]) {
625+
struct commit *commit = lookup_commit(si->shallow->sha1[c]);
626+
627+
if (!si->commits) {
628+
struct commit_array ca;
629+
memset(&ca, 0, sizeof(ca));
630+
head_ref(add_ref, &ca);
631+
for_each_ref(add_ref, &ca);
632+
si->commits = ca.commits;
633+
si->nr_commits = ca.nr;
634+
}
635+
636+
si->reachable[c] = in_merge_bases_many(commit,
637+
si->nr_commits,
638+
si->commits);
639+
si->need_reachability_test[c] = 0;
640+
}
641+
return si->reachable[c];
642+
}

t/t5538-push-shallow.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,19 @@ test_expect_success 'push from shallow clone, with grafted roots' '
6767
git fsck
6868
'
6969

70+
test_expect_success 'add new shallow root with receive.updateshallow on' '
71+
test_config receive.shallowupdate true &&
72+
(
73+
cd shallow2 &&
74+
git push ../.git +master:refs/remotes/shallow2/master
75+
) &&
76+
git log --format=%s shallow2/master >actual &&
77+
git fsck &&
78+
cat <<EOF >expect &&
79+
c
80+
b
81+
EOF
82+
test_cmp expect actual
83+
'
84+
7085
test_done

0 commit comments

Comments
 (0)