Various memory leak fixes
cover Various memory leak fixes
Hi,
my mind had a couple of minutes where it was roaming, and of course it immediately searched for and chased down the next rabbit hole. The result is this patch series which fixes a bunch of leaks all over the place. There isn't really any structure to the leaks that I did fix -- it's mostly things that I stumbled over. In the end, this series makes another 56 test suites pass with leak checking enabled, 13 of which have already been passing without any changes.
While most things are unstructured, there are two topics that stand out:
- Patches 5 to 12 address a shortcoming of our config API. Both git_config_string() and git_config_pathname() have a const char ** out parameter, but they do in fact transfer memory ownership to the caller. This resulted in a bunch of memory leaks all over the place.
These patches thus refactor a bunch of code and then ultimately switch the out parameter to become a char *
- Patches 16 to 20 have the goal of making git-mv(1) memory leak free. I had a very hard time understanding how it tracks memory. I think this wasn't only me, or otherwise there wouldn't be calls to UNLEAK() in there. In any case, I decided to rewrite the arrays to use a struct strvec, which makes tracking and releasing of memory a ton easier.
It does come at the cost of more allocations because we may now duplicate strings that we didn't before. But I think the tradeoff is worth it because the number of strings we may now duplicate is bounded by the number of command line arguments anyway.
Thanks!
Patrick
Patrick Steinhardt (20):
t: mark a bunch of tests as leak-free
transport-helper: fix leaking helper name
strbuf: fix leak when appendwholeline() fails with EOF
checkout: clarify memory ownership in unique_tracking_name()
http: refactor code to clarify memory ownership
config: clarify memory ownership in git_config_pathname()
diff: refactor code to clarify memory ownership of prefixes
convert: refactor code to clarify ownership of
check_roundtrip_encoding
builtin/log: stop using globals for log config builtin/log: stop using globals for format config
config: clarify memory ownership in git_config_string()
config: plug various memory leaks
builtin/credential: clear credential before exit
commit-reach: fix memory leak in ahead_behind()
submodule: fix leaking memory for submodule entries
strvec: add functions to replace and remove strings builtin/mv: refactor add_slash() to always return allocated strings builtin/mv duplicate string list memory
builtin/mv: refactor to use struct strvec
builtin/mv: fix leaks for submodule gitfile paths
Makefile | 1 + alias.c | 6 +- attr.c | 2 +- attr.h | 2 +- builtin/blame.c | 2 +- builtin/checkout.c | 14 +- builtin/commit.c | 4 +- builtin/config.c | 2 +- builtin/credential.c | 2 + builtin/log.c | 708 ++++++++++-------- builtin/merge.c | 4 +- builtin/mv.c | 222 +++--- builtin/rebase.c | 2 +- builtin/receive-pack.c | 6 +- builtin/repack.c | 8 +- builtin/worktree.c | 20 +- checkout.c | 4 +- checkout.h | 6 +- commit-reach.c | 4 + config.c | 52 +- config.h | 10 +- convert.c | 30 +- convert.h | 2 +- delta-islands.c | 2 +- diff.c | 20 +- environment.c | 16 +- environment.h | 14 +- fetch-pack.c | 4 +- fsck.c | 4 +- fsmonitor-settings.c | 5 +- gpg-interface.c | 6 +- http.c | 50 +- imap-send.c | 12 +- mailmap.c | 4 +- mailmap.h | 4 +- merge-ll.c | 6 +- pager.c | 2 +- pretty.c | 14 +- promisor-remote.h | 2 +- remote.c | 20 +- remote.h | 8 +- sequencer.c | 2 +- setup.c | 6 +- strbuf.c | 4 +- strvec.c | 20 + strvec.h | 13 + submodule-config.c | 2 + t/t0300-credentials.sh | 2 + t/t0411-clone-from-partial.sh | 1 + t/t0610-reftable-basics.sh | 1 + t/t0611-reftable-httpd.sh | 1 + t/t1013-read-tree-submodule.sh | 1 + t/t1306-xdg-files.sh | 1 + t/t1350-config-hooks-path.sh | 1 + t/t1400-update-ref.sh | 2 + t/t2013-checkout-submodule.sh | 1 + t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t2405-worktree-submodule.sh | 1 + t/t3007-ls-files-recurse-submodules.sh | 1 + t/t3203-branch-output.sh | 2 + t/t3415-rebase-autosquash.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4001-diff-rename.sh | 4 +- t/t4041-diff-submodule-option.sh | 1 + t/t4043-diff-rename-binary.sh | 1 + t/t4059-diff-submodule-not-initialized.sh | 1 + t/t4060-diff-submodule-option-diff-format.sh | 1 + t/t4120-apply-popt.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t4153-am-resume-override-opts.sh | 1 + t/t4210-log-i18n.sh | 2 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + t/t6006-rev-list-format.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6400-merge-df.sh | 1 + t/t6412-merge-large-rename.sh | 1 + t/t6426-merge-skip-unneeded-updates.sh | 1 + t/t6429-merge-sequence-rename-caching.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + t/t7001-mv.sh | 2 + t/t7005-editor.sh | 1 + t/t7006-pager.sh | 1 + t/t7102-reset.sh | 1 + t/t7112-reset-submodule.sh | 1 + t/t7417-submodule-path-url.sh | 1 + t/t7421-submodule-summary-add.sh | 1 + t/t7423-submodule-symlinks.sh | 1 + t/t9129-git-svn-i18n-commitencoding.sh | 1 - t/t9139-git-svn-non-utf8-commitencoding.sh | 1 - t/t9200-git-cvsexportcommit.sh | 1 + t/t9401-git-cvsserver-crlf.sh | 1 + t/t9600-cvsimport.sh | 1 + t/t9601-cvsimport-vendor-branch.sh | 1 + t/t9602-cvsimport-branches-tags.sh | 1 + t/t9603-cvsimport-patchsets.sh | 2 + t/t9604-cvsimport-timestamps.sh | 2 + t/unit-tests/t-strvec.c | 259 +++++++ t/unit-tests/test-lib.c | 13 + t/unit-tests/test-lib.h | 13 + transport-helper.c | 6 +- transport.c | 1 + upload-pack.c | 2 +- userdiff.h | 12 +- 110 files changed, 1141 insertions(+), 584 deletions(-) create mode 100644 t/unit-tests/t-strvec.c
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 1/20 t: mark a bunch of tests as leak-free
There are a bunch of tests which do not have any leaks:
- t0411: Introduced via 5c5a4a1c05 (t0411: add tests for cloning from
partial repo, 2024-01-28), passes since its inception.
- t0610: Introduced via 57db2a094d (refs: introduce reftable backend,
2024-02-07), passes since its inception.
- t2405: Passes since 6741e917de (repository: avoid leaking
`fsmonitor` data, 2024-04-12).
- t4153: Passes since 71c7916053 (apply: plug a leak in apply_data,
2024-04-23).
- t7006: Passes since at least Git v2.40. I did not care to go back any further than that.
- t7423: Introduced via b20c10fd9b (t7423: add tests for symlinked submodule directories, 2024-01-28), passes since e8d0608944 (submodule: require the submodule path to contain directories only, 2024-03-26). The fix is not ovbiously related, but probably works because we now die early in many code paths.
- t9xxx: All of these are exercising CVS-related tooling and pass since at least Git v2.40. It's likely that these pass for a long time already, but nobody ever noticed because noone has CVS on their machine.
Mark all of these tests as passing.
--- t/t0411-clone-from-partial.sh | 1 + t/t0610-reftable-basics.sh | 1 + t/t2405-worktree-submodule.sh | 1 + t/t4153-am-resume-override-opts.sh | 1 + t/t7006-pager.sh | 1 + t/t7423-submodule-symlinks.sh | 1 + t/t9200-git-cvsexportcommit.sh | 1 + t/t9401-git-cvsserver-crlf.sh | 1 + t/t9600-cvsimport.sh | 1 + t/t9601-cvsimport-vendor-branch.sh | 1 + t/t9602-cvsimport-branches-tags.sh | 1 + t/t9603-cvsimport-patchsets.sh | 2 ++ t/t9604-cvsimport-timestamps.sh | 2 ++ 13 files changed, 15 insertions(+)
t/t0411-clone-from-partial.sh +1 -0
@@ -2,6 +2,7 @@ test_description='check that local clone does not fetch from promisor remotes' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'create evil repo' '
t/t0610-reftable-basics.sh +1 -0
@@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GIT_TEST_DEFAULT_REF_FORMAT=reftable export GIT_TEST_DEFAULT_REF_FORMAT +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh INVALID_OID=$(test_oid 001)
t/t2405-worktree-submodule.sh +1 -0
@@ -5,6 +5,7 @@ test_description='Combination of submodules and multiple worktrees' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh base_path=$(pwd -P)
t/t4153-am-resume-override-opts.sh +1 -0
@@ -2,6 +2,7 @@ test_description='git-am command-line options override saved options' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh
t/t7006-pager.sh +1 -0
@@ -2,6 +2,7 @@ test_description='Test automatic use of a pager.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pager.sh . "$TEST_DIRECTORY"/lib-terminal.sh
t/t7423-submodule-symlinks.sh +1 -0
@@ -2,6 +2,7 @@ test_description='check that submodule operations do not follow symlinks' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare' '
t/t9200-git-cvsexportcommit.sh +1 -0
@@ -4,6 +4,7 @@ # test_description='Test export of commits to CVS' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh if ! test_have_prereq PERL; then
t/t9401-git-cvsserver-crlf.sh +1 -0
@@ -12,6 +12,7 @@ repository using cvs CLI client via git-cvsserver server' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh marked_as () {
t/t9600-cvsimport.sh +1 -0
@@ -4,6 +4,7 @@ test_description='git cvsimport basic tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh if ! test_have_prereq NOT_ROOT; then
t/t9601-cvsimport-vendor-branch.sh +1 -0
@@ -35,6 +35,7 @@ test_description='git cvsimport handling of vendor branches' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9601
t/t9602-cvsimport-branches-tags.sh +1 -0
@@ -7,6 +7,7 @@ test_description='git cvsimport handling of branches and tags' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9602
t/t9603-cvsimport-patchsets.sh +2 -0
@@ -12,6 +12,8 @@ # bug. test_description='git cvsimport testing for correct patchset estimation' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9603
t/t9604-cvsimport-timestamps.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git cvsimport timestamps' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh test_lazy_prereq POSIX_TIMEZONE '
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH 01/20] t: mark a bunch of tests as leak-free
Quoted 5 lines · Patrick Steinhardt
- t4153: Passes since
71c7916053(apply: plug a leak inapply_data,2024-04-23).- t7006: Passes since at least Git v2.40. I did not care to go back any further than that.
Since the base commit you chose to apply this step to (which is unknown to me) and the tip of 'master' today 4365c6fc (The sixth batch, 2024-05-20), we must have reintroduced more leaks.
$ SANITIZE=leak GIT_TEST_PASSING_SANITIZE_LEAK=true \ Meta/Make -j16 --test=4153,7006 test
dies with
Test Summary Report
-------------------
t4153-am-resume-override-opts.sh (Wstat: 256 (exited 1) Tests: 5 Failed: 1)
Failed test: 2
Non-zero exit status: 1
t7006-pager.sh (Wstat: 256 (exited 1) Tests: 109 Failed: 6)
Failed tests: 14, 70-74
Non-zero exit status: 1
Here, Meta/Make is a thin wrapper around "make", I primarily use it for its --test=only,these,tests feature, which is an opposite of GIT_SKIP_TESTS. (Meta/ is a separate checkout of the 'todo' branch of this project, that keeps things like whats-cooking.txt and miscellaneous tools I use to manage the project).
Re: [PATCH 01/20] t: mark a bunch of tests as leak-free
Quoted 26 lines · Junio C Hamano
Quoted 5 lines · Patrick Steinhardt
- t4153: Passes since
71c7916053(apply: plug a leak inapply_data,2024-04-23).- t7006: Passes since at least Git v2.40. I did not care to go back any further than that.
Since the base commit you chose to apply this step to (which is unknown to me) and the tip of 'master' today
4365c6fc(The sixth batch, 2024-05-20), we must have reintroduced more leaks.$ SANITIZE=leak GIT_TEST_PASSING_SANITIZE_LEAK=true \ Meta/Make -j16 --test=4153,7006 testdies with
Test Summary Report
-------------------
t4153-am-resume-override-opts.sh(Wstat: 256 (exited 1) Tests: 5 Failed: 1)
Failed test: 2
Non-zero exit status: 1
t7006-pager.sh(Wstat: 256 (exited 1) Tests: 109 Failed: 6)
Failed tests: 14, 70-74
Non-zero exit status: 1
Quoted 5 lines
Here, Meta/Make is a thin wrapper around "make", I primarily use it for its --test=only,these,tests feature, which is an opposite of
GIT_SKIP_TESTS. (Meta/ is a separate checkout of the 'todo' branch of this project, that keeps things like whats-cooking.txt and miscellaneous tools I use to manage the project).
Hum. Both of these skip a bunch of tests due to a missing TTY prereq on my system. So I guess it's not a regression, just me missing test coverage. And seemingly, the same applies to our CI systems because the pipeline is green there.
And indeed, the TTY prerequisite fails due a totally unrelated error:
Can't locate IO/Pty.pm in @INC
I'll fix this locally and in our CI setup. Ideally, we'd also make this thing more robust going forward, but I'll leave that for a future iteraiton.
Patrick
Re: [PATCH 01/20] t: mark a bunch of tests as leak-free
Quoted 10 lines · Patrick Steinhardt
Hum. Both of these skip a bunch of tests due to a missing TTY prereq on my system. So I guess it's not a regression, just me missing test coverage. And seemingly, the same applies to our CI systems because the pipeline is green there.
And indeed, the TTY prerequisite fails due a totally unrelated error:
Can't locate IO/Pty.pm in @INC
I'll fix this locally and in our CI setup.
Do you mean that you'll make IO::Pty available locally and in the CI setup, which will start revealing the existing leaks in these tests?
So do we expect this step to be adjusted, not to mark these two tests as leak-free (yet)?
Quoted 2 lines
Ideally, we'd also make this thing more robust going forward, but I'll leave that for a future iteraiton.
--- >8 ---
Subject: ci: make IO::Pty available
When t/test-terminal.perl, which requires IO::Pty (and File::Copy, but that comes standard with perl-modules?), does not work, the tests with TTY prerequisite are skipped.
Make sure it is available in the CI environment.
--- ci/install-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
ci/install-dependencies.sh +1 -1
@@ -42,7 +42,7 @@ ubuntu-*) language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \ make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ - libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ + libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl libio-pty-perl \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE mkdir --parents "$CUSTOM_PATH"
Re: [PATCH 01/20] t: mark a bunch of tests as leak-free
Quoted 6 lines · Junio C Hamano
--- >8 ---
Subject: ci: makeIO::PtyavailableWhen t/test-terminal.perl, which requires
IO::Pty(andFile::Copy, but that comes standard with perl-modules?), does not work, the tests with TTY prerequisite are skipped.
... please ignore this, of course ;-) Your latest iteration does the right thing on this one, and unmarks the two that are not yet ready.
Thanks.
Re: [PATCH 01/20] t: mark a bunch of tests as leak-free
In reply to Patrick SteinhardtQuoted 14 lines · Patrick Steinhardt
There are a bunch of tests which do not have any leaks:
- t0411: Introduced via
5c5a4a1c05(t0411: add tests for cloning frompartial repo, 2024-01-28), passes since its inception.- t0610: Introduced via
57db2a094d(refs: introduce reftable backend,2024-02-07), passes since its inception.- t2405: Passes since
6741e917de(repository: avoid leaking`fsmonitor` data, 2024-04-12).- t4153: Passes since
71c7916053(apply: plug a leak inapply_data,2024-04-23).
Quoted 7 lines
- t7006: Passes since at least Git v2.40. I did not care to go back any further than that.
- t7423: Introduced via
b20c10fd9b(t7423: add tests for symlinked submodule directories, 2024-01-28), passes sincee8d0608944(submodule: require the submodule path to contain directories only, 2024-03-26). The fix is not ovbiously related, but probably works
s/ovbiously/obviously
Quoted 5 lines
because we now die early in many code paths.
- t9xxx: All of these are exercising CVS-related tooling and pass since at least Git v2.40. It's likely that these pass for a long time already, but nobody ever noticed because noone has CVS on their
s/noone/no one
Quoted 4 lines
machine.
Mark all of these tests as passing.
[snip]
PATCH 2/20 transport-helper: fix leaking helper name
In reply to Patrick SteinhardtWhen initializing the transport helper in transport_get(), we allocate the name of the helper. We neither end up transferring ownership of the name, nor do we free it. The associated memory thus leaks.
Fix this memory leak and mark now-passing tests as leak free.
--- t/t0611-reftable-httpd.sh | 1 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + transport-helper.c | 6 ++++-- transport.c | 1 + 6 files changed, 9 insertions(+), 2 deletions(-)
t/t0611-reftable-httpd.sh +1 -0
@@ -2,6 +2,7 @@ test_description='reftable HTTPD tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5563-simple-http-auth.sh +1 -0
@@ -2,6 +2,7 @@ test_description='test http auth header and credential helper interop' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5564-http-proxy.sh +1 -0
@@ -2,6 +2,7 @@ test_description="test fetching through http proxy" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5581-http-curl-verbose.sh +1 -0
@@ -4,6 +4,7 @@ test_description='test GIT_CURL_VERBOSE' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
transport-helper.c +4 -2
@@ -22,7 +22,7 @@static int debug;struct helper_data { -const char *name; +char *name;struct child_process *helper;FILE *out; unsigned fetch : 1,
@@ -111,6 +111,7 @@ static void do_take_over(struct transport *transport) data = (struct helper_data *)transport->data;transport_take_over (transport, data->helper);fclose (data->out); +free (data->name);free (data); }
@@ -253,6 +254,7 @@ static int disconnect_helper(struct transport *transport)close (data->helper->out);fclose (data->out); res =finish_command (data->helper); +FREE_AND_NULL (data->name);FREE_AND_NULL (data->helper); }return res;
@@ -1297,7 +1299,7 @@ static struct transport_vtable vtable = {int transport_helper_init (struct transport *transport,const char *name) {struct helper_data *data =xcalloc (1 ,sizeof (*data)); - data->name = name; + data->name =xstrdup (name);transport_check_allowed (name);
transport.c +1 -0
@@ -1176,6 +1176,7 @@ struct transport *transport_get(struct remote *remote, const char *url)int len =external_specification_len (url);char *handler =xmemdupz (url, len);transport_helper_init (ret, handler); +free (handler); }if (ret->smart_options) {
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH 02/20] transport-helper: fix leaking helper name
Quoted 6 lines · Patrick Steinhardt
When initializing the transport helper in
transport_get(), we allocate the name of the helper. We neither end up transferring ownership of the name, nor do we free it. The associated memory thus leaks.Fix this memory leak and mark now-passing tests as leak free.
It would be more helpful to the readers if this said "Fix this memory leak by having the helper own the memory, and mark ...".
Not a huge deal, though.
transport-helper.c static void do_take_over(struct transport *transport) · line 116
Quoted 13 lines
struct
helper_data{transport-helper.c +2
-1-const char *name; +char *name;struct child_process *helper;FILE *out; unsigned fetch : 1,@@ -111,6 +111,7 @@ static void do_take_over(struct transport *transport) data = (struct helper_data *)transport->data;transport_take_over (transport, data->helper);fclose (data->out); +free (data->name);free (data); }
The patch is looking good. Thanks.
Re: [PATCH 02/20] transport-helper: fix leaking helper name
In reply to Patrick SteinhardtQuoted 7 lines · Patrick Steinhardt
When initializing the transport helper in
transport_get(), we allocate the name of the helper. We neither end up transferring ownership of the name, nor do we free it. The associated memory thus leaks.Fix this memory leak and mark now-passing tests as leak free.
So this is done by removing the const from the name field in helper_data so we can assign and free its memory. Looks good.
[snip]
PATCH 3/20 strbuf: fix leak when `appendwholeline()` fails with EOF
In reply to Patrick SteinhardtIn strbuf_appendwholeline() we call strbuf_getwholeline() with a temporary buffer. In case the call returns an error we indicate this by returning EOF, but never release the temporary buffer. This can lead to a memory leak when the line has been partially filled. Fix this.
--- strbuf.c | 4 +++- t/t1400-update-ref.sh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-)
strbuf.c +3 -1
@@ -691,8 +691,10 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)int strbuf_appendwholeline (struct strbuf *sb,FILE *fp,int term) {struct strbuf line =STRBUF_INIT ; -if (strbuf_getwholeline (&line, fp, term)) +if (strbuf_getwholeline (&line, fp, term)) { +strbuf_release (&line);return EOF ; + }strbuf_addbuf (sb, &line);strbuf_release (&line);return 0 ;
t/t1400-update-ref.sh +2 -0
@@ -4,6 +4,8 @@ # test_description='Test git update-ref and basic ref logging' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh Z=$ZERO_OID
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 4/20 checkout: clarify memory ownership in `unique_tracking_name()`
In reply to Patrick SteinhardtThe function unique_tracking_name() returns an allocated string, but does not clearly indicate this because its return type is const char * instead of char *. This has led to various callsites where we never free its returned memory at all, which causes memory leaks.
Plug those leaks and mark now-passing tests as leak free.
--- builtin/checkout.c | 14 +++++++------ builtin/worktree.c | 20 ++++++++++--------- checkout.c | 4 ++-- checkout.h | 6 +++--- t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + 14 files changed, 34 insertions(+), 20 deletions(-)
builtin/checkout.c +8 -6
@@ -1275,12 +1275,12 @@ static void setup_new_branch_info_and_source_tree( } } -static const char *parse_remote_branch (const char *arg, -struct object_id *rev, -int could_be_checkout_paths) +static char *parse_remote_branch (const char *arg, +struct object_id *rev, +int could_be_checkout_paths) {int num_matches =0 ; -const char *remote =unique_tracking_name (arg, rev, &num_matches); +char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"
@@ -1316,6 +1316,7 @@ static int parse_branchname_arg(int argc, const char **argv,const char **new_branch = &opts->new_branch;int argcount =0 ;const char *arg; +char *remote =NULL ;int dash_dash_pos;int has_dash_dash =0 ;int i;
@@ -1416,8 +1417,8 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim =0 ;if (recover_with_dwim) { -const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths); + remote =parse_remote_branch (arg, rev, + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;
@@ -1459,6 +1460,7 @@ static int parse_branchname_arg(int argc, const char **argv, argc--; } +free (remote);return argcount; }
builtin/worktree.c +11 -9
@@ -736,16 +736,14 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)return 1 ; } -static const char *dwim_branch (const char *path,const char **new_branch) +static char *dwim_branch (const char *path,char **new_branch) {int n;int branch_exists;const char *s =worktree_basename (path, &n); -const char *branchname =xstrndup (s, n); +char *branchname =xstrndup (s, n);struct strbuf ref =STRBUF_INIT ; -UNLEAK (branchname); - branch_exists = !strbuf_check_branch_ref (&ref, branchname) &&refs_ref_exists (get_main_ref_store (the_repository), ref.buf);
@@ -756,8 +754,7 @@ static const char *dwim_branch(const char *path, const char **new_branch) *new_branch = branchname;if (guess_remote) {struct object_id oid; -const char *remote = -unique_tracking_name (*new_branch, &oid,NULL ); +char *remote =unique_tracking_name (*new_branch, &oid,NULL );return remote; }return NULL ;
@@ -769,6 +766,8 @@ static int add(int ac, const char **av, const char *prefix)const char *new_branch_force =NULL ;char *path;const char *branch; +char *branch_to_free =NULL ; +char *new_branch_to_free =NULL ;const char *new_branch =NULL ;const char *opt_track =NULL ;const char *lock_reason =NULL ;
@@ -859,16 +858,17 @@ static int add(int ac, const char **av, const char *prefix) opts.orphan =dwim_orphan (&opts, !!opt_track,0 ); }else if (ac <2 ) {/* DWIM: Guess branch name from path. */ -const char *s =dwim_branch (path, &new_branch); +char *s =dwim_branch (path, &new_branch_to_free);if (s) - branch = s; + branch = branch_to_free = s; + new_branch = new_branch_to_free;/* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = (!s) &&dwim_orphan (&opts, !!opt_track,1 ); }else if (ac ==2 ) {struct object_id oid;struct commit *commit; -const char *remote; +char *remote; commit =lookup_commit_reference_by_name (branch);if (!commit) {
@@ -923,6 +923,8 @@ static int add(int ac, const char **av, const char *prefix) ret =add_worktree (path, branch, &opts);free (path); +free (branch_to_free); +free (new_branch_to_free);return ret; }
checkout.c +2 -2
@@ -45,8 +45,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data)return 0 ; } -const char *unique_tracking_name (const char *name,struct object_id *oid, -int *dwim_remotes_matched) +char *unique_tracking_name (const char *name,struct object_id *oid, +int *dwim_remotes_matched) {struct tracking_name_data cb_data =TRACKING_NAME_DATA_INIT ;const char *default_remote =NULL ;
checkout.h +3 -3
@@ -8,8 +8,8 @@* tracking branch. Return the name of the remote if such a branch * exists, NULL otherwise. */ -const char *unique_tracking_name (const char *name, -struct object_id *oid, -int *dwim_remotes_matched); +char *unique_tracking_name (const char *name, +struct object_id *oid, +int *dwim_remotes_matched);#endif /* CHECKOUT_H */
t/t2024-checkout-dwim.sh +1 -0
@@ -4,6 +4,7 @@ test_description='checkout <branch> Ensures that checkout on an unborn branch does what the user expects' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Is the current branch "refs/heads/$1"?
t/t2060-switch.sh +1 -0
@@ -5,6 +5,7 @@ test_description='switch basic functionality' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
t/t3426-rebase-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='rebase can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh . "$TEST_DIRECTORY"/lib-rebase.sh
t/t3512-cherry-pick-submodule.sh +1 -0
@@ -5,6 +5,7 @@ test_description='cherry-pick can handle submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3513-revert-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='revert can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3600-rm.sh +1 -0
@@ -8,6 +8,7 @@ test_description='Test of the various options to git rm.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Setup some files to be removed, some with funny characters
t/t3906-stash-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='stash can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t4137-apply-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='git apply handling submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t6041-bisect-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='bisect can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t6438-submodule-directory-file-conflicts.sh +1 -0
@@ -2,6 +2,7 @@ test_description='merge can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 5/20 http: refactor code to clarify memory ownership
In reply to Patrick SteinhardtThere are various variables assigned via git_config_string() and git_config_pathname() which are never free'd. This bug is relatable because the out parameter of those functions are a const char **, even though memory ownership is transferred to the caller.
We're about to adapt the functions to instead use char **. Prepare the code accordingly. Note that the (const char **) casts will go away once we have adapted the functions.
--- http.c | 62 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 30 deletions(-)
http.c +32 -30
@@ -39,8 +39,8 @@ char curl_errorstr[CURL_ERROR_SIZE];static int curl_ssl_verify =-1 ;static int curl_ssl_try;static const char *curl_http_version =NULL ; -static const char *ssl_cert; -static const char *ssl_cert_type; +static char *ssl_cert; +static char *ssl_cert_type;static const char *ssl_cipherlist;static const char *ssl_version; static struct {
@@ -59,23 +59,23 @@ static struct { { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 }, #endif }; -static const char *ssl_key; -static const char *ssl_key_type; -static const char *ssl_capath; -static const char *curl_no_proxy; +static char *ssl_key; +static char *ssl_key_type; +static char *ssl_capath; +static char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY static const char *ssl_pinnedkey; #endif -static const char *ssl_cainfo; +static char *ssl_cainfo; static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; -static const char *curl_http_proxy; -static const char *http_proxy_authmethod; +static char *curl_http_proxy; +static char *http_proxy_authmethod; -static const char *http_proxy_ssl_cert; -static const char *http_proxy_ssl_key; -static const char *http_proxy_ssl_ca_info; +static char *http_proxy_ssl_cert; +static char *http_proxy_ssl_key; +static char *http_proxy_ssl_ca_info; static struct credential proxy_cert_auth = CREDENTIAL_INIT; static int proxy_ssl_cert_password_required;
@@ -112,7 +112,7 @@ static const char *curl_cookie_file;static int curl_save_cookies;struct credential http_auth =CREDENTIAL_INIT ;static int http_proactive_auth; -static const char *user_agent; +static char *user_agent;static int curl_empty_auth =-1 ;enum http_follow_config http_follow_config =HTTP_FOLLOW_INITIAL ;
@@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslversion" , var))return git_config_string (&ssl_version, var, value);if (!strcmp ("http.sslcert" , var)) -return git_config_pathname (&ssl_cert, var, value); +return git_config_pathname ((const char **)&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var)) -return git_config_string (&ssl_cert_type, var, value); +return git_config_string ((const char **)&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var)) -return git_config_pathname (&ssl_key, var, value); +return git_config_pathname ((const char **)&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var)) -return git_config_string (&ssl_key_type, var, value); +return git_config_string ((const char **)&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var)) -return git_config_pathname (&ssl_capath, var, value); +return git_config_pathname ((const char **)&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var)) -return git_config_pathname (&ssl_cainfo, var, value); +return git_config_pathname ((const char **)&ssl_cainfo, var, value);if (!strcmp ("http.sslcertpasswordprotected" , var)) { ssl_cert_password_required =git_config_bool (var, value);return 0 ;
@@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value,return 0 ; }if (!strcmp ("http.proxy" , var)) -return git_config_string (&curl_http_proxy, var, value); +return git_config_string ((const char **)&curl_http_proxy, var, value);if (!strcmp ("http.proxyauthmethod" , var)) -return git_config_string (&http_proxy_authmethod, var, value); +return git_config_string ((const char **)&http_proxy_authmethod, var, value);if (!strcmp ("http.proxysslcert" , var)) -return git_config_string (&http_proxy_ssl_cert, var, value); +return git_config_string ((const char **)&http_proxy_ssl_cert, var, value);if (!strcmp ("http.proxysslkey" , var)) -return git_config_string (&http_proxy_ssl_key, var, value); +return git_config_string ((const char **)&http_proxy_ssl_key, var, value);if (!strcmp ("http.proxysslcainfo" , var)) -return git_config_string (&http_proxy_ssl_ca_info, var, value); +return git_config_string ((const char **)&http_proxy_ssl_ca_info, var, value);if (!strcmp ("http.proxysslcertpasswordprotected" , var)) { proxy_ssl_cert_password_required =git_config_bool (var, value);
@@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, }if (!strcmp ("http.useragent" , var)) -return git_config_string (&user_agent, var, value); +return git_config_string ((const char **)&user_agent, var, value);if (!strcmp ("http.emptyauth" , var)) {if (value && !strcmp ("auto" , value))
@@ -592,10 +592,10 @@ static void init_curl_http_auth(CURL *result) }/* *var must be free-able */ -static void var_override (const char **var,char *value) +static void var_override (char **var,char *value) {if (value) { -free ((void *)*var); +free (*var); *var =xstrdup (value); } }
@@ -1233,11 +1233,13 @@ static CURL *get_curl_handle(void)return result; } -static void set_from_env (const char **var,const char *envname) +static void set_from_env (char **var,const char *envname) {const char *val =getenv (envname); -if (val) - *var = val; +if (val) { +FREE_AND_NULL (*var); + *var =xstrdup (val); + } }void http_init (struct remote *remote,const char *url,int proactive_auth)
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 6/20 config: clarify memory ownership in `git_config_pathname()`
In reply to Patrick SteinhardtThe out parameter of git_config_pathname() is a const char ** even though we transfer ownership of memory to the caller. This is quite misleading and has led to many memory leaks all over the place. Adapt the parameter to instead be char **.
--- builtin/blame.c | 2 +- builtin/commit.c | 2 +- builtin/config.c | 2 +- builtin/log.c | 2 +- builtin/receive-pack.c | 4 ++-- config.c | 10 +++++----- config.h | 8 ++++---- diff.c | 2 +- environment.c | 6 +++--- environment.h | 6 +++--- fetch-pack.c | 4 ++-- fsck.c | 4 ++-- fsmonitor-settings.c | 5 ++++- gpg-interface.c | 4 +++- http.c | 12 ++++++------ mailmap.c | 2 +- mailmap.h | 2 +- setup.c | 6 +++--- 18 files changed, 44 insertions(+), 39 deletions(-)
builtin/blame.c +1 -1
@@ -718,7 +718,7 @@ static int git_blame_config(const char *var, const char *value,return 0 ; }if (!strcmp (var,"blame.ignorerevsfile" )) { -const char *str; +char *str;int ret; ret =git_config_pathname (&str, var, value);
builtin/commit.c +1 -1
@@ -107,7 +107,7 @@ static enum { } commit_style;static const char *logfile, *force_author; -static const char *template_file; +static char *template_file;/* * The _message variables are commit names from which to take * the commit message and/or authorship.
builtin/config.c +1 -1
@@ -277,7 +277,7 @@ static int format_config(struct strbuf *buf, const char *key_,else strbuf_addstr (buf, v ?"true" :"false" ); }else if (type ==TYPE_PATH ) { -const char *v; +char *v;if (git_config_pathname (&v, key_, value_) <0 )return -1 ;strbuf_addstr (buf, v);
builtin/log.c +1 -1
@@ -957,7 +957,7 @@ static int do_signoff;static enum auto_base_setting auto_base;static char *from;static const char *signature = git_version_string; -static const char *signature_file; +static char *signature_file;static enum cover_setting config_cover_letter;static const char *config_output_directory;static enum cover_from_description cover_from_description_mode =COVER_FROM_MESSAGE ;
builtin/receive-pack.c +2 -2
@@ -168,13 +168,13 @@ static int receive_pack_config(const char *var, const char *value, }if (strcmp (var,"receive.fsck.skiplist" ) ==0 ) { -const char *path; +char *path;if (git_config_pathname (&path, var, value))return 1 ;strbuf_addf (&fsck_msg_types,"%cskiplist=%s" , fsck_msg_types.len ?',' :'=' , path); -free ((char *)path); +free (path);return 0 ; }
config.c +5 -5
@@ -1346,7 +1346,7 @@ int git_config_string(const char **dest, const char *var, const char *value)return 0 ; } -int git_config_pathname (const char **dest,const char *var,const char *value) +int git_config_pathname (char **dest,const char *var,const char *value) {if (!value)return config_error_nonbool (var);
@@ -1597,7 +1597,7 @@ static int git_default_core_config(const char *var, const char *value,return git_config_string (&askpass_program, var, value);if (!strcmp (var,"core.excludesfile" )) { -free ((char *)excludes_file); +free (excludes_file);return git_config_pathname (&excludes_file, var, value); }
@@ -2494,7 +2494,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dreturn 1 ; } -int git_configset_get_pathname (struct config_set *set,const char *key,const char **dest) +int git_configset_get_pathname (struct config_set *set,const char *key,char **dest) {const char *value;if (!git_configset_get_value (set, key, &value,NULL ))
@@ -2639,7 +2639,7 @@ int repo_config_get_maybe_bool(struct repository *repo, }int repo_config_get_pathname (struct repository *repo, -const char *key,const char **dest) +const char *key,char **dest) {int ret;git_config_check_init (repo);
@@ -2738,7 +2738,7 @@ int git_config_get_maybe_bool(const char *key, int *dest)return repo_config_get_maybe_bool (the_repository, key, dest); } -int git_config_get_pathname (const char *key,const char **dest) +int git_config_get_pathname (const char *key,char **dest) {return repo_config_get_pathname (the_repository, key, dest); }
config.h +4 -4
@@ -286,7 +286,7 @@ int git_config_string(const char **, const char *, const char *);* Similar to `git_config_string`, but expands `~` or `~user` into the * user's home directory when found at the beginning of the path. */ -int git_config_pathname (const char **,const char *,const char *); +int git_config_pathname (char **,const char *,const char *);int git_config_expiry_date (timestamp_t *,const char *,const char *);int git_config_color (char *,const char *,const char *);
@@ -541,7 +541,7 @@ int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned lonint git_configset_get_bool (struct config_set *cs,const char *key,int *dest);int git_configset_get_bool_or_int (struct config_set *cs,const char *key,int *is_bool,int *dest);int git_configset_get_maybe_bool (struct config_set *cs,const char *key,int *dest); -int git_configset_get_pathname (struct config_set *cs,const char *key,const char **dest); +int git_configset_get_pathname (struct config_set *cs,const char *key,char **dest);/* Functions for reading a repository's config */ struct repository ;
@@ -577,7 +577,7 @@ int repo_config_get_bool_or_int(struct repository *repo,int repo_config_get_maybe_bool (struct repository *repo,const char *key,int *dest);int repo_config_get_pathname (struct repository *repo, -const char *key,const char **dest); +const char *key,char **dest);/* * Functions for reading protected config. By definition, protected
@@ -687,7 +687,7 @@ int git_config_get_maybe_bool(const char *key, int *dest);* Similar to `git_config_get_string`, but expands `~` or `~user` into * the user's home directory when found at the beginning of the path. */ -int git_config_get_pathname (const char *key,const char **dest); +int git_config_get_pathname (const char *key,char **dest);int git_config_get_index_threads (int *dest);int git_config_get_split_index (void );
diff.c +1 -1
@@ -58,7 +58,7 @@ static int diff_context_default = 3;static int diff_interhunk_context_default;static const char *diff_word_regex_cfg;static const char *external_diff_cmd_cfg; -static const char *diff_order_file_cfg; +static char *diff_order_file_cfg;int diff_auto_refresh_index =1 ;static int diff_mnemonic_prefix;static int diff_no_prefix;
environment.c +3 -3
@@ -46,8 +46,8 @@ const char *git_commit_encoding;const char *git_log_output_encoding;char *apply_default_whitespace;char *apply_default_ignorewhitespace; -const char *git_attributes_file; -const char *git_hooks_path; +char *git_attributes_file; +char *git_hooks_path;int zlib_compression_level =Z_BEST_SPEED ;int pack_compression_level =Z_DEFAULT_COMPRESSION ;int fsync_object_files =-1 ;
@@ -60,7 +60,7 @@ size_t delta_base_cache_limit = 96 * 1024 * 1024;unsigned long big_file_threshold =512 *1024 *1024 ;const char *editor_program;const char *askpass_program; -const char *excludes_file; +char *excludes_file;enum auto_crlf auto_crlf =AUTO_CRLF_FALSE ;enum eol core_eol =EOL_UNSET ;int global_conv_flags_eol =CONV_EOL_RNDTRP_WARN ;
environment.h +3 -3
@@ -131,8 +131,8 @@ extern int warn_ambiguous_refs;extern int warn_on_object_refname_ambiguity;extern char *apply_default_whitespace;extern char *apply_default_ignorewhitespace; -extern const char *git_attributes_file; -extern const char *git_hooks_path; +extern char *git_attributes_file; +extern char *git_hooks_path;extern int zlib_compression_level;extern int pack_compression_level;extern size_t packed_git_window_size;
@@ -229,7 +229,7 @@ extern const char *git_log_output_encoding;extern const char *editor_program;extern const char *askpass_program; -extern const char *excludes_file; +extern char *excludes_file;/* * Should we print an ellipsis after an abbreviated SHA-1 value
fetch-pack.c +2 -2
@@ -1865,13 +1865,13 @@ static int fetch_pack_config_cb(const char *var, const char *value,const char *msg_id;if (strcmp (var,"fetch.fsck.skiplist" ) ==0 ) { -const char *path; +char *path ;if (git_config_pathname (&path, var, value))return 1 ;strbuf_addf (&fsck_msg_types,"%cskiplist=%s" , fsck_msg_types.len ?',' :'=' , path); -free ((char *)path); +free (path);return 0 ; }
fsck.c +2 -2
@@ -1330,13 +1330,13 @@ int git_fsck_config(const char *var, const char *value,const char *msg_id;if (strcmp (var,"fsck.skiplist" ) ==0 ) { -const char *path; +char *path;struct strbuf sb =STRBUF_INIT ;if (git_config_pathname (&path, var, value))return 1 ;strbuf_addf (&sb,"skiplist=%s" , path); -free ((char *)path); +free (path);fsck_set_msg_types (options, sb.buf);strbuf_release (&sb);return 0 ;
fsmonitor-settings.c +4 -1
@@ -103,6 +103,7 @@ static struct fsmonitor_settings *alloc_settings(void)static void lookup_fsmonitor_settings (struct repository *r) {const char *const_str; +char *to_free =NULL ;int bool_value;if (r->settings.fsmonitor)
@@ -129,8 +130,9 @@ static void lookup_fsmonitor_settings(struct repository *r)break ;case -1 :/* config value set to an arbitrary string */ -if (repo_config_get_pathname (r,"core.fsmonitor" , &const_str)) +if (repo_config_get_pathname (r,"core.fsmonitor" , &to_free))return ;/* should not happen */ + const_str = to_free;break ;default :/* should not happen */
@@ -141,6 +143,7 @@ static void lookup_fsmonitor_settings(struct repository *r)fsm_settings__set_hook (r, const_str); elsefsm_settings__set_disabled (r ); +free (to_free); }enum fsmonitor_mode fsm_settings__get_mode (struct repository *r)
gpg-interface.c +3 -1
@@ -27,7 +27,9 @@ static void gpg_interface_lazy_init(void) }static char *configured_signing_key; -static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file; +static const char *ssh_default_key_command; +static char *ssh_allowed_signers; +static char *ssh_revocation_file;static enum signature_trust_level configured_min_trust_level =TRUST_UNDEFINED ;struct gpg_format {
http.c +6 -6
@@ -64,7 +64,7 @@ static char *ssl_key_type;static char *ssl_capath;static char *curl_no_proxy;#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY -static const char *ssl_pinnedkey; +static char *ssl_pinnedkey;#endif static char *ssl_cainfo;static long curl_low_speed_limit =-1 ;
@@ -108,7 +108,7 @@ static struct {static struct credential proxy_auth =CREDENTIAL_INIT ;static const char *curl_proxyuserpwd; -static const char *curl_cookie_file; +static char *curl_cookie_file;static int curl_save_cookies;struct credential http_auth =CREDENTIAL_INIT ;static int http_proactive_auth;
@@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslversion" , var))return git_config_string (&ssl_version, var, value);if (!strcmp ("http.sslcert" , var)) -return git_config_pathname ((const char **)&ssl_cert, var, value); +return git_config_pathname (&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var))return git_config_string ((const char **)&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var)) -return git_config_pathname ((const char **)&ssl_key, var, value); +return git_config_pathname (&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var))return git_config_string ((const char **)&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var)) -return git_config_pathname ((const char **)&ssl_capath, var, value); +return git_config_pathname (&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var)) -return git_config_pathname ((const char **)&ssl_cainfo, var, value); +return git_config_pathname (&ssl_cainfo, var, value);if (!strcmp ("http.sslcertpasswordprotected" , var)) { ssl_cert_password_required =git_config_bool (var, value);return 0 ;
mailmap.c +1 -1
@@ -6,7 +6,7 @@#include "object-store-ll.h" #include "setup.h" -const char *git_mailmap_file; +char *git_mailmap_file;const char *git_mailmap_blob;struct mailmap_info {
mailmap.h +1 -1
@@ -3,7 +3,7 @@struct string_list ; -extern const char *git_mailmap_file; +extern char *git_mailmap_file;extern const char *git_mailmap_blob;int read_mailmap (struct string_list *map);
setup.c +3 -3
@@ -1177,13 +1177,13 @@ static int safe_directory_cb(const char *key, const char *value, }else if (!strcmp (value,"*" )) { data->is_safe =1 ; }else { -const char *interpolated =NULL ; +char *interpolated =NULL ;if (!git_config_pathname (&interpolated, key, value) && !fspathcmp (data->path, interpolated ? interpolated : value)) data->is_safe =1 ; -free ((char *)interpolated); +free (interpolated); }return 0 ;
@@ -1822,7 +1822,7 @@ static int template_dir_cb(const char *key, const char *value,char *path =NULL ;FREE_AND_NULL (data->path); -if (!git_config_pathname ((const char **)&path, key, value)) +if (!git_config_pathname (&path, key, value)) data->path = path ? path :xstrdup (value); }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 7/20 diff: refactor code to clarify memory ownership of prefixes
In reply to Patrick SteinhardtThe source end destination prefixes are tracked in a const char * array, but may at times contain allocated strings. The result is that those strings may be leaking because we never free them.
Refactor the code to always store allocated strings in those variables, freeing them as required. This requires us to handle the default values a bit different compared to before. But given that there is only a single callsite where we use the variables to struct diff_options it's easy to handle the defaults there.
--- diff.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-)
diff.c +10 -8
@@ -62,8 +62,8 @@ static char *diff_order_file_cfg;int diff_auto_refresh_index =1 ;static int diff_mnemonic_prefix;static int diff_no_prefix; -static const char *diff_src_prefix ="a/" ; -static const char *diff_dst_prefix ="b/" ; +static char *diff_src_prefix; +static char *diff_dst_prefix;static int diff_relative;static int diff_stat_name_width;static int diff_stat_graph_width;
@@ -411,10 +411,12 @@ int git_diff_ui_config(const char *var, const char *value,return 0 ; }if (!strcmp (var,"diff.srcprefix" )) { -return git_config_string (&diff_src_prefix, var, value); +FREE_AND_NULL (diff_src_prefix); +return git_config_string ((const char **) &diff_src_prefix, var, value); }if (!strcmp (var,"diff.dstprefix" )) { -return git_config_string (&diff_dst_prefix, var, value); +FREE_AND_NULL (diff_dst_prefix); +return git_config_string ((const char **) &diff_dst_prefix, var, value); }if (!strcmp (var,"diff.relative" )) { diff_relative =git_config_bool (var, value);
@@ -3433,8 +3435,8 @@ void diff_set_noprefix(struct diff_options *options)void diff_set_default_prefix (struct diff_options *options) { - options->a_prefix = diff_src_prefix; - options->b_prefix = diff_dst_prefix; + options->a_prefix = diff_src_prefix ? diff_src_prefix :"a/" ; + options->b_prefix = diff_dst_prefix ? diff_dst_prefix :"b/" ; } struct userdiff_driver *get_textconv(struct repository *r,
@@ -5371,8 +5373,8 @@ static int diff_opt_default_prefix(const struct option *opt,BUG_ON_OPT_NEG (unset);BUG_ON_OPT_ARG (optarg); - diff_src_prefix ="a/" ; - diff_dst_prefix ="b/" ; +FREE_AND_NULL (diff_src_prefix); +FREE_AND_NULL (diff_dst_prefix);diff_set_default_prefix (options);return 0 ; }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH 07/20] diff: refactor code to clarify memory ownership of prefixes
Quoted 1 line · Patrick Steinhardt
The source end destination prefixes are tracked in a
const char *
s/end/and/
Quoted 10 lines
array, but may at times contain allocated strings. The result is that those strings may be leaking because we never free them.
Refactor the code to always store allocated strings in those variables, freeing them as required. This requires us to handle the default values a bit different compared to before. But given that there is only a single callsite where we use the variables to
struct diff_optionsit's easy to handle the defaults there.Signed-off-by Patrick Steinhardt <[email protected]>
PATCH 8/20 convert: refactor code to clarify ownership of check_roundtrip_encoding
In reply to Patrick SteinhardtThe check_roundtrip_encoding variable is tracked in a const char * even though it may contain allocated strings at times. The result is that those strings may be leaking because we never free them.
Refactor the code to always store allocated strings in this variable. The default value is handled in check_roundtrip() now, which is the only user of the variable.
--- config.c | 6 ++++-- convert.c | 24 +++++++++++++----------- convert.h | 2 +- environment.c | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-)
config.c +4 -2
@@ -1564,8 +1564,10 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.checkroundtripencoding" )) -return git_config_string (&check_roundtrip_encoding, var, value); +if (!strcmp (var,"core.checkroundtripencoding" )) { +FREE_AND_NULL (check_roundtrip_encoding); +return git_config_string ((const char **) &check_roundtrip_encoding, var, value); + }if (!strcmp (var,"core.notesref" )) {if (!value)
convert.c +13 -11
@@ -345,30 +345,32 @@ static int check_roundtrip(const char *enc_name)* space separated encodings (eg. "UTF-16, ASCII, CP1125"). * Search for the given encoding in that string. */ -const char *found =strcasestr (check_roundtrip_encoding, enc_name); +const char *encoding = check_roundtrip_encoding ? + check_roundtrip_encoding :"SHIFT-JIS" ; +const char *found =strcasestr (encoding, enc_name);const char *next;int len;if (!found)return 0 ; next = found +strlen (enc_name); - len =strlen (check_roundtrip_encoding); + len =strlen (encoding);return (found && (/* -* check that the found encoding is at the -* beginning of check_roundtrip_encoding or -* that it is prefixed with a space or comma +* Check that the found encoding is at the beginning of +* encoding or that it is prefixed with a space or +* comma. */ - found == check_roundtrip_encoding || ( + found == encoding || ( (isspace (found[-1 ]) || found[-1 ] ==',' ) ) ) && (/* -* check that the found encoding is at the -* end of check_roundtrip_encoding or -* that it is suffixed with a space or comma +* Check that the found encoding is at the end of +* encoding or that it is suffixed with a space +* or comma. */ - next == check_roundtrip_encoding + len || ( - next < check_roundtrip_encoding + len && + next == encoding + len || ( + next < encoding + len && (isspace (next[0 ]) || next[0 ] ==',' ) ) ));
convert.h +1 -1
@@ -92,7 +92,7 @@ void convert_attrs(struct index_state *istate, struct conv_attrs *ca, const char *path);extern enum eol core_eol; -extern const char *check_roundtrip_encoding; +extern char *check_roundtrip_encoding;const char *get_cached_convert_stats_ascii (struct index_state *istate,const char *path);const char *get_wt_convert_stats_ascii (const char *path);
environment.c +1 -1
@@ -64,7 +64,7 @@ char *excludes_file;enum auto_crlf auto_crlf =AUTO_CRLF_FALSE ;enum eol core_eol =EOL_UNSET ;int global_conv_flags_eol =CONV_EOL_RNDTRP_WARN ; -const char *check_roundtrip_encoding ="SHIFT-JIS" ; +char *check_roundtrip_encoding;enum branch_track git_branch_track =BRANCH_TRACK_REMOTE ;enum rebase_setup_type autorebase =AUTOREBASE_NEVER ;enum push_default_type push_default =PUSH_DEFAULT_UNSPECIFIED ;
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 9/20 builtin/log: stop using globals for log config
In reply to Patrick SteinhardtWe're using global variables to store the log configuration. Many of these can be set both via the command line and via the config, and depending on how they are being set, they may contain allocated strings. This leads to hard-to-track memory ownership and memory leaks.
Refactor the code to instead use a struct log_config that is being allocated on the stack. This allows us to more clearly scope the variables, track memory ownership and ultimately release the memory.
This also prepares us for a change to git_config_string(), which will be adapted to have a char ** out parameter instead of const char **.
--- builtin/log.c | 259 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 156 insertions(+), 103 deletions(-)
builtin/log.c +156 -103
@@ -48,22 +48,8 @@#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100#define FORMAT_PATCH_NAME_MAX_DEFAULT 64 -/* Set a default date-time format for git log ("log.date" config variable) */ -static const char *default_date_mode =NULL ; - -static int default_abbrev_commit; -static int default_show_root =1 ; -static int default_follow; -static int default_show_signature; -static int default_encode_email_headers =1 ; -static int decoration_style; -static int decoration_given; -static int use_mailmap_config =1 ;static unsigned int force_in_body_from;static int stdout_mboxrd; -static const char *fmt_patch_subject_prefix ="PATCH" ; -static int fmt_patch_name_max =FORMAT_PATCH_NAME_MAX_DEFAULT ; -static const char *fmt_pretty;static int format_no_prefix;static const char *const builtin_log_usage[] = {
@@ -111,6 +97,39 @@ static int parse_decoration_style(const char *value)return -1 ; } +struct log_config { +int default_abbrev_commit; +int default_show_root; +int default_follow; +int default_show_signature; +int default_encode_email_headers; +int decoration_style; +int decoration_given; +int use_mailmap_config; +char *fmt_patch_subject_prefix; +int fmt_patch_name_max; +char *fmt_pretty; +char *default_date_mode; +}; + +static void log_config_init (struct log_config *cfg) +{ +memset (cfg,0 ,sizeof (*cfg)); + cfg->default_show_root =1 ; + cfg->default_encode_email_headers =1 ; + cfg->use_mailmap_config =1 ; + cfg->fmt_patch_subject_prefix =xstrdup ("PATCH" ); + cfg->fmt_patch_name_max =FORMAT_PATCH_NAME_MAX_DEFAULT ; + cfg->decoration_style =auto_decoration_style (); +} + +static void log_config_release (struct log_config *cfg) +{ +free (cfg->default_date_mode); +free (cfg->fmt_pretty); +free (cfg->fmt_patch_subject_prefix); +} +static int use_default_decoration_filter =1 ;static struct string_list decorate_refs_exclude =STRING_LIST_INIT_NODUP ;static struct string_list decorate_refs_exclude_config =STRING_LIST_INIT_NODUP ;
@@ -127,20 +146,22 @@ static int clear_decorations_callback(const struct option *opt UNUSED,return 0 ; } -static int decorate_callback(const struct option *opt UNUSED, const char *arg, +static int decorate_callback (const struct option *opt,const char *arg,int unset) { +struct log_config *cfg = opt->value; +if (unset) - decoration_style =0 ; + cfg->decoration_style =0 ;else if (arg) - decoration_style =parse_decoration_style (arg); + cfg->decoration_style =parse_decoration_style (arg);else - decoration_style =DECORATE_SHORT_REFS ; + cfg->decoration_style =DECORATE_SHORT_REFS ; -if (decoration_style <0 ) +if (cfg->decoration_style <0 )die (_ ("invalid --decorate option: %s" ), arg); - decoration_given =1 ; + cfg->decoration_given =1 ;return 0 ; }
@@ -160,32 +181,26 @@ static int log_line_range_callback(const struct option *option, const char *arg,return 0 ; } -static void init_log_defaults (void ) +static void cmd_log_init_defaults (struct rev_info *rev, +struct log_config *cfg) { -init_diff_ui_defaults (); - - decoration_style =auto_decoration_style (); -} - -static void cmd_log_init_defaults (struct rev_info *rev) -{ -if (fmt_pretty) -get_commit_format (fmt_pretty, rev); -if (default_follow) +if (cfg->fmt_pretty) +get_commit_format (cfg->fmt_pretty, rev); +if (cfg->default_follow) rev->diffopt.flags.default_follow_renames =1 ; rev->verbose_header =1 ;init_diffstat_widths (&rev->diffopt); rev->diffopt.flags.recursive =1 ; rev->diffopt.flags.allow_textconv =1 ; - rev->abbrev_commit = default_abbrev_commit; - rev->show_root_diff = default_show_root; - rev->subject_prefix = fmt_patch_subject_prefix; - rev->patch_name_max = fmt_patch_name_max; - rev->show_signature = default_show_signature; - rev->encode_email_headers = default_encode_email_headers; + rev->abbrev_commit = cfg->default_abbrev_commit; + rev->show_root_diff = cfg->default_show_root; + rev->subject_prefix = cfg->fmt_patch_subject_prefix; + rev->patch_name_max = cfg->fmt_patch_name_max; + rev->show_signature = cfg->default_show_signature; + rev->encode_email_headers = cfg->default_encode_email_headers; -if (default_date_mode) -parse_date_format (default_date_mode, &rev->date_mode); +if (cfg->default_date_mode) +parse_date_format (cfg->default_date_mode, &rev->date_mode); }static void set_default_decoration_filter (struct decoration_filter *decoration_filter)
@@ -233,7 +248,8 @@ static void set_default_decoration_filter(struct decoration_filter *decoration_f }static void cmd_log_init_finish (int argc,const char **argv,const char *prefix, -struct rev_info *rev,struct setup_revision_opt *opt) +struct rev_info *rev,struct setup_revision_opt *opt, +struct log_config *cfg) {struct userformat_want w;int quiet =0 , source =0 , mailmap;
@@ -258,7 +274,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, N_("pattern"), N_("only decorate refs that match <pattern>")),OPT_STRING_LIST (0 ,"decorate-refs-exclude" , &decorate_refs_exclude,N_ ("pattern" ),N_ ("do not decorate refs that match <pattern>" )), -OPT_CALLBACK_F (0 ,"decorate" ,NULL ,NULL ,N_ ("decorate options" ), +OPT_CALLBACK_F (0 ,"decorate" , cfg,NULL ,N_ ("decorate options" ),PARSE_OPT_OPTARG , decorate_callback), OPT_CALLBACK('L', NULL, &line_cb, "range:file", N_("trace the evolution of line range <start>,<end> or function :<funcname> in <file>"),
@@ -269,7 +285,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, line_cb.rev = rev; line_cb.prefix = prefix; - mailmap = use_mailmap_config; + mailmap = cfg->use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT |
@@ -314,8 +330,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,* "log --pretty=raw" is special; ignore UI oriented * configuration variables such as decoration. */ -if (!decoration_given) - decoration_style =0 ; +if (!cfg->decoration_given) + cfg->decoration_style =0 ;if (!rev->abbrev_commit_given) rev->abbrev_commit =0 ; }
@@ -326,24 +342,24 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,* Disable decoration loading if the format will not * show them anyway. */ - decoration_style =0 ; - }else if (!decoration_style) { + cfg->decoration_style =0 ; + }else if (!cfg->decoration_style) {/* * If we are going to show them, make sure we do load * them here, but taking care not to override a * specific style set by config or --decorate. */ - decoration_style =DECORATE_SHORT_REFS ; + cfg->decoration_style =DECORATE_SHORT_REFS ; } } -if (decoration_style || rev->simplify_by_decoration) { +if (cfg->decoration_style || rev->simplify_by_decoration) {set_default_decoration_filter (&decoration_filter); -if (decoration_style) +if (cfg->decoration_style) rev->show_decorations =1 ; -load_ref_decorations (&decoration_filter, decoration_style); +load_ref_decorations (&decoration_filter, cfg->decoration_style); }if (rev->line_level_traverse)
@@ -353,16 +369,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, }static void cmd_log_init (int argc,const char **argv,const char *prefix, -struct rev_info *rev,struct setup_revision_opt *opt) +struct rev_info *rev,struct setup_revision_opt *opt, +struct log_config *cfg) { -cmd_log_init_defaults (rev); -cmd_log_init_finish (argc, argv, prefix, rev, opt); -} - -static int cmd_log_deinit (int ret,struct rev_info *rev) -{ -release_revisions (rev); -return ret; +cmd_log_init_defaults (rev, cfg); +cmd_log_init_finish (argc, argv, prefix, rev, opt, cfg); }/*
@@ -566,30 +577,37 @@ static int cmd_log_walk(struct rev_info *rev)static int git_log_config (const char *var,const char *value,const struct config_context *ctx,void *cb) { +struct log_config *cfg = cb;const char *slot_name; -if (!strcmp (var,"format.pretty" )) -return git_config_string (&fmt_pretty, var, value); -if (!strcmp (var,"format.subjectprefix" )) -return git_config_string (&fmt_patch_subject_prefix, var, value); +if (!strcmp (var,"format.pretty" )) { +FREE_AND_NULL (cfg->fmt_pretty); +return git_config_string ((const char **) &cfg->fmt_pretty, var, value); + } +if (!strcmp (var,"format.subjectprefix" )) { +FREE_AND_NULL (cfg->fmt_patch_subject_prefix); +return git_config_string ((const char **) &cfg->fmt_patch_subject_prefix, var, value); + }if (!strcmp (var,"format.filenamemaxlength" )) { - fmt_patch_name_max =git_config_int (var, value, ctx->kvi); + cfg->fmt_patch_name_max =git_config_int (var, value, ctx->kvi);return 0 ; }if (!strcmp (var,"format.encodeemailheaders" )) { - default_encode_email_headers =git_config_bool (var, value); + cfg->default_encode_email_headers =git_config_bool (var, value);return 0 ; }if (!strcmp (var,"log.abbrevcommit" )) { - default_abbrev_commit =git_config_bool (var, value); + cfg->default_abbrev_commit =git_config_bool (var, value);return 0 ; } -if (!strcmp (var,"log.date" )) -return git_config_string (&default_date_mode, var, value); +if (!strcmp (var,"log.date" )) { +FREE_AND_NULL (cfg->default_date_mode); +return git_config_string ((const char **) &cfg->default_date_mode, var, value); + }if (!strcmp (var,"log.decorate" )) { - decoration_style =parse_decoration_style (value); -if (decoration_style <0 ) - decoration_style =0 ;/* maybe warn? */ + cfg->decoration_style =parse_decoration_style (value); +if (cfg->decoration_style <0 ) + cfg->decoration_style =0 ;/* maybe warn? */ return 0 ; }if (!strcmp (var,"log.diffmerges" )) {
@@ -598,21 +616,21 @@ static int git_log_config(const char *var, const char *value,return diff_merges_config (value); }if (!strcmp (var,"log.showroot" )) { - default_show_root =git_config_bool (var, value); + cfg->default_show_root =git_config_bool (var, value);return 0 ; }if (!strcmp (var,"log.follow" )) { - default_follow =git_config_bool (var, value); + cfg->default_follow =git_config_bool (var, value);return 0 ; }if (skip_prefix (var,"color.decorate." , &slot_name))return parse_decorate_color_config (var, slot_name, value);if (!strcmp (var,"log.mailmap" )) { - use_mailmap_config =git_config_bool (var, value); + cfg->use_mailmap_config =git_config_bool (var, value);return 0 ; }if (!strcmp (var,"log.showsignature" )) { - default_show_signature =git_config_bool (var, value); + cfg->default_show_signature =git_config_bool (var, value);return 0 ; }
@@ -621,11 +639,14 @@ static int git_log_config(const char *var, const char *value,int cmd_whatchanged (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;struct setup_revision_opt opt; +int ret; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter);
@@ -635,10 +656,15 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)memset (&opt,0 ,sizeof (opt)); opt.def ="HEAD" ; opt.revarg_opt =REVARG_COMMITTISH ; -cmd_log_init (argc, argv, prefix, &rev, &opt); +cmd_log_init (argc, argv, prefix, &rev, &opt, &cfg);if (!rev.diffopt.output_format) rev.diffopt.output_format =DIFF_FORMAT_RAW ; -return cmd_log_deinit (cmd_log_walk (&rev), &rev); + + ret =cmd_log_walk (&rev); + +release_revisions (&rev); +log_config_release (&cfg); +return ret; }static void show_tagger (const char *buf,struct rev_info *rev)
@@ -733,14 +759,16 @@ static void show_setup_revisions_tweak(struct rev_info *rev)int cmd_show (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;unsigned int i;struct setup_revision_opt opt;struct pathspec match_all;int ret =0 ; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);if (the_repository->gitdir) {prepare_repo_settings (the_repository);
@@ -759,10 +787,14 @@ int cmd_show(int argc, const char **argv, const char *prefix)memset (&opt,0 ,sizeof (opt)); opt.def ="HEAD" ; opt.tweak = show_setup_revisions_tweak; -cmd_log_init (argc, argv, prefix, &rev, &opt); +cmd_log_init (argc, argv, prefix, &rev, &opt, &cfg); -if (!rev.no_walk) -return cmd_log_deinit (cmd_log_walk (&rev), &rev); +if (!rev.no_walk) { + ret =cmd_log_walk (&rev); +release_revisions (&rev); +log_config_release (&cfg); +return ret; + } rev.diffopt.no_free =1 ;for (i =0 ; i < rev.pending.nr && !ret; i++) {
@@ -832,8 +864,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.diffopt.no_free =0 ;diff_free (&rev.diffopt); +release_revisions (&rev); +log_config_release (&cfg); -return cmd_log_deinit (ret, &rev); +return ret; }/*
@@ -841,11 +875,14 @@ int cmd_show(int argc, const char **argv, const char *prefix)*/ int cmd_log_reflog (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;struct setup_revision_opt opt; +int ret; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);init_reflog_walk (&rev.reflog_info);
@@ -854,14 +891,18 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) rev.verbose_header =1 ;memset (&opt,0 ,sizeof (opt)); opt.def ="HEAD" ; -cmd_log_init_defaults (&rev); +cmd_log_init_defaults (&rev, &cfg); rev.abbrev_commit =1 ; rev.commit_format =CMIT_FMT_ONELINE ; rev.use_terminator =1 ; rev.always_show_header =1 ; -cmd_log_init_finish (argc, argv, prefix, &rev, &opt); +cmd_log_init_finish (argc, argv, prefix, &rev, &opt, &cfg); + + ret =cmd_log_walk (&rev); -return cmd_log_deinit (cmd_log_walk (&rev), &rev); +release_revisions (&rev); +log_config_release (&cfg); +return ret; }static void log_setup_revisions_tweak (struct rev_info *rev)
@@ -876,11 +917,14 @@ static void log_setup_revisions_tweak(struct rev_info *rev)int cmd_log (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;struct setup_revision_opt opt; +int ret; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter);
@@ -890,8 +934,13 @@ int cmd_log(int argc, const char **argv, const char *prefix) opt.def ="HEAD" ; opt.revarg_opt =REVARG_COMMITTISH ; opt.tweak = log_setup_revisions_tweak; -cmd_log_init (argc, argv, prefix, &rev, &opt); -return cmd_log_deinit (cmd_log_walk (&rev), &rev); +cmd_log_init (argc, argv, prefix, &rev, &opt, &cfg); + + ret =cmd_log_walk (&rev); + +release_revisions (&rev); +log_config_release (&cfg); +return ret; }/* format-patch */
@@ -1884,6 +1933,7 @@ static void infer_range_diff_ranges(struct strbuf *r1,int cmd_format_patch (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct commit *commit;struct commit **list =NULL ;struct rev_info rev;
@@ -1943,7 +1993,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")),OPT_STRING ('v' ,"reroll-count" , &reroll_count,N_ ("reroll-count" ),N_ ("mark the series as Nth re-roll" )), -OPT_INTEGER (0 ,"filename-max-length" , &fmt_patch_name_max, +OPT_INTEGER (0 ,"filename-max-length" , &cfg.fmt_patch_name_max,N_ ("max length of output filename" )), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add <rfc> (default 'RFC') before 'PATCH'"),
@@ -2017,16 +2067,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_to.strdup_strings =1 ; extra_cc.strdup_strings =1 ; -init_log_defaults (); +log_config_init (&cfg); +init_diff_ui_defaults ();init_display_notes (¬es_opt); -git_config (git_format_config,NULL ); +git_config (git_format_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter); rev.show_notes = show_notes;memcpy (&rev.notes_opt, ¬es_opt,sizeof (notes_opt)); rev.commit_format =CMIT_FMT_EMAIL ; - rev.encode_email_headers = default_encode_email_headers; + rev.encode_email_headers = cfg.default_encode_email_headers; rev.expand_tabs_in_log_default =0 ; rev.verbose_header =1 ; rev.diff =1 ;
@@ -2037,7 +2088,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def ="HEAD" ; s_r_opt.revarg_opt =REVARG_COMMITTISH ; -strbuf_addstr (&sprefix, fmt_patch_subject_prefix); +strbuf_addstr (&sprefix, cfg.fmt_patch_subject_prefix);if (format_no_prefix)diff_set_noprefix (&rev.diffopt);
@@ -2059,8 +2110,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from;/* Make sure "0000-$sub.patch" gives non-negative length for $sub */ -if (fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) - fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix); +if (cfg.fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) + cfg.fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix);if (cover_from_description_arg) cover_from_description_mode =parse_cover_from_description (cover_from_description_arg);
@@ -2156,7 +2207,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header =1 ; rev.zero_commit = zero_commit; - rev.patch_name_max = fmt_patch_name_max; + rev.patch_name_max = cfg.fmt_patch_name_max;if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary =1 ;
@@ -2450,7 +2501,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)if (rev.ref_message_ids)string_list_clear (rev.ref_message_ids,0 );free (rev.ref_message_ids); -return cmd_log_deinit (0 , &rev); +release_revisions (&rev); +log_config_release (&cfg); +return 0 ; }static int add_pending_commit (const char *arg,struct rev_info *revs,int flags)
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 10/20 builtin/log: stop using globals for format config
In reply to Patrick SteinhardtThis commit does the exact same as the preceding commit, only for the format configuration instead of the log configuration.
--- builtin/log.c | 467 ++++++++++++++++++++++++++++---------------------- 1 file changed, 265 insertions(+), 202 deletions(-)
builtin/log.c +265 -202
@@ -945,36 +945,6 @@ int cmd_log(int argc, const char **argv, const char *prefix)/* format-patch */ -static const char *fmt_patch_suffix =".patch" ; -static int numbered =0 ; -static int auto_number =1 ; - -static char *default_attach =NULL ; - -static struct string_list extra_hdr =STRING_LIST_INIT_NODUP ; -static struct string_list extra_to =STRING_LIST_INIT_NODUP ; -static struct string_list extra_cc =STRING_LIST_INIT_NODUP ; - -static void add_header (const char *value) -{ -struct string_list_item *item; -int len =strlen (value); -while (len && value[len -1 ] =='\n' ) - len--; - -if (!strncasecmp (value,"to: " ,4 )) { - item =string_list_append (&extra_to, value +4 ); - len -=4 ; - }else if (!strncasecmp (value,"cc: " ,4 )) { - item =string_list_append (&extra_cc, value +4 ); - len -=4 ; - }else { - item =string_list_append (&extra_hdr, value); - } - - item->string[len] ='\0' ; -} -enum cover_setting {COVER_UNSET ,COVER_OFF ,
@@ -1001,17 +971,61 @@ enum auto_base_setting {AUTO_BASE_WHEN_ABLE }; -static enum thread_level thread; -static int do_signoff; -static enum auto_base_setting auto_base; -static char *from; -static const char *signature = git_version_string; -static char *signature_file; -static enum cover_setting config_cover_letter; -static const char *config_output_directory; -static enum cover_from_description cover_from_description_mode =COVER_FROM_MESSAGE ; -static int show_notes; -static struct display_notes_opt notes_opt; +struct format_config { +struct log_config log; +enum thread_level thread; +int do_signoff; +enum auto_base_setting auto_base; +char *base_commit; +char *from; +char *signature; +char *signature_file; +enum cover_setting config_cover_letter; +char *config_output_directory; +enum cover_from_description cover_from_description_mode; +int show_notes; +struct display_notes_opt notes_opt; +int numbered_cmdline_opt; +int numbered; +int auto_number; +char *default_attach; +struct string_list extra_hdr; +struct string_list extra_to; +struct string_list extra_cc; +int keep_subject; +int subject_prefix; +struct strbuf sprefix; +char *fmt_patch_suffix; +}; + +static void format_config_init (struct format_config *cfg) +{ +memset (cfg,0 ,sizeof (*cfg)); +log_config_init (&cfg->log); + cfg->cover_from_description_mode =COVER_FROM_MESSAGE ; + cfg->auto_number =1 ; +string_list_init_dup (&cfg->extra_hdr); +string_list_init_dup (&cfg->extra_to); +string_list_init_dup (&cfg->extra_cc); +strbuf_init (&cfg->sprefix,0 ); + cfg->fmt_patch_suffix =xstrdup (".patch" ); +} + +static void format_config_release (struct format_config *cfg) +{ +log_config_release (&cfg->log); +free (cfg->base_commit); +free (cfg->from); +free (cfg->signature); +free (cfg->signature_file); +free (cfg->config_output_directory); +free (cfg->default_attach); +string_list_clear (&cfg->extra_hdr,0 ); +string_list_clear (&cfg->extra_to,0 ); +string_list_clear (&cfg->extra_cc,0 ); +strbuf_release (&cfg->sprefix); +free (cfg->fmt_patch_suffix); +}static enum cover_from_description parse_cover_from_description (const char *arg) {
@@ -1029,27 +1043,51 @@ static enum cover_from_description parse_cover_from_description(const char *arg)die (_ ("%s: invalid cover from description mode" ), arg); } +static void add_header (struct format_config *cfg,const char *value) +{ +struct string_list_item *item; +int len =strlen (value); +while (len && value[len -1 ] =='\n' ) + len--; + +if (!strncasecmp (value,"to: " ,4 )) { + item =string_list_append (&cfg->extra_to, value +4 ); + len -=4 ; + }else if (!strncasecmp (value,"cc: " ,4 )) { + item =string_list_append (&cfg->extra_cc, value +4 ); + len -=4 ; + }else { + item =string_list_append (&cfg->extra_hdr, value); + } + + item->string[len] ='\0' ; +} +static int git_format_config (const char *var,const char *value,const struct config_context *ctx,void *cb) { +struct format_config *cfg = cb; +if (!strcmp (var,"format.headers" )) {if (!value)die (_ ("format.headers without value" )); -add_header (value); +add_header (cfg, value);return 0 ; } -if (!strcmp (var,"format.suffix" )) -return git_config_string (&fmt_patch_suffix, var, value); +if (!strcmp (var,"format.suffix" )) { +FREE_AND_NULL (cfg->fmt_patch_suffix); +return git_config_string ((const char **) &cfg->fmt_patch_suffix, var, value); + }if (!strcmp (var,"format.to" )) {if (!value)return config_error_nonbool (var); -string_list_append (&extra_to, value); +string_list_append (&cfg->extra_to, value);return 0 ; }if (!strcmp (var,"format.cc" )) {if (!value)return config_error_nonbool (var); -string_list_append (&extra_cc, value); +string_list_append (&cfg->extra_cc, value);return 0 ; } if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
@@ -1058,69 +1096,76 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.numbered" )) {if (value && !strcasecmp (value,"auto" )) { - auto_number =1 ; + cfg->auto_number =1 ;return 0 ; } - numbered =git_config_bool (var, value); - auto_number = auto_number && numbered; + cfg->numbered =git_config_bool (var, value); + cfg->auto_number = cfg->auto_number && cfg->numbered;return 0 ; }if (!strcmp (var,"format.attach" )) { -if (value && *value) - default_attach =xstrdup (value); -else if (value && !*value) -FREE_AND_NULL (default_attach); -else - default_attach =xstrdup (git_version_string); +if (value && *value) { +FREE_AND_NULL (cfg->default_attach); + cfg->default_attach =xstrdup (value); + }else if (value && !*value) { +FREE_AND_NULL (cfg->default_attach); + }else { +FREE_AND_NULL (cfg->default_attach); + cfg->default_attach =xstrdup (git_version_string); + }return 0 ; }if (!strcmp (var,"format.thread" )) {if (value && !strcasecmp (value,"deep" )) { - thread =THREAD_DEEP ; + cfg->thread =THREAD_DEEP ;return 0 ; }if (value && !strcasecmp (value,"shallow" )) { - thread =THREAD_SHALLOW ; + cfg->thread =THREAD_SHALLOW ;return 0 ; } - thread =git_config_bool (var, value) ?THREAD_SHALLOW :THREAD_UNSET ; + cfg->thread =git_config_bool (var, value) ?THREAD_SHALLOW :THREAD_UNSET ;return 0 ; }if (!strcmp (var,"format.signoff" )) { - do_signoff =git_config_bool (var, value); + cfg->do_signoff =git_config_bool (var, value);return 0 ; } -if (!strcmp (var,"format.signature" )) -return git_config_string (&signature, var, value); -if (!strcmp (var,"format.signaturefile" )) -return git_config_pathname (&signature_file, var, value); +if (!strcmp (var,"format.signature" )) { +FREE_AND_NULL (cfg->signature); +return git_config_string ((const char **) &cfg->signature, var, value); + } +if (!strcmp (var,"format.signaturefile" )) { +FREE_AND_NULL (cfg->signature_file); +return git_config_pathname (&cfg->signature_file, var, value); + }if (!strcmp (var,"format.coverletter" )) {if (value && !strcasecmp (value,"auto" )) { - config_cover_letter =COVER_AUTO ; + cfg->config_cover_letter =COVER_AUTO ;return 0 ; } - config_cover_letter =git_config_bool (var, value) ?COVER_ON :COVER_OFF ; + cfg->config_cover_letter =git_config_bool (var, value) ?COVER_ON :COVER_OFF ;return 0 ; } -if (!strcmp (var,"format.outputdirectory" )) -return git_config_string (&config_output_directory, var, value); +if (!strcmp (var,"format.outputdirectory" )) { +FREE_AND_NULL (cfg->config_output_directory); +return git_config_string ((const char **) &cfg->config_output_directory, var, value); + }if (!strcmp (var,"format.useautobase" )) {if (value && !strcasecmp (value,"whenAble" )) { - auto_base =AUTO_BASE_WHEN_ABLE ; + cfg->auto_base =AUTO_BASE_WHEN_ABLE ;return 0 ; } - auto_base =git_config_bool (var, value) ?AUTO_BASE_ALWAYS :AUTO_BASE_NEVER ; + cfg->auto_base =git_config_bool (var, value) ?AUTO_BASE_ALWAYS :AUTO_BASE_NEVER ;return 0 ; }if (!strcmp (var,"format.from" )) {int b =git_parse_maybe_bool (value); -free (from); +FREE_AND_NULL (cfg->from);if (b <0 ) - from =xstrdup (value); + cfg->from =xstrdup (value);else if (b) - from =xstrdup (git_committer_info (IDENT_NO_DATE )); -else - from =NULL ; + cfg->from =xstrdup (git_committer_info (IDENT_NO_DATE ));return 0 ; }if (!strcmp (var,"format.forceinbodyfrom" )) {
@@ -1130,15 +1175,15 @@ static int git_format_config(const char *var, const char *value,if (!strcmp (var,"format.notes" )) {int b =git_parse_maybe_bool (value);if (b <0 ) -enable_ref_display_notes (¬es_opt, &show_notes, value); +enable_ref_display_notes (&cfg->notes_opt, &cfg->show_notes, value);else if (b) -enable_default_display_notes (¬es_opt, &show_notes); +enable_default_display_notes (&cfg->notes_opt, &cfg->show_notes);else -disable_display_notes (¬es_opt, &show_notes); +disable_display_notes (&cfg->notes_opt, &cfg->show_notes);return 0 ; }if (!strcmp (var,"format.coverfromdescription" )) { - cover_from_description_mode =parse_cover_from_description (value); + cfg->cover_from_description_mode =parse_cover_from_description (value);return 0 ; }if (!strcmp (var,"format.mboxrd" )) {
@@ -1159,7 +1204,7 @@ static int git_format_config(const char *var, const char *value,if (!strcmp (var,"diff.noprefix" ))return 0 ; -return git_log_config (var, value, ctx, cb); +return git_log_config (var, value, ctx, &cfg->log); }static const char *output_directory =NULL ;
@@ -1247,7 +1292,7 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id =strbuf_detach (&buf,NULL ); } -static void print_signature (FILE *file) +static void print_signature (const char *signature,FILE *file) {if (!signature || !*signature)return ;
@@ -1317,14 +1362,15 @@ static void prepare_cover_text(struct pretty_print_context *pp,const char *branch_name, struct strbuf *sb, const char *encoding, - int need_8bit_cte) + int need_8bit_cte, + const struct format_config *cfg) { const char *subject = "*** SUBJECT HERE ***";const char *body ="*** BLURB HERE ***" ;struct strbuf description_sb =STRBUF_INIT ;struct strbuf subject_sb =STRBUF_INIT ; -if (cover_from_description_mode ==COVER_FROM_NONE ) +if (cfg->cover_from_description_mode ==COVER_FROM_NONE ) goto do_pp;if (description_file && *description_file)
@@ -1334,13 +1380,13 @@ static void prepare_cover_text(struct pretty_print_context *pp,if (!description_sb.len) goto do_pp; -if (cover_from_description_mode ==COVER_FROM_SUBJECT || - cover_from_description_mode ==COVER_FROM_AUTO ) +if (cfg->cover_from_description_mode ==COVER_FROM_SUBJECT || + cfg->cover_from_description_mode ==COVER_FROM_AUTO ) body =format_subject (&subject_sb, description_sb.buf," " ); -if (cover_from_description_mode ==COVER_FROM_MESSAGE || - (cover_from_description_mode ==COVER_FROM_AUTO && - subject_sb.len >COVER_FROM_AUTO_MAX_SUBJECT_LEN )) +if (cfg->cover_from_description_mode ==COVER_FROM_MESSAGE || + (cfg->cover_from_description_mode ==COVER_FROM_AUTO && + subject_sb.len >COVER_FROM_AUTO_MAX_SUBJECT_LEN )) body = description_sb.buf;else subject = subject_sb.buf;
@@ -1377,7 +1423,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, int nr, struct commit **list, const char *description_file, const char *branch_name, - int quiet) + int quiet, + const struct format_config *cfg) { const char *committer;struct shortlog log;
@@ -1416,7 +1463,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.encode_email_headers = rev->encode_email_headers;pp_user_info (&pp,NULL , &sb, committer, encoding);prepare_cover_text (&pp, description_file, branch_name, &sb, - encoding, need_8bit_cte); + encoding, need_8bit_cte, cfg);fprintf (rev->diffopt.file,"%s\n" , sb.buf);free (pp.after_subject);
@@ -1517,29 +1564,30 @@ static const char * const builtin_format_patch_usage[] = { NULL }; -static int keep_subject =0 ; +struct keep_callback_data { +struct format_config *cfg; +struct rev_info *revs; +};static int keep_callback (const struct option *opt,const char *arg,int unset) { +struct keep_callback_data *data = opt->value;BUG_ON_OPT_NEG (unset);BUG_ON_OPT_ARG (arg); - ((struct rev_info *)opt->value)->total =-1 ; - keep_subject =1 ; + data->revs->total =-1 ; + data->cfg->keep_subject =1 ;return 0 ; } -static int subject_prefix =0 ; -static int subject_prefix_callback (const struct option *opt,const char *arg,int unset) { -struct strbuf *sprefix; +struct format_config *cfg = opt->value;BUG_ON_OPT_NEG (unset); - sprefix = opt->value; - subject_prefix =1 ; -strbuf_reset (sprefix); -strbuf_addstr (sprefix, arg); + cfg->subject_prefix =1 ; +strbuf_reset (&cfg->sprefix); +strbuf_addstr (&cfg->sprefix, arg);return 0 ; }
@@ -1556,15 +1604,14 @@ static int rfc_callback(const struct option *opt, const char *arg,return 0 ; } -static int numbered_cmdline_opt =0 ; -static int numbered_callback (const struct option *opt,const char *arg,int unset) { +struct format_config *cfg = opt->value;BUG_ON_OPT_ARG (arg); - *(int *)opt->value = numbered_cmdline_opt = unset ?0 :1 ; + cfg->numbered = cfg->numbered_cmdline_opt = unset ?0 :1 ;if (unset) - auto_number =0 ; + cfg->auto_number =0 ;return 0 ; }
@@ -1588,13 +1635,14 @@ static int output_directory_callback(const struct option *opt, const char *arg,static int thread_callback (const struct option *opt,const char *arg,int unset) { -enum thread_level *thread = (enum thread_level *)opt->value; +struct format_config *cfg = opt->value; +if (unset) - *thread =THREAD_UNSET ; + cfg->thread =THREAD_UNSET ;else if (!arg || !strcmp (arg,"shallow" )) - *thread =THREAD_SHALLOW ; + cfg->thread =THREAD_SHALLOW ;else if (!strcmp (arg,"deep" )) - *thread =THREAD_DEEP ; + cfg->thread =THREAD_DEEP ;/* * Please update _git_formatpatch() in git-completion.bash * when you add new options.
@@ -1630,15 +1678,17 @@ static int inline_callback(const struct option *opt, const char *arg, int unset)return 0 ; } -static int header_callback(const struct option *opt UNUSED, const char *arg, +static int header_callback (const struct option *opt,const char *arg,int unset) { +struct format_config *cfg = opt->value; +if (unset) { -string_list_clear (&extra_hdr,0 ); -string_list_clear (&extra_to,0 ); -string_list_clear (&extra_cc,0 ); +string_list_clear (&cfg->extra_hdr,0 ); +string_list_clear (&cfg->extra_to,0 ); +string_list_clear (&cfg->extra_cc,0 ); }else { -add_header (arg); +add_header (cfg, arg); }return 0 ; }
@@ -1660,17 +1710,17 @@ static int from_callback(const struct option *opt, const char *arg, int unset)static int base_callback (const struct option *opt,const char *arg,int unset) { -const char **base_commit = opt->value; +struct format_config *cfg = opt->value;if (unset) { - auto_base =AUTO_BASE_NEVER ; - *base_commit =NULL ; + cfg->auto_base =AUTO_BASE_NEVER ; +FREE_AND_NULL (cfg->base_commit); }else if (!strcmp (arg,"auto" )) { - auto_base =AUTO_BASE_ALWAYS ; - *base_commit =NULL ; + cfg->auto_base =AUTO_BASE_ALWAYS ; +FREE_AND_NULL (cfg->base_commit); }else { - auto_base =AUTO_BASE_NEVER ; - *base_commit = arg; + cfg->auto_base =AUTO_BASE_NEVER ; + cfg->base_commit =xstrdup (arg); }return 0 ; }
@@ -1681,7 +1731,7 @@ struct base_tree_info {struct object_id *patch_id; }; -static struct commit *get_base_commit (const char *base_commit, +static struct commit *get_base_commit (const struct format_config *cfg,struct commit **list,int total) {
@@ -1689,9 +1739,9 @@ static struct commit *get_base_commit(const char *base_commit,struct commit **rev;int i =0 , rev_nr =0 , auto_select, die_on_failure, ret; -switch (auto_base) { +switch (cfg->auto_base) {case AUTO_BASE_NEVER : -if (base_commit) { +if (cfg->base_commit) { auto_select =0 ; die_on_failure =1 ; }else {
@@ -1701,11 +1751,11 @@ static struct commit *get_base_commit(const char *base_commit,break ;case AUTO_BASE_ALWAYS :case AUTO_BASE_WHEN_ABLE : -if (base_commit) { +if (cfg->base_commit) {BUG ("requested automatic base selection but a commit was provided" ); }else { auto_select =1 ; - die_on_failure = auto_base ==AUTO_BASE_ALWAYS ; + die_on_failure = cfg->auto_base ==AUTO_BASE_ALWAYS ; }break ;default :
@@ -1713,9 +1763,9 @@ static struct commit *get_base_commit(const char *base_commit, }if (!auto_select) { - base =lookup_commit_reference_by_name (base_commit); + base =lookup_commit_reference_by_name (cfg->base_commit);if (!base) -die (_ ("unknown commit %s" ), base_commit); +die (_ ("unknown commit %s" ), cfg->base_commit); }else {struct branch *curr_branch =branch_get (NULL );const char *upstream =branch_get_upstream (curr_branch,NULL );
@@ -1933,7 +1983,7 @@ static void infer_range_diff_ranges(struct strbuf *r1,int cmd_format_patch (int argc,const char **argv,const char *prefix) { -struct log_config cfg; +struct format_config cfg;struct commit *commit;struct commit **list =NULL ;struct rev_info rev;
@@ -1958,7 +2008,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)char *cover_from_description_arg =NULL ;char *description_file =NULL ;char *branch_name =NULL ; -char *base_commit =NULL ;struct base_tree_info bases;struct commit *base;int show_progress =0 ;
@@ -1969,18 +2018,24 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)struct strbuf rdiff1 =STRBUF_INIT ;struct strbuf rdiff2 =STRBUF_INIT ;struct strbuf rdiff_title =STRBUF_INIT ; -struct strbuf sprefix =STRBUF_INIT ;const char *rfc =NULL ;int creation_factor =-1 ; +const char *signature = git_version_string; +const char *signature_file_arg =NULL ; +struct keep_callback_data keep_callback_data = { + .cfg = &cfg, + .revs = &rev, + }; +const char *fmt_patch_suffix =NULL ;const struct option builtin_format_patch_options[] = { -OPT_CALLBACK_F ('n' ,"numbered" , &numbered,NULL , +OPT_CALLBACK_F ('n' ,"numbered" , &cfg,NULL ,N_ ("use [PATCH n/m] even with a single patch" ),PARSE_OPT_NOARG , numbered_callback), -OPT_CALLBACK_F ('N' ,"no-numbered" , &numbered,NULL , +OPT_CALLBACK_F ('N' ,"no-numbered" , &cfg,NULL ,N_ ("use [PATCH] even with multiple patches" ),PARSE_OPT_NOARG |PARSE_OPT_NONEG , no_numbered_callback), -OPT_BOOL ('s' ,"signoff" , &do_signoff,N_ ("add a Signed-off-by trailer" )), +OPT_BOOL ('s' ,"signoff" , &cfg.do_signoff,N_ ("add a Signed-off-by trailer" )),OPT_BOOL (0 ,"stdout" , &use_stdout,N_ ("print patches to standard out" )), OPT_BOOL(0, "cover-letter", &cover_letter,
@@ -1993,7 +2048,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")),OPT_STRING ('v' ,"reroll-count" , &reroll_count,N_ ("reroll-count" ),N_ ("mark the series as Nth re-roll" )), -OPT_INTEGER (0 ,"filename-max-length" , &cfg.fmt_patch_name_max, +OPT_INTEGER (0 ,"filename-max-length" , &cfg.log.fmt_patch_name_max,N_ ("max length of output filename" )), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add <rfc> (default 'RFC') before 'PATCH'"),
@@ -2003,13 +2058,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("generate parts of a cover letter based on a branch's description")),OPT_FILENAME (0 ,"description-file" , &description_file,N_ ("use branch description from file" )), -OPT_CALLBACK_F (0 ,"subject-prefix" , &sprefix,N_ ("prefix" ), +OPT_CALLBACK_F (0 ,"subject-prefix" , &cfg,N_ ("prefix" ),N_ ("use [<prefix>] instead of [PATCH]" ),PARSE_OPT_NONEG , subject_prefix_callback),OPT_CALLBACK_F ('o' ,"output-directory" , &output_directory,N_ ("dir" ),N_ ("store resulting files in <dir>" ),PARSE_OPT_NONEG , output_directory_callback), -OPT_CALLBACK_F ('k' ,"keep-subject" , &rev,NULL , +OPT_CALLBACK_F ('k' ,"keep-subject" , &keep_callback_data,NULL ,N_ ("don't strip/add [PATCH]" ),PARSE_OPT_NOARG |PARSE_OPT_NONEG , keep_callback), OPT_BOOL(0, "no-binary", &no_binary_diff,
@@ -2022,11 +2077,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("show patch format instead of default (patch + stat)"), 1, PARSE_OPT_NONEG),OPT_GROUP (N_ ("Messaging" )), -OPT_CALLBACK (0 ,"add-header" ,NULL ,N_ ("header" ), +OPT_CALLBACK (0 ,"add-header" , &cfg,N_ ("header" ),N_ ("add email header" ), header_callback), -OPT_STRING_LIST (0 ,"to" , &extra_to,N_ ("email" ),N_ ("add To: header" )), -OPT_STRING_LIST (0 ,"cc" , &extra_cc,N_ ("email" ),N_ ("add Cc: header" )), -OPT_CALLBACK_F (0 ,"from" , &from,N_ ("ident" ), +OPT_STRING_LIST (0 ,"to" , &cfg.extra_to,N_ ("email" ),N_ ("add To: header" )), +OPT_STRING_LIST (0 ,"cc" , &cfg.extra_cc,N_ ("email" ),N_ ("add Cc: header" )), +OPT_CALLBACK_F (0 ,"from" , &cfg.from,N_ ("ident" ),N_ ("set From address to <ident> (or committer ident if absent)" ),PARSE_OPT_OPTARG , from_callback), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
@@ -2038,15 +2093,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, inline_callback), -OPT_CALLBACK_F (0 ,"thread" , &thread,N_ ("style" ), +OPT_CALLBACK_F (0 ,"thread" , &cfg,N_ ("style" ),N_ ("enable message threading, styles: shallow, deep" ),PARSE_OPT_OPTARG , thread_callback),OPT_STRING (0 ,"signature" , &signature,N_ ("signature" ),N_ ("add a signature" )), -OPT_CALLBACK_F (0 ,"base" , &base_commit,N_ ("base-commit" ), +OPT_CALLBACK_F (0 ,"base" , &cfg,N_ ("base-commit" ),N_ ("add prerequisite tree info to the patch series" ),0 , base_callback), -OPT_FILENAME (0 ,"signature-file" , &signature_file, +OPT_FILENAME (0 ,"signature-file" , &signature_file_arg,N_ ("add a signature from a file" )),OPT__QUIET (&quiet,N_ ("don't print the patch filenames" )), OPT_BOOL(0, "progress", &show_progress,
@@ -2063,21 +2118,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_END() }; - extra_hdr.strdup_strings =1 ; - extra_to.strdup_strings =1 ; - extra_cc.strdup_strings =1 ; - -log_config_init (&cfg); +format_config_init (&cfg);init_diff_ui_defaults (); -init_display_notes (¬es_opt); +init_display_notes (&cfg.notes_opt);git_config (git_format_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter); - rev.show_notes = show_notes; -memcpy (&rev.notes_opt, ¬es_opt,sizeof (notes_opt)); + rev.show_notes = cfg.show_notes; +memcpy (&rev.notes_opt, &cfg.notes_opt,sizeof (cfg.notes_opt)); rev.commit_format =CMIT_FMT_EMAIL ; - rev.encode_email_headers = cfg.default_encode_email_headers; + rev.encode_email_headers = cfg.log.default_encode_email_headers; rev.expand_tabs_in_log_default =0 ; rev.verbose_header =1 ; rev.diff =1 ;
@@ -2088,12 +2139,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def ="HEAD" ; s_r_opt.revarg_opt =REVARG_COMMITTISH ; -strbuf_addstr (&sprefix, cfg.fmt_patch_subject_prefix); +strbuf_addstr (&cfg.sprefix, cfg.log.fmt_patch_subject_prefix);if (format_no_prefix)diff_set_noprefix (&rev.diffopt); -if (default_attach) { - rev.mime_boundary = default_attach; +if (cfg.default_attach) { + rev.mime_boundary = cfg.default_attach; rev.no_inline =1 ; }
@@ -2109,60 +2160,63 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from; +if (!fmt_patch_suffix) + fmt_patch_suffix = cfg.fmt_patch_suffix; +/* Make sure "0000-$sub.patch" gives non-negative length for $sub */ -if (cfg.fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) - cfg.fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix); +if (cfg.log.fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) + cfg.log.fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix);if (cover_from_description_arg) - cover_from_description_mode =parse_cover_from_description (cover_from_description_arg); + cfg.cover_from_description_mode =parse_cover_from_description (cover_from_description_arg);if (rfc && rfc[0 ]) { - subject_prefix =1 ; + cfg.subject_prefix =1 ;if (rfc[0 ] =='-' ) -strbuf_addf (&sprefix," %s" , rfc +1 ); +strbuf_addf (&cfg.sprefix," %s" , rfc +1 );else -strbuf_insertf (&sprefix,0 ,"%s " , rfc); +strbuf_insertf (&cfg.sprefix,0 ,"%s " , rfc); }if (reroll_count) { -strbuf_addf (&sprefix," v%s" , reroll_count); +strbuf_addf (&cfg.sprefix," v%s" , reroll_count); rev.reroll_count = reroll_count; } - rev.subject_prefix = sprefix.buf; + rev.subject_prefix = cfg.sprefix.buf; -for (i =0 ; i < extra_hdr.nr; i++) { -strbuf_addstr (&buf, extra_hdr.items[i].string); +for (i =0 ; i < cfg.extra_hdr.nr; i++) { +strbuf_addstr (&buf, cfg.extra_hdr.items[i].string);strbuf_addch (&buf,'\n' ); } -if (extra_to.nr) +if (cfg.extra_to.nr)strbuf_addstr (&buf,"To: " ); -for (i =0 ; i < extra_to.nr; i++) { +for (i =0 ; i < cfg.extra_to.nr; i++) {if (i)strbuf_addstr (&buf," " ); -strbuf_addstr (&buf, extra_to.items[i].string); -if (i +1 < extra_to.nr) +strbuf_addstr (&buf, cfg.extra_to.items[i].string); +if (i +1 < cfg.extra_to.nr)strbuf_addch (&buf,',' );strbuf_addch (&buf,'\n' ); } -if (extra_cc.nr) +if (cfg.extra_cc.nr)strbuf_addstr (&buf,"Cc: " ); -for (i =0 ; i < extra_cc.nr; i++) { +for (i =0 ; i < cfg.extra_cc.nr; i++) {if (i)strbuf_addstr (&buf," " ); -strbuf_addstr (&buf, extra_cc.items[i].string); -if (i +1 < extra_cc.nr) +strbuf_addstr (&buf, cfg.extra_cc.items[i].string); +if (i +1 < cfg.extra_cc.nr)strbuf_addch (&buf,',' );strbuf_addch (&buf,'\n' ); } rev.extra_headers = to_free =strbuf_detach (&buf,NULL ); -if (from) { -if (split_ident_line (&rev.from_ident, from,strlen (from))) -die (_ ("invalid ident line: %s" ), from); +if (cfg.from) { +if (split_ident_line (&rev.from_ident, cfg.from,strlen (cfg.from))) +die (_ ("invalid ident line: %s" ), cfg.from); }if (start_number <0 )
@@ -2173,14 +2227,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)* and it would conflict with --keep-subject (-k) from the * command line, reset "numbered". */ -if (numbered && keep_subject && !numbered_cmdline_opt) - numbered =0 ; +if (cfg.numbered && cfg.keep_subject && !cfg.numbered_cmdline_opt) + cfg.numbered =0 ; -if (numbered && keep_subject) +if (cfg.numbered && cfg.keep_subject)die (_ ("options '%s' and '%s' cannot be used together" ),"-n" ,"-k" ); -if (keep_subject && subject_prefix) +if (cfg.keep_subject && cfg.subject_prefix)die (_ ("options '%s' and '%s' cannot be used together" ),"--subject-prefix/--rfc" ,"-k" ); - rev.preserve_subject = keep_subject; + rev.preserve_subject = cfg.keep_subject; argc =setup_revisions (argc, argv, &rev, &s_r_opt);if (argc >1 )
@@ -2207,7 +2261,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header =1 ; rev.zero_commit = zero_commit; - rev.patch_name_max = cfg.fmt_patch_name_max; + rev.patch_name_max = cfg.log.fmt_patch_name_max;if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary =1 ;
@@ -2228,7 +2282,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)int saved;if (!output_directory) - output_directory = config_output_directory; + output_directory = cfg.config_output_directory; output_directory =set_outdir (prefix, output_directory);if (rev.diffopt.use_color !=GIT_COLOR_ALWAYS )
@@ -2326,14 +2380,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) goto done; total = nr;if (cover_letter ==-1 ) { -if (config_cover_letter ==COVER_AUTO ) +if (cfg.config_cover_letter ==COVER_AUTO ) cover_letter = (total >1 );else - cover_letter = (config_cover_letter ==COVER_ON ); + cover_letter = (cfg.config_cover_letter ==COVER_ON ); } -if (!keep_subject && auto_number && (total >1 || cover_letter)) - numbered =1 ; -if (numbered) +if (!cfg.keep_subject && cfg.auto_number && (total >1 || cover_letter)) + cfg.numbered =1 ; +if (cfg.numbered) rev.total = total + start_number -1 ;if (idiff_prev.nr) {
@@ -2365,27 +2419,40 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) _("Range-diff against v%d:")); } +/* +* The order of precedence is: +* +* 1. The `--signature` and `--no-signature` options. +* 2. The `--signature-file` option. +* 3. The `format.signature` config. +* 4. The `format.signatureFile` config. +* 5. Default `git_version_string`. +*/ if (!signature) { ;/* --no-signature inhibits all signatures */ }else if (signature && signature != git_version_string) { ;/* non-default signature already set */ - }else if (signature_file) { + }else if (signature_file_arg || (cfg.signature_file && !cfg.signature)) {struct strbuf buf =STRBUF_INIT ; +const char *signature_file = signature_file_arg ? + signature_file_arg : cfg.signature_file;if (strbuf_read_file (&buf, signature_file,128 ) <0 )die_errno (_ ("unable to read signature file '%s'" ), signature_file); signature =strbuf_detach (&buf,NULL ); + }else if (cfg.signature) { + signature = cfg.signature; }memset (&bases,0 ,sizeof (bases)); - base =get_base_commit (base_commit, list, nr); + base =get_base_commit (&cfg, list, nr);if (base) {reset_revision_walk ();clear_object_flags (UNINTERESTING );prepare_bases (&bases, base, list, nr); } -if (in_reply_to || thread || cover_letter) { +if (in_reply_to || cfg.thread || cover_letter) { rev.ref_message_ids =xmalloc (sizeof (*rev.ref_message_ids));string_list_init_dup (rev.ref_message_ids); }
@@ -2396,19 +2463,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix;if (cover_letter) { -if (thread) +if (cfg.thread)gen_message_id (&rev,"cover" );make_cover_letter (&rev, !!output_directory, - origin, nr, list, description_file, branch_name, quiet); + origin, nr, list, description_file, branch_name, quiet, &cfg);print_bases (&bases, rev.diffopt.file); -print_signature (rev.diffopt.file); +print_signature (signature, rev.diffopt.file); total++; start_number--;/* interdiff/range-diff in cover-letter; omit from patches */ rev.idiff_oid1 =NULL ; rev.rdiff1 =NULL ; } - rev.add_signoff = do_signoff; + rev.add_signoff = cfg.do_signoff;if (show_progress) progress =start_delayed_progress (_ ("Generating patches" ), total);
@@ -2418,7 +2485,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) commit = list[nr]; rev.nr = total - nr + (start_number -1 );/* Make the second and subsequent mails replies to the first */ -if (thread) { +if (cfg.thread) {/* Have we already had a message ID? */ if (rev.message_id) {/*
@@ -2442,7 +2509,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)* letter is a reply to the * --in-reply-to, if specified. */ -if (thread ==THREAD_SHALLOW +if (cfg.thread ==THREAD_SHALLOW && rev.ref_message_ids->nr >0 && (!cover_letter || rev.nr >1 ))free (rev.message_id);
@@ -2475,7 +2542,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) mime_boundary_leader, rev.mime_boundary); else - print_signature(rev.diffopt.file); + print_signature(signature, rev.diffopt.file); }if (output_directory)fclose (rev.diffopt.file);
@@ -2483,9 +2550,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)stop_progress (&progress);free (list);free (branch_name); -string_list_clear (&extra_to,0 ); -string_list_clear (&extra_cc,0 ); -string_list_clear (&extra_hdr,0 );if (ignore_if_in_upstream)free_patch_ids (&ids);
@@ -2495,14 +2559,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)strbuf_release (&rdiff1);strbuf_release (&rdiff2);strbuf_release (&rdiff_title); -strbuf_release (&sprefix);free (to_free);free (rev.message_id);if (rev.ref_message_ids)string_list_clear (rev.ref_message_ids,0 );free (rev.ref_message_ids);release_revisions (&rev); -log_config_release (&cfg); +format_config_release (&cfg);return 0 ; }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 11/20 config: clarify memory ownership in `git_config_string()`
In reply to Patrick SteinhardtThe out parameter of git_config_string() is a const char ** even though we transfer ownership of memory to the caller. This is quite misleading and has led to many memory leaks all over the place. Adapt the parameter to instead be char **.
--- alias.c | 2 +- attr.c | 2 +- attr.h | 2 +- builtin/commit.c | 2 +- builtin/log.c | 12 ++++++------ builtin/merge.c | 4 ++-- builtin/rebase.c | 2 +- builtin/receive-pack.c | 2 +- builtin/repack.c | 8 ++++---- config.c | 4 ++-- config.h | 2 +- convert.c | 6 +++--- delta-islands.c | 2 +- diff.c | 8 ++++---- environment.c | 8 ++++---- environment.h | 8 ++++---- gpg-interface.c | 4 ++-- http.c | 24 ++++++++++++------------ imap-send.c | 12 ++++++------ mailmap.c | 2 +- mailmap.h | 2 +- merge-ll.c | 6 +++--- pager.c | 2 +- pretty.c | 14 +++++++++----- promisor-remote.h | 2 +- remote.c | 20 ++++++++++---------- remote.h | 8 ++++---- sequencer.c | 2 +- upload-pack.c | 2 +- userdiff.h | 12 ++++++------ 30 files changed, 95 insertions(+), 91 deletions(-)
alias.c +1 -1
@@ -22,7 +22,7 @@ static int config_alias_cb(const char *key, const char *value,if (data->alias) {if (!strcasecmp (p, data->alias)) -return git_config_string ((const char **)&data->v, +return git_config_string (&data->v, key, value); }else if (data->list) {string_list_append (data->list, p);
attr.c +1 -1
@@ -25,7 +25,7 @@#include "tree-walk.h" #include "object-name.h" -const char *git_attr_tree; +char *git_attr_tree;const char git_attr__true[] ="(builtin)true" ;const char git_attr__false[] ="\0(builtin)false" ;
attr.h +1 -1
@@ -236,6 +236,6 @@ const char *git_attr_global_file(void);/* Return whether the system gitattributes file is enabled and should be used. */ int git_attr_system_is_enabled (void ); -extern const char *git_attr_tree; +extern char *git_attr_tree;#endif /* ATTR_H */
builtin/commit.c +1 -1
@@ -133,7 +133,7 @@ static struct strvec trailer_args = STRVEC_INIT;* is specified explicitly. */ static enum commit_msg_cleanup_mode cleanup_mode; -static const char *cleanup_arg; +static char *cleanup_arg;static enum commit_whence whence;static int use_editor =1 , include_status =1 ;
builtin/log.c +6 -6
@@ -582,11 +582,11 @@ static int git_log_config(const char *var, const char *value,if (!strcmp (var,"format.pretty" )) {FREE_AND_NULL (cfg->fmt_pretty); -return git_config_string ((const char **) &cfg->fmt_pretty, var, value); +return git_config_string (&cfg->fmt_pretty, var, value); }if (!strcmp (var,"format.subjectprefix" )) {FREE_AND_NULL (cfg->fmt_patch_subject_prefix); -return git_config_string ((const char **) &cfg->fmt_patch_subject_prefix, var, value); +return git_config_string (&cfg->fmt_patch_subject_prefix, var, value); }if (!strcmp (var,"format.filenamemaxlength" )) { cfg->fmt_patch_name_max =git_config_int (var, value, ctx->kvi);
@@ -602,7 +602,7 @@ static int git_log_config(const char *var, const char *value, }if (!strcmp (var,"log.date" )) {FREE_AND_NULL (cfg->default_date_mode); -return git_config_string ((const char **) &cfg->default_date_mode, var, value); +return git_config_string (&cfg->default_date_mode, var, value); }if (!strcmp (var,"log.decorate" )) { cfg->decoration_style =parse_decoration_style (value);
@@ -1076,7 +1076,7 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.suffix" )) {FREE_AND_NULL (cfg->fmt_patch_suffix); -return git_config_string ((const char **) &cfg->fmt_patch_suffix, var, value); +return git_config_string (&cfg->fmt_patch_suffix, var, value); }if (!strcmp (var,"format.to" )) {if (!value)
@@ -1133,7 +1133,7 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.signature" )) {FREE_AND_NULL (cfg->signature); -return git_config_string ((const char **) &cfg->signature, var, value); +return git_config_string (&cfg->signature, var, value); }if (!strcmp (var,"format.signaturefile" )) {FREE_AND_NULL (cfg->signature_file);
@@ -1149,7 +1149,7 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.outputdirectory" )) {FREE_AND_NULL (cfg->config_output_directory); -return git_config_string ((const char **) &cfg->config_output_directory, var, value); +return git_config_string (&cfg->config_output_directory, var, value); }if (!strcmp (var,"format.useautobase" )) {if (value && !strcasecmp (value,"whenAble" )) {
builtin/merge.c +2 -2
@@ -100,7 +100,7 @@ static struct strategy all_strategy[] = { { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, }; -static const char *pull_twohead, *pull_octopus; +static char *pull_twohead, *pull_octopus;enum ff_type { FF_NO,
@@ -110,7 +110,7 @@ enum ff_type {static enum ff_type fast_forward =FF_ALLOW ; -static const char *cleanup_arg; +static char *cleanup_arg;static enum commit_msg_cleanup_mode cleanup_mode; static int option_parse_message(const struct option *opt,
builtin/rebase.c +1 -1
@@ -83,7 +83,7 @@ static const char *action_names[] = {struct rebase_options {enum rebase_type type;enum empty_type empty; -const char *default_backend; +char *default_backend;const char *state_dir;struct commit *upstream;const char *upstream_name;
builtin/receive-pack.c +1 -1
@@ -88,7 +88,7 @@ static struct strbuf push_cert = STRBUF_INIT;static struct object_id push_cert_oid;static struct signature_check sigcheck;static const char *push_cert_nonce; -static const char *cert_nonce_seed; +static char *cert_nonce_seed;static struct strvec hidden_refs =STRVEC_INIT ;static const char *NONCE_UNSOLICITED ="UNSOLICITED" ;
builtin/repack.c +4 -4
@@ -48,10 +48,10 @@ static const char incremental_bitmap_conflict_error[] = N_( );struct pack_objects_args { -const char *window; -const char *window_memory; -const char *depth; -const char *threads; +char *window; +char *window_memory; +char *depth; +char *threads;unsigned long max_pack_size;int no_reuse_delta;int no_reuse_object;
config.c +2 -2
@@ -1338,7 +1338,7 @@ int git_config_bool(const char *name, const char *value)return v; } -int git_config_string (const char **dest,const char *var,const char *value) +int git_config_string (char **dest,const char *var,const char *value) {if (!value)return config_error_nonbool (var);
@@ -2418,7 +2418,7 @@ int git_configset_get_string(struct config_set *set, const char *key, char **des {const char *value;if (!git_configset_get_value (set, key, &value,NULL )) -return git_config_string ((const char **)dest, key, value); +return git_config_string (dest, key, value);else return 1 ; }
config.h +1 -1
@@ -280,7 +280,7 @@ int git_config_bool(const char *, const char *);* Allocates and copies the value string into the `dest` parameter; if no * string is given, prints an error message and returns -1. */ -int git_config_string (const char **,const char *,const char *); +int git_config_string (char **,const char *,const char *);/** * Similar to `git_config_string`, but expands `~` or `~user` into the
convert.c +3 -3
@@ -981,9 +981,9 @@ int async_query_available_blobs(const char *cmd, struct string_list *available_pstatic struct convert_driver {const char *name;struct convert_driver *next; -const char *smudge; -const char *clean; -const char *process; +char *smudge; +char *clean; +char *process;int required; } *user_convert, **user_convert_tail;
delta-islands.c +1 -1
@@ -313,7 +313,7 @@ struct island_load_data {size_t nr;size_t alloc; }; -static const char *core_island_name; +static char *core_island_name;static void free_config_regexes (struct island_load_data *ild) {
diff.c +4 -4
@@ -56,8 +56,8 @@ static int diff_color_moved_default;static int diff_color_moved_ws_default;static int diff_context_default =3 ;static int diff_interhunk_context_default; -static const char *diff_word_regex_cfg; -static const char *external_diff_cmd_cfg; +static char *diff_word_regex_cfg; +static char *external_diff_cmd_cfg;static char *diff_order_file_cfg;int diff_auto_refresh_index =1 ;static int diff_mnemonic_prefix;
@@ -412,11 +412,11 @@ int git_diff_ui_config(const char *var, const char *value, }if (!strcmp (var,"diff.srcprefix" )) {FREE_AND_NULL (diff_src_prefix); -return git_config_string ((const char **) &diff_src_prefix, var, value); +return git_config_string (&diff_src_prefix, var, value); }if (!strcmp (var,"diff.dstprefix" )) {FREE_AND_NULL (diff_dst_prefix); -return git_config_string ((const char **) &diff_dst_prefix, var, value); +return git_config_string (&diff_dst_prefix, var, value); }if (!strcmp (var,"diff.relative" )) { diff_relative =git_config_bool (var, value);
environment.c +4 -4
@@ -42,8 +42,8 @@ int is_bare_repository_cfg = -1; /* unspecified */int warn_ambiguous_refs =1 ;int warn_on_object_refname_ambiguity =1 ;int repository_format_precious_objects; -const char *git_commit_encoding; -const char *git_log_output_encoding; +char *git_commit_encoding; +char *git_log_output_encoding;char *apply_default_whitespace;char *apply_default_ignorewhitespace;char *git_attributes_file;
@@ -58,8 +58,8 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;size_t packed_git_limit =DEFAULT_PACKED_GIT_LIMIT ;size_t delta_base_cache_limit =96 *1024 *1024 ;unsigned long big_file_threshold =512 *1024 *1024 ; -const char *editor_program; -const char *askpass_program; +char *editor_program; +char *askpass_program;char *excludes_file;enum auto_crlf auto_crlf =AUTO_CRLF_FALSE ;enum eol core_eol =EOL_UNSET ;
environment.h +4 -4
@@ -224,11 +224,11 @@ int odb_pack_keep(const char *name);const char *get_log_output_encoding (void );const char *get_commit_output_encoding (void ); -extern const char *git_commit_encoding; -extern const char *git_log_output_encoding; +extern char *git_commit_encoding; +extern char *git_log_output_encoding; -extern const char *editor_program; -extern const char *askpass_program; +extern char *editor_program; +extern char *askpass_program;extern char *excludes_file;/*
gpg-interface.c +2 -2
@@ -27,14 +27,14 @@ static void gpg_interface_lazy_init(void) }static char *configured_signing_key; -static const char *ssh_default_key_command; +static char *ssh_default_key_command;static char *ssh_allowed_signers;static char *ssh_revocation_file;static enum signature_trust_level configured_min_trust_level =TRUST_UNDEFINED ;struct gpg_format {const char *name; -const char *program; +char *program;const char **verify_args;const char **sigs; int (*verify_signed_buffer)(struct signature_check *sigc,
http.c +12 -12
@@ -38,11 +38,11 @@ char curl_errorstr[CURL_ERROR_SIZE];static int curl_ssl_verify =-1 ;static int curl_ssl_try; -static const char *curl_http_version =NULL ; +static char *curl_http_version;static char *ssl_cert;static char *ssl_cert_type; -static const char *ssl_cipherlist; -static const char *ssl_version; +static char *ssl_cipherlist; +static char *ssl_version; static struct { const char *name; long ssl_version;
@@ -95,7 +95,7 @@ static struct {*/ }; #ifdef CURLGSSAPI_DELEGATION_FLAG -static const char *curl_deleg; +static char *curl_deleg; static struct { const char *name; long curl_deleg_param;
@@ -383,11 +383,11 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslcert" , var))return git_config_pathname (&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var)) -return git_config_string ((const char **)&ssl_cert_type, var, value); +return git_config_string (&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var))return git_config_pathname (&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var)) -return git_config_string ((const char **)&ssl_key_type, var, value); +return git_config_string (&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var))return git_config_pathname (&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var))
@@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value,return 0 ; }if (!strcmp ("http.proxy" , var)) -return git_config_string ((const char **)&curl_http_proxy, var, value); +return git_config_string (&curl_http_proxy, var, value);if (!strcmp ("http.proxyauthmethod" , var)) -return git_config_string ((const char **)&http_proxy_authmethod, var, value); +return git_config_string (&http_proxy_authmethod, var, value);if (!strcmp ("http.proxysslcert" , var)) -return git_config_string ((const char **)&http_proxy_ssl_cert, var, value); +return git_config_string (&http_proxy_ssl_cert, var, value);if (!strcmp ("http.proxysslkey" , var)) -return git_config_string ((const char **)&http_proxy_ssl_key, var, value); +return git_config_string (&http_proxy_ssl_key, var, value);if (!strcmp ("http.proxysslcainfo" , var)) -return git_config_string ((const char **)&http_proxy_ssl_ca_info, var, value); +return git_config_string (&http_proxy_ssl_ca_info, var, value);if (!strcmp ("http.proxysslcertpasswordprotected" , var)) { proxy_ssl_cert_password_required =git_config_bool (var, value);
@@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, }if (!strcmp ("http.useragent" , var)) -return git_config_string ((const char **)&user_agent, var, value); +return git_config_string (&user_agent, var, value);if (!strcmp ("http.emptyauth" , var)) {if (value && !strcmp ("auto" , value))
imap-send.c +6 -6
@@ -70,16 +70,16 @@ static char *next_arg(char **);struct imap_server_conf {const char *name; -const char *tunnel; -const char *host; +char *tunnel; +char *host;int port; -const char *folder; -const char *user; -const char *pass; +char *folder; +char *user; +char *pass;int use_ssl;int ssl_verify;int use_html; -const char *auth_method; +char *auth_method; };static struct imap_server_conf server = {
mailmap.c +1 -1
@@ -7,7 +7,7 @@#include "setup.h" char *git_mailmap_file; -const char *git_mailmap_blob; +char *git_mailmap_blob;struct mailmap_info {char *name;
mailmap.h +1 -1
@@ -4,7 +4,7 @@struct string_list ;extern char *git_mailmap_file; -extern const char *git_mailmap_blob; +extern char *git_mailmap_blob;int read_mailmap (struct string_list *map);void clear_mailmap (struct string_list *map);
merge-ll.c +3 -3
@@ -27,9 +27,9 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,struct ll_merge_driver {const char *name; -const char *description; +char *description;ll_merge_fn fn; -const char *recursive; +char *recursive;struct ll_merge_driver *next;char *cmdline; };
@@ -268,7 +268,7 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,* merge.default and merge.driver configuration items */ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; +static char *default_ll_merge; static int read_merge_config(const char *var, const char *value, const struct config_context *ctx UNUSED,
pager.c +1 -1
@@ -13,7 +13,7 @@ int pager_use_color = 1;#endif static struct child_process pager_process; -static const char *pager_program; +static char *pager_program;/* Is the value coming back from term_columns() just a guess? */ static int term_columns_guessed;
pretty.c +9 -5
@@ -62,7 +62,7 @@ static int git_pretty_formats_config(const char *var, const char *value, {struct cmt_fmt_map *commit_format =NULL ;const char *name; -const char *fmt; +char *fmt;int i;if (!skip_prefix (var,"pretty." , &name))
@@ -93,13 +93,17 @@ static int git_pretty_formats_config(const char *var, const char *value,if (git_config_string (&fmt, var, value))return -1 ; -if (skip_prefix (fmt,"format:" , &fmt)) +if (skip_prefix (fmt,"format:" , &commit_format->user_format)) { commit_format->is_tformat =0 ; -else if (skip_prefix (fmt,"tformat:" , &fmt) ||strchr (fmt,'%' )) + }else if (skip_prefix (fmt,"tformat:" , &commit_format->user_format)) { commit_format->is_tformat =1 ; -else + }else if (strchr (fmt,'%' )) { + commit_format->is_tformat =1 ; + commit_format->user_format = fmt; + }else { commit_format->is_alias =1 ; - commit_format->user_format = fmt; + commit_format->user_format = fmt; + }return 0 ; }
promisor-remote.h +1 -1
@@ -13,7 +13,7 @@ struct object_id;*/ struct promisor_remote {struct promisor_remote *next; -const char *partial_clone_filter; +char *partial_clone_filter;const char name[FLEX_ARRAY ]; };
remote.c +10 -10
@@ -428,29 +428,29 @@ static int handle_config(const char *key, const char *value,else if (!strcmp (subkey,"prunetags" )) remote->prune_tags =git_config_bool (key, value);else if (!strcmp (subkey,"url" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;add_url (remote, v); }else if (!strcmp (subkey,"pushurl" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;add_pushurl (remote, v); }else if (!strcmp (subkey,"push" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;refspec_append (&remote->push, v); -free ((char *)v); +free (v); }else if (!strcmp (subkey,"fetch" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;refspec_append (&remote->fetch, v); -free ((char *)v); +free (v); }else if (!strcmp (subkey,"receivepack" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;if (!remote->receivepack)
@@ -458,7 +458,7 @@ static int handle_config(const char *key, const char *value,else error (_ ("more than one receivepack given, using the first" )); }else if (!strcmp (subkey,"uploadpack" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;if (!remote->uploadpack)
@@ -471,10 +471,10 @@ static int handle_config(const char *key, const char *value,else if (!strcmp (value,"--tags" )) remote->fetch_tags =2 ; }else if (!strcmp (subkey,"proxy" )) { -return git_config_string ((const char **)&remote->http_proxy, +return git_config_string (&remote->http_proxy, key, value); }else if (!strcmp (subkey,"proxyauthmethod" )) { -return git_config_string ((const char **)&remote->http_proxy_authmethod, +return git_config_string (&remote->http_proxy_authmethod, key, value); }else if (!strcmp (subkey,"vcs" )) {return git_config_string (&remote->foreign_vcs, key, value);
remote.h +4 -4
@@ -46,7 +46,7 @@ struct remote_state {struct hashmap branches_hash;struct branch *current_branch; -const char *pushremote_name; +char *pushremote_name;struct rewrites rewrites;struct rewrites rewrites_push;
@@ -65,7 +65,7 @@ struct remote {int origin, configured_in_repo; -const char *foreign_vcs; +char *foreign_vcs;/* An array of all of the url_nr URLs configured for the remote */ const char **url;
@@ -309,9 +309,9 @@ struct branch {const char *refname;/* The name of the remote listed in the configuration. */ -const char *remote_name; +char *remote_name; -const char *pushremote_name; +char *pushremote_name;/* An array of the "merge" lines in the configuration. */ const char **merge_name;
sequencer.c +1 -1
@@ -306,7 +306,7 @@ static int git_sequencer_config(const char *k, const char *v, }if (!opts->default_strategy && !strcmp (k,"pull.twohead" )) { -int ret =git_config_string ((const char **)&opts->default_strategy, k, v); +int ret =git_config_string (&opts->default_strategy, k, v);if (ret ==0 ) {/* * pull.twohead is allowed to be multi-valued; we only
upload-pack.c +1 -1
@@ -94,7 +94,7 @@ struct upload_pack_data {struct packet_writer writer; -const char *pack_objects_hook; +char *pack_objects_hook; unsigned stateless_rpc : 1; /* v0 only */ unsigned no_done : 1; /* v0 only */
userdiff.h +6 -6
@@ -7,19 +7,19 @@ struct index_state;struct repository ;struct userdiff_funcname { -const char *pattern; +char *pattern;int cflags; };struct userdiff_driver {const char *name; -const char *external; -const char *algorithm; +char *external; +char *algorithm;int binary;struct userdiff_funcname funcname; -const char *word_regex; -const char *word_regex_multi_byte; -const char *textconv; +char *word_regex; +char *word_regex_multi_byte; +char *textconv;struct notes_cache *textconv_cache;int textconv_want_cache; };
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 12/20 config: plug various memory leaks
In reply to Patrick SteinhardtNow that memory ownership rules around git_config_string() and git_config_pathname() are clearer, it also got easier to spot that the returned memory needs to be free'd. Plug a subset of those cases and mark now-passing tests as leak free.
--- alias.c | 4 ++- config.c | 38 ++++++++++++++------ t/t1306-xdg-files.sh | 1 + t/t1350-config-hooks-path.sh | 1 + t/t3415-rebase-autosquash.sh | 1 + t/t4041-diff-submodule-option.sh | 1 + t/t4060-diff-submodule-option-diff-format.sh | 1 + t/t4210-log-i18n.sh | 2 ++ t/t6006-rev-list-format.sh | 1 + t/t7005-editor.sh | 1 + t/t7102-reset.sh | 1 + t/t9129-git-svn-i18n-commitencoding.sh | 1 - t/t9139-git-svn-non-utf8-commitencoding.sh | 1 - 13 files changed, 41 insertions(+), 13 deletions(-)
alias.c +3 -1
@@ -21,9 +21,11 @@ static int config_alias_cb(const char *key, const char *value,return 0 ;if (data->alias) { -if (!strcasecmp (p, data->alias)) +if (!strcasecmp (p, data->alias)) { +FREE_AND_NULL (data->v);return git_config_string (&data->v, key, value); + } }else if (data->list) {string_list_append (data->list, p); }
config.c +28 -10
@@ -1414,8 +1414,10 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.attributesfile" )) +if (!strcmp (var,"core.attributesfile" )) { +FREE_AND_NULL (git_attributes_file);return git_config_pathname (&git_attributes_file, var, value); + }if (!strcmp (var,"core.hookspath" )) { if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL &&
@@ -1428,6 +1430,7 @@ static int git_default_core_config(const char *var, const char *value,"again with " "`GIT_CLONE_PROTECTION_ACTIVE=false`"), value); +FREE_AND_NULL (git_hooks_path);return git_config_pathname (&git_hooks_path, var, value); }
@@ -1566,7 +1569,7 @@ static int git_default_core_config(const char *var, const char *value,if (!strcmp (var,"core.checkroundtripencoding" )) {FREE_AND_NULL (check_roundtrip_encoding); -return git_config_string ((const char **) &check_roundtrip_encoding, var, value); +return git_config_string (&check_roundtrip_encoding, var, value); }if (!strcmp (var,"core.notesref" )) {
@@ -1576,8 +1579,10 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.editor" )) +if (!strcmp (var,"core.editor" )) { +FREE_AND_NULL (editor_program);return git_config_string (&editor_program, var, value); + }if (!strcmp (var,"core.commentchar" ) || !strcmp (var,"core.commentstring" )) {
@@ -1595,11 +1600,13 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.askpass" )) +if (!strcmp (var,"core.askpass" )) { +FREE_AND_NULL (askpass_program);return git_config_string (&askpass_program, var, value); + }if (!strcmp (var,"core.excludesfile" )) { -free (excludes_file); +FREE_AND_NULL (excludes_file);return git_config_pathname (&excludes_file, var, value); }
@@ -1702,11 +1709,15 @@ static int git_default_sparse_config(const char *var, const char *value)static int git_default_i18n_config (const char *var,const char *value) { -if (!strcmp (var,"i18n.commitencoding" )) +if (!strcmp (var,"i18n.commitencoding" )) { +FREE_AND_NULL (git_commit_encoding);return git_config_string (&git_commit_encoding, var, value); + } -if (!strcmp (var,"i18n.logoutputencoding" )) +if (!strcmp (var,"i18n.logoutputencoding" )) { +FREE_AND_NULL (git_log_output_encoding);return git_config_string (&git_log_output_encoding, var, value); + }/* Add other config variables here and to Documentation/config.txt. */ return 0 ;
@@ -1779,10 +1790,15 @@ static int git_default_push_config(const char *var, const char *value)static int git_default_mailmap_config (const char *var,const char *value) { -if (!strcmp (var,"mailmap.file" )) +if (!strcmp (var,"mailmap.file" )) { +FREE_AND_NULL (git_mailmap_file);return git_config_pathname (&git_mailmap_file, var, value); -if (!strcmp (var,"mailmap.blob" )) + } + +if (!strcmp (var,"mailmap.blob" )) { +FREE_AND_NULL (git_mailmap_blob);return git_config_string (&git_mailmap_blob, var, value); + }/* Add other config variables here and to Documentation/config.txt. */ return 0 ;
@@ -1790,8 +1806,10 @@ static int git_default_mailmap_config(const char *var, const char *value)static int git_default_attr_config (const char *var,const char *value) { -if (!strcmp (var,"attr.tree" )) +if (!strcmp (var,"attr.tree" )) { +FREE_AND_NULL (git_attr_tree);return git_config_string (&git_attr_tree, var, value); + }/* * Add other attribute related config variables here and to
t/t1306-xdg-files.sh +1 -0
@@ -7,6 +7,7 @@ test_description='Compatibility with $XDG_CONFIG_HOME/git/ files' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
t/t1350-config-hooks-path.sh +1 -0
@@ -2,6 +2,7 @@ test_description='Test the core.hooksPath configuration variable' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up a pre-commit hook in core.hooksPath' '
t/t3415-rebase-autosquash.sh +1 -0
@@ -5,6 +5,7 @@ test_description='auto squash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh
t/t4041-diff-submodule-option.sh +1 -0
@@ -12,6 +12,7 @@ This test tries to verify the sanity of the --submodule option of git diff. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding
t/t4060-diff-submodule-option-diff-format.sh +1 -0
@@ -10,6 +10,7 @@ test_description='Support for diff format verbose submodule difference in git di This test tries to verify the sanity of --submodule=diff option of git diff. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding
t/t4210-log-i18n.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh # two forms of é
t/t6006-rev-list-format.sh +1 -0
@@ -8,6 +8,7 @@ test_description='git rev-list --pretty=format test' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh
t/t7005-editor.sh +1 -0
@@ -2,6 +2,7 @@ test_description='GIT_EDITOR, core.editor, and stuff' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh unset EDITOR VISUAL GIT_EDITOR
t/t7102-reset.sh +1 -0
@@ -10,6 +10,7 @@ Documented tests for git reset' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh commit_msg () {
t/t9129-git-svn-i18n-commitencoding.sh +0 -1
@@ -4,7 +4,6 @@ test_description='git svn honors i18n.commitEncoding in config' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh compare_git_head_with () {
t/t9139-git-svn-non-utf8-commitencoding.sh +0 -1
@@ -4,7 +4,6 @@ test_description='git svn refuses to dcommit non-UTF8 messages' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh # ISO-2022-JP can pass for valid UTF-8, so skipping that in this test
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH 12/20] config: plug various memory leaks
Patrick Steinhardt <[email protected]> writes:
There seems to be a bit of problem with "git mailinfo" with this patch. No need to resend this series, as I think I can cope, but to solicit help from folks diagnosing and fixing the issue with the "git am" pipeline.
t/t4210-log-i18n.sh line 8
Quoted 13 lines
t/t4210-log-i18n.sh +2
-0@@ -1,6 +1,8 @@ #!/bin/sh test_description='test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh # two forms of é
You've been sending your patches as "multipart/signed" (which by the way was cumbersome enough for me but that is primarily the problem with Emacs/GNUS and we should handle multipart/signed well), whose header reads like so:
Content-Type: multipart/signed; micalg=pgp-sha512;
protocol="application/pgp-signature"; boundary="ovDSRLSkA00eIgbN"
The "message" part in it says that it is encoded in iso-8859-1:
--ovDSRLSkA00eIgbN
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
...
diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh
index 75216f19ce..7120030b5c 100755
--- a/t/t4210-log-i18n.sh
+++ b/t/t4210-log-i18n.sh
@@ -1,6 +1,8 @@
#!/bin/sh
=20
test_description=3D'test log with i18n features'
+
+TEST_PASSES_SANITIZE_LEAK=3Dtrue
. ./lib-gettext.sh
=20
# two forms of =E9But the source in t/t4210-log-i18n.sh actually is written in UTF-8. The thing is, the "-u" option (recode into utf-8" is supposed to be the default for "git am", and it is passed down to the underlying mailinfo machinery in builtin/am.c:parse_mail().
But apparently that is not working correctly. I see in the patch an unrecoded byte E9 in the resulting patch file that is fed to the underlying "git apply" machinery, failing the application.
ANyway...
Re: [PATCH 12/20] config: plug various memory leaks
Quoted 20 lines · Junio C Hamano
t/t4210-log-i18n.sh line 8
Quoted 13 lines · Patrick Steinhardt
t/t4210-log-i18n.sh +2
-0@@ -1,6 +1,8 @@ #!/bin/sh test_description='test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh # two forms of éYou've been sending your patches as "multipart/signed" (which by the way was cumbersome enough for me but that is primarily the problem with Emacs/GNUS and we should handle multipart/signed well), whose header reads like so:
Quoted 2 lines
Content-Type: multipart/signed; micalg=pgp-sha512;
protocol="application/pgp-signature"; boundary="ovDSRLSkA00eIgbN"
Well, signing is of dubious value anyway, but it does have the upside of stressing our toolchain a bit more and thus making it (hopefully) more robust :) If it bothers you, then I can probably stop doing so. But for now I will keep it as-is.
Quoted 22 lines
The "message" part in it says that it is encoded in iso-8859-1:
--ovDSRLSkA00eIgbN Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable ... diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index 75216f19ce..7120030b5c 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -1,6 +1,8 @@ #!/bin/sh =20 test_description=3D'test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=3Dtrue . ./lib-gettext.sh =20 # two forms of =E9
Quoted 8 lines
But the source in
t/t4210-log-i18n.shactually is written in UTF-8. The thing is, the "-u" option (recode into utf-8" is supposed to be the default for "git am", and it is passed down to the underlying mailinfo machinery in builtin/am.c:parse_mail().But apparently that is not working correctly. I see in the patch an unrecoded byte E9 in the resulting patch file that is fed to the underlying "git apply" machinery, failing the application.
Hm. I'll double check mail headers before sending out the next iteration.
Patrick
Re: [PATCH 12/20] config: plug various memory leaks
Quoted 29 lines · Patrick Steinhardt
Quoted 28 lines · Junio C Hamano
Patrick Steinhardt <[email protected]> writes:
The "message" part in it says that it is encoded in iso-8859-1:--ovDSRLSkA00eIgbN Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable ... diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index 75216f19ce..7120030b5c 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -1,6 +1,8 @@ #!/bin/sh =20 test_description=3D'test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=3Dtrue . ./lib-gettext.sh =20 # two forms of =E9But the source in
t/t4210-log-i18n.shactually is written in UTF-8. The thing is, the "-u" option (recode into utf-8" is supposed to be the default for "git am", and it is passed down to the underlying mailinfo machinery in builtin/am.c:parse_mail().
Quoted 5 lines
Quoted 3 lines
But apparently that is not working correctly. I see in the patch an unrecoded byte E9 in the resulting patch file that is fed to the underlying "git apply" machinery, failing the application.
Hm. I'll double check mail headers before sending out the next iteration.
I've changed my mutt configuration to prefer UTF-8 over ISO-8859-1, so the next iteration should hopefully work alright. But as you say, this is probably something that needs to be fixed in the git-am(1) machinery.
Patrick
Re: [PATCH 12/20] config: plug various memory leaks
Quoted 3 lines · Patrick Steinhardt
I've changed my mutt configuration to prefer UTF-8 over ISO-8859-1, so the next iteration should hopefully work alright. But as you say, this is probably something that needs to be fixed in the git-am(1) machinery.
Yup, you can probably keep the preference to annoy me into looking into the issue ;-)
Thanks.
Re: [PATCH 12/20] config: plug various memory leaks
In reply to Patrick SteinhardtQuoted 13 lines · Patrick Steinhardt
Quoted 13 lines
The "message" part in it says that it is encoded in iso-8859-1:
--ovDSRLSkA00eIgbN Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable...
But the source in
t/t4210-log-i18n.shactually is written in UTF-8. The thing is, the "-u" option (recode into utf-8" is supposed to be the default for "git am", and it is passed down to the underlying mailinfo machinery in builtin/am.c:parse_mail().
Quoted 5 lines
Quoted 3 lines
But apparently that is not working correctly. I see in the patch an unrecoded byte E9 in the resulting patch file that is fed to the underlying "git apply" machinery, failing the application.
Hm. I'll double check mail headers before sending out the next iteration.
Just to make sure there is no misunderstanding, I am not saying that sending =E9 in a part that is marked as charset=iso-8859-1 using quoted-printable to express the letter é in a context line of a file that is encoded in UTF-8. In other words, I am not saying that your e-mail setup is suspect. Instead, I am saying that the behaviour on the receiving end of "git am" that eventually calls "git mailinfo" is suspicious.
Thanks.
PATCH 13/20 builtin/credential: clear credential before exit
In reply to Patrick SteinhardtWe never release memory associated with struct credential. Fix this and mark the corresponding test as leak free.
--- builtin/credential.c | 2 ++ t/t0300-credentials.sh | 2 ++ 2 files changed, 4 insertions(+)
builtin/credential.c +2 -0
@@ -39,5 +39,7 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) }else {usage (usage_msg); } + +credential_clear (&c);return 0 ; }
t/t0300-credentials.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='basic credential helper tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-credential.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 14/20 commit-reach: fix memory leak in `ahead_behind()`
In reply to Patrick SteinhardtWe use a priority queue in ahead_behind() to compute the ahead/behind count for commits. We may not iterate through all commits part of that queue though in case all of its entries are stale. Consequently, as we never make the effort to release the remaining commits, we end up leaking bit arrays that we have allocated for each of the contained commits.
Plug this leak and mark the corresponding test as leak free.
--- commit-reach.c | 4 ++++ t/t3203-branch-output.sh | 2 ++ 2 files changed, 6 insertions(+)
commit-reach.c +4 -0
@@ -1106,6 +1106,10 @@ void ahead_behind(struct repository *r,/* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks (r,PARENT2 |STALE ); +while (prio_queue_peek (&queue)) { +struct commit *c =prio_queue_get (&queue); +free_bit_array (c); + }clear_bit_arrays (&bit_arrays);clear_prio_queue (&queue); }
t/t3203-branch-output.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git branch display tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 15/20 submodule: fix leaking memory for submodule entries
In reply to Patrick SteinhardtIn free_one_config() we never end up freeing the url and ignore fields and thus leak memory. Fix those leaks and mark now-passing tests as leak free.
--- submodule-config.c | 2 ++ t/t1013-read-tree-submodule.sh | 1 + t/t2013-checkout-submodule.sh | 1 + t/t3007-ls-files-recurse-submodules.sh | 1 + t/t7112-reset-submodule.sh | 1 + 5 files changed, 6 insertions(+)
submodule-config.c +2 -0
@@ -91,6 +91,8 @@ static void free_one_config(struct submodule_entry *entry)free ((void *) entry->config->path);free ((void *) entry->config->name);free ((void *) entry->config->branch); +free ((void *) entry->config->url); +free ((void *) entry->config->ignore);free ((void *) entry->config->update_strategy.command);free (entry->config); }
t/t1013-read-tree-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='read-tree can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t2013-checkout-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='checkout can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3007-ls-files-recurse-submodules.sh +1 -0
@@ -6,6 +6,7 @@ This test verifies the recurse-submodules feature correctly lists files from submodules. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup directory structure and submodules' '
t/t7112-reset-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='reset can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 16/20 strvec: add functions to replace and remove strings
In reply to Patrick SteinhardtAdd two functions that allow to replace and remove strings contained in the strvec. This will be used by a subsequent commit that refactors git-mv(1).
While at it, add a bunch of unit tests that cover both old and new functionality.
--- Makefile | 1 + strvec.c | 20 ++++ strvec.h | 13 ++ t/unit-tests/t-strvec.c | 259 ++++++++++++++++++++++++++++++++++++++++ t/unit-tests/test-lib.c | 13 ++ t/unit-tests/test-lib.h | 13 ++ 6 files changed, 319 insertions(+) create mode 100644 t/unit-tests/t-strvec.c
Makefile +1 -0
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% UNIT_TEST_PROGRAMS += t-mem-pool UNIT_TEST_PROGRAMS += t-strbuf +UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-prio-queue UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
strvec.c +20 -0
@@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items)strvec_push (array, *items); } +const char *strvec_replace (struct strvec *array,size_t idx,const char *replacement) +{ +char *to_free; +if (idx >= array->nr) +BUG ("index outside of array boundary" ); + to_free = (char *) array->v[idx]; + array->v[idx] =xstrdup (replacement); +free (to_free); +return array->v[idx]; +} + +void strvec_remove (struct strvec *array,size_t idx) +{ +if (idx >= array->nr) +BUG ("index outside of array boundary" ); +free ((char *)array->v[idx]); +memmove (array->v + idx, array->v + idx +1 , (array->nr - idx) *sizeof (char *)); + array->nr--; +} +void strvec_pop (struct strvec *array) {if (!array->nr)
strvec.h +13 -0
@@ -64,6 +64,19 @@ void strvec_pushl(struct strvec *, ...);/* Push a null-terminated array of strings onto the end of the array. */ void strvec_pushv (struct strvec *,const char **); +/** +* Replace the value at the given index with a new value. The index must be +* valid. Returns a pointer to the inserted value. +*/ +const char *strvec_replace (struct strvec *array,size_t idx,const char *replacement); + +/* +* Remove the value at the given index. The remainder of the array will be +* moved to fill the resulting gap. The provided index must point into the +* array. +*/ +void strvec_remove (struct strvec *array,size_t idx); +/** * Remove the final element from the array. If there are no * elements in the array, do nothing.
t/unit-tests/t-strvec.c new file +259 -0
259 lines not on this page · raw message
t/unit-tests/test-lib.c +13 -0
@@ -318,6 +318,19 @@ int check_bool_loc(const char *loc, const char *check, int ok)union test__tmp test__tmp[2 ]; +int check_pointer_eq_loc (const char *loc,const char *check,int ok, +const void *a,const void *b) +{ +int ret =test_assert (loc, check, ok); + +if (!ret) { +test_msg (" left: %p" , a); +test_msg (" right: %p" , b); + } + +return ret; +} +int check_int_loc (const char *loc,const char *check,int ok,intmax_t a,intmax_t b) {
t/unit-tests/test-lib.h +13 -0
@@ -75,6 +75,18 @@ int test_assert(const char *location, const char *check, int ok); check_bool_loc(TEST_LOCATION(), #x, x)int check_bool_loc (const char *loc,const char *check,int ok); +/* +* Compare two integers. Prints a message with the two values if the +* comparison fails. NB this is not thread safe. +*/ +#define check_pointer_eq (a, b) \ + (test__tmp[0].p = (a), test__tmp[1].p = (b), \ + check_pointer_eq_loc(TEST_LOCATION(), #a" == "#b, \ + test__tmp[0].p == test__tmp[1].p, \ + test__tmp[0].p, test__tmp[1].p)) +int check_pointer_eq_loc (const char *loc,const char *check,int ok, +const void *a,const void *b); +/* * Compare two integers. Prints a message with the two values if the * comparison fails. NB this is not thread safe.
@@ -136,6 +148,7 @@ union test__tmp {intmax_t i;uintmax_t u;char c; +const void *p; };extern union test__tmp test__tmp[2 ];
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH 16/20] strvec: add functions to replace and remove strings
Quoted 8 lines · Patrick Steinhardt
Add two functions that allow to replace and remove strings contained in the strvec. This will be used by a subsequent commit that refactors git-mv(1).
[...]Signed-off-by Patrick Steinhardt <[email protected]>---
strvec.c void strvec_pushv(struct strvec *array, const char **items) · line 65
Quoted 10 lines
strvec.c +10
-0@@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items) +const char *strvec_replace (struct strvec *array,size_t idx,const char *replacement) +{ +char *to_free; +if (idx >= array->nr) +BUG ("index outside of array boundary" ); + to_free = (char *) array->v[idx]; + array->v[idx] =xstrdup (replacement); +free (to_free); +return array->v[idx]; +}
The reason you delay calling free() until after xstrdup() is to protect against the case when replacement is a substring of the string already stored at v[idx], correct?
Re: [PATCH 16/20] strvec: add functions to replace and remove strings
Quoted 15 lines · Eric Sunshine
Quoted 14 lines · Patrick Steinhardt
Add two functions that allow to replace and remove strings contained in the strvec. This will be used by a subsequent commit that refactors git-mv(1).
[...]Signed-off-by Patrick Steinhardt <[email protected]>---
strvec.c +6
-0@@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items) +const char *strvec_replace (struct strvec *array,size_t idx,const char *replacement) +{ +char *to_free; +if (idx >= array->nr) +BUG ("index outside of array boundary" ); + to_free = (char *) array->v[idx];
Quoted 8 lines
strvec.c void strvec_pushv(struct strvec *array, const char **items) · line 68
Quoted 4 lines
strvec.c +4
-0+ array->v[idx] =xstrdup (replacement); +free (to_free); +return array->v[idx]; +}The reason you delay calling
free()until afterxstrdup()is to protect against the case whenreplacementis a substring of the string already stored atv[idx], correct?
Yup. The patches for the strvec API have been lying around for quite a while as I have originally implemented them in a different context. And there I did hit this edge case indeed. It's rather unlikely to happen overall, but I think it shouldn't hurt to be prepared.
There is no coverage of this scenario in this patch series though. Let me add a unit test for this.
Patrick
PATCH 17/20 builtin/mv: refactor `add_slash()` to always return allocated strings
In reply to Patrick SteinhardtThe add_slash() function will only conditionally return an allocated string when the passed-in string did not yet have a trailing slash. This makes the memory ownership harder to track than really necessary.
It's dubious whether this optimization really buys us all that much. The number of times we execute this function is bounded by the number of arguments to git-mv(1), so in the typical case we may end up saving an allocation or two.
Simplify the code to unconditionally return allocated strings.
--- builtin/mv.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-)
builtin/mv.c +20 -18
@@ -76,7 +76,7 @@ static const char **internal_prefix_pathspec(const char *prefix,return result; } -static const char *add_slash (const char *path) +static char *add_slash (const char *path) {size_t len =strlen (path);if (len && path[len -1 ] !='/' ) {
@@ -86,7 +86,7 @@ static const char *add_slash(const char *path) with_slash[len] =0 ;return with_slash; } -return path; +return xstrdup (path); }#define SUBMODULE_WITH_GITDIR ((const char *)1)
@@ -111,7 +111,7 @@ static void prepare_move_submodule(const char *src, int first,static int index_range_of_same_dir (const char *src,int length,int *first_p,int *last_p) { -const char *src_w_slash =add_slash (src); +char *src_w_slash =add_slash (src);int first, last, len_w_slash = length +1 ; first =index_name_pos (the_repository->index, src_w_slash, len_w_slash);
@@ -124,8 +124,8 @@ static int index_range_of_same_dir(const char *src, int length,if (strncmp (path, src_w_slash, len_w_slash))break ; } -if (src_w_slash != src) -free ((char *)src_w_slash); + +free (src_w_slash); *first_p = first; *last_p = last;return last - first;
@@ -141,7 +141,7 @@ static int index_range_of_same_dir(const char *src, int length,static int empty_dir_has_sparse_contents (const char *name) {int ret =0 ; -const char *with_slash =add_slash (name); +char *with_slash =add_slash (name);int length =strlen (with_slash);int pos =index_name_pos (the_repository->index, with_slash, length);
@@ -159,8 +159,7 @@ static int empty_dir_has_sparse_contents(const char *name) } free_return: -if (with_slash != name) -free ((char *)with_slash); +free (with_slash);return ret; }
@@ -178,7 +177,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_END(), };const char **source, **destination, **dest_path, **submodule_gitfile; -const char *dst_w_slash; +char *dst_w_slash =NULL ;const char **src_dir =NULL ;int src_dir_nr =0 , src_dir_alloc =0 ;struct strbuf a_src_dir =STRBUF_INIT ;
@@ -243,10 +242,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dst_mode =SPARSE ; } } -if (dst_w_slash != dest_path[0 ]) { -free ((char *)dst_w_slash); - dst_w_slash =NULL ; - }/* Checking */ for (i =0 ; i < argc; i++) {
@@ -265,12 +260,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix) pos =index_name_pos (the_repository->index, src, length);if (pos <0 ) { -const char *src_w_slash =add_slash (src); +char *src_w_slash =add_slash (src);if (!path_in_sparse_checkout (src_w_slash, the_repository->index) &&empty_dir_has_sparse_contents (src)) { +free (src_w_slash); modes[i] |=SKIP_WORKTREE_DIR ; goto dir_check; } +free (src_w_slash);/* only error if existence is expected. */ if (!(modes[i] &SPARSE )) bad =_ ("bad source" );
@@ -310,7 +307,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dir_check:if (S_ISDIR (st.st_mode)) { -int j, dst_len, n; +char *dst_with_slash; +size_t dst_with_slash_len; +int j, n;int first =index_name_pos (the_repository->index, src, length), last;if (first >=0 ) {
@@ -335,19 +334,21 @@ int cmd_mv(int argc, const char **argv, const char *prefix)REALLOC_ARRAY (modes, n);REALLOC_ARRAY (submodule_gitfile, n); - dst =add_slash (dst); - dst_len =strlen (dst); + dst_with_slash =add_slash (dst); + dst_with_slash_len =strlen (dst_with_slash);for (j =0 ; j < last - first; j++) {const struct cache_entry *ce = the_repository->index->cache[first + j];const char *path = ce->name; source[argc + j] = path; destination[argc + j] = -prefix_path (dst, dst_len, path + length +1 ); +prefix_path (dst_with_slash, dst_with_slash_len, path + length +1 );memset (modes + argc + j,0 ,sizeof (enum update_mode )); modes[argc + j] |=ce_skip_worktree (ce) ?SPARSE :INDEX ; submodule_gitfile[argc + j] =NULL ; } + +free (dst_with_slash); argc += last - first; goto act_on_entry; }
@@ -565,6 +566,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED))die (_ ("Unable to write new index file" )); +free (dst_w_slash);string_list_clear (&src_for_dst,0 );string_list_clear (&dirty_paths,0 );UNLEAK (source);
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 18/20 builtin/mv duplicate string list memory
In reply to Patrick Steinhardtmakes the next patch easier, where we will migrate to the paths being owned by a strvec. given that we are talking about command line parameters here it's also not like we have tons of allocations that this would save
while at it, fix a memory leak
--- builtin/mv.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-)
builtin/mv.c +13 -6
@@ -183,11 +183,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)struct strbuf a_src_dir =STRBUF_INIT ;enum update_mode *modes, dst_mode =0 ;struct stat st, dest_st; -struct string_list src_for_dst =STRING_LIST_INIT_NODUP ; +struct string_list src_for_dst =STRING_LIST_INIT_DUP ;struct lock_file lock_file =LOCK_INIT ;struct cache_entry *ce; -struct string_list only_match_skip_worktree =STRING_LIST_INIT_NODUP ; -struct string_list dirty_paths =STRING_LIST_INIT_NODUP ; +struct string_list only_match_skip_worktree =STRING_LIST_INIT_DUP ; +struct string_list dirty_paths =STRING_LIST_INIT_DUP ; +int ret;git_config (git_default_config,NULL );
@@ -440,8 +441,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)if (only_match_skip_worktree.nr) {advise_on_updating_sparse_paths (&only_match_skip_worktree); -if (!ignore_errors) -return 1 ; +if (!ignore_errors) { + ret =1 ; + goto out; + } }for (i =0 ; i < argc; i++) {
@@ -566,12 +569,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED))die (_ ("Unable to write new index file" )); + ret =0 ; + +out:free (dst_w_slash);string_list_clear (&src_for_dst,0 );string_list_clear (&dirty_paths,0 ); +string_list_clear (&only_match_skip_worktree,0 );UNLEAK (source);UNLEAK (dest_path);free (submodule_gitfile);free (modes); -return 0 ; +return ret; }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 19/20 builtin/mv: refactor to use `struct strvec`
In reply to Patrick SteinhardtMemory allocation patterns in git-mv(1) are extremely hard to follow:
We copy around string pointers into manually-managed arrays, some of which alias each other, but only sometimes, while we also drop some of those strings at other times without ever daring to free them.
While this may be my own subjective feeling, it seems like others have given up as the code has multiple calls to UNLEAK(). These are not sufficient though, and git-mv(1) is still leaking all over the place even with them.
Refactor the code to instead track strings in struct strvec. While this has the effect of effectively duplicating some of the strings without an actual need, it is way easier to reason about and fixes all of the aliasing of memory that has been going on. It allows us to get rid of the UNLEAK() calls and also fixes leaks that those calls did not paper over.
Mark tests which are now leak-free accordingly.
--- builtin/mv.c | 125 +++++++++++------------ t/t4001-diff-rename.sh | 4 +- t/t4043-diff-rename-binary.sh | 1 + t/t4120-apply-popt.sh | 1 + t/t6400-merge-df.sh | 1 + t/t6412-merge-large-rename.sh | 1 + t/t6426-merge-skip-unneeded-updates.sh | 1 + t/t6429-merge-sequence-rename-caching.sh | 1 + 8 files changed, 68 insertions(+), 67 deletions(-)
builtin/mv.c +60 -65
@@ -20,6 +20,7 @@#include "read-cache-ll.h" #include "repository.h" #include "setup.h" +#include "strvec.h" #include "submodule.h" #include "entry.h"
@@ -38,42 +39,32 @@ enum update_mode {#define DUP_BASENAME 1#define KEEP_TRAILING_SLASH 2 -static const char **internal_prefix_pathspec (const char *prefix, -const char **pathspec, -int count,unsigned flags) +static void internal_prefix_pathspec (struct strvec *out, +const char *prefix, +const char **pathspec, +int count,unsigned flags) { -int i; -const char **result;int prefixlen = prefix ?strlen (prefix) :0 ; -ALLOC_ARRAY (result, count +1 );/* Create an intermediate copy of the pathspec based on the flags */ -for (i =0 ; i < count; i++) { -int length =strlen (pathspec[i]); -int to_copy = length; -char *it; +for (int i =0 ; i < count; i++) { +size_t length =strlen (pathspec[i]); +size_t to_copy = length; +const char *maybe_basename; +char *trimmed, *prefixed_path; +while (!(flags &KEEP_TRAILING_SLASH ) && to_copy >0 &&is_dir_sep (pathspec[i][to_copy -1 ])) to_copy--; - it =xmemdupz (pathspec[i], to_copy); -if (flags &DUP_BASENAME ) { - result[i] =xstrdup (basename (it)); -free (it); - }else { - result[i] = it; - } - } - result[count] =NULL ; + trimmed =xmemdupz (pathspec[i], to_copy); + maybe_basename = (flags &DUP_BASENAME ) ?basename (trimmed) : trimmed; + prefixed_path =prefix_path (prefix, prefixlen, maybe_basename); +strvec_push (out, prefixed_path); -/* Prefix the pathspec and free the old intermediate strings */ -for (i =0 ; i < count; i++) { -const char *match =prefix_path (prefix, prefixlen, result[i]); -free ((char *) result[i]); - result[i] = match; +free (prefixed_path); +free (trimmed); } - -return result; }static char *add_slash (const char *path)
@@ -176,7 +167,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)OPT_BOOL (0 ,"sparse" , &ignore_sparse,N_ ("allow updating entries outside of the sparse-checkout cone" )), OPT_END(), }; -const char **source, **destination, **dest_path, **submodule_gitfile; +struct strvec sources =STRVEC_INIT ; +struct strvec dest_paths =STRVEC_INIT ; +struct strvec destinations =STRVEC_INIT ; +const char **submodule_gitfile;char *dst_w_slash =NULL ;const char **src_dir =NULL ;int src_dir_nr =0 , src_dir_alloc =0 ;
@@ -201,7 +195,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)if (repo_read_index (the_repository) <0 )die (_ ("index file corrupt" )); - source =internal_prefix_pathspec (prefix, argv, argc,0 ); +internal_prefix_pathspec (&sources, prefix, argv, argc,0 );CALLOC_ARRAY (modes, argc);/*
@@ -212,41 +206,39 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags =KEEP_TRAILING_SLASH ;if (argc ==1 &&is_directory (argv[0 ]) && !is_directory (argv[1 ])) flags =0 ; - dest_path =internal_prefix_pathspec (prefix, argv + argc,1 , flags); - dst_w_slash =add_slash (dest_path[0 ]); +internal_prefix_pathspec (&dest_paths, prefix, argv + argc,1 , flags); + dst_w_slash =add_slash (dest_paths.v[0 ]); submodule_gitfile =xcalloc (argc,sizeof (char *)); -if (dest_path[0 ][0 ] =='\0' ) +if (dest_paths.v[0 ][0 ] =='\0' )/* special case: "." was normalized to "" */ - destination =internal_prefix_pathspec (dest_path[0 ], argv, argc,DUP_BASENAME ); -else if (!lstat (dest_path[0 ], &st) && -S_ISDIR (st.st_mode)) { - destination =internal_prefix_pathspec (dst_w_slash, argv, argc,DUP_BASENAME ); +internal_prefix_pathspec (&destinations, dest_paths.v[0 ], argv, argc,DUP_BASENAME ); +else if (!lstat (dest_paths.v[0 ], &st) &&S_ISDIR (st.st_mode)) { +internal_prefix_pathspec (&destinations, dst_w_slash, argv, argc,DUP_BASENAME ); + }else if (!path_in_sparse_checkout (dst_w_slash, the_repository->index) && +empty_dir_has_sparse_contents (dst_w_slash)) { +internal_prefix_pathspec (&destinations, dst_w_slash, argv, argc,DUP_BASENAME ); + dst_mode =SKIP_WORKTREE_DIR ; + }else if (argc !=1 ) { +die (_ ("destination '%s' is not a directory" ), dest_paths.v[0 ]); }else { -if (!path_in_sparse_checkout (dst_w_slash, the_repository->index) && -empty_dir_has_sparse_contents (dst_w_slash)) { - destination =internal_prefix_pathspec (dst_w_slash, argv, argc,DUP_BASENAME ); - dst_mode =SKIP_WORKTREE_DIR ; - }else if (argc !=1 ) { -die (_ ("destination '%s' is not a directory" ), dest_path[0 ]); - }else { - destination = dest_path; -/* -* <destination> is a file outside of sparse-checkout -* cone. Insist on cone mode here for backward -* compatibility. We don't want dst_mode to be assigned -* for a file when the repo is using no-cone mode (which -* is deprecated at this point) sparse-checkout. As -* SPARSE here is only considering cone-mode situation. -*/ -if (!path_in_cone_mode_sparse_checkout (destination[0 ], the_repository->index)) - dst_mode =SPARSE ; - } +strvec_pushv (&destinations, dest_paths.v); + +/* +* <destination> is a file outside of sparse-checkout +* cone. Insist on cone mode here for backward +* compatibility. We don't want dst_mode to be assigned +* for a file when the repo is using no-cone mode (which +* is deprecated at this point) sparse-checkout. As +* SPARSE here is only considering cone-mode situation. +*/ +if (!path_in_cone_mode_sparse_checkout (destinations.v[0 ], the_repository->index)) + dst_mode =SPARSE ; }/* Checking */ for (i =0 ; i < argc; i++) { -const char *src = source[i], *dst = destination[i]; +const char *src = sources.v[i], *dst = destinations.v[i];int length;const char *bad =NULL ;int skip_sparse =0 ;
@@ -330,8 +322,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) src_dir[src_dir_nr++] = src; n = argc + last - first; -REALLOC_ARRAY (source, n); -REALLOC_ARRAY (destination, n);REALLOC_ARRAY (modes, n);REALLOC_ARRAY (submodule_gitfile, n);
@@ -341,12 +331,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)for (j =0 ; j < last - first; j++) {const struct cache_entry *ce = the_repository->index->cache[first + j];const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = -prefix_path (dst_with_slash, dst_with_slash_len, path + length +1 ); +char *prefixed_path =prefix_path (dst_with_slash, dst_with_slash_len, path + length +1 ); + +strvec_push (&sources, path); +strvec_push (&destinations, prefixed_path); +memset (modes + argc + j,0 ,sizeof (enum update_mode )); modes[argc + j] |=ce_skip_worktree (ce) ?SPARSE :INDEX ; submodule_gitfile[argc + j] =NULL ; + +free (prefixed_path); }free (dst_with_slash);
@@ -430,8 +424,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) remove_entry:if (--argc >0 ) {int n = argc - i; -MOVE_ARRAY (source + i, source + i +1 , n); -MOVE_ARRAY (destination + i, destination + i +1 , n); +strvec_remove (&sources, i); +strvec_remove (&destinations, i);MOVE_ARRAY (modes + i, modes + i +1 , n);MOVE_ARRAY (submodule_gitfile + i, submodule_gitfile + i +1 , n);
@@ -448,7 +442,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) }for (i =0 ; i < argc; i++) { -const char *src = source[i], *dst = destination[i]; +const char *src = sources.v[i], *dst = destinations.v[i];enum update_mode mode = modes[i];int pos;int sparse_and_dirty =0 ;
@@ -576,8 +570,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)string_list_clear (&src_for_dst,0 );string_list_clear (&dirty_paths,0 );string_list_clear (&only_match_skip_worktree,0 ); -UNLEAK (source); -UNLEAK (dest_path); +strvec_clear (&sources); +strvec_clear (&dest_paths); +strvec_clear (&destinations);free (submodule_gitfile);free (modes);return ret;
t/t4001-diff-rename.sh +2 -2
@@ -3,9 +3,9 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Test rename detection in diff engine. +test_description='Test rename detection in diff engine.' -' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh
t/t4043-diff-rename-binary.sh +1 -0
@@ -5,6 +5,7 @@ test_description='Move a binary file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh
t/t4120-apply-popt.sh +1 -0
@@ -5,6 +5,7 @@ test_description='git apply -p handling.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup '
t/t6400-merge-df.sh +1 -0
@@ -7,6 +7,7 @@ test_description='Test merge with directory/file conflicts' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare repository' '
t/t6412-merge-large-rename.sh +1 -0
@@ -4,6 +4,7 @@ test_description='merging with large rename matrix' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh count() {
t/t6426-merge-skip-unneeded-updates.sh +1 -0
@@ -22,6 +22,7 @@ test_description="merge cases" # underscore notation is to differentiate different # files that might be renamed into each other's paths.) +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh
t/t6429-merge-sequence-rename-caching.sh +1 -0
@@ -2,6 +2,7 @@ test_description="remember regular & dir renames in sequence of merges" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh #
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH 20/20 builtin/mv: fix leaks for submodule gitfile paths
In reply to Patrick SteinhardtSimilar to the preceding commit, we have effectively given tracking memory ownership of submodule gitfile paths. Refactor the code to start tracking allocated strings in a separate struct strvec such that we can easily plug those leaks. Mark now-passing tests as leak free.
Note that ideally, we wouldn't require two separate data structures to track those paths. But we do need to store NULL pointers for the gitfile paths such that we can indicate that its corresponding entries in the other arrays do not have such a path at all. And given that struct strvecs cannot store NULL pointers we cannot use them to store this information.
There is another small gotcha that is easy to miss: you may be wondering why we don't want to store SUBMODULE_WITH_GITDIR in the strvec. This is because this is a mere sentinel value and not actually a string at all.
--- builtin/mv.c | 44 +++++++++++++---------- t/t4059-diff-submodule-not-initialized.sh | 1 + t/t7001-mv.sh | 2 ++ t/t7417-submodule-path-url.sh | 1 + t/t7421-submodule-summary-add.sh | 1 + 5 files changed, 30 insertions(+), 19 deletions(-)
builtin/mv.c +25 -19
@@ -82,21 +82,23 @@ static char *add_slash(const char *path)#define SUBMODULE_WITH_GITDIR ((const char *)1) -static void prepare_move_submodule (const char *src,int first, -const char **submodule_gitfile) +static const char *submodule_gitfile_path (const char *src,int first) {struct strbuf submodule_dotgit =STRBUF_INIT ; +const char *path; +if (!S_ISGITLINK (the_repository->index->cache[first]->ce_mode))die (_ ("Directory %s is in index and no submodule?" ), src);if (!is_staging_gitmodules_ok (the_repository->index))die (_ ("Please stage your changes to .gitmodules or stash them to proceed" )); +strbuf_addf (&submodule_dotgit,"%s/.git" , src); - *submodule_gitfile =read_gitfile (submodule_dotgit.buf); -if (*submodule_gitfile) - *submodule_gitfile =xstrdup (*submodule_gitfile); -else - *submodule_gitfile =SUBMODULE_WITH_GITDIR ; + + path =read_gitfile (submodule_dotgit.buf);strbuf_release (&submodule_dotgit); +if (path) +return path; +return SUBMODULE_WITH_GITDIR ; } static int index_range_of_same_dir(const char *src, int length,
@@ -170,7 +172,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)struct strvec sources =STRVEC_INIT ;struct strvec dest_paths =STRVEC_INIT ;struct strvec destinations =STRVEC_INIT ; -const char **submodule_gitfile; +struct strvec submodule_gitfiles_to_free =STRVEC_INIT ; +const char **submodule_gitfiles;char *dst_w_slash =NULL ;const char **src_dir =NULL ;int src_dir_nr =0 , src_dir_alloc =0 ;
@@ -208,7 +211,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags =0 ;internal_prefix_pathspec (&dest_paths, prefix, argv + argc,1 , flags); dst_w_slash =add_slash (dest_paths.v[0 ]); - submodule_gitfile =xcalloc (argc,sizeof (char *)); + submodule_gitfiles =xcalloc (argc,sizeof (char *));if (dest_paths.v[0 ][0 ] =='\0' )/* special case: "." was normalized to "" */
@@ -306,8 +309,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)int first =index_name_pos (the_repository->index, src, length), last;if (first >=0 ) { -prepare_move_submodule (src, first, - submodule_gitfile + i); +const char *path =submodule_gitfile_path (src, first); +if (path !=SUBMODULE_WITH_GITDIR ) + path =strvec_push (&submodule_gitfiles_to_free, path); + submodule_gitfiles[i] = path; goto act_on_entry; }else if (index_range_of_same_dir (src, length, &first, &last) <1 ) {
@@ -323,7 +328,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) n = argc + last - first;REALLOC_ARRAY (modes, n); -REALLOC_ARRAY (submodule_gitfile, n); +REALLOC_ARRAY (submodule_gitfiles, n); dst_with_slash =add_slash (dst); dst_with_slash_len =strlen (dst_with_slash);
@@ -338,7 +343,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)memset (modes + argc + j,0 ,sizeof (enum update_mode )); modes[argc + j] |=ce_skip_worktree (ce) ?SPARSE :INDEX ; - submodule_gitfile[argc + j] =NULL ; + submodule_gitfiles[argc + j] =NULL ;free (prefixed_path); }
@@ -427,8 +432,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)strvec_remove (&sources, i);strvec_remove (&destinations, i);MOVE_ARRAY (modes + i, modes + i +1 , n); -MOVE_ARRAY (submodule_gitfile + i, - submodule_gitfile + i +1 , n); +MOVE_ARRAY (submodule_gitfiles + i, + submodule_gitfiles + i +1 , n); i--; } }
@@ -462,12 +467,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)continue ;die_errno (_ ("renaming '%s' failed" ), src); } -if (submodule_gitfile[i]) { +if (submodule_gitfiles[i]) {if (!update_path_in_gitmodules (src, dst)) gitmodules_modified =1 ; -if (submodule_gitfile[i] !=SUBMODULE_WITH_GITDIR ) +if (submodule_gitfiles[i] !=SUBMODULE_WITH_GITDIR )connect_work_tree_and_git_dir (dst, - submodule_gitfile[i], + submodule_gitfiles[i],1 ); }
@@ -573,7 +578,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)strvec_clear (&sources);strvec_clear (&dest_paths);strvec_clear (&destinations); -free (submodule_gitfile); +strvec_clear (&submodule_gitfiles_to_free); +free (submodule_gitfiles);free (modes);return ret; }
t/t4059-diff-submodule-not-initialized.sh +1 -0
@@ -9,6 +9,7 @@ This test tries to verify that add_submodule_odb works when the submodule was initialized previously but the checkout has since been removed. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding
t/t7001-mv.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git mv in subdirs' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff-data.sh
t/t7417-submodule-path-url.sh +1 -0
@@ -4,6 +4,7 @@ test_description='check handling of .gitmodule path with dash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
t/t7421-submodule-summary-add.sh +1 -0
@@ -10,6 +10,7 @@ while making sure to add submodules using `git submodule add` instead of `git add` as done in t7401. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Quoted 7 lines · Patrick Steinhardt
my mind had a couple of minutes where it was roaming, and of course it immediately searched for and chased down the next rabbit hole. The result is this patch series which fixes a bunch of leaks all over the place. There isn't really any structure to the leaks that I did fix -- it's mostly things that I stumbled over. In the end, this series makes another 56 test suites pass with leak checking enabled, 13 of which have already been passing without any changes.
... meaning there were coverage gaps?
Quoted 10 lines
While most things are unstructured, there are two topics that stand out:
- Patches 5 to 12 address a shortcoming of our config API. Both
git_config_string()andgit_config_pathname()have aconst char **out parameter, but they do in fact transfer memory ownership to the caller. This resulted in a bunch of memory leaks all over the place.These patches thus refactor a bunch of code and then ultimately switch the out parameter to become a
char *
I do remember getting hurt by this one relatively recently. Addressing the issue is very much appreciated.
Quoted 11 lines
- Patches 16 to 20 have the goal of making git-mv(1) memory leak free. I had a very hard time understanding how it tracks memory. I think this wasn't only me, or otherwise there wouldn't be calls to
UNLEAK()in there. In any case, I decided to rewrite the arrays to use astruct strvec, which makes tracking and releasing of memory a ton easier.It does come at the cost of more allocations because we may now duplicate strings that we didn't before. But I think the tradeoff is worth it because the number of strings we may now duplicate is bounded by the number of command line arguments anyway.
Nice. I have to admit that "git mv" is not one of the best-done code in this project X-<, and improving it with rewriting was long overdue.
Thanks.
Quoted 11 lines · Junio C Hamano
Quoted 7 lines · Patrick Steinhardt
my mind had a couple of minutes where it was roaming, and of course it immediately searched for and chased down the next rabbit hole. The result is this patch series which fixes a bunch of leaks all over the place. There isn't really any structure to the leaks that I did fix -- it's mostly things that I stumbled over. In the end, this series makes another 56 test suites pass with leak checking enabled, 13 of which have already been passing without any changes.
... meaning there were coverage gaps?
Well, we never run CI jobs with GIT_TEST_PASSING_SANITIZE_LEAK=check. We could add such a job, but the question is how important it is to notice. The much more important part is to not regress, and that we do check in our CI already.
The only oddball here is newly added tests. It's a bit of a shame that the leak checking is opt-in rather than opt out, as this is the primary way for how new tests land that do pass but aren't marked accordingly. Also, it may raise more eyebrows during review if a new test suite is marked as failing compared to not being marked as passing.
Ideally, we'd just get over with all the tests that currently fail with the leak sanitizer. And honestly, that doesn't feel out of reach given that you can fix >10% of all non-passing tests in a day or two. With this patch series, we now have more test suites with the leak checking enabled rather than disabled. If we continue on this track I could certainly see this happening in a release or two.
But maybe that's just whishful thinking.
Quoted 7 lines
Quoted 7 lines
- Patches 16 to 20 have the goal of making git-mv(1) memory leak free. I had a very hard time understanding how it tracks memory. I think this wasn't only me, or otherwise there wouldn't be calls to
UNLEAK()in there. In any case, I decided to rewrite the arrays to use astruct strvec, which makes tracking and releasing of memory a ton easier.
Quoted 8 lines
Quoted 4 lines
It does come at the cost of more allocations because we may now duplicate strings that we didn't before. But I think the tradeoff is worth it because the number of strings we may now duplicate is bounded by the number of command line arguments anyway.
Nice. I have to admit that "git mv" is not one of the best-done code in this project X-<, and improving it with rewriting was long overdue.
Well, the patches only rewrite a very small part of git-mv(1). So while the code may be a bit easier to understand now, I still think that it leaves a lot to be desired after the refactoring.
Patrick
v2 cover Various memory leak fixes
In reply to Patrick SteinhardtHi,
this is the second version of my patch series that fixes various memory leaks in Git. Changes compared to v1:
- t4153 and t7006 aren't marked as passing anymore. I thought they pass because most of these tests were skipped because of a missing TTY prerequisite both on my local machine, but also in our CI.
- Add another patch to install the Perl IO:Pty module on Alpine and Ubuntu. This fulfills the TTY prerequisite and thus surfaces the memory leaks in both of the above tests.
- Add another unit test for strvec that exercise replacing a string in the strvec with a copy of itself.
- A bunch of commit message improvements.
Thanks!
Patrick
Patrick Steinhardt (21):
ci: add missing dependency for TTY prereq
t: mark a bunch of tests as leak-free
transport-helper: fix leaking helper name
strbuf: fix leak when appendwholeline() fails with EOF
checkout: clarify memory ownership in unique_tracking_name()
http: refactor code to clarify memory ownership
config: clarify memory ownership in git_config_pathname()
diff: refactor code to clarify memory ownership of prefixes
convert: refactor code to clarify ownership of
check_roundtrip_encoding
builtin/log: stop using globals for log config builtin/log: stop using globals for format config
config: clarify memory ownership in git_config_string()
config: plug various memory leaks
builtin/credential: clear credential before exit
commit-reach: fix memory leak in ahead_behind()
submodule: fix leaking memory for submodule entries
strvec: add functions to replace and remove strings builtin/mv: refactor add_slash() to always return allocated strings builtin/mv duplicate string list memory
builtin/mv: refactor to use struct strvec
builtin/mv: fix leaks for submodule gitfile paths
Makefile | 1 + alias.c | 6 +- attr.c | 2 +- attr.h | 2 +- builtin/blame.c | 2 +- builtin/checkout.c | 14 +- builtin/commit.c | 4 +- builtin/config.c | 2 +- builtin/credential.c | 2 + builtin/log.c | 708 ++++++++++-------- builtin/merge.c | 4 +- builtin/mv.c | 222 +++--- builtin/rebase.c | 2 +- builtin/receive-pack.c | 6 +- builtin/repack.c | 8 +- builtin/worktree.c | 20 +- checkout.c | 4 +- checkout.h | 6 +- ci/install-dependencies.sh | 4 +- commit-reach.c | 4 + config.c | 52 +- config.h | 10 +- convert.c | 30 +- convert.h | 2 +- delta-islands.c | 2 +- diff.c | 20 +- environment.c | 16 +- environment.h | 14 +- fetch-pack.c | 4 +- fsck.c | 4 +- fsmonitor-settings.c | 5 +- gpg-interface.c | 6 +- http.c | 50 +- imap-send.c | 12 +- mailmap.c | 4 +- mailmap.h | 4 +- merge-ll.c | 6 +- pager.c | 2 +- pretty.c | 14 +- promisor-remote.h | 2 +- remote.c | 20 +- remote.h | 8 +- sequencer.c | 2 +- setup.c | 6 +- strbuf.c | 4 +- strvec.c | 20 + strvec.h | 13 + submodule-config.c | 2 + t/t0300-credentials.sh | 2 + t/t0411-clone-from-partial.sh | 1 + t/t0610-reftable-basics.sh | 1 + t/t0611-reftable-httpd.sh | 1 + t/t1013-read-tree-submodule.sh | 1 + t/t1306-xdg-files.sh | 1 + t/t1350-config-hooks-path.sh | 1 + t/t1400-update-ref.sh | 2 + t/t2013-checkout-submodule.sh | 1 + t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t2405-worktree-submodule.sh | 1 + t/t3007-ls-files-recurse-submodules.sh | 1 + t/t3203-branch-output.sh | 2 + t/t3415-rebase-autosquash.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4001-diff-rename.sh | 4 +- t/t4041-diff-submodule-option.sh | 1 + t/t4043-diff-rename-binary.sh | 1 + t/t4059-diff-submodule-not-initialized.sh | 1 + t/t4060-diff-submodule-option-diff-format.sh | 1 + t/t4120-apply-popt.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t4210-log-i18n.sh | 2 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + t/t6006-rev-list-format.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6400-merge-df.sh | 1 + t/t6412-merge-large-rename.sh | 1 + t/t6426-merge-skip-unneeded-updates.sh | 1 + t/t6429-merge-sequence-rename-caching.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + t/t7001-mv.sh | 2 + t/t7005-editor.sh | 1 + t/t7102-reset.sh | 1 + t/t7112-reset-submodule.sh | 1 + t/t7417-submodule-path-url.sh | 1 + t/t7421-submodule-summary-add.sh | 1 + t/t7423-submodule-symlinks.sh | 1 + t/t9129-git-svn-i18n-commitencoding.sh | 1 - t/t9139-git-svn-non-utf8-commitencoding.sh | 1 - t/t9200-git-cvsexportcommit.sh | 1 + t/t9401-git-cvsserver-crlf.sh | 1 + t/t9600-cvsimport.sh | 1 + t/t9601-cvsimport-vendor-branch.sh | 1 + t/t9602-cvsimport-branches-tags.sh | 1 + t/t9603-cvsimport-patchsets.sh | 2 + t/t9604-cvsimport-timestamps.sh | 2 + t/unit-tests/t-strvec.c | 269 +++++++ t/unit-tests/test-lib.c | 13 + t/unit-tests/test-lib.h | 13 + transport-helper.c | 6 +- transport.c | 1 + upload-pack.c | 2 +- userdiff.h | 12 +- 109 files changed, 1151 insertions(+), 586 deletions(-) create mode 100644 t/unit-tests/t-strvec.c
Range-diff against v1:
-: ---------- > 1: 857b8b14ce ci: add missing dependency for TTY prereq 1: 0e9fa9ca73 ! 2: ceade7dbba t: mark a bunch of tests as leak-free
@@ Commit message
- t2405: Passes since 6741e917de (repository: avoid leaking fsmonitor data, 2024-04-12).
- - t4153: Passes since 71c7916053 (apply: plug a leak in apply_data,
- 2024-04-23).
-
- - t7006: Passes since at least Git v2.40. I did not care to go back
- any further than that.
-
- t7423: Introduced via b20c10fd9b (t7423: add tests for symlinked submodule directories, 2024-01-28), passes since e8d0608944 (submodule: require the submodule path to contain directories only,
@@ t/t2405-worktree-submodule.sh: test_description='Combination of submodules and m
base_path=$(pwd -P)
- ## t/t4153-am-resume-override-opts.sh ##
-@@
-
- test_description='git-am command-line options override saved options' -
-+TEST_PASSES_SANITIZE_LEAK=true
- . ./test-lib.sh
- . "$TEST_DIRECTORY"/lib-terminal.sh
-
-
- ## t/t7006-pager.sh ##
-@@
-
- test_description='Test automatic use of a pager.'
-
-+TEST_PASSES_SANITIZE_LEAK=true
- . ./test-lib.sh
- . "$TEST_DIRECTORY"/lib-pager.sh
- . "$TEST_DIRECTORY"/lib-terminal.sh
-
## t/t7423-submodule-symlinks.sh ##
@@
2: 05fbadbae2 ! 3: a96b5ac359 transport-helper: fix leaking helper name
@@ Commit message
ownership of the name, nor do we free it. The associated memory thus leaks.
- Fix this memory leak and mark now-passing tests as leak free.
+ Fix this memory leak by freeing the string at the calling side in
+ transport_get(). transport_helper_init() now creates its own copy of
+ the string and thus can free it as required.
+
+ An alterantive way to fix this would be to transfer ownership of the
+ string passed into transport_helper_init(), which would avoid the call
+ to xstrdup(1). But it does make for a more surprising calling convention
+ as we do not typically transfer ownership of strings like this.
+
+ Mark now-passing tests as leak free.
3: d76079797f = 4: 9dd8709d1b strbuf: fix leak when `appendwholeline()` fails with EOF 4: ffd9eb795f = 5: 6d4e9ce706 checkout: clarify memory ownership in `unique_tracking_name()` 5: d4bf3c1f95 = 6: 141cae2de1 http: refactor code to clarify memory ownership 6: 1702762772 = 7: ff5e761e55 config: clarify memory ownership in `git_config_pathname()` 7: 18dce492df ! 8: afe69c7303 diff: refactor code to clarify memory ownership of prefixes
@@ Metadata
## Commit message ##
diff: refactor code to clarify memory ownership of prefixes
- The source end destination prefixes are tracked in a const char *
+ The source and destination prefixes are tracked in a const char *
array, but may at times contain allocated strings. The result is that those strings may be leaking because we never free them.
8: 667eb3f8ff = 9: eb7fce55b0 convert: refactor code to clarify ownership of check_roundtrip_encoding
9: 11eed8cea7 = 10: ee2fcf388c builtin/log: stop using globals for log config
10: d8cd9a05f8 = 11: 3490ad3a02 builtin/log: stop using globals for format config
11: a857637e61 = 12: 6cfc28c7e2 config: clarify memory ownership in `git_config_string()`
12: b2f8878b55 = 13: 70e8e26513 config: plug various memory leaks
13: 23e2cf98b7 = 14: f1a1c43e76 builtin/credential: clear credential before exit
14: a11ce6a0ed = 15: 64b92156f8 commit-reach: fix memory leak in `ahead_behind()`
15: 24362604b2 = 16: cd8a992f08 submodule: fix leaking memory for submodule entries
16: c43c93db3b ! 17: 128e2eaf7a strvec: add functions to replace and remove strings
@@ t/unit-tests/t-strvec.c (new)
+ strvec_clear(&vec);
+}
+
++static void t_replace_with_substring(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", NULL);
++ strvec_replace(&vec, 0, vec.v[0] + 1);
++ check_strvec(&vec, "oo", NULL);
++ strvec_clear(&vec);
++}
++
+static void t_remove_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
@@ t/unit-tests/t-strvec.c (new)
+ TEST(t_replace_at_head(), "replace at head");
+ TEST(t_replace_in_between(), "replace in between");
+ TEST(t_replace_at_tail(), "replace at tail");
++ TEST(t_replace_with_substring(), "replace with substring");
+ TEST(t_remove_at_head(), "remove at head");
+ TEST(t_remove_in_between(), "remove in between");
+ TEST(t_remove_at_tail(), "remove at tail");
17: 97470398ad = 18: 1310b24fc2 builtin/mv: refactor `add_slash()` to always return allocated strings
18: 7a2e5e82cc = 19: d4fef9825a builtin/mv duplicate string list memory
19: b546ca4d62 = 20: 797cdb286a builtin/mv: refactor to use `struct strvec`
20: bba735388d = 21: 095469193c builtin/mv: fix leaks for submodule gitfile pathsSignature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 1/21 ci: add missing dependency for TTY prereq
In "t/lib-terminal.sh", we declare a lazy prerequisite for tests that require a TTY. The prerequisite uses a Perl script to figure out whether we do have a usable TTY or not and thus implicitly depends on the PERL prerequisite, as well. Furthermore though, the script requires another dependency that is easy to miss, namely on the IO::Pty module. If that module is not installed, then the script will exit early due to an reason unrelated to missing TTYs.
This easily leads to missing test coverage. But most importantly, our CI systems are missing this dependency and thus don't execute those tests at all. Fix this.
--- ci/install-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
ci/install-dependencies.sh +2 -2
@@ -27,7 +27,7 @@ alpine-*) apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \ pcre2-dev python3 musl-libintl perl-utils ncurses \ apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ - bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null + bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null ;; fedora-*) dnf -yq update >/dev/null &&
@@ -42,7 +42,7 @@ ubuntu-*) language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \ make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ - libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ + libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE mkdir --parents "$CUSTOM_PATH"
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH v2 01/21] ci: add missing dependency for TTY prereq
Quoted 8 lines · Patrick Steinhardt
In "
t/lib-terminal.sh", we declare a lazy prerequisite for tests that require a TTY. The prerequisite uses a Perl script to figure out whether we do have a usable TTY or not and thus implicitly depends on the PERL prerequisite, as well. Furthermore though, the script requires another dependency that is easy to miss, namely on theIO::Ptymodule. If that module is not installed, then the script will exit early due to an reason unrelated to missing TTYs.
Quoted 6 lines
This easily leads to missing test coverage. But most importantly, our CI systems are missing this dependency and thus don't execute those tests at all. Fix this.
--- ci/install-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
Ah, I should have read what I received first before starting to respond to them ;-) You'd want to sign it off, but other than that looks good to me.
Quoted 22 lines
ci/install-dependencies.sh +2
-2@@ -27,7 +27,7 @@ alpine-*) apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \ pcre2-dev python3 musl-libintl perl-utils ncurses \ apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ - bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null + bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null ;; fedora-*) dnf -yq update >/dev/null &&@@ -42,7 +42,7 @@ ubuntu-*) language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \ make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ - libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ + libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE mkdir --parents "$CUSTOM_PATH"
PATCH v2 2/21 t: mark a bunch of tests as leak-free
In reply to Patrick SteinhardtThere are a bunch of tests which do not have any leaks:
- t0411: Introduced via 5c5a4a1c05 (t0411: add tests for cloning from
partial repo, 2024-01-28), passes since its inception.
- t0610: Introduced via 57db2a094d (refs: introduce reftable backend,
2024-02-07), passes since its inception.
- t2405: Passes since 6741e917de (repository: avoid leaking
`fsmonitor` data, 2024-04-12).
- t7423: Introduced via b20c10fd9b (t7423: add tests for symlinked submodule directories, 2024-01-28), passes since e8d0608944 (submodule: require the submodule path to contain directories only, 2024-03-26). The fix is not ovbiously related, but probably works because we now die early in many code paths.
- t9xxx: All of these are exercising CVS-related tooling and pass since at least Git v2.40. It's likely that these pass for a long time already, but nobody ever noticed because noone has CVS on their machine.
Mark all of these tests as passing.
--- t/t0411-clone-from-partial.sh | 1 + t/t0610-reftable-basics.sh | 1 + t/t2405-worktree-submodule.sh | 1 + t/t7423-submodule-symlinks.sh | 1 + t/t9200-git-cvsexportcommit.sh | 1 + t/t9401-git-cvsserver-crlf.sh | 1 + t/t9600-cvsimport.sh | 1 + t/t9601-cvsimport-vendor-branch.sh | 1 + t/t9602-cvsimport-branches-tags.sh | 1 + t/t9603-cvsimport-patchsets.sh | 2 ++ t/t9604-cvsimport-timestamps.sh | 2 ++ 11 files changed, 13 insertions(+)
t/t0411-clone-from-partial.sh +1 -0
@@ -2,6 +2,7 @@ test_description='check that local clone does not fetch from promisor remotes' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'create evil repo' '
t/t0610-reftable-basics.sh +1 -0
@@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GIT_TEST_DEFAULT_REF_FORMAT=reftable export GIT_TEST_DEFAULT_REF_FORMAT +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh INVALID_OID=$(test_oid 001)
t/t2405-worktree-submodule.sh +1 -0
@@ -5,6 +5,7 @@ test_description='Combination of submodules and multiple worktrees' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh base_path=$(pwd -P)
t/t7423-submodule-symlinks.sh +1 -0
@@ -2,6 +2,7 @@ test_description='check that submodule operations do not follow symlinks' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare' '
t/t9200-git-cvsexportcommit.sh +1 -0
@@ -4,6 +4,7 @@ # test_description='Test export of commits to CVS' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh if ! test_have_prereq PERL; then
t/t9401-git-cvsserver-crlf.sh +1 -0
@@ -12,6 +12,7 @@ repository using cvs CLI client via git-cvsserver server' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh marked_as () {
t/t9600-cvsimport.sh +1 -0
@@ -4,6 +4,7 @@ test_description='git cvsimport basic tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh if ! test_have_prereq NOT_ROOT; then
t/t9601-cvsimport-vendor-branch.sh +1 -0
@@ -35,6 +35,7 @@ test_description='git cvsimport handling of vendor branches' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9601
t/t9602-cvsimport-branches-tags.sh +1 -0
@@ -7,6 +7,7 @@ test_description='git cvsimport handling of branches and tags' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9602
t/t9603-cvsimport-patchsets.sh +2 -0
@@ -12,6 +12,8 @@ # bug. test_description='git cvsimport testing for correct patchset estimation' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9603
t/t9604-cvsimport-timestamps.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git cvsimport timestamps' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh test_lazy_prereq POSIX_TIMEZONE '
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 3/21 transport-helper: fix leaking helper name
In reply to Patrick SteinhardtWhen initializing the transport helper in transport_get(), we allocate the name of the helper. We neither end up transferring ownership of the name, nor do we free it. The associated memory thus leaks.
Fix this memory leak by freeing the string at the calling side in transport_get(). transport_helper_init() now creates its own copy of the string and thus can free it as required.
An alterantive way to fix this would be to transfer ownership of the string passed into transport_helper_init(), which would avoid the call to xstrdup(1). But it does make for a more surprising calling convention as we do not typically transfer ownership of strings like this.
Mark now-passing tests as leak free.
--- t/t0611-reftable-httpd.sh | 1 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + transport-helper.c | 6 ++++-- transport.c | 1 + 6 files changed, 9 insertions(+), 2 deletions(-)
t/t0611-reftable-httpd.sh +1 -0
@@ -2,6 +2,7 @@ test_description='reftable HTTPD tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5563-simple-http-auth.sh +1 -0
@@ -2,6 +2,7 @@ test_description='test http auth header and credential helper interop' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5564-http-proxy.sh +1 -0
@@ -2,6 +2,7 @@ test_description="test fetching through http proxy" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5581-http-curl-verbose.sh +1 -0
@@ -4,6 +4,7 @@ test_description='test GIT_CURL_VERBOSE' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
transport-helper.c +4 -2
@@ -22,7 +22,7 @@static int debug;struct helper_data { -const char *name; +char *name;struct child_process *helper;FILE *out; unsigned fetch : 1,
@@ -111,6 +111,7 @@ static void do_take_over(struct transport *transport) data = (struct helper_data *)transport->data;transport_take_over (transport, data->helper);fclose (data->out); +free (data->name);free (data); }
@@ -253,6 +254,7 @@ static int disconnect_helper(struct transport *transport)close (data->helper->out);fclose (data->out); res =finish_command (data->helper); +FREE_AND_NULL (data->name);FREE_AND_NULL (data->helper); }return res;
@@ -1297,7 +1299,7 @@ static struct transport_vtable vtable = {int transport_helper_init (struct transport *transport,const char *name) {struct helper_data *data =xcalloc (1 ,sizeof (*data)); - data->name = name; + data->name =xstrdup (name);transport_check_allowed (name);
transport.c +1 -0
@@ -1176,6 +1176,7 @@ struct transport *transport_get(struct remote *remote, const char *url)int len =external_specification_len (url);char *handler =xmemdupz (url, len);transport_helper_init (ret, handler); +free (handler); }if (ret->smart_options) {
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 4/21 strbuf: fix leak when `appendwholeline()` fails with EOF
In reply to Patrick SteinhardtIn strbuf_appendwholeline() we call strbuf_getwholeline() with a temporary buffer. In case the call returns an error we indicate this by returning EOF, but never release the temporary buffer. This can lead to a memory leak when the line has been partially filled. Fix this.
--- strbuf.c | 4 +++- t/t1400-update-ref.sh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-)
strbuf.c +3 -1
@@ -691,8 +691,10 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)int strbuf_appendwholeline (struct strbuf *sb,FILE *fp,int term) {struct strbuf line =STRBUF_INIT ; -if (strbuf_getwholeline (&line, fp, term)) +if (strbuf_getwholeline (&line, fp, term)) { +strbuf_release (&line);return EOF ; + }strbuf_addbuf (sb, &line);strbuf_release (&line);return 0 ;
t/t1400-update-ref.sh +2 -0
@@ -4,6 +4,8 @@ # test_description='Test git update-ref and basic ref logging' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh Z=$ZERO_OID
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH v2 04/21] strbuf: fix leak when `appendwholeline()` fails with EOF
Quoted 4 lines · Patrick Steinhardt
In
strbuf_appendwholeline()we callstrbuf_getwholeline()with a temporary buffer. In case the call returns an error we indicate this by returning EOF, but never release the temporary buffer. This can lead to a memory leak when the line has been partially filled. Fix this.
Hmm, doesn't this indicate a bug in getwholeline()? Most strbuf functions on error try to leave the allocation as-is.
At the end of the getdelim() version (which is probably what you're running), when we see an error we do:
if (!sb->buf)
strbuf_init(sb, 0);
else
strbuf_reset(sb);
return EOF;So if getdelim() returned error and left us with a buffer (but still returned -1 for error!), I think this code is assuming that the buffer it left us with was the same one that existed beforehand.
But your commit message implies that it might allocate, hit an error, and then return that error along with an allocated buffer? Looks like that matches the getdelim() manpage, which says:
If *lineptr was set to NULL before the call, then the buffer should be freed by the user program even on failure.
So should we do something like:
strbuf.c +1 -1
@@ -659,7 +659,7 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)if (!sb->buf)strbuf_init (sb,0 );else -strbuf_reset (sb); +strbuf_release (sb); return EOF; }#else
That assumes sb->alloc is valid after a failed call, since strbuf_release() checks it. But that seems reasonable. If not, we'd need to free() and re-initialize it ourselves, and the code is more like:
strbuf.c +2 -4
@@ -656,10 +656,8 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)* we can just re-init, but otherwise we should make sure that our * length is empty, and that the result is NUL-terminated. */ -if (!sb->buf) -strbuf_init (sb,0 ); -else -strbuf_reset (sb); +FREE_AND_NULL (sb->buf); +strbuf_init (sb,0 ); return EOF; }#else
But I think either of those would solve your leak, _and_ would help with similar leaks of strbuf_getwholeline() and friends.
-Peff
Re: [PATCH v2 04/21] strbuf: fix leak when `appendwholeline()` fails with EOF
Quoted 68 lines · Jeff King
Quoted 4 lines · Patrick Steinhardt
In
strbuf_appendwholeline()we callstrbuf_getwholeline()with a temporary buffer. In case the call returns an error we indicate this by returning EOF, but never release the temporary buffer. This can lead to a memory leak when the line has been partially filled. Fix this.Hmm, doesn't this indicate a bug in
getwholeline()? Most strbuf functions on error try to leave the allocation as-is.At the end of the
getdelim()version (which is probably what you're running), when we see an error we do:if (!sb->buf) strbuf_init(sb, 0); else strbuf_reset(sb); return EOF;So if
getdelim()returned error and left us with a buffer (but still returned -1 for error!), I think this code is assuming that the buffer it left us with was the same one that existed beforehand.But your commit message implies that it might allocate, hit an error, and then return that error along with an allocated buffer? Looks like that matches the
getdelim()manpage, which says:If *lineptr was set to NULL before the call, then the buffer should be freed by the user program even on failure.
So should we do something like:
strbuf.c +1
-1@@ -659,7 +659,7 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)if (!sb->buf)strbuf_init (sb,0 );else -strbuf_reset (sb); +strbuf_release (sb); return EOF; }#else That assumes
sb->allocis valid after a failed call, sincestrbuf_release()checks it. But that seems reasonable. If not, we'd need tofree()and re-initialize it ourselves, and the code is more like:strbuf.c +2
-4@@ -656,10 +656,8 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)* we can just re-init, but otherwise we should make sure that our * length is empty, and that the result is NUL-terminated. */ -if (!sb->buf) -strbuf_init (sb,0 ); -else -strbuf_reset (sb); +FREE_AND_NULL (sb->buf); +strbuf_init (sb,0 ); return EOF; }#else
Quoted 2 lines
But I think either of those would solve your leak, _and_ would help with similar leaks of
strbuf_getwholeline()and friends.
I'm not quite convinced that strbuf_getwholeline() should deallocate the buffer for the caller, I think that makes for quite a confusing calling convention. The caller may want to reuse the buffer for other operations, and it feels hostile to release the buffer under their feet.
The only edge case where I think it would make sense to free allocated data is when being passed a not-yet-allocated strbuf. But I wonder whether the added complexity would be worth it.
I've been going through all callsites and couldn't spot any that doesn't free the buffer on EOF. So I'd propose to leave this as-is and revisit if we eventually see that this is causing more memory leaks.
Patrick
Re: [PATCH v2 04/21] strbuf: fix leak when `appendwholeline()` fails with EOF
Quoted 27 lines · Patrick Steinhardt
Quoted 19 lines
strbuf.c +0
-0strbuf.c +2
-4@@ -656,10 +656,8 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)* we can just re-init, but otherwise we should make sure that our * length is empty, and that the result is NUL-terminated. */ -if (!sb->buf) -strbuf_init (sb,0 ); -else -strbuf_reset (sb); +FREE_AND_NULL (sb->buf); +strbuf_init (sb,0 ); return EOF; }#else Quoted 2 lines
But I think either of those would solve your leak, _and_ would help with similar leaks of
strbuf_getwholeline()and friends.I'm not quite convinced that
strbuf_getwholeline()should deallocate the buffer for the caller, I think that makes for quite a confusing calling convention. The caller may want to reuse the buffer for other operations, and it feels hostile to release the buffer under their feet.
Quoted 3 lines
The only edge case where I think it would make sense to free allocated data is when being passed a not-yet-allocated strbuf. But I wonder whether the added complexity would be worth it.
I'm not sure what they'd reuse it for. We necessarily have to reset it before reading, so the contents are now garbage. The allocated buffer could be reused, but since everybody has to call strbuf_grow() before assuming they can write, it's not a correctness issue, but only an optimization. But that optimization is pretty unlikely to matter. Since we hit this code only on EOF or error, it's generally going to happen once in a program, and not in a tight loop.
If we really cared, though, I think you could check sb->alloc before the call to getdelim(), and then we'd know whether the original held an allocation or not (and we could restore its state). That's what other syscall-ish strbuf functions like strbuf_readlink() and strbuf_getcwd() do.
That said, I agree that leaks here are not going to be common. Most callers are going to call it in a loop and unconditionally release at the end, whether they get multiple lines or not. The "append" function is the odd man out by reading a single line into a new buffer[1].
Looking through the results of:
git grep -P '(?<!while) \(!?strbuf_get(whole)?line'
I saw only one questionable case. builtin/difftool.c does:
if (strbuf_getline_nul(&lpath, fp))
break;
without freeing lpath. But then...it does not free it in the case that we got a value, either! So I think it is leaking either way, and the solution, to strbuf_release(&lpath) outside of the loop, would fix both cases.
Quoted 3 lines
I've been going through all callsites and couldn't spot any that doesn't free the buffer on EOF. So I'd propose to leave this as-is and revisit if we eventually see that this is causing more memory leaks.
OK. I don't feel too strongly about it, but mostly thought it seemed inconsistent with the philosophy of those other strbuf functions.
-Peff
[1] I was surprised at first that strbuf_appendwholeline() uses that extra copy, as the obvious implementation is just to skip the strbuf_reset() call in getwholeline(), and then implement "get" as a wrapper around "append" to add back in the reset call.
But that only works for the slower getc() implementation. For the getdelim()-based one, there is no notion of append (the interface would have to take an offset into the buffer). We could probably optimize this at the cost of repeating ourselves, but given that there's only one probably-not-very-hot call to appendwholeline, it's likely not worth it.
Re: [PATCH v2 04/21] strbuf: fix leak when `appendwholeline()` fails with EOF
Quoted 40 lines · Jeff King
Quoted 27 lines · Patrick Steinhardt
Quoted 19 lines
strbuf.c +0
-0strbuf.c +2
-4@@ -656,10 +656,8 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)* we can just re-init, but otherwise we should make sure that our * length is empty, and that the result is NUL-terminated. */ -if (!sb->buf) -strbuf_init (sb,0 ); -else -strbuf_reset (sb); +FREE_AND_NULL (sb->buf); +strbuf_init (sb,0 ); return EOF; }#else Quoted 2 lines
But I think either of those would solve your leak, _and_ would help with similar leaks of
strbuf_getwholeline()and friends.I'm not quite convinced that
strbuf_getwholeline()should deallocate the buffer for the caller, I think that makes for quite a confusing calling convention. The caller may want to reuse the buffer for other operations, and it feels hostile to release the buffer under their feet.Quoted 3 lines
The only edge case where I think it would make sense to free allocated data is when being passed a not-yet-allocated strbuf. But I wonder whether the added complexity would be worth it.
I'm not sure what they'd reuse it for. We necessarily have to reset it before reading, so the contents are now garbage. The allocated buffer could be reused, but since everybody has to call
strbuf_grow()before assuming they can write, it's not a correctness issue, but only an optimization. But that optimization is pretty unlikely to matter. Since we hit this code only on EOF or error, it's generally going to happen once in a program, and not in a tight loop.
Quoted 5 lines
If we really cared, though, I think you could check
sb->allocbefore the call togetdelim(), and then we'd know whether the original held an allocation or not (and we could restore its state). That's what other syscall-ish strbuf functions likestrbuf_readlink()andstrbuf_getcwd()do.
Ah, I didn't know that we did similar things in other strbuf functions. With that precedence I think it's less ugly to do this dance.
Quoted 11 lines
That said, I agree that leaks here are not going to be common. Most callers are going to call it in a loop and unconditionally release at the end, whether they get multiple lines or not. The "append" function is the odd man out by reading a single line into a new buffer[1].
Looking through the results of:
git grep -P '(?<!while) \(!?strbuf_get(whole)?line'
I saw only one questionable case.
builtin/difftool.cdoes:
Quoted 7 lines
if (
strbuf_getline_nul(&lpath, fp))break;without freeing lpath. But then...it does not free it in the case that we got a value, either! So I think it is leaking either way, and the solution, to
strbuf_release(&lpath)outside of the loop, would fix both cases.
Indeed. We also didn't free rpath and info. I do have a follow up to this series already, so let me add those leak fixes to it.
Quoted 6 lines
Quoted 3 lines
I've been going through all callsites and couldn't spot any that doesn't free the buffer on EOF. So I'd propose to leave this as-is and revisit if we eventually see that this is causing more memory leaks.
OK. I don't feel too strongly about it, but mostly thought it seemed inconsistent with the philosophy of those other strbuf functions.
I get where you're coming from now with the additional info that other syscall-ish functions do a similar dance. I'll refrain from rerolling this series just to fix this in a different way, also because neither of us did spot any additional leaks caused by this.
Patrick
Re: [PATCH v2 04/21] strbuf: fix leak when `appendwholeline()` fails with EOF
Quoted 8 lines · Patrick Steinhardt
Quoted 5 lines
If we really cared, though, I think you could check
sb->allocbefore the call togetdelim(), and then we'd know whether the original held an allocation or not (and we could restore its state). That's what other syscall-ish strbuf functions likestrbuf_readlink()andstrbuf_getcwd()do.Ah, I didn't know that we did similar things in other strbuf functions. With that precedence I think it's less ugly to do this dance.
Yeah, I probably should have explained that better in my earlier email. ;)
Quoted 5 lines
Quoted 5 lines
I saw only one questionable case.
builtin/difftool.cdoes:if (
strbuf_getline_nul(&lpath, fp))break;
Quoted 7 lines
Quoted 4 lines
without freeing lpath. But then...it does not free it in the case that we got a value, either! So I think it is leaking either way, and the solution, to
strbuf_release(&lpath)outside of the loop, would fix both cases.Indeed. We also didn't free
rpathandinfo. I do have a follow up to this series already, so let me add those leak fixes to it.
Great! Thanks (as usual) for being receptive to me piling more things on your todo list. :)
-Peff
PATCH v2 5/21 checkout: clarify memory ownership in `unique_tracking_name()`
In reply to Patrick SteinhardtThe function unique_tracking_name() returns an allocated string, but does not clearly indicate this because its return type is const char * instead of char *. This has led to various callsites where we never free its returned memory at all, which causes memory leaks.
Plug those leaks and mark now-passing tests as leak free.
--- builtin/checkout.c | 14 +++++++------ builtin/worktree.c | 20 ++++++++++--------- checkout.c | 4 ++-- checkout.h | 6 +++--- t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + 14 files changed, 34 insertions(+), 20 deletions(-)
builtin/checkout.c +8 -6
@@ -1275,12 +1275,12 @@ static void setup_new_branch_info_and_source_tree( } } -static const char *parse_remote_branch (const char *arg, -struct object_id *rev, -int could_be_checkout_paths) +static char *parse_remote_branch (const char *arg, +struct object_id *rev, +int could_be_checkout_paths) {int num_matches =0 ; -const char *remote =unique_tracking_name (arg, rev, &num_matches); +char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"
@@ -1316,6 +1316,7 @@ static int parse_branchname_arg(int argc, const char **argv,const char **new_branch = &opts->new_branch;int argcount =0 ;const char *arg; +char *remote =NULL ;int dash_dash_pos;int has_dash_dash =0 ;int i;
@@ -1416,8 +1417,8 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim =0 ;if (recover_with_dwim) { -const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths); + remote =parse_remote_branch (arg, rev, + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;
@@ -1459,6 +1460,7 @@ static int parse_branchname_arg(int argc, const char **argv, argc--; } +free (remote);return argcount; }
builtin/worktree.c +11 -9
@@ -736,16 +736,14 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)return 1 ; } -static const char *dwim_branch (const char *path,const char **new_branch) +static char *dwim_branch (const char *path,char **new_branch) {int n;int branch_exists;const char *s =worktree_basename (path, &n); -const char *branchname =xstrndup (s, n); +char *branchname =xstrndup (s, n);struct strbuf ref =STRBUF_INIT ; -UNLEAK (branchname); - branch_exists = !strbuf_check_branch_ref (&ref, branchname) &&refs_ref_exists (get_main_ref_store (the_repository), ref.buf);
@@ -756,8 +754,7 @@ static const char *dwim_branch(const char *path, const char **new_branch) *new_branch = branchname;if (guess_remote) {struct object_id oid; -const char *remote = -unique_tracking_name (*new_branch, &oid,NULL ); +char *remote =unique_tracking_name (*new_branch, &oid,NULL );return remote; }return NULL ;
@@ -769,6 +766,8 @@ static int add(int ac, const char **av, const char *prefix)const char *new_branch_force =NULL ;char *path;const char *branch; +char *branch_to_free =NULL ; +char *new_branch_to_free =NULL ;const char *new_branch =NULL ;const char *opt_track =NULL ;const char *lock_reason =NULL ;
@@ -859,16 +858,17 @@ static int add(int ac, const char **av, const char *prefix) opts.orphan =dwim_orphan (&opts, !!opt_track,0 ); }else if (ac <2 ) {/* DWIM: Guess branch name from path. */ -const char *s =dwim_branch (path, &new_branch); +char *s =dwim_branch (path, &new_branch_to_free);if (s) - branch = s; + branch = branch_to_free = s; + new_branch = new_branch_to_free;/* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = (!s) &&dwim_orphan (&opts, !!opt_track,1 ); }else if (ac ==2 ) {struct object_id oid;struct commit *commit; -const char *remote; +char *remote; commit =lookup_commit_reference_by_name (branch);if (!commit) {
@@ -923,6 +923,8 @@ static int add(int ac, const char **av, const char *prefix) ret =add_worktree (path, branch, &opts);free (path); +free (branch_to_free); +free (new_branch_to_free);return ret; }
checkout.c +2 -2
@@ -45,8 +45,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data)return 0 ; } -const char *unique_tracking_name (const char *name,struct object_id *oid, -int *dwim_remotes_matched) +char *unique_tracking_name (const char *name,struct object_id *oid, +int *dwim_remotes_matched) {struct tracking_name_data cb_data =TRACKING_NAME_DATA_INIT ;const char *default_remote =NULL ;
checkout.h +3 -3
@@ -8,8 +8,8 @@* tracking branch. Return the name of the remote if such a branch * exists, NULL otherwise. */ -const char *unique_tracking_name (const char *name, -struct object_id *oid, -int *dwim_remotes_matched); +char *unique_tracking_name (const char *name, +struct object_id *oid, +int *dwim_remotes_matched);#endif /* CHECKOUT_H */
t/t2024-checkout-dwim.sh +1 -0
@@ -4,6 +4,7 @@ test_description='checkout <branch> Ensures that checkout on an unborn branch does what the user expects' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Is the current branch "refs/heads/$1"?
t/t2060-switch.sh +1 -0
@@ -5,6 +5,7 @@ test_description='switch basic functionality' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
t/t3426-rebase-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='rebase can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh . "$TEST_DIRECTORY"/lib-rebase.sh
t/t3512-cherry-pick-submodule.sh +1 -0
@@ -5,6 +5,7 @@ test_description='cherry-pick can handle submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3513-revert-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='revert can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3600-rm.sh +1 -0
@@ -8,6 +8,7 @@ test_description='Test of the various options to git rm.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Setup some files to be removed, some with funny characters
t/t3906-stash-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='stash can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t4137-apply-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='git apply handling submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t6041-bisect-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='bisect can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t6438-submodule-directory-file-conflicts.sh +1 -0
@@ -2,6 +2,7 @@ test_description='merge can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 6/21 http: refactor code to clarify memory ownership
In reply to Patrick SteinhardtThere are various variables assigned via git_config_string() and git_config_pathname() which are never free'd. This bug is relatable because the out parameter of those functions are a const char **, even though memory ownership is transferred to the caller.
We're about to adapt the functions to instead use char **. Prepare the code accordingly. Note that the (const char **) casts will go away once we have adapted the functions.
--- http.c | 62 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 30 deletions(-)
http.c +32 -30
@@ -39,8 +39,8 @@ char curl_errorstr[CURL_ERROR_SIZE];static int curl_ssl_verify =-1 ;static int curl_ssl_try;static const char *curl_http_version =NULL ; -static const char *ssl_cert; -static const char *ssl_cert_type; +static char *ssl_cert; +static char *ssl_cert_type;static const char *ssl_cipherlist;static const char *ssl_version; static struct {
@@ -59,23 +59,23 @@ static struct { { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 }, #endif }; -static const char *ssl_key; -static const char *ssl_key_type; -static const char *ssl_capath; -static const char *curl_no_proxy; +static char *ssl_key; +static char *ssl_key_type; +static char *ssl_capath; +static char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY static const char *ssl_pinnedkey; #endif -static const char *ssl_cainfo; +static char *ssl_cainfo; static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; -static const char *curl_http_proxy; -static const char *http_proxy_authmethod; +static char *curl_http_proxy; +static char *http_proxy_authmethod; -static const char *http_proxy_ssl_cert; -static const char *http_proxy_ssl_key; -static const char *http_proxy_ssl_ca_info; +static char *http_proxy_ssl_cert; +static char *http_proxy_ssl_key; +static char *http_proxy_ssl_ca_info; static struct credential proxy_cert_auth = CREDENTIAL_INIT; static int proxy_ssl_cert_password_required;
@@ -112,7 +112,7 @@ static const char *curl_cookie_file;static int curl_save_cookies;struct credential http_auth =CREDENTIAL_INIT ;static int http_proactive_auth; -static const char *user_agent; +static char *user_agent;static int curl_empty_auth =-1 ;enum http_follow_config http_follow_config =HTTP_FOLLOW_INITIAL ;
@@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslversion" , var))return git_config_string (&ssl_version, var, value);if (!strcmp ("http.sslcert" , var)) -return git_config_pathname (&ssl_cert, var, value); +return git_config_pathname ((const char **)&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var)) -return git_config_string (&ssl_cert_type, var, value); +return git_config_string ((const char **)&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var)) -return git_config_pathname (&ssl_key, var, value); +return git_config_pathname ((const char **)&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var)) -return git_config_string (&ssl_key_type, var, value); +return git_config_string ((const char **)&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var)) -return git_config_pathname (&ssl_capath, var, value); +return git_config_pathname ((const char **)&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var)) -return git_config_pathname (&ssl_cainfo, var, value); +return git_config_pathname ((const char **)&ssl_cainfo, var, value);if (!strcmp ("http.sslcertpasswordprotected" , var)) { ssl_cert_password_required =git_config_bool (var, value);return 0 ;
@@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value,return 0 ; }if (!strcmp ("http.proxy" , var)) -return git_config_string (&curl_http_proxy, var, value); +return git_config_string ((const char **)&curl_http_proxy, var, value);if (!strcmp ("http.proxyauthmethod" , var)) -return git_config_string (&http_proxy_authmethod, var, value); +return git_config_string ((const char **)&http_proxy_authmethod, var, value);if (!strcmp ("http.proxysslcert" , var)) -return git_config_string (&http_proxy_ssl_cert, var, value); +return git_config_string ((const char **)&http_proxy_ssl_cert, var, value);if (!strcmp ("http.proxysslkey" , var)) -return git_config_string (&http_proxy_ssl_key, var, value); +return git_config_string ((const char **)&http_proxy_ssl_key, var, value);if (!strcmp ("http.proxysslcainfo" , var)) -return git_config_string (&http_proxy_ssl_ca_info, var, value); +return git_config_string ((const char **)&http_proxy_ssl_ca_info, var, value);if (!strcmp ("http.proxysslcertpasswordprotected" , var)) { proxy_ssl_cert_password_required =git_config_bool (var, value);
@@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, }if (!strcmp ("http.useragent" , var)) -return git_config_string (&user_agent, var, value); +return git_config_string ((const char **)&user_agent, var, value);if (!strcmp ("http.emptyauth" , var)) {if (value && !strcmp ("auto" , value))
@@ -592,10 +592,10 @@ static void init_curl_http_auth(CURL *result) }/* *var must be free-able */ -static void var_override (const char **var,char *value) +static void var_override (char **var,char *value) {if (value) { -free ((void *)*var); +free (*var); *var =xstrdup (value); } }
@@ -1233,11 +1233,13 @@ static CURL *get_curl_handle(void)return result; } -static void set_from_env (const char **var,const char *envname) +static void set_from_env (char **var,const char *envname) {const char *val =getenv (envname); -if (val) - *var = val; +if (val) { +FREE_AND_NULL (*var); + *var =xstrdup (val); + } }void http_init (struct remote *remote,const char *url,int proactive_auth)
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 7/21 config: clarify memory ownership in `git_config_pathname()`
In reply to Patrick SteinhardtThe out parameter of git_config_pathname() is a const char ** even though we transfer ownership of memory to the caller. This is quite misleading and has led to many memory leaks all over the place. Adapt the parameter to instead be char **.
--- builtin/blame.c | 2 +- builtin/commit.c | 2 +- builtin/config.c | 2 +- builtin/log.c | 2 +- builtin/receive-pack.c | 4 ++-- config.c | 10 +++++----- config.h | 8 ++++---- diff.c | 2 +- environment.c | 6 +++--- environment.h | 6 +++--- fetch-pack.c | 4 ++-- fsck.c | 4 ++-- fsmonitor-settings.c | 5 ++++- gpg-interface.c | 4 +++- http.c | 12 ++++++------ mailmap.c | 2 +- mailmap.h | 2 +- setup.c | 6 +++--- 18 files changed, 44 insertions(+), 39 deletions(-)
builtin/blame.c +1 -1
@@ -718,7 +718,7 @@ static int git_blame_config(const char *var, const char *value,return 0 ; }if (!strcmp (var,"blame.ignorerevsfile" )) { -const char *str; +char *str;int ret; ret =git_config_pathname (&str, var, value);
builtin/commit.c +1 -1
@@ -107,7 +107,7 @@ static enum { } commit_style;static const char *logfile, *force_author; -static const char *template_file; +static char *template_file;/* * The _message variables are commit names from which to take * the commit message and/or authorship.
builtin/config.c +1 -1
@@ -277,7 +277,7 @@ static int format_config(struct strbuf *buf, const char *key_,else strbuf_addstr (buf, v ?"true" :"false" ); }else if (type ==TYPE_PATH ) { -const char *v; +char *v;if (git_config_pathname (&v, key_, value_) <0 )return -1 ;strbuf_addstr (buf, v);
builtin/log.c +1 -1
@@ -957,7 +957,7 @@ static int do_signoff;static enum auto_base_setting auto_base;static char *from;static const char *signature = git_version_string; -static const char *signature_file; +static char *signature_file;static enum cover_setting config_cover_letter;static const char *config_output_directory;static enum cover_from_description cover_from_description_mode =COVER_FROM_MESSAGE ;
builtin/receive-pack.c +2 -2
@@ -168,13 +168,13 @@ static int receive_pack_config(const char *var, const char *value, }if (strcmp (var,"receive.fsck.skiplist" ) ==0 ) { -const char *path; +char *path;if (git_config_pathname (&path, var, value))return 1 ;strbuf_addf (&fsck_msg_types,"%cskiplist=%s" , fsck_msg_types.len ?',' :'=' , path); -free ((char *)path); +free (path);return 0 ; }
config.c +5 -5
@@ -1346,7 +1346,7 @@ int git_config_string(const char **dest, const char *var, const char *value)return 0 ; } -int git_config_pathname (const char **dest,const char *var,const char *value) +int git_config_pathname (char **dest,const char *var,const char *value) {if (!value)return config_error_nonbool (var);
@@ -1597,7 +1597,7 @@ static int git_default_core_config(const char *var, const char *value,return git_config_string (&askpass_program, var, value);if (!strcmp (var,"core.excludesfile" )) { -free ((char *)excludes_file); +free (excludes_file);return git_config_pathname (&excludes_file, var, value); }
@@ -2494,7 +2494,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dreturn 1 ; } -int git_configset_get_pathname (struct config_set *set,const char *key,const char **dest) +int git_configset_get_pathname (struct config_set *set,const char *key,char **dest) {const char *value;if (!git_configset_get_value (set, key, &value,NULL ))
@@ -2639,7 +2639,7 @@ int repo_config_get_maybe_bool(struct repository *repo, }int repo_config_get_pathname (struct repository *repo, -const char *key,const char **dest) +const char *key,char **dest) {int ret;git_config_check_init (repo);
@@ -2738,7 +2738,7 @@ int git_config_get_maybe_bool(const char *key, int *dest)return repo_config_get_maybe_bool (the_repository, key, dest); } -int git_config_get_pathname (const char *key,const char **dest) +int git_config_get_pathname (const char *key,char **dest) {return repo_config_get_pathname (the_repository, key, dest); }
config.h +4 -4
@@ -286,7 +286,7 @@ int git_config_string(const char **, const char *, const char *);* Similar to `git_config_string`, but expands `~` or `~user` into the * user's home directory when found at the beginning of the path. */ -int git_config_pathname (const char **,const char *,const char *); +int git_config_pathname (char **,const char *,const char *);int git_config_expiry_date (timestamp_t *,const char *,const char *);int git_config_color (char *,const char *,const char *);
@@ -541,7 +541,7 @@ int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned lonint git_configset_get_bool (struct config_set *cs,const char *key,int *dest);int git_configset_get_bool_or_int (struct config_set *cs,const char *key,int *is_bool,int *dest);int git_configset_get_maybe_bool (struct config_set *cs,const char *key,int *dest); -int git_configset_get_pathname (struct config_set *cs,const char *key,const char **dest); +int git_configset_get_pathname (struct config_set *cs,const char *key,char **dest);/* Functions for reading a repository's config */ struct repository ;
@@ -577,7 +577,7 @@ int repo_config_get_bool_or_int(struct repository *repo,int repo_config_get_maybe_bool (struct repository *repo,const char *key,int *dest);int repo_config_get_pathname (struct repository *repo, -const char *key,const char **dest); +const char *key,char **dest);/* * Functions for reading protected config. By definition, protected
@@ -687,7 +687,7 @@ int git_config_get_maybe_bool(const char *key, int *dest);* Similar to `git_config_get_string`, but expands `~` or `~user` into * the user's home directory when found at the beginning of the path. */ -int git_config_get_pathname (const char *key,const char **dest); +int git_config_get_pathname (const char *key,char **dest);int git_config_get_index_threads (int *dest);int git_config_get_split_index (void );
diff.c +1 -1
@@ -58,7 +58,7 @@ static int diff_context_default = 3;static int diff_interhunk_context_default;static const char *diff_word_regex_cfg;static const char *external_diff_cmd_cfg; -static const char *diff_order_file_cfg; +static char *diff_order_file_cfg;int diff_auto_refresh_index =1 ;static int diff_mnemonic_prefix;static int diff_no_prefix;
environment.c +3 -3
@@ -46,8 +46,8 @@ const char *git_commit_encoding;const char *git_log_output_encoding;char *apply_default_whitespace;char *apply_default_ignorewhitespace; -const char *git_attributes_file; -const char *git_hooks_path; +char *git_attributes_file; +char *git_hooks_path;int zlib_compression_level =Z_BEST_SPEED ;int pack_compression_level =Z_DEFAULT_COMPRESSION ;int fsync_object_files =-1 ;
@@ -60,7 +60,7 @@ size_t delta_base_cache_limit = 96 * 1024 * 1024;unsigned long big_file_threshold =512 *1024 *1024 ;const char *editor_program;const char *askpass_program; -const char *excludes_file; +char *excludes_file;enum auto_crlf auto_crlf =AUTO_CRLF_FALSE ;enum eol core_eol =EOL_UNSET ;int global_conv_flags_eol =CONV_EOL_RNDTRP_WARN ;
environment.h +3 -3
@@ -131,8 +131,8 @@ extern int warn_ambiguous_refs;extern int warn_on_object_refname_ambiguity;extern char *apply_default_whitespace;extern char *apply_default_ignorewhitespace; -extern const char *git_attributes_file; -extern const char *git_hooks_path; +extern char *git_attributes_file; +extern char *git_hooks_path;extern int zlib_compression_level;extern int pack_compression_level;extern size_t packed_git_window_size;
@@ -229,7 +229,7 @@ extern const char *git_log_output_encoding;extern const char *editor_program;extern const char *askpass_program; -extern const char *excludes_file; +extern char *excludes_file;/* * Should we print an ellipsis after an abbreviated SHA-1 value
fetch-pack.c +2 -2
@@ -1865,13 +1865,13 @@ static int fetch_pack_config_cb(const char *var, const char *value,const char *msg_id;if (strcmp (var,"fetch.fsck.skiplist" ) ==0 ) { -const char *path; +char *path ;if (git_config_pathname (&path, var, value))return 1 ;strbuf_addf (&fsck_msg_types,"%cskiplist=%s" , fsck_msg_types.len ?',' :'=' , path); -free ((char *)path); +free (path);return 0 ; }
fsck.c +2 -2
@@ -1330,13 +1330,13 @@ int git_fsck_config(const char *var, const char *value,const char *msg_id;if (strcmp (var,"fsck.skiplist" ) ==0 ) { -const char *path; +char *path;struct strbuf sb =STRBUF_INIT ;if (git_config_pathname (&path, var, value))return 1 ;strbuf_addf (&sb,"skiplist=%s" , path); -free ((char *)path); +free (path);fsck_set_msg_types (options, sb.buf);strbuf_release (&sb);return 0 ;
fsmonitor-settings.c +4 -1
@@ -103,6 +103,7 @@ static struct fsmonitor_settings *alloc_settings(void)static void lookup_fsmonitor_settings (struct repository *r) {const char *const_str; +char *to_free =NULL ;int bool_value;if (r->settings.fsmonitor)
@@ -129,8 +130,9 @@ static void lookup_fsmonitor_settings(struct repository *r)break ;case -1 :/* config value set to an arbitrary string */ -if (repo_config_get_pathname (r,"core.fsmonitor" , &const_str)) +if (repo_config_get_pathname (r,"core.fsmonitor" , &to_free))return ;/* should not happen */ + const_str = to_free;break ;default :/* should not happen */
@@ -141,6 +143,7 @@ static void lookup_fsmonitor_settings(struct repository *r)fsm_settings__set_hook (r, const_str); elsefsm_settings__set_disabled (r ); +free (to_free); }enum fsmonitor_mode fsm_settings__get_mode (struct repository *r)
gpg-interface.c +3 -1
@@ -27,7 +27,9 @@ static void gpg_interface_lazy_init(void) }static char *configured_signing_key; -static const char *ssh_default_key_command, *ssh_allowed_signers, *ssh_revocation_file; +static const char *ssh_default_key_command; +static char *ssh_allowed_signers; +static char *ssh_revocation_file;static enum signature_trust_level configured_min_trust_level =TRUST_UNDEFINED ;struct gpg_format {
http.c +6 -6
@@ -64,7 +64,7 @@ static char *ssl_key_type;static char *ssl_capath;static char *curl_no_proxy;#ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY -static const char *ssl_pinnedkey; +static char *ssl_pinnedkey;#endif static char *ssl_cainfo;static long curl_low_speed_limit =-1 ;
@@ -108,7 +108,7 @@ static struct {static struct credential proxy_auth =CREDENTIAL_INIT ;static const char *curl_proxyuserpwd; -static const char *curl_cookie_file; +static char *curl_cookie_file;static int curl_save_cookies;struct credential http_auth =CREDENTIAL_INIT ;static int http_proactive_auth;
@@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslversion" , var))return git_config_string (&ssl_version, var, value);if (!strcmp ("http.sslcert" , var)) -return git_config_pathname ((const char **)&ssl_cert, var, value); +return git_config_pathname (&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var))return git_config_string ((const char **)&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var)) -return git_config_pathname ((const char **)&ssl_key, var, value); +return git_config_pathname (&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var))return git_config_string ((const char **)&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var)) -return git_config_pathname ((const char **)&ssl_capath, var, value); +return git_config_pathname (&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var)) -return git_config_pathname ((const char **)&ssl_cainfo, var, value); +return git_config_pathname (&ssl_cainfo, var, value);if (!strcmp ("http.sslcertpasswordprotected" , var)) { ssl_cert_password_required =git_config_bool (var, value);return 0 ;
mailmap.c +1 -1
@@ -6,7 +6,7 @@#include "object-store-ll.h" #include "setup.h" -const char *git_mailmap_file; +char *git_mailmap_file;const char *git_mailmap_blob;struct mailmap_info {
mailmap.h +1 -1
@@ -3,7 +3,7 @@struct string_list ; -extern const char *git_mailmap_file; +extern char *git_mailmap_file;extern const char *git_mailmap_blob;int read_mailmap (struct string_list *map);
setup.c +3 -3
@@ -1177,13 +1177,13 @@ static int safe_directory_cb(const char *key, const char *value, }else if (!strcmp (value,"*" )) { data->is_safe =1 ; }else { -const char *interpolated =NULL ; +char *interpolated =NULL ;if (!git_config_pathname (&interpolated, key, value) && !fspathcmp (data->path, interpolated ? interpolated : value)) data->is_safe =1 ; -free ((char *)interpolated); +free (interpolated); }return 0 ;
@@ -1822,7 +1822,7 @@ static int template_dir_cb(const char *key, const char *value,char *path =NULL ;FREE_AND_NULL (data->path); -if (!git_config_pathname ((const char **)&path, key, value)) +if (!git_config_pathname (&path, key, value)) data->path = path ? path :xstrdup (value); }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 8/21 diff: refactor code to clarify memory ownership of prefixes
In reply to Patrick SteinhardtThe source and destination prefixes are tracked in a const char * array, but may at times contain allocated strings. The result is that those strings may be leaking because we never free them.
Refactor the code to always store allocated strings in those variables, freeing them as required. This requires us to handle the default values a bit different compared to before. But given that there is only a single callsite where we use the variables to struct diff_options it's easy to handle the defaults there.
--- diff.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-)
diff.c +10 -8
@@ -62,8 +62,8 @@ static char *diff_order_file_cfg;int diff_auto_refresh_index =1 ;static int diff_mnemonic_prefix;static int diff_no_prefix; -static const char *diff_src_prefix ="a/" ; -static const char *diff_dst_prefix ="b/" ; +static char *diff_src_prefix; +static char *diff_dst_prefix;static int diff_relative;static int diff_stat_name_width;static int diff_stat_graph_width;
@@ -411,10 +411,12 @@ int git_diff_ui_config(const char *var, const char *value,return 0 ; }if (!strcmp (var,"diff.srcprefix" )) { -return git_config_string (&diff_src_prefix, var, value); +FREE_AND_NULL (diff_src_prefix); +return git_config_string ((const char **) &diff_src_prefix, var, value); }if (!strcmp (var,"diff.dstprefix" )) { -return git_config_string (&diff_dst_prefix, var, value); +FREE_AND_NULL (diff_dst_prefix); +return git_config_string ((const char **) &diff_dst_prefix, var, value); }if (!strcmp (var,"diff.relative" )) { diff_relative =git_config_bool (var, value);
@@ -3433,8 +3435,8 @@ void diff_set_noprefix(struct diff_options *options)void diff_set_default_prefix (struct diff_options *options) { - options->a_prefix = diff_src_prefix; - options->b_prefix = diff_dst_prefix; + options->a_prefix = diff_src_prefix ? diff_src_prefix :"a/" ; + options->b_prefix = diff_dst_prefix ? diff_dst_prefix :"b/" ; } struct userdiff_driver *get_textconv(struct repository *r,
@@ -5371,8 +5373,8 @@ static int diff_opt_default_prefix(const struct option *opt,BUG_ON_OPT_NEG (unset);BUG_ON_OPT_ARG (optarg); - diff_src_prefix ="a/" ; - diff_dst_prefix ="b/" ; +FREE_AND_NULL (diff_src_prefix); +FREE_AND_NULL (diff_dst_prefix);diff_set_default_prefix (options);return 0 ; }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 9/21 convert: refactor code to clarify ownership of check_roundtrip_encoding
In reply to Patrick SteinhardtThe check_roundtrip_encoding variable is tracked in a const char * even though it may contain allocated strings at times. The result is that those strings may be leaking because we never free them.
Refactor the code to always store allocated strings in this variable. The default value is handled in check_roundtrip() now, which is the only user of the variable.
--- config.c | 6 ++++-- convert.c | 24 +++++++++++++----------- convert.h | 2 +- environment.c | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-)
config.c +4 -2
@@ -1564,8 +1564,10 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.checkroundtripencoding" )) -return git_config_string (&check_roundtrip_encoding, var, value); +if (!strcmp (var,"core.checkroundtripencoding" )) { +FREE_AND_NULL (check_roundtrip_encoding); +return git_config_string ((const char **) &check_roundtrip_encoding, var, value); + }if (!strcmp (var,"core.notesref" )) {if (!value)
convert.c +13 -11
@@ -345,30 +345,32 @@ static int check_roundtrip(const char *enc_name)* space separated encodings (eg. "UTF-16, ASCII, CP1125"). * Search for the given encoding in that string. */ -const char *found =strcasestr (check_roundtrip_encoding, enc_name); +const char *encoding = check_roundtrip_encoding ? + check_roundtrip_encoding :"SHIFT-JIS" ; +const char *found =strcasestr (encoding, enc_name);const char *next;int len;if (!found)return 0 ; next = found +strlen (enc_name); - len =strlen (check_roundtrip_encoding); + len =strlen (encoding);return (found && (/* -* check that the found encoding is at the -* beginning of check_roundtrip_encoding or -* that it is prefixed with a space or comma +* Check that the found encoding is at the beginning of +* encoding or that it is prefixed with a space or +* comma. */ - found == check_roundtrip_encoding || ( + found == encoding || ( (isspace (found[-1 ]) || found[-1 ] ==',' ) ) ) && (/* -* check that the found encoding is at the -* end of check_roundtrip_encoding or -* that it is suffixed with a space or comma +* Check that the found encoding is at the end of +* encoding or that it is suffixed with a space +* or comma. */ - next == check_roundtrip_encoding + len || ( - next < check_roundtrip_encoding + len && + next == encoding + len || ( + next < encoding + len && (isspace (next[0 ]) || next[0 ] ==',' ) ) ));
convert.h +1 -1
@@ -92,7 +92,7 @@ void convert_attrs(struct index_state *istate, struct conv_attrs *ca, const char *path);extern enum eol core_eol; -extern const char *check_roundtrip_encoding; +extern char *check_roundtrip_encoding;const char *get_cached_convert_stats_ascii (struct index_state *istate,const char *path);const char *get_wt_convert_stats_ascii (const char *path);
environment.c +1 -1
@@ -64,7 +64,7 @@ char *excludes_file;enum auto_crlf auto_crlf =AUTO_CRLF_FALSE ;enum eol core_eol =EOL_UNSET ;int global_conv_flags_eol =CONV_EOL_RNDTRP_WARN ; -const char *check_roundtrip_encoding ="SHIFT-JIS" ; +char *check_roundtrip_encoding;enum branch_track git_branch_track =BRANCH_TRACK_REMOTE ;enum rebase_setup_type autorebase =AUTOREBASE_NEVER ;enum push_default_type push_default =PUSH_DEFAULT_UNSPECIFIED ;
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 10/21 builtin/log: stop using globals for log config
In reply to Patrick SteinhardtWe're using global variables to store the log configuration. Many of these can be set both via the command line and via the config, and depending on how they are being set, they may contain allocated strings. This leads to hard-to-track memory ownership and memory leaks.
Refactor the code to instead use a struct log_config that is being allocated on the stack. This allows us to more clearly scope the variables, track memory ownership and ultimately release the memory.
This also prepares us for a change to git_config_string(), which will be adapted to have a char ** out parameter instead of const char **.
--- builtin/log.c | 259 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 156 insertions(+), 103 deletions(-)
builtin/log.c +156 -103
@@ -48,22 +48,8 @@#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100#define FORMAT_PATCH_NAME_MAX_DEFAULT 64 -/* Set a default date-time format for git log ("log.date" config variable) */ -static const char *default_date_mode =NULL ; - -static int default_abbrev_commit; -static int default_show_root =1 ; -static int default_follow; -static int default_show_signature; -static int default_encode_email_headers =1 ; -static int decoration_style; -static int decoration_given; -static int use_mailmap_config =1 ;static unsigned int force_in_body_from;static int stdout_mboxrd; -static const char *fmt_patch_subject_prefix ="PATCH" ; -static int fmt_patch_name_max =FORMAT_PATCH_NAME_MAX_DEFAULT ; -static const char *fmt_pretty;static int format_no_prefix;static const char *const builtin_log_usage[] = {
@@ -111,6 +97,39 @@ static int parse_decoration_style(const char *value)return -1 ; } +struct log_config { +int default_abbrev_commit; +int default_show_root; +int default_follow; +int default_show_signature; +int default_encode_email_headers; +int decoration_style; +int decoration_given; +int use_mailmap_config; +char *fmt_patch_subject_prefix; +int fmt_patch_name_max; +char *fmt_pretty; +char *default_date_mode; +}; + +static void log_config_init (struct log_config *cfg) +{ +memset (cfg,0 ,sizeof (*cfg)); + cfg->default_show_root =1 ; + cfg->default_encode_email_headers =1 ; + cfg->use_mailmap_config =1 ; + cfg->fmt_patch_subject_prefix =xstrdup ("PATCH" ); + cfg->fmt_patch_name_max =FORMAT_PATCH_NAME_MAX_DEFAULT ; + cfg->decoration_style =auto_decoration_style (); +} + +static void log_config_release (struct log_config *cfg) +{ +free (cfg->default_date_mode); +free (cfg->fmt_pretty); +free (cfg->fmt_patch_subject_prefix); +} +static int use_default_decoration_filter =1 ;static struct string_list decorate_refs_exclude =STRING_LIST_INIT_NODUP ;static struct string_list decorate_refs_exclude_config =STRING_LIST_INIT_NODUP ;
@@ -127,20 +146,22 @@ static int clear_decorations_callback(const struct option *opt UNUSED,return 0 ; } -static int decorate_callback(const struct option *opt UNUSED, const char *arg, +static int decorate_callback (const struct option *opt,const char *arg,int unset) { +struct log_config *cfg = opt->value; +if (unset) - decoration_style =0 ; + cfg->decoration_style =0 ;else if (arg) - decoration_style =parse_decoration_style (arg); + cfg->decoration_style =parse_decoration_style (arg);else - decoration_style =DECORATE_SHORT_REFS ; + cfg->decoration_style =DECORATE_SHORT_REFS ; -if (decoration_style <0 ) +if (cfg->decoration_style <0 )die (_ ("invalid --decorate option: %s" ), arg); - decoration_given =1 ; + cfg->decoration_given =1 ;return 0 ; }
@@ -160,32 +181,26 @@ static int log_line_range_callback(const struct option *option, const char *arg,return 0 ; } -static void init_log_defaults (void ) +static void cmd_log_init_defaults (struct rev_info *rev, +struct log_config *cfg) { -init_diff_ui_defaults (); - - decoration_style =auto_decoration_style (); -} - -static void cmd_log_init_defaults (struct rev_info *rev) -{ -if (fmt_pretty) -get_commit_format (fmt_pretty, rev); -if (default_follow) +if (cfg->fmt_pretty) +get_commit_format (cfg->fmt_pretty, rev); +if (cfg->default_follow) rev->diffopt.flags.default_follow_renames =1 ; rev->verbose_header =1 ;init_diffstat_widths (&rev->diffopt); rev->diffopt.flags.recursive =1 ; rev->diffopt.flags.allow_textconv =1 ; - rev->abbrev_commit = default_abbrev_commit; - rev->show_root_diff = default_show_root; - rev->subject_prefix = fmt_patch_subject_prefix; - rev->patch_name_max = fmt_patch_name_max; - rev->show_signature = default_show_signature; - rev->encode_email_headers = default_encode_email_headers; + rev->abbrev_commit = cfg->default_abbrev_commit; + rev->show_root_diff = cfg->default_show_root; + rev->subject_prefix = cfg->fmt_patch_subject_prefix; + rev->patch_name_max = cfg->fmt_patch_name_max; + rev->show_signature = cfg->default_show_signature; + rev->encode_email_headers = cfg->default_encode_email_headers; -if (default_date_mode) -parse_date_format (default_date_mode, &rev->date_mode); +if (cfg->default_date_mode) +parse_date_format (cfg->default_date_mode, &rev->date_mode); }static void set_default_decoration_filter (struct decoration_filter *decoration_filter)
@@ -233,7 +248,8 @@ static void set_default_decoration_filter(struct decoration_filter *decoration_f }static void cmd_log_init_finish (int argc,const char **argv,const char *prefix, -struct rev_info *rev,struct setup_revision_opt *opt) +struct rev_info *rev,struct setup_revision_opt *opt, +struct log_config *cfg) {struct userformat_want w;int quiet =0 , source =0 , mailmap;
@@ -258,7 +274,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, N_("pattern"), N_("only decorate refs that match <pattern>")),OPT_STRING_LIST (0 ,"decorate-refs-exclude" , &decorate_refs_exclude,N_ ("pattern" ),N_ ("do not decorate refs that match <pattern>" )), -OPT_CALLBACK_F (0 ,"decorate" ,NULL ,NULL ,N_ ("decorate options" ), +OPT_CALLBACK_F (0 ,"decorate" , cfg,NULL ,N_ ("decorate options" ),PARSE_OPT_OPTARG , decorate_callback), OPT_CALLBACK('L', NULL, &line_cb, "range:file", N_("trace the evolution of line range <start>,<end> or function :<funcname> in <file>"),
@@ -269,7 +285,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, line_cb.rev = rev; line_cb.prefix = prefix; - mailmap = use_mailmap_config; + mailmap = cfg->use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT |
@@ -314,8 +330,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,* "log --pretty=raw" is special; ignore UI oriented * configuration variables such as decoration. */ -if (!decoration_given) - decoration_style =0 ; +if (!cfg->decoration_given) + cfg->decoration_style =0 ;if (!rev->abbrev_commit_given) rev->abbrev_commit =0 ; }
@@ -326,24 +342,24 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,* Disable decoration loading if the format will not * show them anyway. */ - decoration_style =0 ; - }else if (!decoration_style) { + cfg->decoration_style =0 ; + }else if (!cfg->decoration_style) {/* * If we are going to show them, make sure we do load * them here, but taking care not to override a * specific style set by config or --decorate. */ - decoration_style =DECORATE_SHORT_REFS ; + cfg->decoration_style =DECORATE_SHORT_REFS ; } } -if (decoration_style || rev->simplify_by_decoration) { +if (cfg->decoration_style || rev->simplify_by_decoration) {set_default_decoration_filter (&decoration_filter); -if (decoration_style) +if (cfg->decoration_style) rev->show_decorations =1 ; -load_ref_decorations (&decoration_filter, decoration_style); +load_ref_decorations (&decoration_filter, cfg->decoration_style); }if (rev->line_level_traverse)
@@ -353,16 +369,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, }static void cmd_log_init (int argc,const char **argv,const char *prefix, -struct rev_info *rev,struct setup_revision_opt *opt) +struct rev_info *rev,struct setup_revision_opt *opt, +struct log_config *cfg) { -cmd_log_init_defaults (rev); -cmd_log_init_finish (argc, argv, prefix, rev, opt); -} - -static int cmd_log_deinit (int ret,struct rev_info *rev) -{ -release_revisions (rev); -return ret; +cmd_log_init_defaults (rev, cfg); +cmd_log_init_finish (argc, argv, prefix, rev, opt, cfg); }/*
@@ -566,30 +577,37 @@ static int cmd_log_walk(struct rev_info *rev)static int git_log_config (const char *var,const char *value,const struct config_context *ctx,void *cb) { +struct log_config *cfg = cb;const char *slot_name; -if (!strcmp (var,"format.pretty" )) -return git_config_string (&fmt_pretty, var, value); -if (!strcmp (var,"format.subjectprefix" )) -return git_config_string (&fmt_patch_subject_prefix, var, value); +if (!strcmp (var,"format.pretty" )) { +FREE_AND_NULL (cfg->fmt_pretty); +return git_config_string ((const char **) &cfg->fmt_pretty, var, value); + } +if (!strcmp (var,"format.subjectprefix" )) { +FREE_AND_NULL (cfg->fmt_patch_subject_prefix); +return git_config_string ((const char **) &cfg->fmt_patch_subject_prefix, var, value); + }if (!strcmp (var,"format.filenamemaxlength" )) { - fmt_patch_name_max =git_config_int (var, value, ctx->kvi); + cfg->fmt_patch_name_max =git_config_int (var, value, ctx->kvi);return 0 ; }if (!strcmp (var,"format.encodeemailheaders" )) { - default_encode_email_headers =git_config_bool (var, value); + cfg->default_encode_email_headers =git_config_bool (var, value);return 0 ; }if (!strcmp (var,"log.abbrevcommit" )) { - default_abbrev_commit =git_config_bool (var, value); + cfg->default_abbrev_commit =git_config_bool (var, value);return 0 ; } -if (!strcmp (var,"log.date" )) -return git_config_string (&default_date_mode, var, value); +if (!strcmp (var,"log.date" )) { +FREE_AND_NULL (cfg->default_date_mode); +return git_config_string ((const char **) &cfg->default_date_mode, var, value); + }if (!strcmp (var,"log.decorate" )) { - decoration_style =parse_decoration_style (value); -if (decoration_style <0 ) - decoration_style =0 ;/* maybe warn? */ + cfg->decoration_style =parse_decoration_style (value); +if (cfg->decoration_style <0 ) + cfg->decoration_style =0 ;/* maybe warn? */ return 0 ; }if (!strcmp (var,"log.diffmerges" )) {
@@ -598,21 +616,21 @@ static int git_log_config(const char *var, const char *value,return diff_merges_config (value); }if (!strcmp (var,"log.showroot" )) { - default_show_root =git_config_bool (var, value); + cfg->default_show_root =git_config_bool (var, value);return 0 ; }if (!strcmp (var,"log.follow" )) { - default_follow =git_config_bool (var, value); + cfg->default_follow =git_config_bool (var, value);return 0 ; }if (skip_prefix (var,"color.decorate." , &slot_name))return parse_decorate_color_config (var, slot_name, value);if (!strcmp (var,"log.mailmap" )) { - use_mailmap_config =git_config_bool (var, value); + cfg->use_mailmap_config =git_config_bool (var, value);return 0 ; }if (!strcmp (var,"log.showsignature" )) { - default_show_signature =git_config_bool (var, value); + cfg->default_show_signature =git_config_bool (var, value);return 0 ; }
@@ -621,11 +639,14 @@ static int git_log_config(const char *var, const char *value,int cmd_whatchanged (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;struct setup_revision_opt opt; +int ret; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter);
@@ -635,10 +656,15 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)memset (&opt,0 ,sizeof (opt)); opt.def ="HEAD" ; opt.revarg_opt =REVARG_COMMITTISH ; -cmd_log_init (argc, argv, prefix, &rev, &opt); +cmd_log_init (argc, argv, prefix, &rev, &opt, &cfg);if (!rev.diffopt.output_format) rev.diffopt.output_format =DIFF_FORMAT_RAW ; -return cmd_log_deinit (cmd_log_walk (&rev), &rev); + + ret =cmd_log_walk (&rev); + +release_revisions (&rev); +log_config_release (&cfg); +return ret; }static void show_tagger (const char *buf,struct rev_info *rev)
@@ -733,14 +759,16 @@ static void show_setup_revisions_tweak(struct rev_info *rev)int cmd_show (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;unsigned int i;struct setup_revision_opt opt;struct pathspec match_all;int ret =0 ; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);if (the_repository->gitdir) {prepare_repo_settings (the_repository);
@@ -759,10 +787,14 @@ int cmd_show(int argc, const char **argv, const char *prefix)memset (&opt,0 ,sizeof (opt)); opt.def ="HEAD" ; opt.tweak = show_setup_revisions_tweak; -cmd_log_init (argc, argv, prefix, &rev, &opt); +cmd_log_init (argc, argv, prefix, &rev, &opt, &cfg); -if (!rev.no_walk) -return cmd_log_deinit (cmd_log_walk (&rev), &rev); +if (!rev.no_walk) { + ret =cmd_log_walk (&rev); +release_revisions (&rev); +log_config_release (&cfg); +return ret; + } rev.diffopt.no_free =1 ;for (i =0 ; i < rev.pending.nr && !ret; i++) {
@@ -832,8 +864,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.diffopt.no_free =0 ;diff_free (&rev.diffopt); +release_revisions (&rev); +log_config_release (&cfg); -return cmd_log_deinit (ret, &rev); +return ret; }/*
@@ -841,11 +875,14 @@ int cmd_show(int argc, const char **argv, const char *prefix)*/ int cmd_log_reflog (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;struct setup_revision_opt opt; +int ret; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);init_reflog_walk (&rev.reflog_info);
@@ -854,14 +891,18 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) rev.verbose_header =1 ;memset (&opt,0 ,sizeof (opt)); opt.def ="HEAD" ; -cmd_log_init_defaults (&rev); +cmd_log_init_defaults (&rev, &cfg); rev.abbrev_commit =1 ; rev.commit_format =CMIT_FMT_ONELINE ; rev.use_terminator =1 ; rev.always_show_header =1 ; -cmd_log_init_finish (argc, argv, prefix, &rev, &opt); +cmd_log_init_finish (argc, argv, prefix, &rev, &opt, &cfg); + + ret =cmd_log_walk (&rev); -return cmd_log_deinit (cmd_log_walk (&rev), &rev); +release_revisions (&rev); +log_config_release (&cfg); +return ret; }static void log_setup_revisions_tweak (struct rev_info *rev)
@@ -876,11 +917,14 @@ static void log_setup_revisions_tweak(struct rev_info *rev)int cmd_log (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct rev_info rev;struct setup_revision_opt opt; +int ret; -init_log_defaults (); -git_config (git_log_config,NULL ); +log_config_init (&cfg); +init_diff_ui_defaults (); +git_config (git_log_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter);
@@ -890,8 +934,13 @@ int cmd_log(int argc, const char **argv, const char *prefix) opt.def ="HEAD" ; opt.revarg_opt =REVARG_COMMITTISH ; opt.tweak = log_setup_revisions_tweak; -cmd_log_init (argc, argv, prefix, &rev, &opt); -return cmd_log_deinit (cmd_log_walk (&rev), &rev); +cmd_log_init (argc, argv, prefix, &rev, &opt, &cfg); + + ret =cmd_log_walk (&rev); + +release_revisions (&rev); +log_config_release (&cfg); +return ret; }/* format-patch */
@@ -1884,6 +1933,7 @@ static void infer_range_diff_ranges(struct strbuf *r1,int cmd_format_patch (int argc,const char **argv,const char *prefix) { +struct log_config cfg;struct commit *commit;struct commit **list =NULL ;struct rev_info rev;
@@ -1943,7 +1993,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")),OPT_STRING ('v' ,"reroll-count" , &reroll_count,N_ ("reroll-count" ),N_ ("mark the series as Nth re-roll" )), -OPT_INTEGER (0 ,"filename-max-length" , &fmt_patch_name_max, +OPT_INTEGER (0 ,"filename-max-length" , &cfg.fmt_patch_name_max,N_ ("max length of output filename" )), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add <rfc> (default 'RFC') before 'PATCH'"),
@@ -2017,16 +2067,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_to.strdup_strings =1 ; extra_cc.strdup_strings =1 ; -init_log_defaults (); +log_config_init (&cfg); +init_diff_ui_defaults ();init_display_notes (¬es_opt); -git_config (git_format_config,NULL ); +git_config (git_format_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter); rev.show_notes = show_notes;memcpy (&rev.notes_opt, ¬es_opt,sizeof (notes_opt)); rev.commit_format =CMIT_FMT_EMAIL ; - rev.encode_email_headers = default_encode_email_headers; + rev.encode_email_headers = cfg.default_encode_email_headers; rev.expand_tabs_in_log_default =0 ; rev.verbose_header =1 ; rev.diff =1 ;
@@ -2037,7 +2088,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def ="HEAD" ; s_r_opt.revarg_opt =REVARG_COMMITTISH ; -strbuf_addstr (&sprefix, fmt_patch_subject_prefix); +strbuf_addstr (&sprefix, cfg.fmt_patch_subject_prefix);if (format_no_prefix)diff_set_noprefix (&rev.diffopt);
@@ -2059,8 +2110,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from;/* Make sure "0000-$sub.patch" gives non-negative length for $sub */ -if (fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) - fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix); +if (cfg.fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) + cfg.fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix);if (cover_from_description_arg) cover_from_description_mode =parse_cover_from_description (cover_from_description_arg);
@@ -2156,7 +2207,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header =1 ; rev.zero_commit = zero_commit; - rev.patch_name_max = fmt_patch_name_max; + rev.patch_name_max = cfg.fmt_patch_name_max;if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary =1 ;
@@ -2450,7 +2501,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)if (rev.ref_message_ids)string_list_clear (rev.ref_message_ids,0 );free (rev.ref_message_ids); -return cmd_log_deinit (0 , &rev); +release_revisions (&rev); +log_config_release (&cfg); +return 0 ; }static int add_pending_commit (const char *arg,struct rev_info *revs,int flags)
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 11/21 builtin/log: stop using globals for format config
In reply to Patrick SteinhardtThis commit does the exact same as the preceding commit, only for the format configuration instead of the log configuration.
--- builtin/log.c | 467 ++++++++++++++++++++++++++++---------------------- 1 file changed, 265 insertions(+), 202 deletions(-)
builtin/log.c +265 -202
@@ -945,36 +945,6 @@ int cmd_log(int argc, const char **argv, const char *prefix)/* format-patch */ -static const char *fmt_patch_suffix =".patch" ; -static int numbered =0 ; -static int auto_number =1 ; - -static char *default_attach =NULL ; - -static struct string_list extra_hdr =STRING_LIST_INIT_NODUP ; -static struct string_list extra_to =STRING_LIST_INIT_NODUP ; -static struct string_list extra_cc =STRING_LIST_INIT_NODUP ; - -static void add_header (const char *value) -{ -struct string_list_item *item; -int len =strlen (value); -while (len && value[len -1 ] =='\n' ) - len--; - -if (!strncasecmp (value,"to: " ,4 )) { - item =string_list_append (&extra_to, value +4 ); - len -=4 ; - }else if (!strncasecmp (value,"cc: " ,4 )) { - item =string_list_append (&extra_cc, value +4 ); - len -=4 ; - }else { - item =string_list_append (&extra_hdr, value); - } - - item->string[len] ='\0' ; -} -enum cover_setting {COVER_UNSET ,COVER_OFF ,
@@ -1001,17 +971,61 @@ enum auto_base_setting {AUTO_BASE_WHEN_ABLE }; -static enum thread_level thread; -static int do_signoff; -static enum auto_base_setting auto_base; -static char *from; -static const char *signature = git_version_string; -static char *signature_file; -static enum cover_setting config_cover_letter; -static const char *config_output_directory; -static enum cover_from_description cover_from_description_mode =COVER_FROM_MESSAGE ; -static int show_notes; -static struct display_notes_opt notes_opt; +struct format_config { +struct log_config log; +enum thread_level thread; +int do_signoff; +enum auto_base_setting auto_base; +char *base_commit; +char *from; +char *signature; +char *signature_file; +enum cover_setting config_cover_letter; +char *config_output_directory; +enum cover_from_description cover_from_description_mode; +int show_notes; +struct display_notes_opt notes_opt; +int numbered_cmdline_opt; +int numbered; +int auto_number; +char *default_attach; +struct string_list extra_hdr; +struct string_list extra_to; +struct string_list extra_cc; +int keep_subject; +int subject_prefix; +struct strbuf sprefix; +char *fmt_patch_suffix; +}; + +static void format_config_init (struct format_config *cfg) +{ +memset (cfg,0 ,sizeof (*cfg)); +log_config_init (&cfg->log); + cfg->cover_from_description_mode =COVER_FROM_MESSAGE ; + cfg->auto_number =1 ; +string_list_init_dup (&cfg->extra_hdr); +string_list_init_dup (&cfg->extra_to); +string_list_init_dup (&cfg->extra_cc); +strbuf_init (&cfg->sprefix,0 ); + cfg->fmt_patch_suffix =xstrdup (".patch" ); +} + +static void format_config_release (struct format_config *cfg) +{ +log_config_release (&cfg->log); +free (cfg->base_commit); +free (cfg->from); +free (cfg->signature); +free (cfg->signature_file); +free (cfg->config_output_directory); +free (cfg->default_attach); +string_list_clear (&cfg->extra_hdr,0 ); +string_list_clear (&cfg->extra_to,0 ); +string_list_clear (&cfg->extra_cc,0 ); +strbuf_release (&cfg->sprefix); +free (cfg->fmt_patch_suffix); +}static enum cover_from_description parse_cover_from_description (const char *arg) {
@@ -1029,27 +1043,51 @@ static enum cover_from_description parse_cover_from_description(const char *arg)die (_ ("%s: invalid cover from description mode" ), arg); } +static void add_header (struct format_config *cfg,const char *value) +{ +struct string_list_item *item; +int len =strlen (value); +while (len && value[len -1 ] =='\n' ) + len--; + +if (!strncasecmp (value,"to: " ,4 )) { + item =string_list_append (&cfg->extra_to, value +4 ); + len -=4 ; + }else if (!strncasecmp (value,"cc: " ,4 )) { + item =string_list_append (&cfg->extra_cc, value +4 ); + len -=4 ; + }else { + item =string_list_append (&cfg->extra_hdr, value); + } + + item->string[len] ='\0' ; +} +static int git_format_config (const char *var,const char *value,const struct config_context *ctx,void *cb) { +struct format_config *cfg = cb; +if (!strcmp (var,"format.headers" )) {if (!value)die (_ ("format.headers without value" )); -add_header (value); +add_header (cfg, value);return 0 ; } -if (!strcmp (var,"format.suffix" )) -return git_config_string (&fmt_patch_suffix, var, value); +if (!strcmp (var,"format.suffix" )) { +FREE_AND_NULL (cfg->fmt_patch_suffix); +return git_config_string ((const char **) &cfg->fmt_patch_suffix, var, value); + }if (!strcmp (var,"format.to" )) {if (!value)return config_error_nonbool (var); -string_list_append (&extra_to, value); +string_list_append (&cfg->extra_to, value);return 0 ; }if (!strcmp (var,"format.cc" )) {if (!value)return config_error_nonbool (var); -string_list_append (&extra_cc, value); +string_list_append (&cfg->extra_cc, value);return 0 ; } if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
@@ -1058,69 +1096,76 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.numbered" )) {if (value && !strcasecmp (value,"auto" )) { - auto_number =1 ; + cfg->auto_number =1 ;return 0 ; } - numbered =git_config_bool (var, value); - auto_number = auto_number && numbered; + cfg->numbered =git_config_bool (var, value); + cfg->auto_number = cfg->auto_number && cfg->numbered;return 0 ; }if (!strcmp (var,"format.attach" )) { -if (value && *value) - default_attach =xstrdup (value); -else if (value && !*value) -FREE_AND_NULL (default_attach); -else - default_attach =xstrdup (git_version_string); +if (value && *value) { +FREE_AND_NULL (cfg->default_attach); + cfg->default_attach =xstrdup (value); + }else if (value && !*value) { +FREE_AND_NULL (cfg->default_attach); + }else { +FREE_AND_NULL (cfg->default_attach); + cfg->default_attach =xstrdup (git_version_string); + }return 0 ; }if (!strcmp (var,"format.thread" )) {if (value && !strcasecmp (value,"deep" )) { - thread =THREAD_DEEP ; + cfg->thread =THREAD_DEEP ;return 0 ; }if (value && !strcasecmp (value,"shallow" )) { - thread =THREAD_SHALLOW ; + cfg->thread =THREAD_SHALLOW ;return 0 ; } - thread =git_config_bool (var, value) ?THREAD_SHALLOW :THREAD_UNSET ; + cfg->thread =git_config_bool (var, value) ?THREAD_SHALLOW :THREAD_UNSET ;return 0 ; }if (!strcmp (var,"format.signoff" )) { - do_signoff =git_config_bool (var, value); + cfg->do_signoff =git_config_bool (var, value);return 0 ; } -if (!strcmp (var,"format.signature" )) -return git_config_string (&signature, var, value); -if (!strcmp (var,"format.signaturefile" )) -return git_config_pathname (&signature_file, var, value); +if (!strcmp (var,"format.signature" )) { +FREE_AND_NULL (cfg->signature); +return git_config_string ((const char **) &cfg->signature, var, value); + } +if (!strcmp (var,"format.signaturefile" )) { +FREE_AND_NULL (cfg->signature_file); +return git_config_pathname (&cfg->signature_file, var, value); + }if (!strcmp (var,"format.coverletter" )) {if (value && !strcasecmp (value,"auto" )) { - config_cover_letter =COVER_AUTO ; + cfg->config_cover_letter =COVER_AUTO ;return 0 ; } - config_cover_letter =git_config_bool (var, value) ?COVER_ON :COVER_OFF ; + cfg->config_cover_letter =git_config_bool (var, value) ?COVER_ON :COVER_OFF ;return 0 ; } -if (!strcmp (var,"format.outputdirectory" )) -return git_config_string (&config_output_directory, var, value); +if (!strcmp (var,"format.outputdirectory" )) { +FREE_AND_NULL (cfg->config_output_directory); +return git_config_string ((const char **) &cfg->config_output_directory, var, value); + }if (!strcmp (var,"format.useautobase" )) {if (value && !strcasecmp (value,"whenAble" )) { - auto_base =AUTO_BASE_WHEN_ABLE ; + cfg->auto_base =AUTO_BASE_WHEN_ABLE ;return 0 ; } - auto_base =git_config_bool (var, value) ?AUTO_BASE_ALWAYS :AUTO_BASE_NEVER ; + cfg->auto_base =git_config_bool (var, value) ?AUTO_BASE_ALWAYS :AUTO_BASE_NEVER ;return 0 ; }if (!strcmp (var,"format.from" )) {int b =git_parse_maybe_bool (value); -free (from); +FREE_AND_NULL (cfg->from);if (b <0 ) - from =xstrdup (value); + cfg->from =xstrdup (value);else if (b) - from =xstrdup (git_committer_info (IDENT_NO_DATE )); -else - from =NULL ; + cfg->from =xstrdup (git_committer_info (IDENT_NO_DATE ));return 0 ; }if (!strcmp (var,"format.forceinbodyfrom" )) {
@@ -1130,15 +1175,15 @@ static int git_format_config(const char *var, const char *value,if (!strcmp (var,"format.notes" )) {int b =git_parse_maybe_bool (value);if (b <0 ) -enable_ref_display_notes (¬es_opt, &show_notes, value); +enable_ref_display_notes (&cfg->notes_opt, &cfg->show_notes, value);else if (b) -enable_default_display_notes (¬es_opt, &show_notes); +enable_default_display_notes (&cfg->notes_opt, &cfg->show_notes);else -disable_display_notes (¬es_opt, &show_notes); +disable_display_notes (&cfg->notes_opt, &cfg->show_notes);return 0 ; }if (!strcmp (var,"format.coverfromdescription" )) { - cover_from_description_mode =parse_cover_from_description (value); + cfg->cover_from_description_mode =parse_cover_from_description (value);return 0 ; }if (!strcmp (var,"format.mboxrd" )) {
@@ -1159,7 +1204,7 @@ static int git_format_config(const char *var, const char *value,if (!strcmp (var,"diff.noprefix" ))return 0 ; -return git_log_config (var, value, ctx, cb); +return git_log_config (var, value, ctx, &cfg->log); }static const char *output_directory =NULL ;
@@ -1247,7 +1292,7 @@ static void gen_message_id(struct rev_info *info, char *base) info->message_id =strbuf_detach (&buf,NULL ); } -static void print_signature (FILE *file) +static void print_signature (const char *signature,FILE *file) {if (!signature || !*signature)return ;
@@ -1317,14 +1362,15 @@ static void prepare_cover_text(struct pretty_print_context *pp,const char *branch_name, struct strbuf *sb, const char *encoding, - int need_8bit_cte) + int need_8bit_cte, + const struct format_config *cfg) { const char *subject = "*** SUBJECT HERE ***";const char *body ="*** BLURB HERE ***" ;struct strbuf description_sb =STRBUF_INIT ;struct strbuf subject_sb =STRBUF_INIT ; -if (cover_from_description_mode ==COVER_FROM_NONE ) +if (cfg->cover_from_description_mode ==COVER_FROM_NONE ) goto do_pp;if (description_file && *description_file)
@@ -1334,13 +1380,13 @@ static void prepare_cover_text(struct pretty_print_context *pp,if (!description_sb.len) goto do_pp; -if (cover_from_description_mode ==COVER_FROM_SUBJECT || - cover_from_description_mode ==COVER_FROM_AUTO ) +if (cfg->cover_from_description_mode ==COVER_FROM_SUBJECT || + cfg->cover_from_description_mode ==COVER_FROM_AUTO ) body =format_subject (&subject_sb, description_sb.buf," " ); -if (cover_from_description_mode ==COVER_FROM_MESSAGE || - (cover_from_description_mode ==COVER_FROM_AUTO && - subject_sb.len >COVER_FROM_AUTO_MAX_SUBJECT_LEN )) +if (cfg->cover_from_description_mode ==COVER_FROM_MESSAGE || + (cfg->cover_from_description_mode ==COVER_FROM_AUTO && + subject_sb.len >COVER_FROM_AUTO_MAX_SUBJECT_LEN )) body = description_sb.buf;else subject = subject_sb.buf;
@@ -1377,7 +1423,8 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, int nr, struct commit **list, const char *description_file, const char *branch_name, - int quiet) + int quiet, + const struct format_config *cfg) { const char *committer;struct shortlog log;
@@ -1416,7 +1463,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.encode_email_headers = rev->encode_email_headers;pp_user_info (&pp,NULL , &sb, committer, encoding);prepare_cover_text (&pp, description_file, branch_name, &sb, - encoding, need_8bit_cte); + encoding, need_8bit_cte, cfg);fprintf (rev->diffopt.file,"%s\n" , sb.buf);free (pp.after_subject);
@@ -1517,29 +1564,30 @@ static const char * const builtin_format_patch_usage[] = { NULL }; -static int keep_subject =0 ; +struct keep_callback_data { +struct format_config *cfg; +struct rev_info *revs; +};static int keep_callback (const struct option *opt,const char *arg,int unset) { +struct keep_callback_data *data = opt->value;BUG_ON_OPT_NEG (unset);BUG_ON_OPT_ARG (arg); - ((struct rev_info *)opt->value)->total =-1 ; - keep_subject =1 ; + data->revs->total =-1 ; + data->cfg->keep_subject =1 ;return 0 ; } -static int subject_prefix =0 ; -static int subject_prefix_callback (const struct option *opt,const char *arg,int unset) { -struct strbuf *sprefix; +struct format_config *cfg = opt->value;BUG_ON_OPT_NEG (unset); - sprefix = opt->value; - subject_prefix =1 ; -strbuf_reset (sprefix); -strbuf_addstr (sprefix, arg); + cfg->subject_prefix =1 ; +strbuf_reset (&cfg->sprefix); +strbuf_addstr (&cfg->sprefix, arg);return 0 ; }
@@ -1556,15 +1604,14 @@ static int rfc_callback(const struct option *opt, const char *arg,return 0 ; } -static int numbered_cmdline_opt =0 ; -static int numbered_callback (const struct option *opt,const char *arg,int unset) { +struct format_config *cfg = opt->value;BUG_ON_OPT_ARG (arg); - *(int *)opt->value = numbered_cmdline_opt = unset ?0 :1 ; + cfg->numbered = cfg->numbered_cmdline_opt = unset ?0 :1 ;if (unset) - auto_number =0 ; + cfg->auto_number =0 ;return 0 ; }
@@ -1588,13 +1635,14 @@ static int output_directory_callback(const struct option *opt, const char *arg,static int thread_callback (const struct option *opt,const char *arg,int unset) { -enum thread_level *thread = (enum thread_level *)opt->value; +struct format_config *cfg = opt->value; +if (unset) - *thread =THREAD_UNSET ; + cfg->thread =THREAD_UNSET ;else if (!arg || !strcmp (arg,"shallow" )) - *thread =THREAD_SHALLOW ; + cfg->thread =THREAD_SHALLOW ;else if (!strcmp (arg,"deep" )) - *thread =THREAD_DEEP ; + cfg->thread =THREAD_DEEP ;/* * Please update _git_formatpatch() in git-completion.bash * when you add new options.
@@ -1630,15 +1678,17 @@ static int inline_callback(const struct option *opt, const char *arg, int unset)return 0 ; } -static int header_callback(const struct option *opt UNUSED, const char *arg, +static int header_callback (const struct option *opt,const char *arg,int unset) { +struct format_config *cfg = opt->value; +if (unset) { -string_list_clear (&extra_hdr,0 ); -string_list_clear (&extra_to,0 ); -string_list_clear (&extra_cc,0 ); +string_list_clear (&cfg->extra_hdr,0 ); +string_list_clear (&cfg->extra_to,0 ); +string_list_clear (&cfg->extra_cc,0 ); }else { -add_header (arg); +add_header (cfg, arg); }return 0 ; }
@@ -1660,17 +1710,17 @@ static int from_callback(const struct option *opt, const char *arg, int unset)static int base_callback (const struct option *opt,const char *arg,int unset) { -const char **base_commit = opt->value; +struct format_config *cfg = opt->value;if (unset) { - auto_base =AUTO_BASE_NEVER ; - *base_commit =NULL ; + cfg->auto_base =AUTO_BASE_NEVER ; +FREE_AND_NULL (cfg->base_commit); }else if (!strcmp (arg,"auto" )) { - auto_base =AUTO_BASE_ALWAYS ; - *base_commit =NULL ; + cfg->auto_base =AUTO_BASE_ALWAYS ; +FREE_AND_NULL (cfg->base_commit); }else { - auto_base =AUTO_BASE_NEVER ; - *base_commit = arg; + cfg->auto_base =AUTO_BASE_NEVER ; + cfg->base_commit =xstrdup (arg); }return 0 ; }
@@ -1681,7 +1731,7 @@ struct base_tree_info {struct object_id *patch_id; }; -static struct commit *get_base_commit (const char *base_commit, +static struct commit *get_base_commit (const struct format_config *cfg,struct commit **list,int total) {
@@ -1689,9 +1739,9 @@ static struct commit *get_base_commit(const char *base_commit,struct commit **rev;int i =0 , rev_nr =0 , auto_select, die_on_failure, ret; -switch (auto_base) { +switch (cfg->auto_base) {case AUTO_BASE_NEVER : -if (base_commit) { +if (cfg->base_commit) { auto_select =0 ; die_on_failure =1 ; }else {
@@ -1701,11 +1751,11 @@ static struct commit *get_base_commit(const char *base_commit,break ;case AUTO_BASE_ALWAYS :case AUTO_BASE_WHEN_ABLE : -if (base_commit) { +if (cfg->base_commit) {BUG ("requested automatic base selection but a commit was provided" ); }else { auto_select =1 ; - die_on_failure = auto_base ==AUTO_BASE_ALWAYS ; + die_on_failure = cfg->auto_base ==AUTO_BASE_ALWAYS ; }break ;default :
@@ -1713,9 +1763,9 @@ static struct commit *get_base_commit(const char *base_commit, }if (!auto_select) { - base =lookup_commit_reference_by_name (base_commit); + base =lookup_commit_reference_by_name (cfg->base_commit);if (!base) -die (_ ("unknown commit %s" ), base_commit); +die (_ ("unknown commit %s" ), cfg->base_commit); }else {struct branch *curr_branch =branch_get (NULL );const char *upstream =branch_get_upstream (curr_branch,NULL );
@@ -1933,7 +1983,7 @@ static void infer_range_diff_ranges(struct strbuf *r1,int cmd_format_patch (int argc,const char **argv,const char *prefix) { -struct log_config cfg; +struct format_config cfg;struct commit *commit;struct commit **list =NULL ;struct rev_info rev;
@@ -1958,7 +2008,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)char *cover_from_description_arg =NULL ;char *description_file =NULL ;char *branch_name =NULL ; -char *base_commit =NULL ;struct base_tree_info bases;struct commit *base;int show_progress =0 ;
@@ -1969,18 +2018,24 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)struct strbuf rdiff1 =STRBUF_INIT ;struct strbuf rdiff2 =STRBUF_INIT ;struct strbuf rdiff_title =STRBUF_INIT ; -struct strbuf sprefix =STRBUF_INIT ;const char *rfc =NULL ;int creation_factor =-1 ; +const char *signature = git_version_string; +const char *signature_file_arg =NULL ; +struct keep_callback_data keep_callback_data = { + .cfg = &cfg, + .revs = &rev, + }; +const char *fmt_patch_suffix =NULL ;const struct option builtin_format_patch_options[] = { -OPT_CALLBACK_F ('n' ,"numbered" , &numbered,NULL , +OPT_CALLBACK_F ('n' ,"numbered" , &cfg,NULL ,N_ ("use [PATCH n/m] even with a single patch" ),PARSE_OPT_NOARG , numbered_callback), -OPT_CALLBACK_F ('N' ,"no-numbered" , &numbered,NULL , +OPT_CALLBACK_F ('N' ,"no-numbered" , &cfg,NULL ,N_ ("use [PATCH] even with multiple patches" ),PARSE_OPT_NOARG |PARSE_OPT_NONEG , no_numbered_callback), -OPT_BOOL ('s' ,"signoff" , &do_signoff,N_ ("add a Signed-off-by trailer" )), +OPT_BOOL ('s' ,"signoff" , &cfg.do_signoff,N_ ("add a Signed-off-by trailer" )),OPT_BOOL (0 ,"stdout" , &use_stdout,N_ ("print patches to standard out" )), OPT_BOOL(0, "cover-letter", &cover_letter,
@@ -1993,7 +2048,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")),OPT_STRING ('v' ,"reroll-count" , &reroll_count,N_ ("reroll-count" ),N_ ("mark the series as Nth re-roll" )), -OPT_INTEGER (0 ,"filename-max-length" , &cfg.fmt_patch_name_max, +OPT_INTEGER (0 ,"filename-max-length" , &cfg.log.fmt_patch_name_max,N_ ("max length of output filename" )), OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), N_("add <rfc> (default 'RFC') before 'PATCH'"),
@@ -2003,13 +2058,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("generate parts of a cover letter based on a branch's description")),OPT_FILENAME (0 ,"description-file" , &description_file,N_ ("use branch description from file" )), -OPT_CALLBACK_F (0 ,"subject-prefix" , &sprefix,N_ ("prefix" ), +OPT_CALLBACK_F (0 ,"subject-prefix" , &cfg,N_ ("prefix" ),N_ ("use [<prefix>] instead of [PATCH]" ),PARSE_OPT_NONEG , subject_prefix_callback),OPT_CALLBACK_F ('o' ,"output-directory" , &output_directory,N_ ("dir" ),N_ ("store resulting files in <dir>" ),PARSE_OPT_NONEG , output_directory_callback), -OPT_CALLBACK_F ('k' ,"keep-subject" , &rev,NULL , +OPT_CALLBACK_F ('k' ,"keep-subject" , &keep_callback_data,NULL ,N_ ("don't strip/add [PATCH]" ),PARSE_OPT_NOARG |PARSE_OPT_NONEG , keep_callback), OPT_BOOL(0, "no-binary", &no_binary_diff,
@@ -2022,11 +2077,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("show patch format instead of default (patch + stat)"), 1, PARSE_OPT_NONEG),OPT_GROUP (N_ ("Messaging" )), -OPT_CALLBACK (0 ,"add-header" ,NULL ,N_ ("header" ), +OPT_CALLBACK (0 ,"add-header" , &cfg,N_ ("header" ),N_ ("add email header" ), header_callback), -OPT_STRING_LIST (0 ,"to" , &extra_to,N_ ("email" ),N_ ("add To: header" )), -OPT_STRING_LIST (0 ,"cc" , &extra_cc,N_ ("email" ),N_ ("add Cc: header" )), -OPT_CALLBACK_F (0 ,"from" , &from,N_ ("ident" ), +OPT_STRING_LIST (0 ,"to" , &cfg.extra_to,N_ ("email" ),N_ ("add To: header" )), +OPT_STRING_LIST (0 ,"cc" , &cfg.extra_cc,N_ ("email" ),N_ ("add Cc: header" )), +OPT_CALLBACK_F (0 ,"from" , &cfg.from,N_ ("ident" ),N_ ("set From address to <ident> (or committer ident if absent)" ),PARSE_OPT_OPTARG , from_callback), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
@@ -2038,15 +2093,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, inline_callback), -OPT_CALLBACK_F (0 ,"thread" , &thread,N_ ("style" ), +OPT_CALLBACK_F (0 ,"thread" , &cfg,N_ ("style" ),N_ ("enable message threading, styles: shallow, deep" ),PARSE_OPT_OPTARG , thread_callback),OPT_STRING (0 ,"signature" , &signature,N_ ("signature" ),N_ ("add a signature" )), -OPT_CALLBACK_F (0 ,"base" , &base_commit,N_ ("base-commit" ), +OPT_CALLBACK_F (0 ,"base" , &cfg,N_ ("base-commit" ),N_ ("add prerequisite tree info to the patch series" ),0 , base_callback), -OPT_FILENAME (0 ,"signature-file" , &signature_file, +OPT_FILENAME (0 ,"signature-file" , &signature_file_arg,N_ ("add a signature from a file" )),OPT__QUIET (&quiet,N_ ("don't print the patch filenames" )), OPT_BOOL(0, "progress", &show_progress,
@@ -2063,21 +2118,17 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_END() }; - extra_hdr.strdup_strings =1 ; - extra_to.strdup_strings =1 ; - extra_cc.strdup_strings =1 ; - -log_config_init (&cfg); +format_config_init (&cfg);init_diff_ui_defaults (); -init_display_notes (¬es_opt); +init_display_notes (&cfg.notes_opt);git_config (git_format_config, &cfg);repo_init_revisions (the_repository, &rev, prefix);git_config (grep_config, &rev.grep_filter); - rev.show_notes = show_notes; -memcpy (&rev.notes_opt, ¬es_opt,sizeof (notes_opt)); + rev.show_notes = cfg.show_notes; +memcpy (&rev.notes_opt, &cfg.notes_opt,sizeof (cfg.notes_opt)); rev.commit_format =CMIT_FMT_EMAIL ; - rev.encode_email_headers = cfg.default_encode_email_headers; + rev.encode_email_headers = cfg.log.default_encode_email_headers; rev.expand_tabs_in_log_default =0 ; rev.verbose_header =1 ; rev.diff =1 ;
@@ -2088,12 +2139,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def ="HEAD" ; s_r_opt.revarg_opt =REVARG_COMMITTISH ; -strbuf_addstr (&sprefix, cfg.fmt_patch_subject_prefix); +strbuf_addstr (&cfg.sprefix, cfg.log.fmt_patch_subject_prefix);if (format_no_prefix)diff_set_noprefix (&rev.diffopt); -if (default_attach) { - rev.mime_boundary = default_attach; +if (cfg.default_attach) { + rev.mime_boundary = cfg.default_attach; rev.no_inline =1 ; }
@@ -2109,60 +2160,63 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.force_in_body_from = force_in_body_from; +if (!fmt_patch_suffix) + fmt_patch_suffix = cfg.fmt_patch_suffix; +/* Make sure "0000-$sub.patch" gives non-negative length for $sub */ -if (cfg.fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) - cfg.fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix); +if (cfg.log.fmt_patch_name_max <=strlen ("0000-" ) +strlen (fmt_patch_suffix)) + cfg.log.fmt_patch_name_max =strlen ("0000-" ) +strlen (fmt_patch_suffix);if (cover_from_description_arg) - cover_from_description_mode =parse_cover_from_description (cover_from_description_arg); + cfg.cover_from_description_mode =parse_cover_from_description (cover_from_description_arg);if (rfc && rfc[0 ]) { - subject_prefix =1 ; + cfg.subject_prefix =1 ;if (rfc[0 ] =='-' ) -strbuf_addf (&sprefix," %s" , rfc +1 ); +strbuf_addf (&cfg.sprefix," %s" , rfc +1 );else -strbuf_insertf (&sprefix,0 ,"%s " , rfc); +strbuf_insertf (&cfg.sprefix,0 ,"%s " , rfc); }if (reroll_count) { -strbuf_addf (&sprefix," v%s" , reroll_count); +strbuf_addf (&cfg.sprefix," v%s" , reroll_count); rev.reroll_count = reroll_count; } - rev.subject_prefix = sprefix.buf; + rev.subject_prefix = cfg.sprefix.buf; -for (i =0 ; i < extra_hdr.nr; i++) { -strbuf_addstr (&buf, extra_hdr.items[i].string); +for (i =0 ; i < cfg.extra_hdr.nr; i++) { +strbuf_addstr (&buf, cfg.extra_hdr.items[i].string);strbuf_addch (&buf,'\n' ); } -if (extra_to.nr) +if (cfg.extra_to.nr)strbuf_addstr (&buf,"To: " ); -for (i =0 ; i < extra_to.nr; i++) { +for (i =0 ; i < cfg.extra_to.nr; i++) {if (i)strbuf_addstr (&buf," " ); -strbuf_addstr (&buf, extra_to.items[i].string); -if (i +1 < extra_to.nr) +strbuf_addstr (&buf, cfg.extra_to.items[i].string); +if (i +1 < cfg.extra_to.nr)strbuf_addch (&buf,',' );strbuf_addch (&buf,'\n' ); } -if (extra_cc.nr) +if (cfg.extra_cc.nr)strbuf_addstr (&buf,"Cc: " ); -for (i =0 ; i < extra_cc.nr; i++) { +for (i =0 ; i < cfg.extra_cc.nr; i++) {if (i)strbuf_addstr (&buf," " ); -strbuf_addstr (&buf, extra_cc.items[i].string); -if (i +1 < extra_cc.nr) +strbuf_addstr (&buf, cfg.extra_cc.items[i].string); +if (i +1 < cfg.extra_cc.nr)strbuf_addch (&buf,',' );strbuf_addch (&buf,'\n' ); } rev.extra_headers = to_free =strbuf_detach (&buf,NULL ); -if (from) { -if (split_ident_line (&rev.from_ident, from,strlen (from))) -die (_ ("invalid ident line: %s" ), from); +if (cfg.from) { +if (split_ident_line (&rev.from_ident, cfg.from,strlen (cfg.from))) +die (_ ("invalid ident line: %s" ), cfg.from); }if (start_number <0 )
@@ -2173,14 +2227,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)* and it would conflict with --keep-subject (-k) from the * command line, reset "numbered". */ -if (numbered && keep_subject && !numbered_cmdline_opt) - numbered =0 ; +if (cfg.numbered && cfg.keep_subject && !cfg.numbered_cmdline_opt) + cfg.numbered =0 ; -if (numbered && keep_subject) +if (cfg.numbered && cfg.keep_subject)die (_ ("options '%s' and '%s' cannot be used together" ),"-n" ,"-k" ); -if (keep_subject && subject_prefix) +if (cfg.keep_subject && cfg.subject_prefix)die (_ ("options '%s' and '%s' cannot be used together" ),"--subject-prefix/--rfc" ,"-k" ); - rev.preserve_subject = keep_subject; + rev.preserve_subject = cfg.keep_subject; argc =setup_revisions (argc, argv, &rev, &s_r_opt);if (argc >1 )
@@ -2207,7 +2261,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.always_show_header =1 ; rev.zero_commit = zero_commit; - rev.patch_name_max = cfg.fmt_patch_name_max; + rev.patch_name_max = cfg.log.fmt_patch_name_max;if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary =1 ;
@@ -2228,7 +2282,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)int saved;if (!output_directory) - output_directory = config_output_directory; + output_directory = cfg.config_output_directory; output_directory =set_outdir (prefix, output_directory);if (rev.diffopt.use_color !=GIT_COLOR_ALWAYS )
@@ -2326,14 +2380,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) goto done; total = nr;if (cover_letter ==-1 ) { -if (config_cover_letter ==COVER_AUTO ) +if (cfg.config_cover_letter ==COVER_AUTO ) cover_letter = (total >1 );else - cover_letter = (config_cover_letter ==COVER_ON ); + cover_letter = (cfg.config_cover_letter ==COVER_ON ); } -if (!keep_subject && auto_number && (total >1 || cover_letter)) - numbered =1 ; -if (numbered) +if (!cfg.keep_subject && cfg.auto_number && (total >1 || cover_letter)) + cfg.numbered =1 ; +if (cfg.numbered) rev.total = total + start_number -1 ;if (idiff_prev.nr) {
@@ -2365,27 +2419,40 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) _("Range-diff against v%d:")); } +/* +* The order of precedence is: +* +* 1. The `--signature` and `--no-signature` options. +* 2. The `--signature-file` option. +* 3. The `format.signature` config. +* 4. The `format.signatureFile` config. +* 5. Default `git_version_string`. +*/ if (!signature) { ;/* --no-signature inhibits all signatures */ }else if (signature && signature != git_version_string) { ;/* non-default signature already set */ - }else if (signature_file) { + }else if (signature_file_arg || (cfg.signature_file && !cfg.signature)) {struct strbuf buf =STRBUF_INIT ; +const char *signature_file = signature_file_arg ? + signature_file_arg : cfg.signature_file;if (strbuf_read_file (&buf, signature_file,128 ) <0 )die_errno (_ ("unable to read signature file '%s'" ), signature_file); signature =strbuf_detach (&buf,NULL ); + }else if (cfg.signature) { + signature = cfg.signature; }memset (&bases,0 ,sizeof (bases)); - base =get_base_commit (base_commit, list, nr); + base =get_base_commit (&cfg, list, nr);if (base) {reset_revision_walk ();clear_object_flags (UNINTERESTING );prepare_bases (&bases, base, list, nr); } -if (in_reply_to || thread || cover_letter) { +if (in_reply_to || cfg.thread || cover_letter) { rev.ref_message_ids =xmalloc (sizeof (*rev.ref_message_ids));string_list_init_dup (rev.ref_message_ids); }
@@ -2396,19 +2463,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix;if (cover_letter) { -if (thread) +if (cfg.thread)gen_message_id (&rev,"cover" );make_cover_letter (&rev, !!output_directory, - origin, nr, list, description_file, branch_name, quiet); + origin, nr, list, description_file, branch_name, quiet, &cfg);print_bases (&bases, rev.diffopt.file); -print_signature (rev.diffopt.file); +print_signature (signature, rev.diffopt.file); total++; start_number--;/* interdiff/range-diff in cover-letter; omit from patches */ rev.idiff_oid1 =NULL ; rev.rdiff1 =NULL ; } - rev.add_signoff = do_signoff; + rev.add_signoff = cfg.do_signoff;if (show_progress) progress =start_delayed_progress (_ ("Generating patches" ), total);
@@ -2418,7 +2485,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) commit = list[nr]; rev.nr = total - nr + (start_number -1 );/* Make the second and subsequent mails replies to the first */ -if (thread) { +if (cfg.thread) {/* Have we already had a message ID? */ if (rev.message_id) {/*
@@ -2442,7 +2509,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)* letter is a reply to the * --in-reply-to, if specified. */ -if (thread ==THREAD_SHALLOW +if (cfg.thread ==THREAD_SHALLOW && rev.ref_message_ids->nr >0 && (!cover_letter || rev.nr >1 ))free (rev.message_id);
@@ -2475,7 +2542,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) mime_boundary_leader, rev.mime_boundary); else - print_signature(rev.diffopt.file); + print_signature(signature, rev.diffopt.file); }if (output_directory)fclose (rev.diffopt.file);
@@ -2483,9 +2550,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)stop_progress (&progress);free (list);free (branch_name); -string_list_clear (&extra_to,0 ); -string_list_clear (&extra_cc,0 ); -string_list_clear (&extra_hdr,0 );if (ignore_if_in_upstream)free_patch_ids (&ids);
@@ -2495,14 +2559,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)strbuf_release (&rdiff1);strbuf_release (&rdiff2);strbuf_release (&rdiff_title); -strbuf_release (&sprefix);free (to_free);free (rev.message_id);if (rev.ref_message_ids)string_list_clear (rev.ref_message_ids,0 );free (rev.ref_message_ids);release_revisions (&rev); -log_config_release (&cfg); +format_config_release (&cfg);return 0 ; }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 12/21 config: clarify memory ownership in `git_config_string()`
In reply to Patrick SteinhardtThe out parameter of git_config_string() is a const char ** even though we transfer ownership of memory to the caller. This is quite misleading and has led to many memory leaks all over the place. Adapt the parameter to instead be char **.
--- alias.c | 2 +- attr.c | 2 +- attr.h | 2 +- builtin/commit.c | 2 +- builtin/log.c | 12 ++++++------ builtin/merge.c | 4 ++-- builtin/rebase.c | 2 +- builtin/receive-pack.c | 2 +- builtin/repack.c | 8 ++++---- config.c | 4 ++-- config.h | 2 +- convert.c | 6 +++--- delta-islands.c | 2 +- diff.c | 8 ++++---- environment.c | 8 ++++---- environment.h | 8 ++++---- gpg-interface.c | 4 ++-- http.c | 24 ++++++++++++------------ imap-send.c | 12 ++++++------ mailmap.c | 2 +- mailmap.h | 2 +- merge-ll.c | 6 +++--- pager.c | 2 +- pretty.c | 14 +++++++++----- promisor-remote.h | 2 +- remote.c | 20 ++++++++++---------- remote.h | 8 ++++---- sequencer.c | 2 +- upload-pack.c | 2 +- userdiff.h | 12 ++++++------ 30 files changed, 95 insertions(+), 91 deletions(-)
alias.c +1 -1
@@ -22,7 +22,7 @@ static int config_alias_cb(const char *key, const char *value,if (data->alias) {if (!strcasecmp (p, data->alias)) -return git_config_string ((const char **)&data->v, +return git_config_string (&data->v, key, value); }else if (data->list) {string_list_append (data->list, p);
attr.c +1 -1
@@ -25,7 +25,7 @@#include "tree-walk.h" #include "object-name.h" -const char *git_attr_tree; +char *git_attr_tree;const char git_attr__true[] ="(builtin)true" ;const char git_attr__false[] ="\0(builtin)false" ;
attr.h +1 -1
@@ -236,6 +236,6 @@ const char *git_attr_global_file(void);/* Return whether the system gitattributes file is enabled and should be used. */ int git_attr_system_is_enabled (void ); -extern const char *git_attr_tree; +extern char *git_attr_tree;#endif /* ATTR_H */
builtin/commit.c +1 -1
@@ -133,7 +133,7 @@ static struct strvec trailer_args = STRVEC_INIT;* is specified explicitly. */ static enum commit_msg_cleanup_mode cleanup_mode; -static const char *cleanup_arg; +static char *cleanup_arg;static enum commit_whence whence;static int use_editor =1 , include_status =1 ;
builtin/log.c +6 -6
@@ -582,11 +582,11 @@ static int git_log_config(const char *var, const char *value,if (!strcmp (var,"format.pretty" )) {FREE_AND_NULL (cfg->fmt_pretty); -return git_config_string ((const char **) &cfg->fmt_pretty, var, value); +return git_config_string (&cfg->fmt_pretty, var, value); }if (!strcmp (var,"format.subjectprefix" )) {FREE_AND_NULL (cfg->fmt_patch_subject_prefix); -return git_config_string ((const char **) &cfg->fmt_patch_subject_prefix, var, value); +return git_config_string (&cfg->fmt_patch_subject_prefix, var, value); }if (!strcmp (var,"format.filenamemaxlength" )) { cfg->fmt_patch_name_max =git_config_int (var, value, ctx->kvi);
@@ -602,7 +602,7 @@ static int git_log_config(const char *var, const char *value, }if (!strcmp (var,"log.date" )) {FREE_AND_NULL (cfg->default_date_mode); -return git_config_string ((const char **) &cfg->default_date_mode, var, value); +return git_config_string (&cfg->default_date_mode, var, value); }if (!strcmp (var,"log.decorate" )) { cfg->decoration_style =parse_decoration_style (value);
@@ -1076,7 +1076,7 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.suffix" )) {FREE_AND_NULL (cfg->fmt_patch_suffix); -return git_config_string ((const char **) &cfg->fmt_patch_suffix, var, value); +return git_config_string (&cfg->fmt_patch_suffix, var, value); }if (!strcmp (var,"format.to" )) {if (!value)
@@ -1133,7 +1133,7 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.signature" )) {FREE_AND_NULL (cfg->signature); -return git_config_string ((const char **) &cfg->signature, var, value); +return git_config_string (&cfg->signature, var, value); }if (!strcmp (var,"format.signaturefile" )) {FREE_AND_NULL (cfg->signature_file);
@@ -1149,7 +1149,7 @@ static int git_format_config(const char *var, const char *value, }if (!strcmp (var,"format.outputdirectory" )) {FREE_AND_NULL (cfg->config_output_directory); -return git_config_string ((const char **) &cfg->config_output_directory, var, value); +return git_config_string (&cfg->config_output_directory, var, value); }if (!strcmp (var,"format.useautobase" )) {if (value && !strcasecmp (value,"whenAble" )) {
builtin/merge.c +2 -2
@@ -100,7 +100,7 @@ static struct strategy all_strategy[] = { { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, }; -static const char *pull_twohead, *pull_octopus; +static char *pull_twohead, *pull_octopus;enum ff_type { FF_NO,
@@ -110,7 +110,7 @@ enum ff_type {static enum ff_type fast_forward =FF_ALLOW ; -static const char *cleanup_arg; +static char *cleanup_arg;static enum commit_msg_cleanup_mode cleanup_mode; static int option_parse_message(const struct option *opt,
builtin/rebase.c +1 -1
@@ -83,7 +83,7 @@ static const char *action_names[] = {struct rebase_options {enum rebase_type type;enum empty_type empty; -const char *default_backend; +char *default_backend;const char *state_dir;struct commit *upstream;const char *upstream_name;
builtin/receive-pack.c +1 -1
@@ -88,7 +88,7 @@ static struct strbuf push_cert = STRBUF_INIT;static struct object_id push_cert_oid;static struct signature_check sigcheck;static const char *push_cert_nonce; -static const char *cert_nonce_seed; +static char *cert_nonce_seed;static struct strvec hidden_refs =STRVEC_INIT ;static const char *NONCE_UNSOLICITED ="UNSOLICITED" ;
builtin/repack.c +4 -4
@@ -48,10 +48,10 @@ static const char incremental_bitmap_conflict_error[] = N_( );struct pack_objects_args { -const char *window; -const char *window_memory; -const char *depth; -const char *threads; +char *window; +char *window_memory; +char *depth; +char *threads;unsigned long max_pack_size;int no_reuse_delta;int no_reuse_object;
config.c +2 -2
@@ -1338,7 +1338,7 @@ int git_config_bool(const char *name, const char *value)return v; } -int git_config_string (const char **dest,const char *var,const char *value) +int git_config_string (char **dest,const char *var,const char *value) {if (!value)return config_error_nonbool (var);
@@ -2418,7 +2418,7 @@ int git_configset_get_string(struct config_set *set, const char *key, char **des {const char *value;if (!git_configset_get_value (set, key, &value,NULL )) -return git_config_string ((const char **)dest, key, value); +return git_config_string (dest, key, value);else return 1 ; }
config.h +1 -1
@@ -280,7 +280,7 @@ int git_config_bool(const char *, const char *);* Allocates and copies the value string into the `dest` parameter; if no * string is given, prints an error message and returns -1. */ -int git_config_string (const char **,const char *,const char *); +int git_config_string (char **,const char *,const char *);/** * Similar to `git_config_string`, but expands `~` or `~user` into the
convert.c +3 -3
@@ -981,9 +981,9 @@ int async_query_available_blobs(const char *cmd, struct string_list *available_pstatic struct convert_driver {const char *name;struct convert_driver *next; -const char *smudge; -const char *clean; -const char *process; +char *smudge; +char *clean; +char *process;int required; } *user_convert, **user_convert_tail;
delta-islands.c +1 -1
@@ -313,7 +313,7 @@ struct island_load_data {size_t nr;size_t alloc; }; -static const char *core_island_name; +static char *core_island_name;static void free_config_regexes (struct island_load_data *ild) {
diff.c +4 -4
@@ -56,8 +56,8 @@ static int diff_color_moved_default;static int diff_color_moved_ws_default;static int diff_context_default =3 ;static int diff_interhunk_context_default; -static const char *diff_word_regex_cfg; -static const char *external_diff_cmd_cfg; +static char *diff_word_regex_cfg; +static char *external_diff_cmd_cfg;static char *diff_order_file_cfg;int diff_auto_refresh_index =1 ;static int diff_mnemonic_prefix;
@@ -412,11 +412,11 @@ int git_diff_ui_config(const char *var, const char *value, }if (!strcmp (var,"diff.srcprefix" )) {FREE_AND_NULL (diff_src_prefix); -return git_config_string ((const char **) &diff_src_prefix, var, value); +return git_config_string (&diff_src_prefix, var, value); }if (!strcmp (var,"diff.dstprefix" )) {FREE_AND_NULL (diff_dst_prefix); -return git_config_string ((const char **) &diff_dst_prefix, var, value); +return git_config_string (&diff_dst_prefix, var, value); }if (!strcmp (var,"diff.relative" )) { diff_relative =git_config_bool (var, value);
environment.c +4 -4
@@ -42,8 +42,8 @@ int is_bare_repository_cfg = -1; /* unspecified */int warn_ambiguous_refs =1 ;int warn_on_object_refname_ambiguity =1 ;int repository_format_precious_objects; -const char *git_commit_encoding; -const char *git_log_output_encoding; +char *git_commit_encoding; +char *git_log_output_encoding;char *apply_default_whitespace;char *apply_default_ignorewhitespace;char *git_attributes_file;
@@ -58,8 +58,8 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;size_t packed_git_limit =DEFAULT_PACKED_GIT_LIMIT ;size_t delta_base_cache_limit =96 *1024 *1024 ;unsigned long big_file_threshold =512 *1024 *1024 ; -const char *editor_program; -const char *askpass_program; +char *editor_program; +char *askpass_program;char *excludes_file;enum auto_crlf auto_crlf =AUTO_CRLF_FALSE ;enum eol core_eol =EOL_UNSET ;
environment.h +4 -4
@@ -224,11 +224,11 @@ int odb_pack_keep(const char *name);const char *get_log_output_encoding (void );const char *get_commit_output_encoding (void ); -extern const char *git_commit_encoding; -extern const char *git_log_output_encoding; +extern char *git_commit_encoding; +extern char *git_log_output_encoding; -extern const char *editor_program; -extern const char *askpass_program; +extern char *editor_program; +extern char *askpass_program;extern char *excludes_file;/*
gpg-interface.c +2 -2
@@ -27,14 +27,14 @@ static void gpg_interface_lazy_init(void) }static char *configured_signing_key; -static const char *ssh_default_key_command; +static char *ssh_default_key_command;static char *ssh_allowed_signers;static char *ssh_revocation_file;static enum signature_trust_level configured_min_trust_level =TRUST_UNDEFINED ;struct gpg_format {const char *name; -const char *program; +char *program;const char **verify_args;const char **sigs; int (*verify_signed_buffer)(struct signature_check *sigc,
http.c +12 -12
@@ -38,11 +38,11 @@ char curl_errorstr[CURL_ERROR_SIZE];static int curl_ssl_verify =-1 ;static int curl_ssl_try; -static const char *curl_http_version =NULL ; +static char *curl_http_version;static char *ssl_cert;static char *ssl_cert_type; -static const char *ssl_cipherlist; -static const char *ssl_version; +static char *ssl_cipherlist; +static char *ssl_version; static struct { const char *name; long ssl_version;
@@ -95,7 +95,7 @@ static struct {*/ }; #ifdef CURLGSSAPI_DELEGATION_FLAG -static const char *curl_deleg; +static char *curl_deleg; static struct { const char *name; long curl_deleg_param;
@@ -383,11 +383,11 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslcert" , var))return git_config_pathname (&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var)) -return git_config_string ((const char **)&ssl_cert_type, var, value); +return git_config_string (&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var))return git_config_pathname (&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var)) -return git_config_string ((const char **)&ssl_key_type, var, value); +return git_config_string (&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var))return git_config_pathname (&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var))
@@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value,return 0 ; }if (!strcmp ("http.proxy" , var)) -return git_config_string ((const char **)&curl_http_proxy, var, value); +return git_config_string (&curl_http_proxy, var, value);if (!strcmp ("http.proxyauthmethod" , var)) -return git_config_string ((const char **)&http_proxy_authmethod, var, value); +return git_config_string (&http_proxy_authmethod, var, value);if (!strcmp ("http.proxysslcert" , var)) -return git_config_string ((const char **)&http_proxy_ssl_cert, var, value); +return git_config_string (&http_proxy_ssl_cert, var, value);if (!strcmp ("http.proxysslkey" , var)) -return git_config_string ((const char **)&http_proxy_ssl_key, var, value); +return git_config_string (&http_proxy_ssl_key, var, value);if (!strcmp ("http.proxysslcainfo" , var)) -return git_config_string ((const char **)&http_proxy_ssl_ca_info, var, value); +return git_config_string (&http_proxy_ssl_ca_info, var, value);if (!strcmp ("http.proxysslcertpasswordprotected" , var)) { proxy_ssl_cert_password_required =git_config_bool (var, value);
@@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, }if (!strcmp ("http.useragent" , var)) -return git_config_string ((const char **)&user_agent, var, value); +return git_config_string (&user_agent, var, value);if (!strcmp ("http.emptyauth" , var)) {if (value && !strcmp ("auto" , value))
imap-send.c +6 -6
@@ -70,16 +70,16 @@ static char *next_arg(char **);struct imap_server_conf {const char *name; -const char *tunnel; -const char *host; +char *tunnel; +char *host;int port; -const char *folder; -const char *user; -const char *pass; +char *folder; +char *user; +char *pass;int use_ssl;int ssl_verify;int use_html; -const char *auth_method; +char *auth_method; };static struct imap_server_conf server = {
mailmap.c +1 -1
@@ -7,7 +7,7 @@#include "setup.h" char *git_mailmap_file; -const char *git_mailmap_blob; +char *git_mailmap_blob;struct mailmap_info {char *name;
mailmap.h +1 -1
@@ -4,7 +4,7 @@struct string_list ;extern char *git_mailmap_file; -extern const char *git_mailmap_blob; +extern char *git_mailmap_blob;int read_mailmap (struct string_list *map);void clear_mailmap (struct string_list *map);
merge-ll.c +3 -3
@@ -27,9 +27,9 @@ typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,struct ll_merge_driver {const char *name; -const char *description; +char *description;ll_merge_fn fn; -const char *recursive; +char *recursive;struct ll_merge_driver *next;char *cmdline; };
@@ -268,7 +268,7 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,* merge.default and merge.driver configuration items */ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail; -static const char *default_ll_merge; +static char *default_ll_merge; static int read_merge_config(const char *var, const char *value, const struct config_context *ctx UNUSED,
pager.c +1 -1
@@ -13,7 +13,7 @@ int pager_use_color = 1;#endif static struct child_process pager_process; -static const char *pager_program; +static char *pager_program;/* Is the value coming back from term_columns() just a guess? */ static int term_columns_guessed;
pretty.c +9 -5
@@ -62,7 +62,7 @@ static int git_pretty_formats_config(const char *var, const char *value, {struct cmt_fmt_map *commit_format =NULL ;const char *name; -const char *fmt; +char *fmt;int i;if (!skip_prefix (var,"pretty." , &name))
@@ -93,13 +93,17 @@ static int git_pretty_formats_config(const char *var, const char *value,if (git_config_string (&fmt, var, value))return -1 ; -if (skip_prefix (fmt,"format:" , &fmt)) +if (skip_prefix (fmt,"format:" , &commit_format->user_format)) { commit_format->is_tformat =0 ; -else if (skip_prefix (fmt,"tformat:" , &fmt) ||strchr (fmt,'%' )) + }else if (skip_prefix (fmt,"tformat:" , &commit_format->user_format)) { commit_format->is_tformat =1 ; -else + }else if (strchr (fmt,'%' )) { + commit_format->is_tformat =1 ; + commit_format->user_format = fmt; + }else { commit_format->is_alias =1 ; - commit_format->user_format = fmt; + commit_format->user_format = fmt; + }return 0 ; }
promisor-remote.h +1 -1
@@ -13,7 +13,7 @@ struct object_id;*/ struct promisor_remote {struct promisor_remote *next; -const char *partial_clone_filter; +char *partial_clone_filter;const char name[FLEX_ARRAY ]; };
remote.c +10 -10
@@ -428,29 +428,29 @@ static int handle_config(const char *key, const char *value,else if (!strcmp (subkey,"prunetags" )) remote->prune_tags =git_config_bool (key, value);else if (!strcmp (subkey,"url" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;add_url (remote, v); }else if (!strcmp (subkey,"pushurl" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;add_pushurl (remote, v); }else if (!strcmp (subkey,"push" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;refspec_append (&remote->push, v); -free ((char *)v); +free (v); }else if (!strcmp (subkey,"fetch" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;refspec_append (&remote->fetch, v); -free ((char *)v); +free (v); }else if (!strcmp (subkey,"receivepack" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;if (!remote->receivepack)
@@ -458,7 +458,7 @@ static int handle_config(const char *key, const char *value,else error (_ ("more than one receivepack given, using the first" )); }else if (!strcmp (subkey,"uploadpack" )) { -const char *v; +char *v;if (git_config_string (&v, key, value))return -1 ;if (!remote->uploadpack)
@@ -471,10 +471,10 @@ static int handle_config(const char *key, const char *value,else if (!strcmp (value,"--tags" )) remote->fetch_tags =2 ; }else if (!strcmp (subkey,"proxy" )) { -return git_config_string ((const char **)&remote->http_proxy, +return git_config_string (&remote->http_proxy, key, value); }else if (!strcmp (subkey,"proxyauthmethod" )) { -return git_config_string ((const char **)&remote->http_proxy_authmethod, +return git_config_string (&remote->http_proxy_authmethod, key, value); }else if (!strcmp (subkey,"vcs" )) {return git_config_string (&remote->foreign_vcs, key, value);
remote.h +4 -4
@@ -46,7 +46,7 @@ struct remote_state {struct hashmap branches_hash;struct branch *current_branch; -const char *pushremote_name; +char *pushremote_name;struct rewrites rewrites;struct rewrites rewrites_push;
@@ -65,7 +65,7 @@ struct remote {int origin, configured_in_repo; -const char *foreign_vcs; +char *foreign_vcs;/* An array of all of the url_nr URLs configured for the remote */ const char **url;
@@ -309,9 +309,9 @@ struct branch {const char *refname;/* The name of the remote listed in the configuration. */ -const char *remote_name; +char *remote_name; -const char *pushremote_name; +char *pushremote_name;/* An array of the "merge" lines in the configuration. */ const char **merge_name;
sequencer.c +1 -1
@@ -306,7 +306,7 @@ static int git_sequencer_config(const char *k, const char *v, }if (!opts->default_strategy && !strcmp (k,"pull.twohead" )) { -int ret =git_config_string ((const char **)&opts->default_strategy, k, v); +int ret =git_config_string (&opts->default_strategy, k, v);if (ret ==0 ) {/* * pull.twohead is allowed to be multi-valued; we only
upload-pack.c +1 -1
@@ -94,7 +94,7 @@ struct upload_pack_data {struct packet_writer writer; -const char *pack_objects_hook; +char *pack_objects_hook; unsigned stateless_rpc : 1; /* v0 only */ unsigned no_done : 1; /* v0 only */
userdiff.h +6 -6
@@ -7,19 +7,19 @@ struct index_state;struct repository ;struct userdiff_funcname { -const char *pattern; +char *pattern;int cflags; };struct userdiff_driver {const char *name; -const char *external; -const char *algorithm; +char *external; +char *algorithm;int binary;struct userdiff_funcname funcname; -const char *word_regex; -const char *word_regex_multi_byte; -const char *textconv; +char *word_regex; +char *word_regex_multi_byte; +char *textconv;struct notes_cache *textconv_cache;int textconv_want_cache; };
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 13/21 config: plug various memory leaks
In reply to Patrick SteinhardtNow that memory ownership rules around git_config_string() and git_config_pathname() are clearer, it also got easier to spot that the returned memory needs to be free'd. Plug a subset of those cases and mark now-passing tests as leak free.
--- alias.c | 4 ++- config.c | 38 ++++++++++++++------ t/t1306-xdg-files.sh | 1 + t/t1350-config-hooks-path.sh | 1 + t/t3415-rebase-autosquash.sh | 1 + t/t4041-diff-submodule-option.sh | 1 + t/t4060-diff-submodule-option-diff-format.sh | 1 + t/t4210-log-i18n.sh | 2 ++ t/t6006-rev-list-format.sh | 1 + t/t7005-editor.sh | 1 + t/t7102-reset.sh | 1 + t/t9129-git-svn-i18n-commitencoding.sh | 1 - t/t9139-git-svn-non-utf8-commitencoding.sh | 1 - 13 files changed, 41 insertions(+), 13 deletions(-)
alias.c +3 -1
@@ -21,9 +21,11 @@ static int config_alias_cb(const char *key, const char *value,return 0 ;if (data->alias) { -if (!strcasecmp (p, data->alias)) +if (!strcasecmp (p, data->alias)) { +FREE_AND_NULL (data->v);return git_config_string (&data->v, key, value); + } }else if (data->list) {string_list_append (data->list, p); }
config.c +28 -10
@@ -1414,8 +1414,10 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.attributesfile" )) +if (!strcmp (var,"core.attributesfile" )) { +FREE_AND_NULL (git_attributes_file);return git_config_pathname (&git_attributes_file, var, value); + }if (!strcmp (var,"core.hookspath" )) { if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL &&
@@ -1428,6 +1430,7 @@ static int git_default_core_config(const char *var, const char *value,"again with " "`GIT_CLONE_PROTECTION_ACTIVE=false`"), value); +FREE_AND_NULL (git_hooks_path);return git_config_pathname (&git_hooks_path, var, value); }
@@ -1566,7 +1569,7 @@ static int git_default_core_config(const char *var, const char *value,if (!strcmp (var,"core.checkroundtripencoding" )) {FREE_AND_NULL (check_roundtrip_encoding); -return git_config_string ((const char **) &check_roundtrip_encoding, var, value); +return git_config_string (&check_roundtrip_encoding, var, value); }if (!strcmp (var,"core.notesref" )) {
@@ -1576,8 +1579,10 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.editor" )) +if (!strcmp (var,"core.editor" )) { +FREE_AND_NULL (editor_program);return git_config_string (&editor_program, var, value); + }if (!strcmp (var,"core.commentchar" ) || !strcmp (var,"core.commentstring" )) {
@@ -1595,11 +1600,13 @@ static int git_default_core_config(const char *var, const char *value,return 0 ; } -if (!strcmp (var,"core.askpass" )) +if (!strcmp (var,"core.askpass" )) { +FREE_AND_NULL (askpass_program);return git_config_string (&askpass_program, var, value); + }if (!strcmp (var,"core.excludesfile" )) { -free (excludes_file); +FREE_AND_NULL (excludes_file);return git_config_pathname (&excludes_file, var, value); }
@@ -1702,11 +1709,15 @@ static int git_default_sparse_config(const char *var, const char *value)static int git_default_i18n_config (const char *var,const char *value) { -if (!strcmp (var,"i18n.commitencoding" )) +if (!strcmp (var,"i18n.commitencoding" )) { +FREE_AND_NULL (git_commit_encoding);return git_config_string (&git_commit_encoding, var, value); + } -if (!strcmp (var,"i18n.logoutputencoding" )) +if (!strcmp (var,"i18n.logoutputencoding" )) { +FREE_AND_NULL (git_log_output_encoding);return git_config_string (&git_log_output_encoding, var, value); + }/* Add other config variables here and to Documentation/config.txt. */ return 0 ;
@@ -1779,10 +1790,15 @@ static int git_default_push_config(const char *var, const char *value)static int git_default_mailmap_config (const char *var,const char *value) { -if (!strcmp (var,"mailmap.file" )) +if (!strcmp (var,"mailmap.file" )) { +FREE_AND_NULL (git_mailmap_file);return git_config_pathname (&git_mailmap_file, var, value); -if (!strcmp (var,"mailmap.blob" )) + } + +if (!strcmp (var,"mailmap.blob" )) { +FREE_AND_NULL (git_mailmap_blob);return git_config_string (&git_mailmap_blob, var, value); + }/* Add other config variables here and to Documentation/config.txt. */ return 0 ;
@@ -1790,8 +1806,10 @@ static int git_default_mailmap_config(const char *var, const char *value)static int git_default_attr_config (const char *var,const char *value) { -if (!strcmp (var,"attr.tree" )) +if (!strcmp (var,"attr.tree" )) { +FREE_AND_NULL (git_attr_tree);return git_config_string (&git_attr_tree, var, value); + }/* * Add other attribute related config variables here and to
t/t1306-xdg-files.sh +1 -0
@@ -7,6 +7,7 @@ test_description='Compatibility with $XDG_CONFIG_HOME/git/ files' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'read config: xdg file exists and ~/.gitconfig doesn'\''t' '
t/t1350-config-hooks-path.sh +1 -0
@@ -2,6 +2,7 @@ test_description='Test the core.hooksPath configuration variable' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up a pre-commit hook in core.hooksPath' '
t/t3415-rebase-autosquash.sh +1 -0
@@ -5,6 +5,7 @@ test_description='auto squash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh
t/t4041-diff-submodule-option.sh +1 -0
@@ -12,6 +12,7 @@ This test tries to verify the sanity of the --submodule option of git diff. GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding
t/t4060-diff-submodule-option-diff-format.sh +1 -0
@@ -10,6 +10,7 @@ test_description='Support for diff format verbose submodule difference in git di This test tries to verify the sanity of --submodule=diff option of git diff. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding
t/t4210-log-i18n.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='test log with i18n features' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh # two forms of é
t/t6006-rev-list-format.sh +1 -0
@@ -8,6 +8,7 @@ test_description='git rev-list --pretty=format test' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh
t/t7005-editor.sh +1 -0
@@ -2,6 +2,7 @@ test_description='GIT_EDITOR, core.editor, and stuff' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh unset EDITOR VISUAL GIT_EDITOR
t/t7102-reset.sh +1 -0
@@ -10,6 +10,7 @@ Documented tests for git reset' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh commit_msg () {
t/t9129-git-svn-i18n-commitencoding.sh +0 -1
@@ -4,7 +4,6 @@ test_description='git svn honors i18n.commitEncoding in config' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh compare_git_head_with () {
t/t9139-git-svn-non-utf8-commitencoding.sh +0 -1
@@ -4,7 +4,6 @@ test_description='git svn refuses to dcommit non-UTF8 messages' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh # ISO-2022-JP can pass for valid UTF-8, so skipping that in this test
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Re: [PATCH v2 13/21] config: plug various memory leaks
Quoted 4 lines · Patrick Steinhardt
Now that memory ownership rules around
git_config_string()andgit_config_pathname()are clearer, it also got easier to spot that the returned memory needs to be free'd. Plug a subset of those cases and mark now-passing tests as leak free.
Junio,
this mail now uses UTF-8 as encoding. Does that fix the issues for you?
Patrick
Re: [PATCH v2 13/21] config: plug various memory leaks
In reply to Patrick SteinhardtQuoted 7 lines · Patrick Steinhardt
alias.c +0
-0@@ -21,9 +21,11 @@ static int config_alias_cb(const char *key, const char *value,return 0 ;
alias.c static int config_alias_cb(const char *key, const char *value, · line 31
Quoted 10 lines
alias.c +3
-1if (data->alias) { -if (!strcasecmp (p, data->alias)) +if (!strcasecmp (p, data->alias)) { +FREE_AND_NULL (data->v);return git_config_string (&data->v, key, value); + } }else if (data->list) {string_list_append (data->list, p); }
IMHO this should be done automatically by git_config_string(). The current design is an accident waiting to happen, and in the long run every call is going to need this FREE_AND_NULL(). By doing it in the function the calling code is shorter, and there's no way we'll forget.
I posted a series along those lines a month or so ago:
https://lore.kernel.org/git/[email protected]/
The catch is that you can't do this:
const char *foo = "bar"; git_config_string(&foo, ...);
So I introduced a new function that took a non-const pointer with the new behavior, with the idea that we'd eventually migrate everything over. It looks like you may have already done that migration earlier in your series, since the move to "char *" in the previous patch was OK.
Though as a side note, sadly:
char *foo = "bar";
does not produce an error or even a warning without -Wwrite-strings. I think in the long run we should enable that, but there's a little cleanup required to do so.
The main reason I didn't follow up on that earlier series is that there was some discussion about maybe moving this stuff over to strbufs (after teaching it to handle literal initializers). But if you've managed to remove all of the cases that needed that, I think just sticking with "char *" is fine.
The other issue raised in that thread is that many of these config variables are also passed to parse-options, which treats them as const strings (and we get no compiler support because it goes through a void pointer). So they may leak if we overwrite them, or in the unusual case that we load config after parsing options, we may try to free a non-heap string. The one we discussed was log's signature_file, and it looks like you split that to use two variables, which works. Junio suggested an OPT_FILENAME_DUP() option, which I'm also OK with. The main challenge to me is being sure we found all such spots (and not accidentally introducing new ones). But I don't have a good solution there.
alias.c static int git_default_core_config(const char *var, const char *value, · line 1573
Quoted 7 lines
alias.c +1
-1@@ -1566,7 +1569,7 @@ static int git_default_core_config(const char *var, const char *value,if (!strcmp (var,"core.checkroundtripencoding" )) {FREE_AND_NULL (check_roundtrip_encoding); -return git_config_string ((const char **) &check_roundtrip_encoding, var, value); +return git_config_string (&check_roundtrip_encoding, var, value); }
This should have lost its cast in the previous commit, no? Applying up to patch 12 and building with DEVELOPER=1 gets a warning.
-Peff
Re: [PATCH v2 13/21] config: plug various memory leaks
Quoted 9 lines · Jeff King
Quoted 7 lines · Patrick Steinhardt
alias.c +0
-0@@ -21,9 +21,11 @@ static int config_alias_cb(const char *key, const char *value,return 0 ;
Quoted 15 lines
alias.c static int config_alias_cb(const char *key, const char *value, · line 31
Quoted 10 lines
alias.c +3
-1if (data->alias) { -if (!strcasecmp (p, data->alias)) +if (!strcasecmp (p, data->alias)) { +FREE_AND_NULL (data->v);return git_config_string (&data->v, key, value); + } }else if (data->list) {string_list_append (data->list, p); }IMHO this should be done automatically by
git_config_string(). The current design is an accident waiting to happen, and in the long run every call is going to need thisFREE_AND_NULL(). By doing it in the function the calling code is shorter, and there's no way we'll forget.
In fact, I had this in my first iteration. But I didn't feel comfortable with it exactly due to the reasons you mention below, where often times we assign string constants as default values. This requires us to be extremely careful, also because we do not yet have -Wwrite-strings enabled as you mention.
Quoted 14 lines
I posted a series along those lines a month or so ago:
https://lore.kernel.org/git/[email protected]/
The catch is that you can't do this:
const char *foo = "bar"; git_config_string(&foo, ...);So I introduced a new function that took a non-const pointer with the new behavior, with the idea that we'd eventually migrate everything over. It looks like you may have already done that migration earlier in your series, since the move to "char *" in the previous patch was OK.
Quoted 7 lines
Though as a side note, sadly:
char *foo = "bar";does not produce an error or even a warning without -Wwrite-strings. I think in the long run we should enable that, but there's a little cleanup required to do so.
Indeed, I had the exact same observation. I've already got a patch series that enables -Wwrite-strings and that adapts our codebase to compile cleanly with it. I'll send that series once this one here has landed.
So my proposal would be to leave this patch as-is for the time being, but revisit it once both patch series have landed. Does that work for you?
Quoted 5 lines
The main reason I didn't follow up on that earlier series is that there was some discussion about maybe moving this stuff over to strbufs (after teaching it to handle literal initializers). But if you've managed to remove all of the cases that needed that, I think just sticking with "char *" is fine.
I don't think I managed to hit every callsite yet that leaks memory. But I think it shouldn't be too bad, and especiall if we follow up this patch series with FREE_AND_NULL() on the out-parameter then this should be fine.
Quoted 11 lines
The other issue raised in that thread is that many of these config variables are also passed to parse-options, which treats them as const strings (and we get no compiler support because it goes through a void pointer). So they may leak if we overwrite them, or in the unusual case that we load config after parsing options, we may try to free a non-heap string. The one we discussed was log's
signature_file, and it looks like you split that to use two variables, which works. Junio suggested anOPT_FILENAME_DUP()option, which I'm also OK with. The main challenge to me is being sure we found all such spots (and not accidentally introducing new ones). But I don't have a good solution there.
Yup, I remember having some issues with OPT_FILENAME(), but to the best of my knowledge I've fixed all of them.
Quoted 11 lines
alias.c static int git_default_core_config(const char · line 1575
Quoted 8 lines
alias.c +0
-0@@ -1566,7 +1569,7 @@ static int git_default_core_config(const char *var, const char *value, if (!strcmp(var, "core.checkroundtripencoding")) { FREE_AND_NULL(check_roundtrip_encoding); - returngit_config_string ((const char **) &check_roundtrip_encoding, var, value); + returngit_config_string (&check_roundtrip_encoding, var, value); }This should have lost its cast in the previous commit, no? Applying up to patch 12 and building with DEVELOPER=1 gets a warning.
Ah, good catch. Will fix.
Patrick
Re: [PATCH v2 13/21] config: plug various memory leaks
Quoted 8 lines · Patrick Steinhardt
Indeed, I had the exact same observation. I've already got a patch series that enables
-Wwrite-stringsand that adapts our codebase to compile cleanly with it. I'll send that series once this one here has landed.So my proposal would be to leave this patch as-is for the time being, but revisit it once both patch series have landed. Does that work for you?
Yeah, I can certainly live with that. One of my biggest concerns was that your patch would invalidate the investigation and work I had already done, and I'd fail to follow up. But if you are putting it onto your todo list, then that is even less work for me. ;)
-Peff
PATCH v2 14/21 builtin/credential: clear credential before exit
In reply to Patrick SteinhardtWe never release memory associated with struct credential. Fix this and mark the corresponding test as leak free.
--- builtin/credential.c | 2 ++ t/t0300-credentials.sh | 2 ++ 2 files changed, 4 insertions(+)
builtin/credential.c +2 -0
@@ -39,5 +39,7 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) }else {usage (usage_msg); } + +credential_clear (&c);return 0 ; }
t/t0300-credentials.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='basic credential helper tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-credential.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 15/21 commit-reach: fix memory leak in `ahead_behind()`
In reply to Patrick SteinhardtWe use a priority queue in ahead_behind() to compute the ahead/behind count for commits. We may not iterate through all commits part of that queue though in case all of its entries are stale. Consequently, as we never make the effort to release the remaining commits, we end up leaking bit arrays that we have allocated for each of the contained commits.
Plug this leak and mark the corresponding test as leak free.
--- commit-reach.c | 4 ++++ t/t3203-branch-output.sh | 2 ++ 2 files changed, 6 insertions(+)
commit-reach.c +4 -0
@@ -1106,6 +1106,10 @@ void ahead_behind(struct repository *r,/* STALE is used here, PARENT2 is used by insert_no_dup(). */ repo_clear_commit_marks (r,PARENT2 |STALE ); +while (prio_queue_peek (&queue)) { +struct commit *c =prio_queue_get (&queue); +free_bit_array (c); + }clear_bit_arrays (&bit_arrays);clear_prio_queue (&queue); }
t/t3203-branch-output.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git branch display tests' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 16/21 submodule: fix leaking memory for submodule entries
In reply to Patrick SteinhardtIn free_one_config() we never end up freeing the url and ignore fields and thus leak memory. Fix those leaks and mark now-passing tests as leak free.
--- submodule-config.c | 2 ++ t/t1013-read-tree-submodule.sh | 1 + t/t2013-checkout-submodule.sh | 1 + t/t3007-ls-files-recurse-submodules.sh | 1 + t/t7112-reset-submodule.sh | 1 + 5 files changed, 6 insertions(+)
submodule-config.c +2 -0
@@ -91,6 +91,8 @@ static void free_one_config(struct submodule_entry *entry)free ((void *) entry->config->path);free ((void *) entry->config->name);free ((void *) entry->config->branch); +free ((void *) entry->config->url); +free ((void *) entry->config->ignore);free ((void *) entry->config->update_strategy.command);free (entry->config); }
t/t1013-read-tree-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='read-tree can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t2013-checkout-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='checkout can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3007-ls-files-recurse-submodules.sh +1 -0
@@ -6,6 +6,7 @@ This test verifies the recurse-submodules feature correctly lists files from submodules. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup directory structure and submodules' '
t/t7112-reset-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='reset can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 17/21 strvec: add functions to replace and remove strings
In reply to Patrick SteinhardtAdd two functions that allow to replace and remove strings contained in the strvec. This will be used by a subsequent commit that refactors git-mv(1).
While at it, add a bunch of unit tests that cover both old and new functionality.
--- Makefile | 1 + strvec.c | 20 +++ strvec.h | 13 ++ t/unit-tests/t-strvec.c | 269 ++++++++++++++++++++++++++++++++++++++++ t/unit-tests/test-lib.c | 13 ++ t/unit-tests/test-lib.h | 13 ++ 6 files changed, 329 insertions(+) create mode 100644 t/unit-tests/t-strvec.c
Makefile +1 -0
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/% UNIT_TEST_PROGRAMS += t-mem-pool UNIT_TEST_PROGRAMS += t-strbuf +UNIT_TEST_PROGRAMS += t-strvec UNIT_TEST_PROGRAMS += t-ctype UNIT_TEST_PROGRAMS += t-prio-queue UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
strvec.c +20 -0
@@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items)strvec_push (array, *items); } +const char *strvec_replace (struct strvec *array,size_t idx,const char *replacement) +{ +char *to_free; +if (idx >= array->nr) +BUG ("index outside of array boundary" ); + to_free = (char *) array->v[idx]; + array->v[idx] =xstrdup (replacement); +free (to_free); +return array->v[idx]; +} + +void strvec_remove (struct strvec *array,size_t idx) +{ +if (idx >= array->nr) +BUG ("index outside of array boundary" ); +free ((char *)array->v[idx]); +memmove (array->v + idx, array->v + idx +1 , (array->nr - idx) *sizeof (char *)); + array->nr--; +} +void strvec_pop (struct strvec *array) {if (!array->nr)
strvec.h +13 -0
@@ -64,6 +64,19 @@ void strvec_pushl(struct strvec *, ...);/* Push a null-terminated array of strings onto the end of the array. */ void strvec_pushv (struct strvec *,const char **); +/** +* Replace the value at the given index with a new value. The index must be +* valid. Returns a pointer to the inserted value. +*/ +const char *strvec_replace (struct strvec *array,size_t idx,const char *replacement); + +/* +* Remove the value at the given index. The remainder of the array will be +* moved to fill the resulting gap. The provided index must point into the +* array. +*/ +void strvec_remove (struct strvec *array,size_t idx); +/** * Remove the final element from the array. If there are no * elements in the array, do nothing.
t/unit-tests/t-strvec.c new file +269 -0
269 lines not on this page · raw message
t/unit-tests/test-lib.c +13 -0
@@ -318,6 +318,19 @@ int check_bool_loc(const char *loc, const char *check, int ok)union test__tmp test__tmp[2 ]; +int check_pointer_eq_loc (const char *loc,const char *check,int ok, +const void *a,const void *b) +{ +int ret =test_assert (loc, check, ok); + +if (!ret) { +test_msg (" left: %p" , a); +test_msg (" right: %p" , b); + } + +return ret; +} +int check_int_loc (const char *loc,const char *check,int ok,intmax_t a,intmax_t b) {
t/unit-tests/test-lib.h +13 -0
@@ -75,6 +75,18 @@ int test_assert(const char *location, const char *check, int ok); check_bool_loc(TEST_LOCATION(), #x, x)int check_bool_loc (const char *loc,const char *check,int ok); +/* +* Compare two integers. Prints a message with the two values if the +* comparison fails. NB this is not thread safe. +*/ +#define check_pointer_eq (a, b) \ + (test__tmp[0].p = (a), test__tmp[1].p = (b), \ + check_pointer_eq_loc(TEST_LOCATION(), #a" == "#b, \ + test__tmp[0].p == test__tmp[1].p, \ + test__tmp[0].p, test__tmp[1].p)) +int check_pointer_eq_loc (const char *loc,const char *check,int ok, +const void *a,const void *b); +/* * Compare two integers. Prints a message with the two values if the * comparison fails. NB this is not thread safe.
@@ -136,6 +148,7 @@ union test__tmp {intmax_t i;uintmax_t u;char c; +const void *p; };extern union test__tmp test__tmp[2 ];
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 18/21 builtin/mv: refactor `add_slash()` to always return allocated strings
In reply to Patrick SteinhardtThe add_slash() function will only conditionally return an allocated string when the passed-in string did not yet have a trailing slash. This makes the memory ownership harder to track than really necessary.
It's dubious whether this optimization really buys us all that much. The number of times we execute this function is bounded by the number of arguments to git-mv(1), so in the typical case we may end up saving an allocation or two.
Simplify the code to unconditionally return allocated strings.
--- builtin/mv.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-)
builtin/mv.c +20 -18
@@ -76,7 +76,7 @@ static const char **internal_prefix_pathspec(const char *prefix,return result; } -static const char *add_slash (const char *path) +static char *add_slash (const char *path) {size_t len =strlen (path);if (len && path[len -1 ] !='/' ) {
@@ -86,7 +86,7 @@ static const char *add_slash(const char *path) with_slash[len] =0 ;return with_slash; } -return path; +return xstrdup (path); }#define SUBMODULE_WITH_GITDIR ((const char *)1)
@@ -111,7 +111,7 @@ static void prepare_move_submodule(const char *src, int first,static int index_range_of_same_dir (const char *src,int length,int *first_p,int *last_p) { -const char *src_w_slash =add_slash (src); +char *src_w_slash =add_slash (src);int first, last, len_w_slash = length +1 ; first =index_name_pos (the_repository->index, src_w_slash, len_w_slash);
@@ -124,8 +124,8 @@ static int index_range_of_same_dir(const char *src, int length,if (strncmp (path, src_w_slash, len_w_slash))break ; } -if (src_w_slash != src) -free ((char *)src_w_slash); + +free (src_w_slash); *first_p = first; *last_p = last;return last - first;
@@ -141,7 +141,7 @@ static int index_range_of_same_dir(const char *src, int length,static int empty_dir_has_sparse_contents (const char *name) {int ret =0 ; -const char *with_slash =add_slash (name); +char *with_slash =add_slash (name);int length =strlen (with_slash);int pos =index_name_pos (the_repository->index, with_slash, length);
@@ -159,8 +159,7 @@ static int empty_dir_has_sparse_contents(const char *name) } free_return: -if (with_slash != name) -free ((char *)with_slash); +free (with_slash);return ret; }
@@ -178,7 +177,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_END(), };const char **source, **destination, **dest_path, **submodule_gitfile; -const char *dst_w_slash; +char *dst_w_slash =NULL ;const char **src_dir =NULL ;int src_dir_nr =0 , src_dir_alloc =0 ;struct strbuf a_src_dir =STRBUF_INIT ;
@@ -243,10 +242,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dst_mode =SPARSE ; } } -if (dst_w_slash != dest_path[0 ]) { -free ((char *)dst_w_slash); - dst_w_slash =NULL ; - }/* Checking */ for (i =0 ; i < argc; i++) {
@@ -265,12 +260,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix) pos =index_name_pos (the_repository->index, src, length);if (pos <0 ) { -const char *src_w_slash =add_slash (src); +char *src_w_slash =add_slash (src);if (!path_in_sparse_checkout (src_w_slash, the_repository->index) &&empty_dir_has_sparse_contents (src)) { +free (src_w_slash); modes[i] |=SKIP_WORKTREE_DIR ; goto dir_check; } +free (src_w_slash);/* only error if existence is expected. */ if (!(modes[i] &SPARSE )) bad =_ ("bad source" );
@@ -310,7 +307,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dir_check:if (S_ISDIR (st.st_mode)) { -int j, dst_len, n; +char *dst_with_slash; +size_t dst_with_slash_len; +int j, n;int first =index_name_pos (the_repository->index, src, length), last;if (first >=0 ) {
@@ -335,19 +334,21 @@ int cmd_mv(int argc, const char **argv, const char *prefix)REALLOC_ARRAY (modes, n);REALLOC_ARRAY (submodule_gitfile, n); - dst =add_slash (dst); - dst_len =strlen (dst); + dst_with_slash =add_slash (dst); + dst_with_slash_len =strlen (dst_with_slash);for (j =0 ; j < last - first; j++) {const struct cache_entry *ce = the_repository->index->cache[first + j];const char *path = ce->name; source[argc + j] = path; destination[argc + j] = -prefix_path (dst, dst_len, path + length +1 ); +prefix_path (dst_with_slash, dst_with_slash_len, path + length +1 );memset (modes + argc + j,0 ,sizeof (enum update_mode )); modes[argc + j] |=ce_skip_worktree (ce) ?SPARSE :INDEX ; submodule_gitfile[argc + j] =NULL ; } + +free (dst_with_slash); argc += last - first; goto act_on_entry; }
@@ -565,6 +566,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED))die (_ ("Unable to write new index file" )); +free (dst_w_slash);string_list_clear (&src_for_dst,0 );string_list_clear (&dirty_paths,0 );UNLEAK (source);
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 19/21 builtin/mv duplicate string list memory
In reply to Patrick Steinhardtmakes the next patch easier, where we will migrate to the paths being owned by a strvec. given that we are talking about command line parameters here it's also not like we have tons of allocations that this would save
while at it, fix a memory leak
--- builtin/mv.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-)
builtin/mv.c +13 -6
@@ -183,11 +183,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)struct strbuf a_src_dir =STRBUF_INIT ;enum update_mode *modes, dst_mode =0 ;struct stat st, dest_st; -struct string_list src_for_dst =STRING_LIST_INIT_NODUP ; +struct string_list src_for_dst =STRING_LIST_INIT_DUP ;struct lock_file lock_file =LOCK_INIT ;struct cache_entry *ce; -struct string_list only_match_skip_worktree =STRING_LIST_INIT_NODUP ; -struct string_list dirty_paths =STRING_LIST_INIT_NODUP ; +struct string_list only_match_skip_worktree =STRING_LIST_INIT_DUP ; +struct string_list dirty_paths =STRING_LIST_INIT_DUP ; +int ret;git_config (git_default_config,NULL );
@@ -440,8 +441,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)if (only_match_skip_worktree.nr) {advise_on_updating_sparse_paths (&only_match_skip_worktree); -if (!ignore_errors) -return 1 ; +if (!ignore_errors) { + ret =1 ; + goto out; + } }for (i =0 ; i < argc; i++) {
@@ -566,12 +569,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix) COMMIT_LOCK | SKIP_IF_UNCHANGED))die (_ ("Unable to write new index file" )); + ret =0 ; + +out:free (dst_w_slash);string_list_clear (&src_for_dst,0 );string_list_clear (&dirty_paths,0 ); +string_list_clear (&only_match_skip_worktree,0 );UNLEAK (source);UNLEAK (dest_path);free (submodule_gitfile);free (modes); -return 0 ; +return ret; }
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 20/21 builtin/mv: refactor to use `struct strvec`
In reply to Patrick SteinhardtMemory allocation patterns in git-mv(1) are extremely hard to follow:
We copy around string pointers into manually-managed arrays, some of which alias each other, but only sometimes, while we also drop some of those strings at other times without ever daring to free them.
While this may be my own subjective feeling, it seems like others have given up as the code has multiple calls to UNLEAK(). These are not sufficient though, and git-mv(1) is still leaking all over the place even with them.
Refactor the code to instead track strings in struct strvec. While this has the effect of effectively duplicating some of the strings without an actual need, it is way easier to reason about and fixes all of the aliasing of memory that has been going on. It allows us to get rid of the UNLEAK() calls and also fixes leaks that those calls did not paper over.
Mark tests which are now leak-free accordingly.
--- builtin/mv.c | 125 +++++++++++------------ t/t4001-diff-rename.sh | 4 +- t/t4043-diff-rename-binary.sh | 1 + t/t4120-apply-popt.sh | 1 + t/t6400-merge-df.sh | 1 + t/t6412-merge-large-rename.sh | 1 + t/t6426-merge-skip-unneeded-updates.sh | 1 + t/t6429-merge-sequence-rename-caching.sh | 1 + 8 files changed, 68 insertions(+), 67 deletions(-)
builtin/mv.c +60 -65
@@ -20,6 +20,7 @@#include "read-cache-ll.h" #include "repository.h" #include "setup.h" +#include "strvec.h" #include "submodule.h" #include "entry.h"
@@ -38,42 +39,32 @@ enum update_mode {#define DUP_BASENAME 1#define KEEP_TRAILING_SLASH 2 -static const char **internal_prefix_pathspec (const char *prefix, -const char **pathspec, -int count,unsigned flags) +static void internal_prefix_pathspec (struct strvec *out, +const char *prefix, +const char **pathspec, +int count,unsigned flags) { -int i; -const char **result;int prefixlen = prefix ?strlen (prefix) :0 ; -ALLOC_ARRAY (result, count +1 );/* Create an intermediate copy of the pathspec based on the flags */ -for (i =0 ; i < count; i++) { -int length =strlen (pathspec[i]); -int to_copy = length; -char *it; +for (int i =0 ; i < count; i++) { +size_t length =strlen (pathspec[i]); +size_t to_copy = length; +const char *maybe_basename; +char *trimmed, *prefixed_path; +while (!(flags &KEEP_TRAILING_SLASH ) && to_copy >0 &&is_dir_sep (pathspec[i][to_copy -1 ])) to_copy--; - it =xmemdupz (pathspec[i], to_copy); -if (flags &DUP_BASENAME ) { - result[i] =xstrdup (basename (it)); -free (it); - }else { - result[i] = it; - } - } - result[count] =NULL ; + trimmed =xmemdupz (pathspec[i], to_copy); + maybe_basename = (flags &DUP_BASENAME ) ?basename (trimmed) : trimmed; + prefixed_path =prefix_path (prefix, prefixlen, maybe_basename); +strvec_push (out, prefixed_path); -/* Prefix the pathspec and free the old intermediate strings */ -for (i =0 ; i < count; i++) { -const char *match =prefix_path (prefix, prefixlen, result[i]); -free ((char *) result[i]); - result[i] = match; +free (prefixed_path); +free (trimmed); } - -return result; }static char *add_slash (const char *path)
@@ -176,7 +167,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)OPT_BOOL (0 ,"sparse" , &ignore_sparse,N_ ("allow updating entries outside of the sparse-checkout cone" )), OPT_END(), }; -const char **source, **destination, **dest_path, **submodule_gitfile; +struct strvec sources =STRVEC_INIT ; +struct strvec dest_paths =STRVEC_INIT ; +struct strvec destinations =STRVEC_INIT ; +const char **submodule_gitfile;char *dst_w_slash =NULL ;const char **src_dir =NULL ;int src_dir_nr =0 , src_dir_alloc =0 ;
@@ -201,7 +195,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)if (repo_read_index (the_repository) <0 )die (_ ("index file corrupt" )); - source =internal_prefix_pathspec (prefix, argv, argc,0 ); +internal_prefix_pathspec (&sources, prefix, argv, argc,0 );CALLOC_ARRAY (modes, argc);/*
@@ -212,41 +206,39 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags =KEEP_TRAILING_SLASH ;if (argc ==1 &&is_directory (argv[0 ]) && !is_directory (argv[1 ])) flags =0 ; - dest_path =internal_prefix_pathspec (prefix, argv + argc,1 , flags); - dst_w_slash =add_slash (dest_path[0 ]); +internal_prefix_pathspec (&dest_paths, prefix, argv + argc,1 , flags); + dst_w_slash =add_slash (dest_paths.v[0 ]); submodule_gitfile =xcalloc (argc,sizeof (char *)); -if (dest_path[0 ][0 ] =='\0' ) +if (dest_paths.v[0 ][0 ] =='\0' )/* special case: "." was normalized to "" */ - destination =internal_prefix_pathspec (dest_path[0 ], argv, argc,DUP_BASENAME ); -else if (!lstat (dest_path[0 ], &st) && -S_ISDIR (st.st_mode)) { - destination =internal_prefix_pathspec (dst_w_slash, argv, argc,DUP_BASENAME ); +internal_prefix_pathspec (&destinations, dest_paths.v[0 ], argv, argc,DUP_BASENAME ); +else if (!lstat (dest_paths.v[0 ], &st) &&S_ISDIR (st.st_mode)) { +internal_prefix_pathspec (&destinations, dst_w_slash, argv, argc,DUP_BASENAME ); + }else if (!path_in_sparse_checkout (dst_w_slash, the_repository->index) && +empty_dir_has_sparse_contents (dst_w_slash)) { +internal_prefix_pathspec (&destinations, dst_w_slash, argv, argc,DUP_BASENAME ); + dst_mode =SKIP_WORKTREE_DIR ; + }else if (argc !=1 ) { +die (_ ("destination '%s' is not a directory" ), dest_paths.v[0 ]); }else { -if (!path_in_sparse_checkout (dst_w_slash, the_repository->index) && -empty_dir_has_sparse_contents (dst_w_slash)) { - destination =internal_prefix_pathspec (dst_w_slash, argv, argc,DUP_BASENAME ); - dst_mode =SKIP_WORKTREE_DIR ; - }else if (argc !=1 ) { -die (_ ("destination '%s' is not a directory" ), dest_path[0 ]); - }else { - destination = dest_path; -/* -* <destination> is a file outside of sparse-checkout -* cone. Insist on cone mode here for backward -* compatibility. We don't want dst_mode to be assigned -* for a file when the repo is using no-cone mode (which -* is deprecated at this point) sparse-checkout. As -* SPARSE here is only considering cone-mode situation. -*/ -if (!path_in_cone_mode_sparse_checkout (destination[0 ], the_repository->index)) - dst_mode =SPARSE ; - } +strvec_pushv (&destinations, dest_paths.v); + +/* +* <destination> is a file outside of sparse-checkout +* cone. Insist on cone mode here for backward +* compatibility. We don't want dst_mode to be assigned +* for a file when the repo is using no-cone mode (which +* is deprecated at this point) sparse-checkout. As +* SPARSE here is only considering cone-mode situation. +*/ +if (!path_in_cone_mode_sparse_checkout (destinations.v[0 ], the_repository->index)) + dst_mode =SPARSE ; }/* Checking */ for (i =0 ; i < argc; i++) { -const char *src = source[i], *dst = destination[i]; +const char *src = sources.v[i], *dst = destinations.v[i];int length;const char *bad =NULL ;int skip_sparse =0 ;
@@ -330,8 +322,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) src_dir[src_dir_nr++] = src; n = argc + last - first; -REALLOC_ARRAY (source, n); -REALLOC_ARRAY (destination, n);REALLOC_ARRAY (modes, n);REALLOC_ARRAY (submodule_gitfile, n);
@@ -341,12 +331,16 @@ int cmd_mv(int argc, const char **argv, const char *prefix)for (j =0 ; j < last - first; j++) {const struct cache_entry *ce = the_repository->index->cache[first + j];const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = -prefix_path (dst_with_slash, dst_with_slash_len, path + length +1 ); +char *prefixed_path =prefix_path (dst_with_slash, dst_with_slash_len, path + length +1 ); + +strvec_push (&sources, path); +strvec_push (&destinations, prefixed_path); +memset (modes + argc + j,0 ,sizeof (enum update_mode )); modes[argc + j] |=ce_skip_worktree (ce) ?SPARSE :INDEX ; submodule_gitfile[argc + j] =NULL ; + +free (prefixed_path); }free (dst_with_slash);
@@ -430,8 +424,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) remove_entry:if (--argc >0 ) {int n = argc - i; -MOVE_ARRAY (source + i, source + i +1 , n); -MOVE_ARRAY (destination + i, destination + i +1 , n); +strvec_remove (&sources, i); +strvec_remove (&destinations, i);MOVE_ARRAY (modes + i, modes + i +1 , n);MOVE_ARRAY (submodule_gitfile + i, submodule_gitfile + i +1 , n);
@@ -448,7 +442,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) }for (i =0 ; i < argc; i++) { -const char *src = source[i], *dst = destination[i]; +const char *src = sources.v[i], *dst = destinations.v[i];enum update_mode mode = modes[i];int pos;int sparse_and_dirty =0 ;
@@ -576,8 +570,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix)string_list_clear (&src_for_dst,0 );string_list_clear (&dirty_paths,0 );string_list_clear (&only_match_skip_worktree,0 ); -UNLEAK (source); -UNLEAK (dest_path); +strvec_clear (&sources); +strvec_clear (&dest_paths); +strvec_clear (&destinations);free (submodule_gitfile);free (modes);return ret;
t/t4001-diff-rename.sh +2 -2
@@ -3,9 +3,9 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Test rename detection in diff engine. +test_description='Test rename detection in diff engine.' -' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff.sh
t/t4043-diff-rename-binary.sh +1 -0
@@ -5,6 +5,7 @@ test_description='Move a binary file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh
t/t4120-apply-popt.sh +1 -0
@@ -5,6 +5,7 @@ test_description='git apply -p handling.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup '
t/t6400-merge-df.sh +1 -0
@@ -7,6 +7,7 @@ test_description='Test merge with directory/file conflicts' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare repository' '
t/t6412-merge-large-rename.sh +1 -0
@@ -4,6 +4,7 @@ test_description='merging with large rename matrix' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh count() {
t/t6426-merge-skip-unneeded-updates.sh +1 -0
@@ -22,6 +22,7 @@ test_description="merge cases" # underscore notation is to differentiate different # files that might be renamed into each other's paths.) +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh
t/t6429-merge-sequence-rename-caching.sh +1 -0
@@ -2,6 +2,7 @@ test_description="remember regular & dir renames in sequence of merges" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh #
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
PATCH v2 21/21 builtin/mv: fix leaks for submodule gitfile paths
In reply to Patrick SteinhardtSimilar to the preceding commit, we have effectively given tracking memory ownership of submodule gitfile paths. Refactor the code to start tracking allocated strings in a separate struct strvec such that we can easily plug those leaks. Mark now-passing tests as leak free.
Note that ideally, we wouldn't require two separate data structures to track those paths. But we do need to store NULL pointers for the gitfile paths such that we can indicate that its corresponding entries in the other arrays do not have such a path at all. And given that struct strvecs cannot store NULL pointers we cannot use them to store this information.
There is another small gotcha that is easy to miss: you may be wondering why we don't want to store SUBMODULE_WITH_GITDIR in the strvec. This is because this is a mere sentinel value and not actually a string at all.
--- builtin/mv.c | 44 +++++++++++++---------- t/t4059-diff-submodule-not-initialized.sh | 1 + t/t7001-mv.sh | 2 ++ t/t7417-submodule-path-url.sh | 1 + t/t7421-submodule-summary-add.sh | 1 + 5 files changed, 30 insertions(+), 19 deletions(-)
builtin/mv.c +25 -19
@@ -82,21 +82,23 @@ static char *add_slash(const char *path)#define SUBMODULE_WITH_GITDIR ((const char *)1) -static void prepare_move_submodule (const char *src,int first, -const char **submodule_gitfile) +static const char *submodule_gitfile_path (const char *src,int first) {struct strbuf submodule_dotgit =STRBUF_INIT ; +const char *path; +if (!S_ISGITLINK (the_repository->index->cache[first]->ce_mode))die (_ ("Directory %s is in index and no submodule?" ), src);if (!is_staging_gitmodules_ok (the_repository->index))die (_ ("Please stage your changes to .gitmodules or stash them to proceed" )); +strbuf_addf (&submodule_dotgit,"%s/.git" , src); - *submodule_gitfile =read_gitfile (submodule_dotgit.buf); -if (*submodule_gitfile) - *submodule_gitfile =xstrdup (*submodule_gitfile); -else - *submodule_gitfile =SUBMODULE_WITH_GITDIR ; + + path =read_gitfile (submodule_dotgit.buf);strbuf_release (&submodule_dotgit); +if (path) +return path; +return SUBMODULE_WITH_GITDIR ; } static int index_range_of_same_dir(const char *src, int length,
@@ -170,7 +172,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)struct strvec sources =STRVEC_INIT ;struct strvec dest_paths =STRVEC_INIT ;struct strvec destinations =STRVEC_INIT ; -const char **submodule_gitfile; +struct strvec submodule_gitfiles_to_free =STRVEC_INIT ; +const char **submodule_gitfiles;char *dst_w_slash =NULL ;const char **src_dir =NULL ;int src_dir_nr =0 , src_dir_alloc =0 ;
@@ -208,7 +211,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) flags =0 ;internal_prefix_pathspec (&dest_paths, prefix, argv + argc,1 , flags); dst_w_slash =add_slash (dest_paths.v[0 ]); - submodule_gitfile =xcalloc (argc,sizeof (char *)); + submodule_gitfiles =xcalloc (argc,sizeof (char *));if (dest_paths.v[0 ][0 ] =='\0' )/* special case: "." was normalized to "" */
@@ -306,8 +309,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)int first =index_name_pos (the_repository->index, src, length), last;if (first >=0 ) { -prepare_move_submodule (src, first, - submodule_gitfile + i); +const char *path =submodule_gitfile_path (src, first); +if (path !=SUBMODULE_WITH_GITDIR ) + path =strvec_push (&submodule_gitfiles_to_free, path); + submodule_gitfiles[i] = path; goto act_on_entry; }else if (index_range_of_same_dir (src, length, &first, &last) <1 ) {
@@ -323,7 +328,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) n = argc + last - first;REALLOC_ARRAY (modes, n); -REALLOC_ARRAY (submodule_gitfile, n); +REALLOC_ARRAY (submodule_gitfiles, n); dst_with_slash =add_slash (dst); dst_with_slash_len =strlen (dst_with_slash);
@@ -338,7 +343,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)memset (modes + argc + j,0 ,sizeof (enum update_mode )); modes[argc + j] |=ce_skip_worktree (ce) ?SPARSE :INDEX ; - submodule_gitfile[argc + j] =NULL ; + submodule_gitfiles[argc + j] =NULL ;free (prefixed_path); }
@@ -427,8 +432,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)strvec_remove (&sources, i);strvec_remove (&destinations, i);MOVE_ARRAY (modes + i, modes + i +1 , n); -MOVE_ARRAY (submodule_gitfile + i, - submodule_gitfile + i +1 , n); +MOVE_ARRAY (submodule_gitfiles + i, + submodule_gitfiles + i +1 , n); i--; } }
@@ -462,12 +467,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)continue ;die_errno (_ ("renaming '%s' failed" ), src); } -if (submodule_gitfile[i]) { +if (submodule_gitfiles[i]) {if (!update_path_in_gitmodules (src, dst)) gitmodules_modified =1 ; -if (submodule_gitfile[i] !=SUBMODULE_WITH_GITDIR ) +if (submodule_gitfiles[i] !=SUBMODULE_WITH_GITDIR )connect_work_tree_and_git_dir (dst, - submodule_gitfile[i], + submodule_gitfiles[i],1 ); }
@@ -573,7 +578,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)strvec_clear (&sources);strvec_clear (&dest_paths);strvec_clear (&destinations); -free (submodule_gitfile); +strvec_clear (&submodule_gitfiles_to_free); +free (submodule_gitfiles);free (modes);return ret; }
t/t4059-diff-submodule-not-initialized.sh +1 -0
@@ -9,6 +9,7 @@ This test tries to verify that add_submodule_odb works when the submodule was initialized previously but the checkout has since been removed. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Tested non-UTF-8 encoding
t/t7001-mv.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git mv in subdirs' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff-data.sh
t/t7417-submodule-path-url.sh +1 -0
@@ -4,6 +4,7 @@ test_description='check handling of .gitmodule path with dash' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
t/t7421-submodule-summary-add.sh +1 -0
@@ -10,6 +10,7 @@ while making sure to add submodules using `git submodule add` instead of `git add` as done in t7401. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
Signature · 1 line
2.45.1.216.g4365c6fcf9.dirty
Quoted 7 lines · Patrick Steinhardt
this is the second version of my patch series that fixes various memory leaks in Git. Changes compared to v1:
- t4153 and t7006 aren't marked as passing anymore. I thought they pass because most of these tests were skipped because of a missing TTY prerequisite both on my local machine, but also in our CI.
Quoted 8 lines
- Add another patch to install the Perl IO:Pty module on Alpine and Ubuntu. This fulfills the TTY prerequisite and thus surfaces the memory leaks in both of the above tests.
- Add another unit test for strvec that exercise replacing a string in the strvec with a copy of itself.
- A bunch of commit message improvements.
Looking very good. This seems to reveal existing leaks when merged to 'seen'; other topics that are not in 'master' may be introducing these leaks. I'll see if a trial merge to 'next' is leak-free (in which case I'll merge it down to 'next') or there are other topics in 'next' that are leaking (in which case we'll play by ear---either mark the tests again as non-leak-free, or plug the leak if it seems trivial).
https://github.com/git/git/actions/runs/9231313414/job/25400998823
says t1400-update-ref has many "stdin symref-update" things are failing.
Also
https://github.com/git/git/actions/runs/9231313414/job/25401102951
shows that t1460-refs-migrate fails on Windows.
Thanks.
Quoted 26 lines · Junio C Hamano
Quoted 7 lines · Patrick Steinhardt
this is the second version of my patch series that fixes various memory leaks in Git. Changes compared to v1:
- t4153 and t7006 aren't marked as passing anymore. I thought they pass because most of these tests were skipped because of a missing TTY prerequisite both on my local machine, but also in our CI.
Quoted 8 lines
- Add another patch to install the Perl IO:Pty module on Alpine and Ubuntu. This fulfills the TTY prerequisite and thus surfaces the memory leaks in both of the above tests.
- Add another unit test for strvec that exercise replacing a string in the strvec with a copy of itself.
- A bunch of commit message improvements.
Looking very good. This seems to reveal existing leaks when merged to 'seen'; other topics that are not in 'master' may be introducing these leaks. I'll see if a trial merge to 'next' is leak-free (in which case I'll merge it down to 'next') or there are other topics in 'next' that are leaking (in which case we'll play by ear---either mark the tests again as non-leak-free, or plug the leak if it seems trivial).
Quoted 4 lines
https://github.com/git/git/actions/runs/9231313414/job/25400998823
says t1400-update-ref has many "stdin symref-update" things are failing.
Indeed. The following diff fixes the leak:
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 7d2a419230..e54be9c429 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -130,6 +130,8 @@ static char *parse_next_arg(const char **next)
if (arg.len)
return strbuf_detach(&arg, NULL);
+
+ strbuf_release(&arg);
return NULL;
}Karthik is out of office this week, so you may want to add this as a "SQUASH???" commit on top of his topic branch to make "seen" pass.
Quoted 5 lines
Also
https://github.com/git/git/actions/runs/9231313414/job/25401102951
shows that t1460-refs-migrate fails on Windows.
Hm, this one is curious. There are no leak logs at all, and the exit code is 139. Might be SIGSEGV, indicating that something else is going on here than a memory leak.
I'll investigate.
Patrick
Quoted 16 lines · Patrick Steinhardt
Indeed. The following diff fixes the leak:
diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 7d2a419230..e54be9c429 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -130,6 +130,8 @@ static char *parse_next_arg(const char **next) if (arg.len) return strbuf_detach(&arg, NULL); + + strbuf_release(&arg); return NULL; }
Quoted 2 lines
Karthik is out of office this week, so you may want to add this as a "SQUASH???" commit on top of his topic branch to make "seen" pass.
Alright. Thanks.
Quoted 9 lines
Quoted 5 lines
Also
https://github.com/git/git/actions/runs/9231313414/job/25401102951
shows that t1460-refs-migrate fails on Windows.
Hm, this one is curious. There are no leak logs at all, and the exit code is 139. Might be SIGSEGV, indicating that something else is going on here than a memory leak.
Sorry, I wasn't clear enough. I do not suspect this is about leaks (and the failing job on Windows is not about leaks).
Quoted 18 lines · Junio C Hamano
Quoted 16 lines · Patrick Steinhardt
Indeed. The following diff fixes the leak:
diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 7d2a419230..e54be9c429 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -130,6 +130,8 @@ static char *parse_next_arg(const char **next) if (arg.len) return strbuf_detach(&arg, NULL); + + strbuf_release(&arg); return NULL; }
Quoted 4 lines
Quoted 2 lines
Karthik is out of office this week, so you may want to add this as a "SQUASH???" commit on top of his topic branch to make "seen" pass.
Alright. Thanks.
But I am curious. How would this leak even be possible?
arg.len==0 and arg.buf != strbuf_slopbuf would mean that somebody who touched arg (either parse_arg() call or strbuf_addstr() call) allocated arg.buf but ended up not adding any byte (or added some bytes but at the end rewound with "arg.len = 0").
Ahh, strbuf_addstr() does an unconditional strbuf_grow(). I wonder if the following patch is a good idea in general, then (not as a replacement for your fix, but as a general code hygiene---unless you know you need more memory, don't allocate).
I have a suspicion that this is optimizing for a wrong case, though, so I'd probably refrain from committing it. If the caller often has an empty string, and if the caller feels like it is worth checking to omit such an extra "opportunistic" allocation, the caller can do so.
strbuf.c | 8 ++++++++ 1 file changed, 8 insertions(+)
strbuf.c +8 -0
@@ -308,6 +308,8 @@ void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)void strbuf_add (struct strbuf *sb,const void *data,size_t len) { +if (!len) +return ;strbuf_grow (sb, len);memcpy (sb->buf + sb->len, data, len);strbuf_setlen (sb, sb->len + len);
@@ -315,6 +317,8 @@ void strbuf_add(struct strbuf *sb, const void *data, size_t len)void strbuf_addbuf (struct strbuf *sb,const struct strbuf *sb2) { +if (!sb2->len) +return ;strbuf_grow (sb, sb2->len);memcpy (sb->buf + sb->len, sb2->buf, sb2->len);strbuf_setlen (sb, sb->len + sb2->len);
@@ -337,6 +341,8 @@ const char *strbuf_join_argv(struct strbuf *buf,void strbuf_addchars (struct strbuf *sb,int c,size_t n) { +if (!n) +return ;strbuf_grow (sb, n);memset (sb->buf + sb->len, c, n);strbuf_setlen (sb, sb->len + n);
@@ -805,6 +811,8 @@ void strbuf_addstr_xml_quoted(struct strbuf *buf, const char *s)static void strbuf_add_urlencode (struct strbuf *sb,const char *s,size_t len,char_predicate allow_unencoded_fn) { +if (!len) +return ;strbuf_grow (sb, len);while (len--) {char ch = *s++;
Quoted 5 lines · Junio C Hamano
Quoted 4 lines · Patrick Steinhardt
Quoted 4 lines
Also
https://github.com/git/git/actions/runs/9231313414/job/25401102951
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
shows that t1460-refs-migrate fails on Windows.
Hm, this one is curious. There are no leak logs at all, and the exit code is 139. Might be SIGSEGV, indicating that something else is going on here than a memory leak.
Sorry, I wasn't clear enough. I do not suspect this is about leaks (and the failing job on Windows is not about leaks).
I figured this one out now: Windows of course refuses to remove some of the files because they are kept open. This shouldn't lead to a segfault itself. But we call free_ref_cache() on a partially initialized files ref store, nad that function didn't handle the case when it is being passed a NULL pointer.
I've addressed this by releasing the reftable ref store before trying to remove it from disk so that all files are being closed. But it also surfaced another bug: our worktree code creates a copy of the main ref store by accident because we set up wt->is_current _after_ we have called get_worktree_ref_store().
I fixed all of this and will send a new version in a bit.
Patrick
Quoted 48 lines · Patrick Steinhardt
Quoted 26 lines · Junio C Hamano
Quoted 7 lines · Patrick Steinhardt
this is the second version of my patch series that fixes various memory leaks in Git. Changes compared to v1:
- t4153 and t7006 aren't marked as passing anymore. I thought they pass because most of these tests were skipped because of a missing TTY prerequisite both on my local machine, but also in our CI.
Quoted 8 lines
- Add another patch to install the Perl IO:Pty module on Alpine and Ubuntu. This fulfills the TTY prerequisite and thus surfaces the memory leaks in both of the above tests.
- Add another unit test for strvec that exercise replacing a string in the strvec with a copy of itself.
- A bunch of commit message improvements.
Looking very good. This seems to reveal existing leaks when merged to 'seen'; other topics that are not in 'master' may be introducing these leaks. I'll see if a trial merge to 'next' is leak-free (in which case I'll merge it down to 'next') or there are other topics in 'next' that are leaking (in which case we'll play by ear---either mark the tests again as non-leak-free, or plug the leak if it seems trivial).
Quoted 4 lines
https://github.com/git/git/actions/runs/9231313414/job/25400998823
says t1400-update-ref has many "stdin symref-update" things are failing.
Indeed. The following diff fixes the leak:
diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 7d2a419230..e54be9c429 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -130,6 +130,8 @@ static char *parse_next_arg(const char **next) if (arg.len) return strbuf_detach(&arg, NULL); + + strbuf_release(&arg); return NULL; }
Quoted 3 lines
Karthik is out of office this week, so you may want to add this as a "SQUASH???" commit on top of his topic branch to make "seen" pass.
Thanks Patrick. Indeed I'm a bit slow on my responses, since I'm on vacation, but yeah, I too came about adding this as a fix.
I'll mostly check for all tests and send a new version based on this series soon.
Quoted 13 lines
Quoted 5 lines
Also
https://github.com/git/git/actions/runs/9231313414/job/25401102951
shows that t1460-refs-migrate fails on Windows.
Hm, this one is curious. There are no leak logs at all, and the exit code is 139. Might be SIGSEGV, indicating that something else is going on here than a memory leak.
I'll investigate.
Patrick
v3 cover Various memory leak fixes
In reply to Patrick SteinhardtHi,
this is the third version of my patch series that fixes various memory leaks throughout Git.
Changes compared to v2:
- Add a missing SOB.
- Fix some typos in commit messages.
- Explain more thoroughly why appendwholeline() may leak even on error.
- Move removal of const char ** cast to the correct commit.
Thanks!
Patrick
Patrick Steinhardt (21):
ci: add missing dependency for TTY prereq
t: mark a bunch of tests as leak-free
transport-helper: fix leaking helper name
strbuf: fix leak when appendwholeline() fails with EOF
checkout: clarify memory ownership in unique_tracking_name()
http: refactor code to clarify memory ownership
config: clarify memory ownership in git_config_pathname()
diff: refactor code to clarify memory ownership of prefixes
convert: refactor code to clarify ownership of
check_roundtrip_encoding
builtin/log: stop using globals for log config builtin/log: stop using globals for format config
config: clarify memory ownership in git_config_string()
config: plug various memory leaks
builtin/credential: clear credential before exit
commit-reach: fix memory leak in ahead_behind()
submodule: fix leaking memory for submodule entries
strvec: add functions to replace and remove strings builtin/mv: refactor add_slash() to always return allocated strings builtin/mv duplicate string list memory
builtin/mv: refactor to use struct strvec
builtin/mv: fix leaks for submodule gitfile paths
Makefile | 1 + alias.c | 6 +- attr.c | 2 +- attr.h | 2 +- builtin/blame.c | 2 +- builtin/checkout.c | 14 +- builtin/commit.c | 4 +- builtin/config.c | 2 +- builtin/credential.c | 2 + builtin/log.c | 708 ++++++++++-------- builtin/merge.c | 4 +- builtin/mv.c | 222 +++--- builtin/rebase.c | 2 +- builtin/receive-pack.c | 6 +- builtin/repack.c | 8 +- builtin/worktree.c | 20 +- checkout.c | 4 +- checkout.h | 6 +- ci/install-dependencies.sh | 4 +- commit-reach.c | 4 + config.c | 52 +- config.h | 10 +- convert.c | 30 +- convert.h | 2 +- delta-islands.c | 2 +- diff.c | 20 +- environment.c | 16 +- environment.h | 14 +- fetch-pack.c | 4 +- fsck.c | 4 +- fsmonitor-settings.c | 5 +- gpg-interface.c | 6 +- http.c | 50 +- imap-send.c | 12 +- mailmap.c | 4 +- mailmap.h | 4 +- merge-ll.c | 6 +- pager.c | 2 +- pretty.c | 14 +- promisor-remote.h | 2 +- remote.c | 20 +- remote.h | 8 +- sequencer.c | 2 +- setup.c | 6 +- strbuf.c | 4 +- strvec.c | 20 + strvec.h | 13 + submodule-config.c | 2 + t/t0300-credentials.sh | 2 + t/t0411-clone-from-partial.sh | 1 + t/t0610-reftable-basics.sh | 1 + t/t0611-reftable-httpd.sh | 1 + t/t1013-read-tree-submodule.sh | 1 + t/t1306-xdg-files.sh | 1 + t/t1350-config-hooks-path.sh | 1 + t/t1400-update-ref.sh | 2 + t/t2013-checkout-submodule.sh | 1 + t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t2405-worktree-submodule.sh | 1 + t/t3007-ls-files-recurse-submodules.sh | 1 + t/t3203-branch-output.sh | 2 + t/t3415-rebase-autosquash.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4001-diff-rename.sh | 4 +- t/t4041-diff-submodule-option.sh | 1 + t/t4043-diff-rename-binary.sh | 1 + t/t4059-diff-submodule-not-initialized.sh | 1 + t/t4060-diff-submodule-option-diff-format.sh | 1 + t/t4120-apply-popt.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t4210-log-i18n.sh | 2 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + t/t6006-rev-list-format.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6400-merge-df.sh | 1 + t/t6412-merge-large-rename.sh | 1 + t/t6426-merge-skip-unneeded-updates.sh | 1 + t/t6429-merge-sequence-rename-caching.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + t/t7001-mv.sh | 2 + t/t7005-editor.sh | 1 + t/t7102-reset.sh | 1 + t/t7112-reset-submodule.sh | 1 + t/t7417-submodule-path-url.sh | 1 + t/t7421-submodule-summary-add.sh | 1 + t/t7423-submodule-symlinks.sh | 1 + t/t9129-git-svn-i18n-commitencoding.sh | 1 - t/t9139-git-svn-non-utf8-commitencoding.sh | 1 - t/t9200-git-cvsexportcommit.sh | 1 + t/t9401-git-cvsserver-crlf.sh | 1 + t/t9600-cvsimport.sh | 1 + t/t9601-cvsimport-vendor-branch.sh | 1 + t/t9602-cvsimport-branches-tags.sh | 1 + t/t9603-cvsimport-patchsets.sh | 2 + t/t9604-cvsimport-timestamps.sh | 2 + t/unit-tests/t-strvec.c | 269 +++++++ t/unit-tests/test-lib.c | 13 + t/unit-tests/test-lib.h | 13 + transport-helper.c | 6 +- transport.c | 1 + upload-pack.c | 2 +- userdiff.h | 12 +- 109 files changed, 1151 insertions(+), 586 deletions(-) create mode 100644 t/unit-tests/t-strvec.c
Range-diff against v2:
1: 857b8b14ce ! 1: ded220a06b ci: add missing dependency for TTY prereq
@@ Commit message
systems are missing this dependency and thus don't execute those tests at all. Fix this.
+ Signed-off-by: Patrick Steinhardt <[email protected]>
+
## ci/install-dependencies.sh ##
@@ ci/install-dependencies.sh: alpine-*)
apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \
2: ceade7dbba ! 2: f305e4bc46 t: mark a bunch of tests as leak-free
@@ Commit message
- t7423: Introduced via b20c10fd9b (t7423: add tests for symlinked submodule directories, 2024-01-28), passes since e8d0608944 (submodule: require the submodule path to contain directories only,
- 2024-03-26). The fix is not ovbiously related, but probably works
+ 2024-03-26). The fix is not obviously related, but probably works
because we now die early in many code paths.
- t9xxx: All of these are exercising CVS-related tooling and pass since at least Git v2.40. It's likely that these pass for a long
- time already, but nobody ever noticed because noone has CVS on their
- machine.
+ time already, but nobody ever noticed because Git developers do not
+ tend to have CVS on their machines.
Mark all of these tests as passing.
3: a96b5ac359 = 3: 9e946c1a83 transport-helper: fix leaking helper name 4: 9dd8709d1b ! 4: aa5cbd9d14 strbuf: fix leak when `appendwholeline()` fails with EOF
@@ Commit message
In strbuf_appendwholeline() we call strbuf_getwholeline() with a temporary buffer. In case the call returns an error we indicate this by
- returning EOF, but never release the temporary buffer. This can lead to
- a memory leak when the line has been partially filled. Fix this.
+ returning EOF, but never release the temporary buffer. This can cause a
+ leak though because strbuf_getwholeline() calls getline(3). Quoting
+ its documentation:
+
+ If *lineptr was set to NULL before the call, then the buffer
+ should be freed by the user program even on failure.
+
+ Consequently, the temporary buffer may hold allocated memory even when
+ the call to strbuf_getwholeline() fails.
+
+ Fix this by releasing the temporary buffer on error.
5: 6d4e9ce706 = 5: 4a6dd9b6a8 checkout: clarify memory ownership in `unique_tracking_name()`
6: 141cae2de1 = 6: fa91a3942e http: refactor code to clarify memory ownership
7: ff5e761e55 = 7: 88babf1abf config: clarify memory ownership in `git_config_pathname()`
8: afe69c7303 = 8: feec7e971f diff: refactor code to clarify memory ownership of prefixes
9: eb7fce55b0 = 9: dae00f1b63 convert: refactor code to clarify ownership of check_roundtrip_encoding
10: ee2fcf388c = 10: 02c5be27be builtin/log: stop using globals for log config
11: 3490ad3a02 = 11: eeba79678a builtin/log: stop using globals for format config
12: 6cfc28c7e2 ! 12: a4fafcd079 config: clarify memory ownership in `git_config_string()`
@@ config.c: int git_config_bool(const char *name, const char *value)
{
if (!value)
return config_error_nonbool(var);
+@@ config.c: static int git_default_core_config(const char *var, const char *value,
+
+ if (!strcmp(var, "core.checkroundtripencoding")) {
+ FREE_AND_NULL(check_roundtrip_encoding);
+- return git_config_string((const char **) &check_roundtrip_encoding, var, value);
++ return git_config_string(&check_roundtrip_encoding, var, value);
+ }
+
+ if (!strcmp(var, "core.notesref")) {
@@ config.c: int git_configset_get_string(struct config_set *set, const char *key, char **des
{
const char *value;
13: 70e8e26513 ! 13: 8b74dff678 config: plug various memory leaks
@@ config.c: static int git_default_core_config(const char *var, const char *value,
return git_config_pathname(&git_hooks_path, var, value);
}
-@@ config.c: static int git_default_core_config(const char *var, const char *value,
-
- if (!strcmp(var, "core.checkroundtripencoding")) {
- FREE_AND_NULL(check_roundtrip_encoding);
-- return git_config_string((const char **) &check_roundtrip_encoding, var, value);
-+ return git_config_string(&check_roundtrip_encoding, var, value);
- }
-
- if (!strcmp(var, "core.notesref")) {
@@ config.c: static int git_default_core_config(const char *var, const char *value,
return 0;
}
14: f1a1c43e76 = 14: 265665fe6c builtin/credential: clear credential before exit
15: 64b92156f8 = 15: b315a5bb5c commit-reach: fix memory leak in `ahead_behind()`
16: cd8a992f08 = 16: 7c75a94756 submodule: fix leaking memory for submodule entries
17: 128e2eaf7a = 17: 0f61ee9929 strvec: add functions to replace and remove strings
18: 1310b24fc2 = 18: 1830e2a568 builtin/mv: refactor `add_slash()` to always return allocated strings
19: d4fef9825a = 19: 9eeafac365 builtin/mv duplicate string list memory
20: 797cdb286a = 20: 48b9d3e343 builtin/mv: refactor to use `struct strvec`
21: 095469193c = 21: add7946446 builtin/mv: fix leaks for submodule gitfile pathsSignature · 1 line
2.45.1.246.gb9cfe4845c.dirty
PATCH v3 1/21 ci: add missing dependency for TTY prereq
In "t/lib-terminal.sh", we declare a lazy prerequisite for tests that require a TTY. The prerequisite uses a Perl script to figure out whether we do have a usable TTY or not and thus implicitly depends on the PERL prerequisite, as well. Furthermore though, the script requires another dependency that is easy to miss, namely on the IO::Pty module. If that module is not installed, then the script will exit early due to an reason unrelated to missing TTYs.
This easily leads to missing test coverage. But most importantly, our CI systems are missing this dependency and thus don't execute those tests at all. Fix this.
--- ci/install-dependencies.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
ci/install-dependencies.sh +2 -2
@@ -27,7 +27,7 @@ alpine-*) apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \ pcre2-dev python3 musl-libintl perl-utils ncurses \ apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ - bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null + bash cvs gnupg perl-cgi perl-dbd-sqlite perl-io-tty >/dev/null ;; fedora-*) dnf -yq update >/dev/null &&
@@ -42,7 +42,7 @@ ubuntu-*) language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \ make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ - libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ + libemail-valid-perl libio-pty-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE mkdir --parents "$CUSTOM_PATH"
Signature · 1 line
2.45.1.246.gb9cfe4845c.dirty
PATCH v3 2/21 t: mark a bunch of tests as leak-free
In reply to Patrick SteinhardtThere are a bunch of tests which do not have any leaks:
- t0411: Introduced via 5c5a4a1c05 (t0411: add tests for cloning from
partial repo, 2024-01-28), passes since its inception.
- t0610: Introduced via 57db2a094d (refs: introduce reftable backend,
2024-02-07), passes since its inception.
- t2405: Passes since 6741e917de (repository: avoid leaking
`fsmonitor` data, 2024-04-12).
- t7423: Introduced via b20c10fd9b (t7423: add tests for symlinked submodule directories, 2024-01-28), passes since e8d0608944 (submodule: require the submodule path to contain directories only, 2024-03-26). The fix is not obviously related, but probably works because we now die early in many code paths.
- t9xxx: All of these are exercising CVS-related tooling and pass since at least Git v2.40. It's likely that these pass for a long time already, but nobody ever noticed because Git developers do not tend to have CVS on their machines.
Mark all of these tests as passing.
--- t/t0411-clone-from-partial.sh | 1 + t/t0610-reftable-basics.sh | 1 + t/t2405-worktree-submodule.sh | 1 + t/t7423-submodule-symlinks.sh | 1 + t/t9200-git-cvsexportcommit.sh | 1 + t/t9401-git-cvsserver-crlf.sh | 1 + t/t9600-cvsimport.sh | 1 + t/t9601-cvsimport-vendor-branch.sh | 1 + t/t9602-cvsimport-branches-tags.sh | 1 + t/t9603-cvsimport-patchsets.sh | 2 ++ t/t9604-cvsimport-timestamps.sh | 2 ++ 11 files changed, 13 insertions(+)
t/t0411-clone-from-partial.sh +1 -0
@@ -2,6 +2,7 @@ test_description='check that local clone does not fetch from promisor remotes' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'create evil repo' '
t/t0610-reftable-basics.sh +1 -0
@@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GIT_TEST_DEFAULT_REF_FORMAT=reftable export GIT_TEST_DEFAULT_REF_FORMAT +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh INVALID_OID=$(test_oid 001)
t/t2405-worktree-submodule.sh +1 -0
@@ -5,6 +5,7 @@ test_description='Combination of submodules and multiple worktrees' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh base_path=$(pwd -P)
t/t7423-submodule-symlinks.sh +1 -0
@@ -2,6 +2,7 @@ test_description='check that submodule operations do not follow symlinks' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'prepare' '
t/t9200-git-cvsexportcommit.sh +1 -0
@@ -4,6 +4,7 @@ # test_description='Test export of commits to CVS' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh if ! test_have_prereq PERL; then
t/t9401-git-cvsserver-crlf.sh +1 -0
@@ -12,6 +12,7 @@ repository using cvs CLI client via git-cvsserver server' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh marked_as () {
t/t9600-cvsimport.sh +1 -0
@@ -4,6 +4,7 @@ test_description='git cvsimport basic tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh if ! test_have_prereq NOT_ROOT; then
t/t9601-cvsimport-vendor-branch.sh +1 -0
@@ -35,6 +35,7 @@ test_description='git cvsimport handling of vendor branches' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9601
t/t9602-cvsimport-branches-tags.sh +1 -0
@@ -7,6 +7,7 @@ test_description='git cvsimport handling of branches and tags' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9602
t/t9603-cvsimport-patchsets.sh +2 -0
@@ -12,6 +12,8 @@ # bug. test_description='git cvsimport testing for correct patchset estimation' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh setup_cvs_test_repository t9603
t/t9604-cvsimport-timestamps.sh +2 -0
@@ -1,6 +1,8 @@ #!/bin/sh test_description='git cvsimport timestamps' + +TEST_PASSES_SANITIZE_LEAK=true . ./lib-cvs.sh test_lazy_prereq POSIX_TIMEZONE '
Signature · 1 line
2.45.1.246.gb9cfe4845c.dirty
PATCH v3 3/21 transport-helper: fix leaking helper name
In reply to Patrick SteinhardtWhen initializing the transport helper in transport_get(), we allocate the name of the helper. We neither end up transferring ownership of the name, nor do we free it. The associated memory thus leaks.
Fix this memory leak by freeing the string at the calling side in transport_get(). transport_helper_init() now creates its own copy of the string and thus can free it as required.
An alterantive way to fix this would be to transfer ownership of the string passed into transport_helper_init(), which would avoid the call to xstrdup(1). But it does make for a more surprising calling convention as we do not typically transfer ownership of strings like this.
Mark now-passing tests as leak free.
--- t/t0611-reftable-httpd.sh | 1 + t/t5563-simple-http-auth.sh | 1 + t/t5564-http-proxy.sh | 1 + t/t5581-http-curl-verbose.sh | 1 + transport-helper.c | 6 ++++-- transport.c | 1 + 6 files changed, 9 insertions(+), 2 deletions(-)
t/t0611-reftable-httpd.sh +1 -0
@@ -2,6 +2,7 @@ test_description='reftable HTTPD tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5563-simple-http-auth.sh +1 -0
@@ -2,6 +2,7 @@ test_description='test http auth header and credential helper interop' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5564-http-proxy.sh +1 -0
@@ -2,6 +2,7 @@ test_description="test fetching through http proxy" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh
t/t5581-http-curl-verbose.sh +1 -0
@@ -4,6 +4,7 @@ test_description='test GIT_CURL_VERBOSE' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
transport-helper.c +4 -2
@@ -22,7 +22,7 @@static int debug;struct helper_data { -const char *name; +char *name;struct child_process *helper;FILE *out; unsigned fetch : 1,
@@ -111,6 +111,7 @@ static void do_take_over(struct transport *transport) data = (struct helper_data *)transport->data;transport_take_over (transport, data->helper);fclose (data->out); +free (data->name);free (data); }
@@ -253,6 +254,7 @@ static int disconnect_helper(struct transport *transport)close (data->helper->out);fclose (data->out); res =finish_command (data->helper); +FREE_AND_NULL (data->name);FREE_AND_NULL (data->helper); }return res;
@@ -1297,7 +1299,7 @@ static struct transport_vtable vtable = {int transport_helper_init (struct transport *transport,const char *name) {struct helper_data *data =xcalloc (1 ,sizeof (*data)); - data->name = name; + data->name =xstrdup (name);transport_check_allowed (name);
transport.c +1 -0
@@ -1176,6 +1176,7 @@ struct transport *transport_get(struct remote *remote, const char *url)int len =external_specification_len (url);char *handler =xmemdupz (url, len);transport_helper_init (ret, handler); +free (handler); }if (ret->smart_options) {
Signature · 1 line
2.45.1.246.gb9cfe4845c.dirty
PATCH v3 4/21 strbuf: fix leak when `appendwholeline()` fails with EOF
In reply to Patrick SteinhardtIn strbuf_appendwholeline() we call strbuf_getwholeline() with a temporary buffer. In case the call returns an error we indicate this by returning EOF, but never release the temporary buffer. This can cause a leak though because strbuf_getwholeline() calls getline(3). Quoting its documentation:
If *lineptr was set to NULL before the call, then the buffer should be freed by the user program even on failure.
Consequently, the temporary buffer may hold allocated memory even when the call to strbuf_getwholeline() fails.
Fix this by releasing the temporary buffer on error.
--- strbuf.c | 4 +++- t/t1400-update-ref.sh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-)
strbuf.c +3 -1
@@ -691,8 +691,10 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)int strbuf_appendwholeline (struct strbuf *sb,FILE *fp,int term) {struct strbuf line =STRBUF_INIT ; -if (strbuf_getwholeline (&line, fp, term)) +if (strbuf_getwholeline (&line, fp, term)) { +strbuf_release (&line);return EOF ; + }strbuf_addbuf (sb, &line);strbuf_release (&line);return 0 ;
t/t1400-update-ref.sh +2 -0
@@ -4,6 +4,8 @@ # test_description='Test git update-ref and basic ref logging' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh Z=$ZERO_OID
Signature · 1 line
2.45.1.246.gb9cfe4845c.dirty
PATCH v3 5/21 checkout: clarify memory ownership in `unique_tracking_name()`
In reply to Patrick SteinhardtThe function unique_tracking_name() returns an allocated string, but does not clearly indicate this because its return type is const char * instead of char *. This has led to various callsites where we never free its returned memory at all, which causes memory leaks.
Plug those leaks and mark now-passing tests as leak free.
--- builtin/checkout.c | 14 +++++++------ builtin/worktree.c | 20 ++++++++++--------- checkout.c | 4 ++-- checkout.h | 6 +++--- t/t2024-checkout-dwim.sh | 1 + t/t2060-switch.sh | 1 + t/t3426-rebase-submodule.sh | 1 + t/t3512-cherry-pick-submodule.sh | 1 + t/t3513-revert-submodule.sh | 1 + t/t3600-rm.sh | 1 + t/t3906-stash-submodule.sh | 1 + t/t4137-apply-submodule.sh | 1 + t/t6041-bisect-submodule.sh | 1 + t/t6438-submodule-directory-file-conflicts.sh | 1 + 14 files changed, 34 insertions(+), 20 deletions(-)
builtin/checkout.c +8 -6
@@ -1275,12 +1275,12 @@ static void setup_new_branch_info_and_source_tree( } } -static const char *parse_remote_branch (const char *arg, -struct object_id *rev, -int could_be_checkout_paths) +static char *parse_remote_branch (const char *arg, +struct object_id *rev, +int could_be_checkout_paths) {int num_matches =0 ; -const char *remote =unique_tracking_name (arg, rev, &num_matches); +char *remote =unique_tracking_name (arg, rev, &num_matches);if (remote && could_be_checkout_paths) { die(_("'%s' could be both a local file and a tracking branch.\n"
@@ -1316,6 +1316,7 @@ static int parse_branchname_arg(int argc, const char **argv,const char **new_branch = &opts->new_branch;int argcount =0 ;const char *arg; +char *remote =NULL ;int dash_dash_pos;int has_dash_dash =0 ;int i;
@@ -1416,8 +1417,8 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim =0 ;if (recover_with_dwim) { -const char *remote =parse_remote_branch (arg, rev, - could_be_checkout_paths); + remote =parse_remote_branch (arg, rev, + could_be_checkout_paths);if (remote) { *new_branch = arg; arg = remote;
@@ -1459,6 +1460,7 @@ static int parse_branchname_arg(int argc, const char **argv, argc--; } +free (remote);return argcount; }
builtin/worktree.c +11 -9
@@ -736,16 +736,14 @@ static int dwim_orphan(const struct add_opts *opts, int opt_track, int remote)return 1 ; } -static const char *dwim_branch (const char *path,const char **new_branch) +static char *dwim_branch (const char *path,char **new_branch) {int n;int branch_exists;const char *s =worktree_basename (path, &n); -const char *branchname =xstrndup (s, n); +char *branchname =xstrndup (s, n);struct strbuf ref =STRBUF_INIT ; -UNLEAK (branchname); - branch_exists = !strbuf_check_branch_ref (&ref, branchname) &&refs_ref_exists (get_main_ref_store (the_repository), ref.buf);
@@ -756,8 +754,7 @@ static const char *dwim_branch(const char *path, const char **new_branch) *new_branch = branchname;if (guess_remote) {struct object_id oid; -const char *remote = -unique_tracking_name (*new_branch, &oid,NULL ); +char *remote =unique_tracking_name (*new_branch, &oid,NULL );return remote; }return NULL ;
@@ -769,6 +766,8 @@ static int add(int ac, const char **av, const char *prefix)const char *new_branch_force =NULL ;char *path;const char *branch; +char *branch_to_free =NULL ; +char *new_branch_to_free =NULL ;const char *new_branch =NULL ;const char *opt_track =NULL ;const char *lock_reason =NULL ;
@@ -859,16 +858,17 @@ static int add(int ac, const char **av, const char *prefix) opts.orphan =dwim_orphan (&opts, !!opt_track,0 ); }else if (ac <2 ) {/* DWIM: Guess branch name from path. */ -const char *s =dwim_branch (path, &new_branch); +char *s =dwim_branch (path, &new_branch_to_free);if (s) - branch = s; + branch = branch_to_free = s; + new_branch = new_branch_to_free;/* DWIM: Infer --orphan when repo has no refs. */ opts.orphan = (!s) &&dwim_orphan (&opts, !!opt_track,1 ); }else if (ac ==2 ) {struct object_id oid;struct commit *commit; -const char *remote; +char *remote; commit =lookup_commit_reference_by_name (branch);if (!commit) {
@@ -923,6 +923,8 @@ static int add(int ac, const char **av, const char *prefix) ret =add_worktree (path, branch, &opts);free (path); +free (branch_to_free); +free (new_branch_to_free);return ret; }
checkout.c +2 -2
@@ -45,8 +45,8 @@ static int check_tracking_name(struct remote *remote, void *cb_data)return 0 ; } -const char *unique_tracking_name (const char *name,struct object_id *oid, -int *dwim_remotes_matched) +char *unique_tracking_name (const char *name,struct object_id *oid, +int *dwim_remotes_matched) {struct tracking_name_data cb_data =TRACKING_NAME_DATA_INIT ;const char *default_remote =NULL ;
checkout.h +3 -3
@@ -8,8 +8,8 @@* tracking branch. Return the name of the remote if such a branch * exists, NULL otherwise. */ -const char *unique_tracking_name (const char *name, -struct object_id *oid, -int *dwim_remotes_matched); +char *unique_tracking_name (const char *name, +struct object_id *oid, +int *dwim_remotes_matched);#endif /* CHECKOUT_H */
t/t2024-checkout-dwim.sh +1 -0
@@ -4,6 +4,7 @@ test_description='checkout <branch> Ensures that checkout on an unborn branch does what the user expects' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Is the current branch "refs/heads/$1"?
t/t2060-switch.sh +1 -0
@@ -5,6 +5,7 @@ test_description='switch basic functionality' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' '
t/t3426-rebase-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='rebase can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh . "$TEST_DIRECTORY"/lib-rebase.sh
t/t3512-cherry-pick-submodule.sh +1 -0
@@ -5,6 +5,7 @@ test_description='cherry-pick can handle submodules' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3513-revert-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='revert can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t3600-rm.sh +1 -0
@@ -8,6 +8,7 @@ test_description='Test of the various options to git rm.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Setup some files to be removed, some with funny characters
t/t3906-stash-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='stash can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t4137-apply-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='git apply handling submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t6041-bisect-submodule.sh +1 -0
@@ -2,6 +2,7 @@ test_description='bisect can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
t/t6438-submodule-directory-file-conflicts.sh +1 -0
@@ -2,6 +2,7 @@ test_description='merge can handle submodules' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh
Signature · 1 line
2.45.1.246.gb9cfe4845c.dirty
PATCH v3 6/21 http: refactor code to clarify memory ownership
In reply to Patrick SteinhardtThere are various variables assigned via git_config_string() and git_config_pathname() which are never free'd. This bug is relatable because the out parameter of those functions are a const char **, even though memory ownership is transferred to the caller.
We're about to adapt the functions to instead use char **. Prepare the code accordingly. Note that the (const char **) casts will go away once we have adapted the functions.
--- http.c | 62 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 30 deletions(-)
http.c +32 -30
@@ -39,8 +39,8 @@ char curl_errorstr[CURL_ERROR_SIZE];static int curl_ssl_verify =-1 ;static int curl_ssl_try;static const char *curl_http_version =NULL ; -static const char *ssl_cert; -static const char *ssl_cert_type; +static char *ssl_cert; +static char *ssl_cert_type;static const char *ssl_cipherlist;static const char *ssl_version; static struct {
@@ -59,23 +59,23 @@ static struct { { "tlsv1.3", CURL_SSLVERSION_TLSv1_3 }, #endif }; -static const char *ssl_key; -static const char *ssl_key_type; -static const char *ssl_capath; -static const char *curl_no_proxy; +static char *ssl_key; +static char *ssl_key_type; +static char *ssl_capath; +static char *curl_no_proxy; #ifdef GIT_CURL_HAVE_CURLOPT_PINNEDPUBLICKEY static const char *ssl_pinnedkey; #endif -static const char *ssl_cainfo; +static char *ssl_cainfo; static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; -static const char *curl_http_proxy; -static const char *http_proxy_authmethod; +static char *curl_http_proxy; +static char *http_proxy_authmethod; -static const char *http_proxy_ssl_cert; -static const char *http_proxy_ssl_key; -static const char *http_proxy_ssl_ca_info; +static char *http_proxy_ssl_cert; +static char *http_proxy_ssl_key; +static char *http_proxy_ssl_ca_info; static struct credential proxy_cert_auth = CREDENTIAL_INIT; static int proxy_ssl_cert_password_required;
@@ -112,7 +112,7 @@ static const char *curl_cookie_file;static int curl_save_cookies;struct credential http_auth =CREDENTIAL_INIT ;static int http_proactive_auth; -static const char *user_agent; +static char *user_agent;static int curl_empty_auth =-1 ;enum http_follow_config http_follow_config =HTTP_FOLLOW_INITIAL ;
@@ -381,17 +381,17 @@ static int http_options(const char *var, const char *value,if (!strcmp ("http.sslversion" , var))return git_config_string (&ssl_version, var, value);if (!strcmp ("http.sslcert" , var)) -return git_config_pathname (&ssl_cert, var, value); +return git_config_pathname ((const char **)&ssl_cert, var, value);if (!strcmp ("http.sslcerttype" , var)) -return git_config_string (&ssl_cert_type, var, value); +return git_config_string ((const char **)&ssl_cert_type, var, value);if (!strcmp ("http.sslkey" , var)) -return git_config_pathname (&ssl_key, var, value); +return git_config_pathname ((const char **)&ssl_key, var, value);if (!strcmp ("http.sslkeytype" , var)) -return git_config_string (&ssl_key_type, var, value); +return git_config_string ((const char **)&ssl_key_type, var, value);if (!strcmp ("http.sslcapath" , var)) -return git_config_pathname (&ssl_capath, var, value); +return git_config_pathname ((const char **)&ssl_capath, var, value);if (!strcmp ("http.sslcainfo" , var)) -return git_config_pathname (&ssl_cainfo, var, value); +return git_config_pathname ((const char **)&ssl_cainfo, var, value);if (!strcmp ("http.sslcertpasswordprotected" , var)) { ssl_cert_password_required =git_config_bool (var, value);return 0 ;
@@ -440,19 +440,19 @@ static int http_options(const char *var, const char *value,return 0 ; }if (!strcmp ("http.proxy" , var)) -return git_config_string (&curl_http_proxy, var, value); +return git_config_string ((const char **)&curl_http_proxy, var, value);if (!strcmp ("http.proxyauthmethod" , var)) -return git_config_string (&http_proxy_authmethod, var, value); +return git_config_string ((const char **)&http_proxy_authmethod, var, value);if (!strcmp ("http.proxysslcert" , var)) -return git_config_string (&http_proxy_ssl_cert, var, value); +return git_config_string ((const char **)&http_proxy_ssl_cert, var, value);if (!strcmp ("http.proxysslkey" , var)) -return git_config_string (&http_proxy_ssl_key, var, value); +return git_config_string ((const char **)&http_proxy_ssl_key, var, value);if (!strcmp ("http.proxysslcainfo" , var)) -return git_config_string (&http_proxy_ssl_ca_info, var, value); +return git_config_string ((const char **)&http_proxy_ssl_ca_info, var, value);if (!strcmp ("http.proxysslcertpasswordprotected" , var)) { proxy_ssl_cert_password_required =git_config_bool (var, value);
@@ -476,7 +476,7 @@ static int http_options(const char *var, const char *value, }if (!strcmp ("http.useragent" , var)) -return git_config_string (&user_agent, var, value); +return git_config_string ((const char **)&user_agent, var, value);if (!strcmp ("http.emptyauth" , var)) {if (value && !strcmp ("auto" , value))
@@ -592,10 +592,10 @@ static void init_curl_http_auth(CURL *result) }/* *var must be free-able */ -static void var_override (const char **var,char *value) +static void var_override (char **var,char *value) {if (value) { -free ((void *)*var); +free (*var); *var =xstrdup (value); } }
@@ -1233,11 +1233,13 @@ static CURL *get_curl_handle(void)return result; } -static void set_from_env (const char **var,const char *envname) +static void set_from_env (char **var,const char *envname) {const char *val =getenv (envname); -if (val) - *var = val; +if (val) { +FREE_AND_NULL (*var); + *var =xstrdup (val); + } }void http_init (struct remote *remote,const char *url,int proactive_auth)
Signature · 1 line
2.45.1.246.gb9cfe4845c.dirty