summaryrefslogtreecommitdiff
path: root/t/t5543-atomic-push.sh
blob: bfee461861e456b091ea4c68025a0127d09dbee6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#!/bin/sh

test_description='pushing to a repository using the atomic push option'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

mk_repo_pair () {
	rm -rf workbench upstream &&
	test_create_repo upstream &&
	test_create_repo workbench &&
	(
		cd upstream &&
		git config receive.denyCurrentBranch warn
	) &&
	(
		cd workbench &&
		git remote add up ../upstream
	)
}

# Compare the ref ($1) in upstream with a ref value from workbench ($2)
# i.e. test_refs second HEAD@{2}
test_refs () {
	test $# = 2 &&
	git -C upstream rev-parse --verify "$1" >expect &&
	git -C workbench rev-parse --verify "$2" >actual &&
	test_cmp expect actual
}

fmt_status_report () {
	sed -n \
		-e "/^To / { s/   */ /g; p; }" \
		-e "/^ ! / { s/   */ /g; p; }"
}

test_expect_success 'atomic push works for a single branch' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git push --mirror up &&
		test_commit two &&
		git push --atomic up main
	) &&
	test_refs main main
'

test_expect_success 'atomic push works for two branches' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git branch second &&
		git push --mirror up &&
		test_commit two &&
		git checkout second &&
		test_commit three &&
		git push --atomic up main second
	) &&
	test_refs main main &&
	test_refs second second
'

test_expect_success 'atomic push works in combination with --mirror' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git checkout -b second &&
		test_commit two &&
		git push --atomic --mirror up
	) &&
	test_refs main main &&
	test_refs second second
'

test_expect_success 'atomic push works in combination with --force' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git branch second main &&
		test_commit two_a &&
		git checkout second &&
		test_commit two_b &&
		test_commit three_b &&
		test_commit four &&
		git push --mirror up &&
		# The actual test is below
		git checkout main &&
		test_commit three_a &&
		git checkout second &&
		git reset --hard HEAD^ &&
		git push --force --atomic up main second
	) &&
	test_refs main main &&
	test_refs second second
'

# set up two branches where main can be pushed but second can not
# (non-fast-forward). Since second can not be pushed the whole operation
# will fail and leave main untouched.
test_expect_success 'atomic push fails if one branch fails' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git checkout -b second main &&
		test_commit two &&
		test_commit three &&
		test_commit four &&
		git push --mirror up &&
		git reset --hard HEAD~2 &&
		test_commit five &&
		git checkout main &&
		test_commit six &&
		test_must_fail git push --atomic --all up
	) &&
	test_refs main HEAD@{7} &&
	test_refs second HEAD@{4}
'

test_expect_success 'atomic push fails if one tag fails remotely' '
	# prepare the repo
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git checkout -b second main &&
		test_commit two &&
		git push --mirror up
	) &&
	# a third party modifies the server side:
	(
		cd upstream &&
		git checkout second &&
		git tag test_tag second
	) &&
	# see if we can now push both branches.
	(
		cd workbench &&
		git checkout main &&
		test_commit three &&
		git checkout second &&
		test_commit four &&
		git tag test_tag &&
		test_must_fail git push --tags --atomic up main second
	) &&
	test_refs main HEAD@{3} &&
	test_refs second HEAD@{1}
'

test_expect_success 'atomic push obeys update hook preventing a branch to be pushed' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git checkout -b second main &&
		test_commit two &&
		git push --mirror up
	) &&
	(
		cd upstream &&
		HOOKDIR="$(git rev-parse --git-dir)/hooks" &&
		HOOK="$HOOKDIR/update" &&
		mkdir -p "$HOOKDIR" &&
		write_script "$HOOK" <<-\EOF
			# only allow update to main from now on
			test "$1" = "refs/heads/main"
		EOF
	) &&
	(
		cd workbench &&
		git checkout main &&
		test_commit three &&
		git checkout second &&
		test_commit four &&
		test_must_fail git push --atomic up main second
	) &&
	test_refs main HEAD@{3} &&
	test_refs second HEAD@{1}
'

test_expect_success 'atomic push is not advertised if configured' '
	mk_repo_pair &&
	(
		cd upstream &&
		git config receive.advertiseatomic 0
	) &&
	(
		cd workbench &&
		test_commit one &&
		git push --mirror up &&
		test_commit two &&
		test_must_fail git push --atomic up main
	) &&
	test_refs main HEAD@{1}
'

# References in upstream : main(1) one(1) foo(1)
# References in workbench: main(2)        foo(1) two(2) bar(2)
# Atomic push            : main(2)               two(2) bar(2)
test_expect_success 'atomic push reports (reject by update hook)' '
	mk_repo_pair &&
	(
		cd workbench &&
		test_commit one &&
		git branch foo &&
		git push up main one foo &&
		git tag -d one
	) &&
	(
		mkdir -p upstream/.git/hooks &&
		cat >upstream/.git/hooks/update <<-EOF &&
		#!/bin/sh

		if test "\$1" = "refs/heads/bar"
		then
			echo >&2 "Pusing to branch bar is prohibited"
			exit 1
		fi
		EOF
		chmod a+x upstream/.git/hooks/update
	) &&
	(
		cd workbench &&
		test_commit two &&
		git branch bar
	) &&
	test_must_fail git -C workbench \
		push --atomic up main two bar >out 2>&1 &&
	fmt_status_report <out >actual &&
	cat >expect <<-EOF &&
	To ../upstream
	 ! [remote rejected] main -> main (atomic push failure)
	 ! [remote rejected] two -> two (atomic push failure)
	 ! [remote rejected] bar -> bar (hook declined)
	EOF
	test_cmp expect actual
'

# References in upstream : main(1) one(1) foo(1)
# References in workbench: main(2)        foo(1) two(2) bar(2)
test_expect_success 'atomic push reports (mirror, but reject by update hook)' '
	(
		cd workbench &&
		git remote remove up &&
		git remote add up ../upstream
	) &&
	test_must_fail git -C workbench \
		push --atomic --mirror up >out 2>&1 &&
	fmt_status_report <out >actual &&
	cat >expect <<-EOF &&
	To ../upstream
	 ! [remote rejected] main -> main (atomic push failure)
	 ! [remote rejected] one (atomic push failure)
	 ! [remote rejected] bar -> bar (hook declined)
	 ! [remote rejected] two -> two (atomic push failure)
	EOF
	test_cmp expect actual
'

# References in upstream : main(2) one(1) foo(1)
# References in workbench: main(1)        foo(1) two(2) bar(2)
test_expect_success 'atomic push reports (reject by non-ff)' '
	rm upstream/.git/hooks/update &&
	(
		cd workbench &&
		git push up main &&
		git reset --hard HEAD^
	) &&
	test_must_fail git -C workbench \
		push --atomic up main foo bar >out 2>&1 &&
	fmt_status_report <out >actual &&
	cat >expect <<-EOF &&
	To ../upstream
	 ! [rejected] main -> main (non-fast-forward)
	 ! [rejected] bar -> bar (atomic push failed)
	EOF
	test_cmp expect actual
'

test_done