3 test_description
='git am running'
7 test_expect_success
'setup: messages' '
11 Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy
12 eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
13 voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita
14 kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem
15 ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
16 tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
17 vero eos et accusam et justo duo dolores et ea rebum.
20 qz_to_tab_space <<-\EOF >>msg &&
21 QDuis autem vel eum iriure dolor in hendrerit in vulputate velit
22 Qesse molestie consequat, vel illum dolore eu feugiat nulla facilisis
23 Qat vero eros et accumsan et iusto odio dignissim qui blandit
24 Qpraesent luptatum zzril delenit augue duis dolore te feugait nulla
29 Lorem ipsum dolor sit amet,
30 consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut
31 laoreet dolore magna aliquam erat volutpat.
37 Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit
38 lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure
39 dolor in hendrerit in vulputate velit esse molestie consequat, vel illum
40 dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio
41 dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te
42 feugait nulla facilisi.
45 cat >failmail <<-\EOF &&
46 From foo@example.com Fri May 23 10:43:49 2008
49 Subject: Re: [RFC/PATCH] git-foo.sh
50 Date: Fri, 23 May 2008 05:23:42 +0200
52 Sometimes we have to find out that there'\''s nothing left.
57 From MAILER-DAEMON Fri May 23 10:43:49 2008
58 Date: 23 May 2008 05:23:42 +0200
59 From: Mail System Internal Data <MAILER-DAEMON@example.com>
60 Subject: DON'\''T DELETE THIS MESSAGE -- FOLDER INTERNAL DATA
61 Message-ID: <foo-0001@example.com>
63 This text is part of the internal format of your mail folder, and is not
64 a real message. It is created automatically by the mail system software.
65 If deleted, important folder data will be lost, and it will be re-created
66 with the data reset to initial values.
70 cat >scissors-msg <<-\EOF &&
71 Test git-am with scissors line
73 This line should be included in the commit message.
76 cat - scissors-msg >no-scissors-msg <<-\EOF &&
77 This line should not be included in the commit message with --scissors enabled.
79 - - >8 - - remove everything above this line - - >8 - -
83 signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
86 test_expect_success setup
'
90 git commit -m first &&
96 git commit -s -F msg &&
99 git format-patch --stdout first >patch1 &&
101 echo "Message-Id: <1226501681-24923-1-git-send-email-bda@mnsspb.ru>" &&
102 echo "X-Fake-Field: Line One" &&
103 echo "X-Fake-Field: Line Two" &&
104 echo "X-Fake-Field: Line Three" &&
105 git format-patch --stdout first | sed -e "1d"
108 echo "X-Fake-Field: Line One" &&
109 echo "X-Fake-Field: Line Two" &&
110 echo "X-Fake-Field: Line Three" &&
111 git format-patch --stdout first | sed -e "1d"
112 } | append_cr >patch1-crlf.eml &&
115 echo "X-Fake-Field: Line One" &&
116 echo "X-Fake-Field: Line Two" &&
117 echo "X-Fake-Field: Line Three" &&
118 git format-patch --stdout first | sed -e "1d"
121 echo scissors-file >scissors-file &&
122 git add scissors-file &&
123 git commit -F scissors-msg &&
125 git format-patch --stdout scissors^ >scissors-patch.eml &&
126 git reset --hard HEAD^ &&
128 echo no-scissors-file >no-scissors-file &&
129 git add no-scissors-file &&
130 git commit -F no-scissors-msg &&
131 git tag no-scissors &&
132 git format-patch --stdout no-scissors^ >no-scissors-patch.eml &&
133 git reset --hard HEAD^ &&
135 sed -n -e "3,\$p" msg >file &&
138 git commit -m third &&
140 git format-patch --stdout first >patch2 &&
142 git checkout -b lorem &&
143 sed -n -e "11,\$p" msg >file &&
144 head -n 9 msg >>file &&
146 git commit -a -m "moved stuff" &&
148 echo goodbye >another &&
151 git commit -m "added another file" &&
153 git format-patch --stdout master >lorem-move.patch &&
154 git format-patch --no-prefix --stdout master >lorem-zero.patch &&
156 git checkout -b rename &&
157 git mv file renamed &&
158 git commit -m "renamed a file" &&
160 git format-patch -M --stdout lorem >rename.patch &&
162 git reset --soft lorem^ &&
163 git commit -m "renamed a file and added another" &&
165 git format-patch -M --stdout lorem^ >rename-add.patch &&
168 sane_unset test_tick &&
172 test_expect_success
'am applies patch correctly' '
173 rm -fr .git/rebase-apply &&
175 git checkout first &&
178 test_path_is_missing .git/rebase-apply &&
179 git diff --exit-code second &&
180 test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
181 test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
184 test_expect_success
'am fails if index is dirty' '
185 test_when_finished "rm -f dirtyfile" &&
186 rm -fr .git/rebase-apply &&
188 git checkout first &&
189 echo dirtyfile >dirtyfile &&
191 test_must_fail git am patch1 &&
192 test_path_is_dir .git/rebase-apply &&
193 test_cmp_rev first HEAD
196 test_expect_success
'am applies patch e-mail not in a mbox' '
197 rm -fr .git/rebase-apply &&
199 git checkout first &&
201 test_path_is_missing .git/rebase-apply &&
202 git diff --exit-code second &&
203 test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
204 test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
207 test_expect_success
'am applies patch e-mail not in a mbox with CRLF' '
208 rm -fr .git/rebase-apply &&
210 git checkout first &&
211 git am patch1-crlf.eml &&
212 test_path_is_missing .git/rebase-apply &&
213 git diff --exit-code second &&
214 test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
215 test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
218 test_expect_success
'am applies patch e-mail with preceding whitespace' '
219 rm -fr .git/rebase-apply &&
221 git checkout first &&
222 git am patch1-ws.eml &&
223 test_path_is_missing .git/rebase-apply &&
224 git diff --exit-code second &&
225 test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
226 test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
229 test_expect_success
'am with applypatch-msg hook' '
230 test_when_finished "rm -f .git/hooks/applypatch-msg" &&
231 rm -fr .git/rebase-apply &&
233 git checkout first &&
234 mkdir -p .git/hooks &&
235 write_script .git/hooks/applypatch-msg <<-\EOF &&
236 cat "$1" >actual-msg &&
237 echo hook-message >"$1"
240 test_path_is_missing .git/rebase-apply &&
241 git diff --exit-code second &&
242 echo hook-message >expected &&
243 git log -1 --format=format:%B >actual &&
244 test_cmp expected actual &&
245 git log -1 --format=format:%B second >expected &&
246 test_cmp expected actual-msg
249 test_expect_success
'am with failing applypatch-msg hook' '
250 test_when_finished "rm -f .git/hooks/applypatch-msg" &&
251 rm -fr .git/rebase-apply &&
253 git checkout first &&
254 mkdir -p .git/hooks &&
255 write_script .git/hooks/applypatch-msg <<-\EOF &&
258 test_must_fail git am patch1 &&
259 test_path_is_dir .git/rebase-apply &&
260 git diff --exit-code first &&
261 test_cmp_rev first HEAD
264 test_expect_success
'am with pre-applypatch hook' '
265 test_when_finished "rm -f .git/hooks/pre-applypatch" &&
266 rm -fr .git/rebase-apply &&
268 git checkout first &&
269 mkdir -p .git/hooks &&
270 write_script .git/hooks/pre-applypatch <<-\EOF &&
271 git diff first >diff.actual
275 test_path_is_missing .git/rebase-apply &&
276 git diff --exit-code second &&
277 test_cmp_rev second HEAD &&
278 git diff first..second >diff.expected &&
279 test_cmp diff.expected diff.actual
282 test_expect_success
'am with failing pre-applypatch hook' '
283 test_when_finished "rm -f .git/hooks/pre-applypatch" &&
284 rm -fr .git/rebase-apply &&
286 git checkout first &&
287 mkdir -p .git/hooks &&
288 write_script .git/hooks/pre-applypatch <<-\EOF &&
291 test_must_fail git am patch1 &&
292 test_path_is_dir .git/rebase-apply &&
293 git diff --exit-code second &&
294 test_cmp_rev first HEAD
297 test_expect_success
'am with post-applypatch hook' '
298 test_when_finished "rm -f .git/hooks/post-applypatch" &&
299 rm -fr .git/rebase-apply &&
301 git checkout first &&
302 mkdir -p .git/hooks &&
303 write_script .git/hooks/post-applypatch <<-\EOF &&
304 git rev-parse HEAD >head.actual
305 git diff second >diff.actual
309 test_path_is_missing .git/rebase-apply &&
310 test_cmp_rev second HEAD &&
311 git rev-parse second >head.expected &&
312 test_cmp head.expected head.actual &&
313 git diff second >diff.expected &&
314 test_cmp diff.expected diff.actual
317 test_expect_success
'am with failing post-applypatch hook' '
318 test_when_finished "rm -f .git/hooks/post-applypatch" &&
319 rm -fr .git/rebase-apply &&
321 git checkout first &&
322 mkdir -p .git/hooks &&
323 write_script .git/hooks/post-applypatch <<-\EOF &&
324 git rev-parse HEAD >head.actual
328 test_path_is_missing .git/rebase-apply &&
329 git diff --exit-code second &&
330 test_cmp_rev second HEAD &&
331 git rev-parse second >head.expected &&
332 test_cmp head.expected head.actual
335 test_expect_success
'am --scissors cuts the message at the scissors line' '
336 rm -fr .git/rebase-apply &&
338 git checkout second &&
339 git am --scissors scissors-patch.eml &&
340 test_path_is_missing .git/rebase-apply &&
341 git diff --exit-code scissors &&
342 test_cmp_rev scissors HEAD
345 test_expect_success
'am --no-scissors overrides mailinfo.scissors' '
346 rm -fr .git/rebase-apply &&
348 git checkout second &&
349 test_config mailinfo.scissors true &&
350 git am --no-scissors no-scissors-patch.eml &&
351 test_path_is_missing .git/rebase-apply &&
352 git diff --exit-code no-scissors &&
353 test_cmp_rev no-scissors HEAD
356 test_expect_success
'setup: new author and committer' '
357 GIT_AUTHOR_NAME="Another Thor" &&
358 GIT_AUTHOR_EMAIL="a.thor@example.com" &&
359 GIT_COMMITTER_NAME="Co M Miter" &&
360 GIT_COMMITTER_EMAIL="c.miter@example.com" &&
361 export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
365 a
=$
(git cat-file commit
"$2" |
grep "^$1 ") &&
366 b
=$
(git cat-file commit
"$3" |
grep "^$1 ") &&
370 test_expect_success
'am changes committer and keeps author' '
372 rm -fr .git/rebase-apply &&
374 git checkout first &&
376 test_path_is_missing .git/rebase-apply &&
377 test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
378 git diff --exit-code master..HEAD &&
379 git diff --exit-code master^..HEAD^ &&
380 compare author master HEAD &&
381 compare author master^ HEAD^ &&
382 test "$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" = \
383 "$(git log -1 --pretty=format:"%cn <%ce>" HEAD)"
386 test_expect_success
'am --signoff adds Signed-off-by: line' '
387 rm -fr .git/rebase-apply &&
389 git checkout -b master2 first &&
390 git am --signoff <patch2 &&
391 printf "%s\n" "$signoff" >expected &&
392 echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected &&
393 git cat-file commit HEAD^ | grep "Signed-off-by:" >actual &&
394 test_cmp expected actual &&
395 echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected &&
396 git cat-file commit HEAD | grep "Signed-off-by:" >actual &&
397 test_cmp expected actual
400 test_expect_success
'am stays in branch' '
401 echo refs/heads/master2 >expected &&
402 git symbolic-ref HEAD >actual &&
403 test_cmp expected actual
406 test_expect_success
'am --signoff does not add Signed-off-by: line if already there' '
407 git format-patch --stdout HEAD^ >patch3 &&
408 sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 &&
409 rm -fr .git/rebase-apply &&
411 git checkout HEAD^ &&
412 git am --signoff patch4 &&
413 git cat-file commit HEAD >actual &&
414 test $(grep -c "^Signed-off-by:" actual) -eq 1
417 test_expect_success
'am without --keep removes Re: and [PATCH] stuff' '
418 git rev-parse HEAD >expected &&
419 git rev-parse master2 >actual &&
420 test_cmp expected actual
423 test_expect_success
'am --keep really keeps the subject' '
424 rm -fr .git/rebase-apply &&
426 git checkout HEAD^ &&
427 git am --keep patch4 &&
428 test_path_is_missing .git/rebase-apply &&
429 git cat-file commit HEAD >actual &&
430 grep "Re: Re: Re: \[PATCH 1/5 v2\] \[foo\] third" actual
433 test_expect_success
'am --keep-non-patch really keeps the non-patch part' '
434 rm -fr .git/rebase-apply &&
436 git checkout HEAD^ &&
437 git am --keep-non-patch patch4 &&
438 test_path_is_missing .git/rebase-apply &&
439 git cat-file commit HEAD >actual &&
440 grep "^\[foo\] third" actual
443 test_expect_success
'setup am -3' '
444 rm -fr .git/rebase-apply &&
446 git checkout -b base3way master2 &&
447 sed -n -e "3,\$p" msg >file &&
448 head -n 9 msg >>file &&
451 git commit -m "copied stuff"
454 test_expect_success
'am -3 falls back to 3-way merge' '
455 rm -fr .git/rebase-apply &&
457 git checkout -b lorem2 base3way &&
458 git am -3 lorem-move.patch &&
459 test_path_is_missing .git/rebase-apply &&
460 git diff --exit-code lorem
463 test_expect_success
'am -3 -p0 can read --no-prefix patch' '
464 rm -fr .git/rebase-apply &&
466 git checkout -b lorem3 base3way &&
467 git am -3 -p0 lorem-zero.patch &&
468 test_path_is_missing .git/rebase-apply &&
469 git diff --exit-code lorem
472 test_expect_success
'am with config am.threeWay falls back to 3-way merge' '
473 rm -fr .git/rebase-apply &&
475 git checkout -b lorem4 base3way &&
476 test_config am.threeWay 1 &&
477 git am lorem-move.patch &&
478 test_path_is_missing .git/rebase-apply &&
479 git diff --exit-code lorem
482 test_expect_success
'am with config am.threeWay overridden by --no-3way' '
483 rm -fr .git/rebase-apply &&
485 git checkout -b lorem5 base3way &&
486 test_config am.threeWay 1 &&
487 test_must_fail git am --no-3way lorem-move.patch &&
488 test_path_is_dir .git/rebase-apply
491 test_expect_success
'am can rename a file' '
492 grep "^rename from" rename.patch &&
493 rm -fr .git/rebase-apply &&
495 git checkout lorem^0 &&
496 git am rename.patch &&
497 test_path_is_missing .git/rebase-apply &&
498 git update-index --refresh &&
499 git diff --exit-code rename
502 test_expect_success
'am -3 can rename a file' '
503 grep "^rename from" rename.patch &&
504 rm -fr .git/rebase-apply &&
506 git checkout lorem^0 &&
507 git am -3 rename.patch &&
508 test_path_is_missing .git/rebase-apply &&
509 git update-index --refresh &&
510 git diff --exit-code rename
513 test_expect_success
'am -3 can rename a file after falling back to 3-way merge' '
514 grep "^rename from" rename-add.patch &&
515 rm -fr .git/rebase-apply &&
517 git checkout lorem^0 &&
518 git am -3 rename-add.patch &&
519 test_path_is_missing .git/rebase-apply &&
520 git update-index --refresh &&
521 git diff --exit-code rename
524 test_expect_success
'am -3 -q is quiet' '
525 rm -fr .git/rebase-apply &&
526 git checkout -f lorem2 &&
527 git reset base3way --hard &&
528 git am -3 -q lorem-move.patch >output.out 2>&1 &&
532 test_expect_success
'am pauses on conflict' '
533 rm -fr .git/rebase-apply &&
535 git checkout lorem2^^ &&
536 test_must_fail git am lorem-move.patch &&
537 test -d .git/rebase-apply
540 test_expect_success
'am --skip works' '
541 echo goodbye >expected &&
543 test_path_is_missing .git/rebase-apply &&
544 git diff --exit-code lorem2^^ -- file &&
545 test_cmp expected another
548 test_expect_success
'am --abort removes a stray directory' '
549 mkdir .git/rebase-apply &&
551 test_path_is_missing .git/rebase-apply
554 test_expect_success
'am refuses patches when paused' '
555 rm -fr .git/rebase-apply &&
557 git checkout lorem2^^ &&
559 test_must_fail git am lorem-move.patch &&
560 test_path_is_dir .git/rebase-apply &&
561 test_cmp_rev lorem2^^ HEAD &&
563 test_must_fail git am <lorem-move.patch &&
564 test_path_is_dir .git/rebase-apply &&
565 test_cmp_rev lorem2^^ HEAD
568 test_expect_success
'am --resolved works' '
569 echo goodbye >expected &&
570 rm -fr .git/rebase-apply &&
572 git checkout lorem2^^ &&
573 test_must_fail git am lorem-move.patch &&
574 test -d .git/rebase-apply &&
575 echo resolved >>file &&
578 test_path_is_missing .git/rebase-apply &&
579 test_cmp expected another
582 test_expect_success
'am --resolved fails if index has no changes' '
583 rm -fr .git/rebase-apply &&
585 git checkout lorem2^^ &&
586 test_must_fail git am lorem-move.patch &&
587 test_path_is_dir .git/rebase-apply &&
588 test_cmp_rev lorem2^^ HEAD &&
589 test_must_fail git am --resolved &&
590 test_path_is_dir .git/rebase-apply &&
591 test_cmp_rev lorem2^^ HEAD
594 test_expect_success
'am --resolved fails if index has unmerged entries' '
595 rm -fr .git/rebase-apply &&
597 git checkout second &&
598 test_must_fail git am -3 lorem-move.patch &&
599 test_path_is_dir .git/rebase-apply &&
600 test_cmp_rev second HEAD &&
601 test_must_fail git am --resolved >err &&
602 test_path_is_dir .git/rebase-apply &&
603 test_cmp_rev second HEAD &&
604 test_i18ngrep "still have unmerged paths" err
607 test_expect_success
'am takes patches from a Pine mailbox' '
608 rm -fr .git/rebase-apply &&
610 git checkout first &&
611 cat pine patch1 | git am &&
612 test_path_is_missing .git/rebase-apply &&
613 git diff --exit-code master^..HEAD
616 test_expect_success
'am fails on mail without patch' '
617 rm -fr .git/rebase-apply &&
619 test_must_fail git am <failmail &&
621 test_path_is_missing .git/rebase-apply
624 test_expect_success
'am fails on empty patch' '
625 rm -fr .git/rebase-apply &&
627 echo "---" >>failmail &&
628 test_must_fail git am <failmail &&
630 test_path_is_missing .git/rebase-apply
633 test_expect_success
'am works from stdin in subdirectory' '
635 rm -fr .git/rebase-apply &&
637 git checkout first &&
643 git diff --exit-code second
646 test_expect_success
'am works from file (relative path given) in subdirectory' '
648 rm -fr .git/rebase-apply &&
650 git checkout first &&
656 git diff --exit-code second
659 test_expect_success
'am works from file (absolute path given) in subdirectory' '
661 rm -fr .git/rebase-apply &&
663 git checkout first &&
670 git diff --exit-code second
673 test_expect_success
'am --committer-date-is-author-date' '
674 rm -fr .git/rebase-apply &&
676 git checkout first &&
678 git am --committer-date-is-author-date patch1 &&
679 git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
680 sed -ne "/^author /s/.*> //p" head1 >at &&
681 sed -ne "/^committer /s/.*> //p" head1 >ct &&
685 test_expect_success
'am without --committer-date-is-author-date' '
686 rm -fr .git/rebase-apply &&
688 git checkout first &&
691 git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
692 sed -ne "/^author /s/.*> //p" head1 >at &&
693 sed -ne "/^committer /s/.*> //p" head1 >ct &&
697 # This checks for +0000 because TZ is set to UTC and that should
698 # show up when the current time is used. The date in message is set
699 # by test_tick that uses -0700 timezone; if this feature does not
700 # work, we will see that instead of +0000.
701 test_expect_success
'am --ignore-date' '
702 rm -fr .git/rebase-apply &&
704 git checkout first &&
706 git am --ignore-date patch1 &&
707 git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
708 sed -ne "/^author /s/.*> //p" head1 >at &&
712 test_expect_success
'am into an unborn branch' '
713 git rev-parse first^{tree} >expected &&
714 rm -fr .git/rebase-apply &&
718 git format-patch --numbered-files -o subdir -1 first &&
726 git rev-parse HEAD^{tree} >../actual
728 test_cmp expected actual
731 test_expect_success
'am newline in subject' '
732 rm -fr .git/rebase-apply &&
734 git checkout first &&
736 sed -e "s/second/second \\\n foo/" patch1 >patchnl &&
737 git am <patchnl >output.out 2>&1 &&
738 test_i18ngrep "^Applying: second \\\n foo$" output.out
741 test_expect_success
'am -q is quiet' '
742 rm -fr .git/rebase-apply &&
744 git checkout first &&
746 git am -q <patch1 >output.out 2>&1 &&
750 test_expect_success
'am empty-file does not infloop' '
751 rm -fr .git/rebase-apply &&
755 test_must_fail git am empty-file 2>actual &&
756 echo Patch format detection failed. >expected &&
757 test_i18ncmp expected actual
760 test_expect_success
'am --message-id really adds the message id' '
761 rm -fr .git/rebase-apply &&
763 git checkout HEAD^ &&
764 git am --message-id patch1.eml &&
765 test_path_is_missing .git/rebase-apply &&
766 git cat-file commit HEAD | tail -n1 >actual &&
767 grep Message-Id patch1.eml >expected &&
768 test_cmp expected actual
771 test_expect_success
'am.messageid really adds the message id' '
772 rm -fr .git/rebase-apply &&
774 git checkout HEAD^ &&
775 test_config am.messageid true &&
777 test_path_is_missing .git/rebase-apply &&
778 git cat-file commit HEAD | tail -n1 >actual &&
779 grep Message-Id patch1.eml >expected &&
780 test_cmp expected actual
783 test_expect_success
'am --message-id -s signs off after the message id' '
784 rm -fr .git/rebase-apply &&
786 git checkout HEAD^ &&
787 git am -s --message-id patch1.eml &&
788 test_path_is_missing .git/rebase-apply &&
789 git cat-file commit HEAD | tail -n2 | head -n1 >actual &&
790 grep Message-Id patch1.eml >expected &&
791 test_cmp expected actual