Compare commits

...

292 Commits

Author SHA1 Message Date
Junio C Hamano
4fea514e20 Merge branch 'bc/sha1-256-interop-02' into seen
The code to maintain mapping between object names in multiple hash
functions is being added, written in Rust.

* bc/sha1-256-interop-02:
  object-file-convert: always make sure object ID algo is valid
  rust: add a small wrapper around the hashfile code
  rust: add a new binary object map format
  rust: add functionality to hash an object
  rust: add a build.rs script for tests
  hash: expose hash context functions to Rust
  write-or-die: add an fsync component for the object map
  csum-file: define hashwrite's count as a uint32_t
  rust: add additional helpers for ObjectID
  hash: add a function to look up hash algo structs
  rust: add a hash algorithm abstraction
  rust: add a ObjectID struct
  hash: use uint32_t for object_id algorithm
  conversion: don't crash when no destination algo
  repository: require Rust support for interoperability
2026-01-09 08:41:39 -08:00
Junio C Hamano
9df1fbfbc2 ### CI 2026-01-09 08:41:39 -08:00
Junio C Hamano
c88051ca0e Merge branch 'pw/replay-drop-empty' into seen
"git replay" is taught to drop commits that become empty (not the
ones that are empty in the original).

* pw/replay-drop-empty:
  replay: drop commits that become empty
2026-01-09 08:41:39 -08:00
Junio C Hamano
276ca121e0 Merge branch 'ps/history' into seen
"git history" history rewriting UI.

Comments?

* ps/history:
  builtin/history: implement "reword" subcommand
  builtin: add new "history" command
  wt-status: provide function to expose status for trees
  replay: yield the object ID of the final rewritten commit
  replay: small set of cleanups
  builtin/replay: move core logic into "libgit.a"
  builtin/replay: extract core logic to replay revisions
2026-01-09 08:41:39 -08:00
Junio C Hamano
1247d873e3 Merge branch 'ps/ref-consistency-checks' into seen
* ps/ref-consistency-checks:
  builtin/fsck: drop `fsck_head_link()`
  builtin/fsck: move generic HEAD check into `refs_fsck()`
  builtin/fsck: move generic object ID checks into `refs_fsck()`
  refs/reftable: introduce generic checks for refs
  refs/reftable: fix consistency checks with worktrees
  refs/reftable: extract function to retrieve backend for worktree
  refs/reftable: adapt includes to become consistent
  refs/files: introduce function to perform normal ref checks
  refs/files: extract generic symref target checks
  fsck: drop unused fields from `struct fsck_ref_report`
  refs/files: perform consistency checks for root refs
  refs/files: improve error handling when verifying symrefs
  refs/files: extract function to check single ref
  refs/files: remove useless indirection
  refs/files: remove `refs_check_dir` parameter
  refs/files: move fsck functions into global scope
  refs/files: simplify iterating through root refs
2026-01-09 08:41:23 -08:00
Patrick Steinhardt
df7333d8e6 builtin/fsck: drop fsck_head_link()
The function `fsck_head_link()` was historically used to perform a
couple of consistency checks for refs. (Almost) all of these checks have
now been moved into the refs subsystem. There's only a single check
remaining that verifies whether `refs_resolve_ref_unsafe()` returns a
`NULL` pointer. This may happen in a couple of cases:

  - When `refs_is_safe()` declares the ref to be unsafe. We already have
    checks for this as we verify refnames with `check_refname_format()`.

  - When the ref doesn't exist. A repository without "HEAD" is
    completely broken though, and we would notice this error ahead of
    time already.

  - In case the caller passes `RESOLVE_REF_READING` and the ref is a
    symref that doesn't resolve. We don't pass this flag though.

As such, this check doesn't cover anything anymore that isn't already
covered by `refs_fsck()`. Drop it, which also allows us to inline the
call to `refs_resolve_ref_unsafe()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:14 -08:00
Patrick Steinhardt
b2f2a58a25 builtin/fsck: move generic HEAD check into refs_fsck()
Move the check that detects "HEAD" refs that do not point at a branch
into `refs_fsck()`. This follows the same motivation as the preceding
commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:14 -08:00
Patrick Steinhardt
aa3b256f55 builtin/fsck: move generic object ID checks into refs_fsck()
While most of the logic that verifies the consistency of refs is
driven by `refs_fsck()`, we still have a small handful of checks in
`fsck_head_link()`. These checks don't use the git-fsck(1) reporting
infrastructure, and as such it's impossible to for example disable
some of those checks.

One such check detects refs that point to the all-zeroes object ID.
Extract this check into the generic `refs_fsck_ref()` function that is
used by both the "files" and "reftable" backends.

Note that this will cause us to not return an error code from
`fsck_head_link()` anymore in case this error was detected. This is fine
though: the only caller of this function does not check the error code
anyway. To demonstrate this, adapt the function to drop its return value
altogether. The function will be removed in a subsequent commit anyway.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:14 -08:00
Patrick Steinhardt
8a3de73c64 refs/reftable: introduce generic checks for refs
In a preceding commit we have extracted generic checks for both direct
and symbolic refs that apply for all backends. Wire up those checks for
the "reftable" backend.

Note that this is done by iterating through all refs manually with the
low-level reftable ref iterator. We explicitly don't want to use the
higher-level iterator that is exposed to users of the reftable backend
as that iterator may swallow for example broken refs.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:13 -08:00
Patrick Steinhardt
d385bc1b75 refs/reftable: fix consistency checks with worktrees
The ref consistency checks are driven via `cmd_refs_verify()`. That
function loops through all worktrees (including the main worktree) and
then checks the ref store for each of them individually. It follows that
the backend is expected to only verify refs that belong to the specified
worktree.

While the "files" backend handles this correctly, the "reftable" backend
doesn't. In fact, it completely ignores the passed worktree and instead
verifies refs of _all_ worktrees. The consequence is that we'll end up
every ref store N times, where N is the number of worktrees.

Or rather, that would be the case if we actually iterated through the
worktree reftable stacks correctly. But we use `strmap_for_each_entry()`
to iterate through the stacks, but the map is in fact not even properly
populated. So instead of checking stacks N^2 times, we actually only end
up checking the reftable stack of the main worktree.

Fix this bug by only verifying the stack of the passed-in worktree and
constructing the backends via `backend_for_worktree()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:13 -08:00
Patrick Steinhardt
eb9c42c5a2 refs/reftable: extract function to retrieve backend for worktree
Pull out the logic to retrieve a backend for a given worktree. This
function will be used in a subsequent commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:13 -08:00
Patrick Steinhardt
3a423a3c0c refs/reftable: adapt includes to become consistent
Adapt the includes to be sorted and to use include paths that are
relative to the "refs/" directory.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:12 -08:00
Patrick Steinhardt
0a83639888 refs/files: introduce function to perform normal ref checks
In a subsequent commit we'll introduce new generic checks for direct
refs. These checks will be independent of the actual backend.

Introduce a new function `refs_fsck_ref()` that will be used for this
purpose. At the current point in time it's still empty, but it will get
populated in a subsequent commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:12 -08:00
Patrick Steinhardt
0213630269 refs/files: extract generic symref target checks
The consistency checks for the "files" backend contain a couple of
verifications for symrefs that verify generic properties of the target
reference. These properties need to hold for every backend, no matter
whether it's using the "files" or "reftable" backend.

Reimplementing these checks for every single backend doesn't really make
sense. Extract it into a generic `refs_fsck_symref()` function that can
be used my other backends, as well. The "reftable" backend will be wired
up in a subsequent commit.

While at it, improve the consistency checks so that we don't complain
about refs pointing to a non-ref target in case the target refname
format does not verify. Otherwise it's very likely that we'll generate
both error messages, which feels somewhat redundant in this case.

Note that the function has a couple of `UNUSED` parameters. These will
become referenced in a subsequent commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:11 -08:00
Patrick Steinhardt
3f330b445b fsck: drop unused fields from struct fsck_ref_report
The `struct fsck_ref_report` has a couple fields that are intended to
improve the error reporting for broken ref reports by showing which
object ID or target reference the ref points to. These fields are never
set though and are thus essentially unused.

Remove them.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:10 -08:00
Patrick Steinhardt
4144f09d78 refs/files: perform consistency checks for root refs
While the "files" backend already knows to perform consistency checks
for the "refs/" hierarchy, it doesn't verify any of its root refs. Plug
this omission.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:10 -08:00
Patrick Steinhardt
04bbf2f0d8 refs/files: improve error handling when verifying symrefs
The error handling when verifying symbolic refs is a bit on the wild
side:

  - `fsck_report_ref()` can be told to ignore specific errors. If an
    error has been ignored and a previous check raised an unignored
    error, then assigning `ret = fsck_report_ref()` will cause us to
    swallow the previous error.

  - When the target reference is not valid we bail out early without
    checking for other errors.

Fix both of these issues by consistently or'ing the return value and not
bailing out early.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:10 -08:00
Patrick Steinhardt
5c8cf26919 refs/files: extract function to check single ref
When checking the consistency of references we create a directory
iterator and then verify each single reference in a loop. The logic to
perform the actual checks is embedded into that loop, which makes it
hard to reuse. But In a subsequent commit we're about to introduce a
second path that wants to verify references.

Prepare for this by extracting the logic to check a single reference
into a standalone function.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:09 -08:00
Patrick Steinhardt
328de3c71e refs/files: remove useless indirection
The function `files_fsck_refs()` only has a single callsite and forwards
all of its arguments as-is, so it's basically a useless indirection.
Inline the function call.

While at it, also remove the bitwise or that we have for return values.
We don't really want to or them at all, but rather just want to return
an error in case either of the functions has failed.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:09 -08:00
Patrick Steinhardt
9262da11b5 refs/files: remove refs_check_dir parameter
The parameter `refs_check_dir` determines which directory we want to
check references for. But as we always want to check the complete
refs hierarchy, this parameter is always set to "refs".

Drop the parameter and hardcode it.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:09 -08:00
Patrick Steinhardt
ec03c75707 refs/files: move fsck functions into global scope
When performing consistency checks we pass the functions that perform
the verification down the calling stack. This is somewhat unnecessary
though, as the set of functions doesn't ever change.

Simplify the code by moving the array into global scope and remove the
parameter.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:09 -08:00
Patrick Steinhardt
20f3107931 refs/files: simplify iterating through root refs
When iterating through root refs we first need to determine the
directory in which the refs live. This is done by retrieving the root of
the loose refs via `refs->loose->root->name`, and putting it through
`files_ref_path()` to derive the final path.

This is somewhat redundant though: the root name of the loose files
cache is always going to be the empty string. As such, we always end up
passing that empty string to `files_ref_path()` as the ref hierarchy we
want to start. And this actually makes sense: `files_ref_path()` already
computes the location of the root directory, so of course we need to
pass the empty string for the ref hierarchy itself. So going via the
loose ref cache to figure out that the root of a ref hierarchy is empty
is only causing confusion.

But next to the added confusion, it can also lead to a segfault. The
loose ref cache is populated lazily, so it may not always be set. It
seems to be sheer luck that this is a condition we do not currently hit.
The right thing to do would be to call `get_loose_ref_cache()`, which
knows to populate the cache if required.

Simplify the code and fix the potential segfault by simply removing the
indirection via the loose ref cache completely.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 07:23:09 -08:00
Junio C Hamano
49c668b2fd Merge branch 'cc/lop-filter-auto' into seen
"auto filter" logic for large-object promisor remote.

Comments?

* cc/lop-filter-auto:
  fetch-pack: wire up and enable auto filter logic
  promisor-remote: keep advertised filter in memory
  list-objects-filter-options: implement auto filter resolution
  list-objects-filter-options: support 'auto' mode for --filter
  doc: fetch: document `--filter=<filter-spec>` option
  fetch: make filter_options local to cmd_fetch()
  clone: make filter_options local to cmd_clone()
  promisor-remote: allow a client to store fields
  promisor-remote: refactor initialising field lists
2026-01-09 06:55:57 -08:00
Junio C Hamano
77e128ef7f Merge branch 'hn/status-compare-with-push' into seen
"git status" learned to show comparison between the current branch
and its push destination as well as its upstream, when the two are
different (i.e., triangular workflow).

Comments?

* hn/status-compare-with-push:
  status: show comparison with push remote tracking branch
  refactor format_branch_comparison in preparation
2026-01-09 06:55:56 -08:00
Junio C Hamano
c7e23cd287 Merge branch 'pt/t7527-flake-workaround' into seen
Test fixup.

Comments?

* pt/t7527-flake-workaround:
  t7527: fix flaky fsmonitor event tests with retry logic
2026-01-09 06:55:56 -08:00
Junio C Hamano
e21d1ffdbd Merge branch 'pt/fsmonitor-linux' into seen
The fsmonitor daemon has been implemented for Linux.

Comments?

* pt/fsmonitor-linux:
  fsmonitor: implement filesystem change listener for Linux
2026-01-09 06:55:56 -08:00
Junio C Hamano
6bf4dc4701 Merge branch 'pc/lockfile-pid' into seen
Allow recording process ID of the process that holds the lock next
to a lockfile for diagnosis.

Comments?

* pc/lockfile-pid:
  lockfile: add PID file for debugging stale locks
2026-01-09 06:55:55 -08:00
Junio C Hamano
635138e8b1 Merge branch 'js/neuter-sideband' into seen
Invalidate control characters in sideband messages, to avoid
terminal state getting messed up.

Comments?
cf. <aS-D5lD2Kk6BHNIl@fruit.crustytoothpaste.net>

* js/neuter-sideband:
  sideband: add options to allow more control sequences to be passed through
  sideband: do allow ANSI color sequences by default
  sideband: introduce an "escape hatch" to allow control characters
  sideband: mask control characters
2026-01-09 06:55:55 -08:00
Junio C Hamano
39afbf3c5d Merge branch 'lo/repo-info-keys' into seen
"git repo info" learns "--keys" action to list known keys.

Comments?

* lo/repo-info-keys:
  repo: add new flag --keys to git-repo-info
  repo: add a default output format to enum output_format
2026-01-09 06:55:54 -08:00
Junio C Hamano
e931506ade Merge branch 'sp/shallow-time-boundary' into seen
The set of shallow boundary "git clone --shallow-since" leaves
contained commits that are not on the boundary, which has been
corrected.

Comments?

* sp/shallow-time-boundary:
  shallow: set borders which are all reachable after clone shallow since
2026-01-09 06:55:54 -08:00
Junio C Hamano
15598099d3 Merge branch 'dw/config-global-list' into seen
"git config --list --global", unlike "git config --list", did not
consult both of the two possible per-user sources of the
configuration files, i.e. $HOME/.gitconfig and the XDG one, which
has been corrected.

* dw/config-global-list:
  config: keep bailing on unreadable global files
  config: read global scope via config_sequence
  config: test home and xdg files in `list --global`
  cleanup_path: force forward slashes on Windows
2026-01-09 06:55:54 -08:00
Junio C Hamano
e7da08446f Merge branch 'lc/rebase-trailer' into seen
Refactor code paths to run "interpret-trailers" from "git
commit/tag" and use it in "git rebase".

* lc/rebase-trailer:
  rebase: support --trailer
  trailer: append trailers in-process and drop the fork to `interpret-trailers`
  trailer: move process_trailers to trailer.h
  interpret-trailers: factor out buffer-based processing to process_trailers()
2026-01-09 06:55:54 -08:00
Junio C Hamano
22908a59b7 Merge branch 'ms/doc-worktree-side-by-side' into seen
Document "git worktree add" and use of out-of-tree worktrees with
examples.

* ms/doc-worktree-side-by-side:
  doc: git-worktree: Add side by side branch checkout example
  doc: git-worktree: Link to examples
2026-01-09 06:55:53 -08:00
Junio C Hamano
4d487d0b7c Merge branch 'jc/exclude-with-gitignore' into seen
"git add ':(exclude)foo.o'" is clearly a request not to add 'foo.o',
but the command complained about listing an ignored path foo.o on
the command line, which has been corrected.

Comments?

* jc/exclude-with-gitignore:
  dir.c: do not be fooled by :(exclude) pathspec elements
2026-01-09 06:55:53 -08:00
Junio C Hamano
72035efcbb Merge branch 'tb/incremental-midx-part-3.2' into seen
Further work on incremental repacking using MIDX/bitmap

* tb/incremental-midx-part-3.2:
  midx: enable reachability bitmaps during MIDX compaction
  midx: implement MIDX compaction
  t/helper/test-read-midx.c: plug memory leak when selecting layer
  midx-write.c: factor fanout layering from `compute_sorted_entries()`
  midx-write.c: enumerate `pack_int_id` values directly
  midx-write.c: extract `fill_pack_from_midx()`
  midx-write.c: introduce `midx_pack_perm()` helper
  git-compat-util.h: introduce `u32_add()`
  midx: do not require packs to be sorted in lexicographic order
  midx-write.c: introduce `struct write_midx_opts`
  midx-write.c: don't use `pack_perm` when assigning `bitmap_pos`
  t/t5319-multi-pack-index.sh: fix copy-and-paste error in t5319.39
  git-multi-pack-index(1): align SYNOPSIS with 'git multi-pack-index -h'
  git-multi-pack-index(1): remove non-existent incompatibility
  builtin/multi-pack-index.c: make '--progress' a common option
  midx: split `get_midx_checksum()` by adding `get_midx_hash()`
  midx: mark `get_midx_checksum()` arguments as const
2026-01-09 06:55:52 -08:00
Junio C Hamano
6b24017454 Merge branch 'en/xdiff-cleanup-3' into seen
Preparation of xdiff/ codebase to work with Rust

Comments?

* en/xdiff-cleanup-3:
  SQUASH??? cocci
  xdiff: move xdl_cleanup_records() from xprepare.c to xdiffi.c
  xdiff: remove dependence on xdlclassifier from xdl_cleanup_records()
  xdiff: replace xdfile_t.dend with xdfenv_t.delta_end
  xdiff: replace xdfile_t.dstart with xdfenv_t.delta_start
  xdiff: cleanup xdl_trim_ends()
  xdiff: use xdfenv_t in xdl_trim_ends() and xdl_cleanup_records()
  xdiff: let patience and histogram benefit from xdl_trim_ends()
  xdiff: don't waste time guessing the number of lines
  xdiff: make classic diff explicit by creating xdl_do_classic_diff()
  ivec: introduce the C side of ivec
2026-01-09 06:55:52 -08:00
Junio C Hamano
389343c68f Merge branch 'ob/core-attributesfile-in-repository' into seen
The core.attributesfile is intended to be set per repository, but
were kept track of by a single global variable in-core, which has
been corrected by moving it to per-repository data structure.

Comments?

* ob/core-attributesfile-in-repository:
  environment: move "core.attributesFile" into repo-setting
2026-01-09 06:55:52 -08:00
Junio C Hamano
0ad3607094 Merge branch 'je/doc-reset' into seen
Documentation updates.

* je/doc-reset:
  doc: git-reset: clarify `git reset <pathspec>`
  doc: git-reset: clarify `git reset [mode]`
  doc: git-reset: clarify intro
  doc: git-reset: reorder the forms
2026-01-09 06:55:51 -08:00
Junio C Hamano
d2391a94c9 Merge branch 'kh/doc-patch-id' into jch
* kh/doc-patch-id:
  doc: patch-id: --verbatim locks in --stable
  doc: patch-id: spell out the git-diff-tree(1) form
  doc: patch-id: use definite article for the result
  patch-id: use “patch ID” throughout
  doc: patch-id: capitalize Git version
  doc: patch-id: don’t use semicolon between bullet points
2026-01-09 06:55:44 -08:00
Junio C Hamano
0a5c831c05 Merge branch 'aa/add-p-previous-decisions' into jch
"git add -p" and friends notes what the current status of the hunk
being shown is.

Comments?

* aa/add-p-previous-decisions:
  add -p: show user's hunk decision when selecting hunks
2026-01-09 06:55:44 -08:00
Junio C Hamano
aaac2643fd Merge branch 'ml/doc-blame-markup' into jch
Doc mark-up update.

* ml/doc-blame-markup:
  doc: git-blame: convert to new doc format
  doc: blame-options: convert to new doc format
2026-01-09 06:55:44 -08:00
Junio C Hamano
bc3e4c6c7b Merge branch 'tb/macos-iconv-workarounds' into jch
The iconv library on macOS fails to correctly handle stateful
ISO/IEC 2022 encoded strings.  Work it around instead of replacing
it wholesale from homebrew.

RFC.
needs to be debased from older rs/macos-iconv-workaround topic.

* tb/macos-iconv-workarounds:
  utf8.c: Enable workaround for iconv under macOS 14/15
  utf8.c: Prepare workaround for iconv under macOS 14/15
2026-01-09 06:55:44 -08:00
Junio C Hamano
4eb8fddcd9 Merge branch 'bc/doc-stash-import-export' into jch
* bc/doc-stash-import-export:
  gitfaq: document using stash import/export to sync working tree
2026-01-09 06:55:44 -08:00
Junio C Hamano
794e69d021 Merge branch 'kj/t7101-modernize' into jch
* kj/t7101-modernize:
  t7101: modernize test path checks
2026-01-09 06:55:43 -08:00
Junio C Hamano
2e186ee137 Merge branch 'ds/builtin-doc-update' into jch
* ds/builtin-doc-update:
  builtin.h: update documentation
2026-01-09 06:55:43 -08:00
Junio C Hamano
5dbfe67eaf Merge branch 'ag/http-netrc-tests' into jch
Additional tests were introduced to see the interaction with netrc
auth with auth failure on the http transport.

Comments?

* ag/http-netrc-tests:
  t5550: add netrc tests for http 401/403
2026-01-09 06:49:53 -08:00
Junio C Hamano
f0bdb8978c Merge branch 'ac/t1420-use-more-direct-check' into jch
Test update.

* ac/t1420-use-more-direct-check:
  t1420: modernize the lost-found test
2026-01-09 06:49:53 -08:00
Junio C Hamano
71f9facd0b Merge branch 'jk/cat-file-avoid-bitmap-when-unneeded' into jch
Fix for a performance regression in "git cat-file".

* jk/cat-file-avoid-bitmap-when-unneeded:
  cat-file: only use bitmaps when filtering
2026-01-09 06:49:52 -08:00
Junio C Hamano
2daf6f0bc8 Merge branch 'jk/t-perf-fixes' into jch
Perf-test fixes.

* jk/t-perf-fixes:
  t/perf/run: preserve GIT_PERF_* from environment
  t/perf/perf-lib: fix assignment of TEST_OUTPUT_DIRECTORY
2026-01-09 06:49:52 -08:00
Junio C Hamano
2ad7318f62 Merge branch 'ps/read-object-info-improvements' into jch
The object-info API has been cleaned up.

Comments?

* ps/read-object-info-improvements:
  packfile: drop repository parameter from `packed_object_info()`
  packfile: skip unpacking object header for disk size requests
  packfile: disentangle return value of `packed_object_info()`
  packfile: always populate pack-specific info when reading object info
  packfile: extend `is_delta` field to allow for "unknown" state
  packfile: always declare object info to be OI_PACKED
  object-file: always set OI_LOOSE when reading object info
2026-01-09 06:49:52 -08:00
Junio C Hamano
ecc50847b3 Merge branch 'en/fsck-snapshot-ref-state' into jch
"git fsck" used inconsistent set of refs to show a confused
warning, which has been corrected.

* en/fsck-snapshot-ref-state:
  fsck: snapshot default refs before object walk
2026-01-09 06:49:51 -08:00
Junio C Hamano
8b8e26a05a Merge branch 'tt/receive-pack-oo-namespace-symref-fix' into jch
"git receive-pack", when namespace is involved, segfaulted when a
symbolic ref cross the namespace boundary.

Comments?

* tt/receive-pack-oo-namespace-symref-fix:
  receive-pack: fix crash on out-of-namespace symref
2026-01-09 06:49:51 -08:00
Junio C Hamano
0c32ac7aea Merge branch 'sb/doc-worktree-prune-expire-improvement' into jch
The help text and the documentation for the "--expire" option of
"git worktree [list|prune]" have been improved.

* sb/doc-worktree-prune-expire-improvement:
  worktree: use 'prune' instead of 'expire' in help text
  worktree: clarify --expire applies to missing worktrees
2026-01-09 06:49:51 -08:00
Junio C Hamano
87e0d405f9 Merge branch 'js/symlink-windows' into jch
Upstream symbolic link support on Windows from Git-for-Windows.

* js/symlink-windows:
  mingw: special-case index entries for symlinks with buggy size
  mingw: emulate `stat()` a little more faithfully
  mingw: try to create symlinks without elevated permissions
  mingw: add support for symlinks to directories
  mingw: implement basic `symlink()` functionality (file symlinks only)
  mingw: implement `readlink()`
  mingw: allow `mingw_chdir()` to change to symlink-resolved directories
  mingw: support renaming symlinks
  mingw: handle symlinks to directories in `mingw_unlink()`
  mingw: add symlink-specific error codes
  mingw: change default of `core.symlinks` to false
  mingw: factor out the retry logic
  mingw: compute the correct size for symlinks in `mingw_lstat()`
  mingw: teach dirent about symlinks
  mingw: let `mingw_lstat()` error early upon problems with reparse points
  mingw: drop the separate `do_lstat()` function
  mingw: implement `stat()` with symlink support
  mingw: don't call `GetFileAttributes()` twice in `mingw_lstat()`
2026-01-09 06:49:51 -08:00
Junio C Hamano
2142534239 Merge branch 'js/prep-symlink-windows' into jch
Further preparation to upstream symbolic link support on Windows.

* js/prep-symlink-windows:
  trim_last_path_component(): avoid hard-coding the directory separator
  strbuf_readlink(): support link targets that exceed PATH_MAX
  strbuf_readlink(): avoid calling `readlink()` twice in corner-cases
  init: do parse _all_ core.* settings early
  mingw: do resolve symlinks in `getcwd()`
2026-01-09 06:49:51 -08:00
Junio C Hamano
f5b7e90e18 Merge branch 'ap/http-probe-rpc-use-auth' into jch
* ap/http-probe-rpc-use-auth:
  remote-curl: Use auth for probe_rpc() requests too
2026-01-09 06:49:50 -08:00
Junio C Hamano
61a27ae934 Merge branch 'sb/doc-update-ref-markup-fix' into jch
Doc mark-up fix.

Will merget to 'next'.

* sb/doc-update-ref-markup-fix:
  doc: fix `update-ref` `symref-create` formatting
2026-01-09 06:49:50 -08:00
Junio C Hamano
5c7955b477 Merge branch 'yc/histogram-hunk-shift-fix' into jch
The final clean-up phase of the diff output could turn the result of
histogram diff algorithm suboptimal, which has been corrected.

Comments?

* yc/histogram-hunk-shift-fix:
  xdiff: re-diff shifted change groups when using histogram algorithm
2026-01-09 06:49:49 -08:00
Junio C Hamano
66f2b50cb3 Merge branch 'jk/parse-int' into jch
Introduce a more robust way to parse a decimal integer stored in a
piece of memory that is not necessarily terminated with NUL (which
Asan strict-string-check complains even when use of strtol() is
safe due to varified existence of whitespace after the digits).

* jk/parse-int:
  fsck: use parse_unsigned_from_buf() for parsing timestamp
  cache-tree: use parse_int_from_buf()
  parse: add functions for parsing from non-string buffers
  parse: prefer bool to int for boolean returns
2026-01-09 06:49:49 -08:00
Junio C Hamano
07ab3aca24 Merge branch 'tc/last-modified-options-cleanup' into jch
The "-z" and "--max-depth" documentation (and implementation of
"-z") in the "git last-modified" command have been updated.

* tc/last-modified-options-cleanup:
  fixup! last-modified: document option --max-depth
  last-modified: document how depth is handled better
  last-modified: document option --max-depth
  last-modified: handle and document NUL termination
2026-01-09 06:49:49 -08:00
Junio C Hamano
5727ec74f3 Merge branch 'kn/ref-location' into jch
A mechanism to specify what reference backend to use and store
references in which directory is introduced, which would likely to
be useful during ref migration.

Comments?

* kn/ref-location:
  refs: add GIT_REF_URI to specify reference backend and directory
  refs: support obtaining ref_store for given dir
2026-01-09 06:49:49 -08:00
Junio C Hamano
14eceb5b5d Merge branch 'wm/complete-git-short-opts' into jch
The command line completion script (in contrib/) learned to
complete "git -<TAB>" to give single-letter options like "-C".

* wm/complete-git-short-opts:
  completion: complete "git -<TAB>" with short options
2026-01-09 06:49:48 -08:00
Junio C Hamano
7a2296736a Merge branch 'ar/submodule-gitdir-tweak' into jch
Avoid local submodule repository directory paths overlapping with
each other by encoding submodule names before using them as path
components.

Comments?

* ar/submodule-gitdir-tweak:
  submodule: detect conflicts with existing gitdir configs
  submodule: hash the submodule name for the gitdir path
  submodule: fix case-folding gitdir filesystem collisions
  submodule--helper: fix filesystem collisions by encoding gitdir paths
  builtin/credential-store: move is_rfc3986_unreserved to url.[ch]
  submodule--helper: add gitdir migration command
  submodule: allow runtime enabling extensions.submodulePathConfig
  submodule: introduce extensions.submodulePathConfig
  builtin/submodule--helper: add gitdir command
  submodule: always validate gitdirs inside submodule_name_to_gitdir
  submodule--helper: use submodule_name_to_gitdir in add_submodule
2026-01-09 06:49:48 -08:00
Junio C Hamano
fc869c7958 Merge branch 'ps/packfile-store-in-odb-source' into jch
The packfile_store data structure is moved from object store to odb
source.

* ps/packfile-store-in-odb-source:
  packfile: move MIDX into packfile store
  packfile: refactor `find_pack_entry()` to work on the packfile store
  packfile: inline `find_kept_pack_entry()`
  packfile: only prepare owning store in `packfile_store_prepare()`
  packfile: only prepare owning store in `packfile_store_get_packs()`
  packfile: move packfile store into object source
  packfile: refactor misleading code when unusing pack windows
  packfile: refactor kept-pack cache to work with packfile stores
  packfile: pass source to `prepare_pack()`
  packfile: create store via its owning source
2026-01-09 06:49:48 -08:00
Junio C Hamano
b878089e58 ### match next 2026-01-09 06:49:47 -08:00
Junio C Hamano
6cc4e8b75d Merge branch 'ps/clar-integers' into jch
Import newer version of "clar", unit testing framework.

* ps/clar-integers:
  gitattributes: disable blank-at-eof errors for clar test expectations
  t/unit-tests: demonstrate use of integer comparison assertions
  t/unit-tests: update clar to 39f11fe
2026-01-09 06:49:47 -08:00
Junio C Hamano
47b4112c1a Merge branch 'kh/replay-invalid-onto-advance' into jch
Improve the error message when a bad argument is given to the
`--onto` option of "git replay".  Test coverage of "git replay" has
been improved.

* kh/replay-invalid-onto-advance:
  t3650: add more regression tests for failure conditions
  replay: die if we cannot parse object
  replay: improve code comment and die message
  replay: die descriptively when invalid commit-ish is given
  replay: find *onto only after testing for ref name
  replay: remove dead code and rearrange
2026-01-09 06:49:46 -08:00
Junio C Hamano
16580c650a Merge branch 'ps/odb-misc-fixes' into jch
Miscellaneous fixes on object database layer.

* ps/odb-misc-fixes:
  odb: properly close sources before freeing them
  builtin/gc: fix condition for whether to write commit graphs
2026-01-09 06:49:46 -08:00
Junio C Hamano
ad01f132b4 Merge branch 'pt/t7800-difftool-test-racefix' into jch
Test fixup.

* pt/t7800-difftool-test-racefix:
  t7800: fix racy "difftool --dir-diff syncs worktree" test
2026-01-09 06:49:46 -08:00
Junio C Hamano
c7a544f09f Merge branch 'ps/t1300-2021-use-test-path-is-helpers' into jch
Test updates.

* ps/t1300-2021-use-test-path-is-helpers:
  t1300: use test helpers instead of `test` command
2026-01-09 06:49:46 -08:00
Junio C Hamano
aefd897e60 Merge branch 'rs/commit-stack' into jch
Code clean-up, unifying various hand-rolled "list of commit
objects" and use the commit_stack API.

* rs/commit-stack:
  commit-reach: use commit_stack
  commit-graph: use commit_stack
  commit: add commit_stack_grow()
  shallow: use commit_stack
  pack-bitmap-write: use commit_stack
  commit: add commit_stack_init()
  test-reach: use commit_stack
  remote: use commit_stack for src_commits
  remote: use commit_stack for sent_tips
  remote: use commit_stack for local_commits
  name-rev: use commit_stack
  midx: use commit_stack
  log: use commit_stack
  revision: export commit_stack
2026-01-09 06:49:46 -08:00
Junio C Hamano
87c8ccb2b5 Merge branch 'sb/bundle-uri-without-uri' into jch
Diagnose invalid bundle-URI that lack the URI entry, instead of
crashing.

* sb/bundle-uri-without-uri:
  bundle-uri: validate that bundle entries have a uri
2026-01-09 06:49:45 -08:00
Junio C Hamano
5a3ac92d76 Merge branch 'ja/doc-synopsis-style-more' into jch
More doc style updates.

* ja/doc-synopsis-style-more:
  doc: convert git-remote to synopsis style
  doc: convert git stage to use synopsis block
  doc: convert git-status tables to AsciiDoc format
  doc: convert git-status to synopsis style
  doc: fix t0450-txt-doc-vs-help to select only first synopsis block
2026-01-09 06:49:45 -08:00
Phillip Wood
2f8b312ee9 replay: drop commits that become empty
If the changes in a commit being replayed are already in the branch
that the commits are being replayed onto, then "git replay" creates an
empty commit. This is confusing because the commit message no longer
matches the contents of the commit. Drop the commit instead. Commits
that start off empty are not dropped. This matches the behavior of
"git rebase --reapply-cherry-pick --empty=drop" and "git cherry-pick
--empty-drop".

If a branch points to a commit that is dropped it will be updated
to point to the last commit that was not dropped. This can be seen
in the new test where "topic1" is updated to point to the rebased
"C" as "F" is dropped because it is already upstream. While this is
a breaking change, "git replay" is marked as experimental to allow
improvements like this that change the behavior.

Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:44:52 -08:00
Junio C Hamano
176e463cac Merge branch 'ps/history' into pw/replay-drop-empty
* ps/history: (185 commits)
  builtin/history: implement "reword" subcommand
  builtin: add new "history" command
  wt-status: provide function to expose status for trees
  replay: yield the object ID of the final rewritten commit
  replay: small set of cleanups
  builtin/replay: move core logic into "libgit.a"
  builtin/replay: extract core logic to replay revisions
  The 15th batch
  t3650: add more regression tests for failure conditions
  replay: die if we cannot parse object
  replay: improve code comment and die message
  replay: die descriptively when invalid commit-ish is given
  replay: find *onto only after testing for ref name
  replay: remove dead code and rearrange
  The 14th batch
  The 13th batch
  config: use git_parse_int() in git_config_get_expiry_in_days()
  receive-pack: convert receive hooks to hook API
  receive-pack: convert update hooks to new API
  hooks: allow callers to capture output
  ...
2026-01-09 06:44:21 -08:00
Patrick Steinhardt
e125df7961 builtin/history: implement "reword" subcommand
Implement a new "reword" subcommand for git-history(1). This subcommand
is similar to the user performing an interactive rebase with a single
commit changed to use the "reword" instruction.

The "reword" subcommand is built on top of the replay subsystem
instead of the sequencer. This leads to some major differences compared
to git-rebase(1):

  - We do not check out the commit that is to be reworded and instead
    perform the operation in-memory. This has the obvious benefit of
    being significantly faster compared to git-rebase(1), but even more
    importantly it allows the user to rewrite history even if there are
    local changes in the working tree or in the index.

  - We do not execute any hooks, even though we leave some room for
    changing this in the future.

  - By default, all local branches that contain the commit will be
    rewritten. This especially helps with workflows that use stacked
    branches.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:10 -08:00
Patrick Steinhardt
baf46a880d builtin: add new "history" command
When rewriting history via git-rebase(1) there are a few very common use
cases:

  - The ordering of two commits should be reversed.

  - A commit should be split up into two commits.

  - A commit should be dropped from the history completely.

  - Multiple commits should be squashed into one.

  - Editing an existing commit that is not the tip of the current
    branch.

While these operations are all doable, it often feels needlessly kludgey
to do so by doing an interactive rebase, using the editor to say what
one wants, and then perform the actions. Also, some operations like
splitting up a commit into two are way more involved than that and
require a whole series of commands.

Rebases also do not update dependent branches. The use of stacked
branches has grown quite common with competing version control systems
like Jujutsu though, so it clearly is a need that users have. While
rebases _can_ serve this use case if one always works on the latest
stacked branch, it is somewhat awkward and very easy to get wrong.

Add a new "history" command to plug these gaps. This command will have
several different subcommands to imperatively rewrite history for common
use cases like the above.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:10 -08:00
Patrick Steinhardt
7f3be938d3 wt-status: provide function to expose status for trees
The "wt-status" subsystem is responsible for printing status information
around the current state of the working tree. This most importantly
includes information around whether the working tree or the index have
any changes.

We're about to introduce a new command where the changes in neither of
them are actually relevant to us. Instead, what we want is to format the
changes between two different trees. While it is a little bit of a
stretch to add this as functionality to _working tree_ status, it
doesn't make any sense to open-code this functionality, either.

Implement a new function `wt_status_collect_changes_trees()` that diffs
two trees and formats the status accordingly. This function is not yet
used, but will be in a subsequent commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:10 -08:00
Patrick Steinhardt
a9f788acf0 replay: yield the object ID of the final rewritten commit
In a subsequent commit we'll introduce a new git-history(1) command that
uses the replay machinery to rewrite commits. One of its supported modes
will only want to update the "HEAD" reference, but that is not currently
supported by the replay machinery.

Allow implementing this use case by exposing a `final_oid` field for the
reference updates. This field will be set to the last commit that was
rewritten, which is sufficient information for us to implement this mode
in git-history(1).

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:10 -08:00
Patrick Steinhardt
6a91f2abfb replay: small set of cleanups
Perform a small set of cleanups so that the "replay" logic compiles with
"-Wsign-compare" and doesn't use `the_repository` anymore. Note that
there are still some implicit dependencies on `the_repository`, e.g.
because we use `get_commit_output_encoding()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:09 -08:00
Patrick Steinhardt
8ad30d58f5 builtin/replay: move core logic into "libgit.a"
Move the core logic used to replay commits into "libgit.a" so that it
can be easily reused by other commands. It will be used in a subsequent
commit where we're about to introduce a new git-history(1) command.

Note that with this change we have no sign-comparison warnings anymore,
and neither do we depend on `the_repository`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:09 -08:00
Patrick Steinhardt
a1ac2f8ac4 builtin/replay: extract core logic to replay revisions
We're about to move the core logic used to replay revisions onto a new
base into the "libgit.a" library. Prepare for this by pulling out the
logic into a new function `replay_revisions()` that:

  1. Takes a set of revisions to replay and some options that tell it how
     it ought to replay the revisions.

  2. Replays the commits.

  3. Records any reference updates that would be caused by replaying the
     commits in a structure that is owned by the caller.

The logic itself will be moved into a separate file in the next commit.
This change is not expected to cause user-visible change in behaviour.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:42:09 -08:00
Patrick Steinhardt
a282a8f163 packfile: move MIDX into packfile store
The multi-pack index still is tracked as a member of the object database
source, but ultimately the MIDX is always tied to one specific packfile
store.

Move the structure into `struct packfile_store` accordingly. This
ensures that the packfile store now keeps track of all data related to
packfiles.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:08 -08:00
Patrick Steinhardt
a593373b09 packfile: refactor find_pack_entry() to work on the packfile store
The function `find_pack_entry()` doesn't work on a specific packfile
store, but instead works on the whole repository. This causes a bit of a
conceptual mismatch in its callers:

  - `packfile_store_freshen_object()` supposedly acts on a store, and
    its callers know to iterate through all sources already.

  - `packfile_store_read_object_info()` behaves likewise.

The only exception that doesn't know to handle iteration through sources
is `has_object_pack()`, but that function is trivial to adapt.

Refactor the code so that `find_pack_entry()` works on the packfile
store level instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:07 -08:00
Patrick Steinhardt
6acefa0d2c packfile: inline find_kept_pack_entry()
The `find_kept_pack_entry()` function is only used in
`has_object_kept_pack()`, which is only a trivial wrapper itself. Inline
the latter into the former.

Furthermore, reorder the code so that we can drop the declaration of the
function in "packfile.h". This allows us to make the function file-local.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:07 -08:00
Patrick Steinhardt
8384cbcb4c packfile: only prepare owning store in packfile_store_prepare()
When calling `packfile_store_prepare()` we prepare not only the provided
packfile store, but also all those of all other sources part of the same
object database. This was required when the store was still sitting on
the object database level. But now that it sits on the source level it's
not anymore.

Refactor the code so that we only prepare the single packfile store
passed by the caller. Adapt callers accordingly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:07 -08:00
Patrick Steinhardt
7b330a11de packfile: only prepare owning store in packfile_store_get_packs()
When calling `packfile_store_get_packs()` we prepare not only the
provided packfile store, but also all those of all other sources part of
the same object database. This was required when the store was still
sitting on the object database level. But now that it sits on the source
level it's not anymore.

Adapt the code so that we only prepare the MIDX of the provided store.
All callers only work in the context of a single store or call the
function in a loop over all sources, so this change shouldn't have any
practical effects.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:07 -08:00
Patrick Steinhardt
84f0e60b28 packfile: move packfile store into object source
The packfile store is a member of `struct object_database`, which means
that we have a single store per database. This doesn't really make much
sense though: each source connected to the database has its own set of
packfiles, so there is a conceptual mismatch here. This hasn't really
caused much of a problem in the past, but with the advent of pluggable
object databases this is becoming more of a problem because some of the
sources may not even use packfiles in the first place.

Move the packfile store down by one level from the object database into
the object database source. This ensures that each source now has its
own packfile store, and we can eventually start to abstract it away
entirely so that the caller doesn't even know what kind of store it
uses.

Note that we only need to adjust a relatively small number of callers,
way less than one might expect. This is because most callers are using
`repo_for_each_pack()`, which handles enumeration of all packfiles that
exist in the repository. So for now, none of these callers need to be
adapted. The remaining callers that iterate through the packfiles
directly and that need adjustment are those that are a bit more tangled
with packfiles. These will be adjusted over time.

Note that this patch only moves the packfile store, and there is still a
bunch of functions that seemingly operate on a packfile store but that
end up iterating over all sources. These will be adjusted in subsequent
commits.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:07 -08:00
Patrick Steinhardt
eb9ec52d95 packfile: refactor misleading code when unusing pack windows
The function `unuse_one_window()` is responsible for unmapping one of
the packfile windows, which is done when we have exceeded the allowed
number of window.

The function receives a `struct packed_git` as input, which serves as an
additional packfile that should be considered to be closed. If not
given, we seemingly skip that and instead go through all of the
repository's packfiles. The conditional that checks whether we have a
packfile though does not make much sense anymore, as we dereference the
packfile regardless of whether or not it is a `NULL` pointer to derive
the repository's packfile store.

The function was originally introduced via f0e17e86e1 (pack: move
release_pack_memory(), 2017-08-18), and here we indeed had a caller that
passed a `NULL` pointer. That caller was later removed via 9827d4c185
(packfile: drop release_pack_memory(), 2019-08-12), so starting with
that commit we always pass a `struct packed_git`. In 9c5ce06d74
(packfile: use `repository` from `packed_git` directly, 2024-12-03) we
then inadvertently started to rely on the fact that the pointer is never
`NULL` because we use it now to identify the repository.

Arguably, it didn't really make sense in the first place that the caller
provides a packfile, as the selected window would have been overridden
anyway by the subsequent loop over all packfiles if there was an older
window. So the overall logic is quite misleading overall. The only case
where it _could_ make a difference is when there were two packfiles with
the same `last_used` value, but that case doesn't ever happen because
the `pack_used_ctr` is strictly increasing.

Refactor the code so that we instead pass in the object database to
help make the code less misleading.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:06 -08:00
Patrick Steinhardt
085de91b95 packfile: refactor kept-pack cache to work with packfile stores
The kept pack cache is a cache of packfiles that are marked as kept
either via an accompanying ".kept" file or via an in-memory flag. The
cache can be retrieved via `kept_pack_cache()`, where one needs to pass
in a repository.

Ultimately though the kept-pack cache is a property of the packfile
store, and this causes problems in a subsequent commit where we want to
move down the packfile store to be a per-object-source entity.

Prepare for this and refactor the kept-pack cache to work on top of a
packfile store instead. While at it, rename both the function and flags
specific to the kept-pack cache so that they can be properly attributed
to the respective subsystems.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:06 -08:00
Patrick Steinhardt
0316c63ca4 packfile: pass source to prepare_pack()
When preparing a packfile we pass various pieces attached to the pack's
object database source via the `struct prepare_pack_data`. Refactor this
code to instead pass in the source directly. This reduces the number of
variables we need to pass and allows for a subsequent refactoring where
we start to prepare the pack via the source.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:06 -08:00
Patrick Steinhardt
480336a9ce packfile: create store via its owning source
In subsequent patches we're about to move the packfile store from the
object database layer into the object database source layer. Once done,
we'll have one packfile store per source, where the source is owning the
store.

Prepare for this future and refactor `packfile_store_new()` to be
initialized via an object database source instead of via the object
database itself.

This refactoring leads to a weird in-between state where the store is
owned by the object database but created via the source. But this makes
subsequent refactorings easier because we can now start to access the
owning source of a given store.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:40:06 -08:00
Derrick Stolee
2ac93bfcbc builtin.h: update documentation
The documentation for the builtin API was moved from the technical
documentation and into a comment in builtin.h by ec14d4ecb5 (builtin.h: take
over documentation from api-builtin.txt, 2017-08-02). This documentation
wasn't updated as part of the major overhaul to include a repository struct
in 9b1cb5070f (builtin: add a repository parameter for builtin functions,
2024-09-13).

There was a brief update regarding the move from *.txt to *.adoc by
e8015223c7 (builtin.h: *.txt -> *.adoc fixes, 2025-03-03).

I noticed that there was quite a bit missing from the old documentation,
which is still visible on git-scm.com [1].

[1] https://github.com/git/git-scm.com/issues/2124

This change updates the documentation in the following ways:

 1. Updates the cmd_foo() prototype to include a repository.
 2. Adds some newlines to have uniformity in the list of flags.
 3. Adds a description of the NO_PARSEOPT flag.
 4. Describes the tests that perform checks on all builtins, which may trip
    up a contributor working on a new builtin.

I double-checked these instructions against a toy example in my local branch
to be sure that it was complete.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:37:02 -08:00
K Jayatheerth
dbbf6a901b t7101: modernize test path checks
Replace old-style `test -[df]` and `! test -[df]` assertions with
the modern `test_path_is_file`, `test_path_is_dir`, and
`test_path_is_missing` helpers.

These helpers provide more informative error messages in case of
failure (e.g., "File 'foo' is missing" instead of just exit code 1).

While at it, fix a typo and an incorrect path
reference in one of the test descriptions.

Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:36:07 -08:00
brian m. carlson
02fc44a989 gitfaq: document using stash import/export to sync working tree
Git 2.51 learned how to import and export stashes.  This is a
secure and robust way to transfer working tree states across machines
and comes with almost none of the pitfalls of rsync or other tools.
Recommend this as an alternative in the FAQ.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:32:56 -08:00
Torsten Bögershausen
be5864e08d utf8.c: Enable workaround for iconv under macOS 14/15
The previous commit introduced a workaround in utf8.c to deal
with broken iconv implementations.

It is enabled when
  A MacOS version is used that has a buggy iconv library and
  there is no external library provided (and linked against)
  from neither MacPorts nor Homebrew.

Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:19:58 -08:00
Torsten Bögershausen
9e76ed9bdc utf8.c: Prepare workaround for iconv under macOS 14/15
MacOS14 (Sonoma) has started to ship an iconv library with bugs.
The same bugs exists even in MacOS 15 (Sequoia)

A bug report running the Git test suite says:

three tests of t3900 fail on macOS 26.1 for me:

  not ok 17 - ISO-2022-JP should be shown in UTF-8 now
  not ok 25 - ISO-2022-JP should be shown in UTF-8 now
  not ok 38 - commit --fixup into ISO-2022-JP from UTF-8

Here's the verbose output of the first one:

    ----- snip! -----
    expecting success of 3900.17 'ISO-2022-JP should be shown in UTF-8 now':
		    compare_with ISO-2022-JP "$TEST_DIRECTORY"/t3900/2-UTF-8.t    xt

    --- /Users/x/src/git/t/t3900/2-UTF-8.txt 2024-10-01 19:43:24.605230684 +    0000
    +++ current     2025-12-08 21:52:45.786161909 +0000
    @@ -1,4 +1,4 @@
     はれひほふ

     しているのが、い    るので。
    -濱浜ほれぷりぽれ    まびぐりろへ。
    +濱浜ほれぷりぽれ    まび$0$j$m$X!#
    not ok 17 - ISO-2022-JP should be shown in UTF-8 now
    1..17
    ----- snap! -----

compare_with runs git show to display a commit message, which in this
case here was encoded using ISO-2022-JP and is supposed to be reencoded
to UTF-8, but git show only does that half-way -- the "$0$j$m$X!#" part
is from the original ISO-2022-JP representation.

That botched conversion is done by utf8.c::reencode_string_iconv().  It
calls iconv(3) to do the actual work, initially with an output buffer of
the same size as the input.  If the output needs more space the function
enlarges the buffer and calls iconv(3) again.

iconv(3) won't tell us how much space it needs, but it will report what
part it already managed to convert, so we can increase the buffer and
continue from there.  ISO-2022-JP has escape codes for switching between
character sets, so it's a stateful encoding.  I guess the iconv(3) on my
machine forgets the state at the end of part one and then messes up part
two.

[end of citation]

Working around the buggy iconv shipped with the OS can be done in
two  ways:
a) Link Git against a different version of iconv
b) Improve the handling when iconv needs a larger output buffer

a) is already done by default when either Fink [1] or MacPorts [2]
   or Homebrew [3] is installed.
b) is implemented here, in case that no fixed iconv is available:
   When the output buffer is too short, increase it (as before)
   and start from scratch (this is new).

This workound needs to be enabled with
'#define ICONV_RESTART_RESET'
and a makefile knob will be added in the next commit

Suggested-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Torsten Bögershausen <tboegi@web.de>

[1] https://www.finkproject.org/
[2] https://www.macports.org/
[3] https://brew.sh/

Signed-off-by: Torsten Bögershausen <tboegi@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:19:58 -08:00
Michael Lyons
e40e01a75a doc: git-blame: convert to new doc format
- Use _<placeholder>_ instead of <placeholder> in the description
- Use _underscores_ around math associated with <placeholders>
- Use `backticks` for keywords and more complex option
descriptions. The new rendering engine will apply synopsis rules to
these spans.

Signed-off-by: Michael Lyons <git@michael.lyo.nz>
Acked-by: Jean-Noël AVILA <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:15:53 -08:00
Michael Lyons
2bfc69e648 doc: blame-options: convert to new doc format
- Use _<placeholder>_ instead of <placeholder> in the description
- Modify some samples to use <placeholders>
- Use `backticks` for keywords and more complex option
descriptions. The new rendering engine will apply synopsis rules to
these spans.

Signed-off-by: Michael Lyons <git@michael.lyo.nz>
Acked-by: Jean-Noël AVILA <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:15:31 -08:00
Abraham Samuel Adekunle
8cafc305e2 add -p: show user's hunk decision when selecting hunks
When a user is interactively deciding which hunks to use or skip for
staging, unstaging, stashing etc, there is no way to know the
decision previously chosen for a hunk when navigating through the
previous and next hunks using K/J respectively.

Improve the UI to explicitly show if a user has previously decided to
use a hunk (by pressing 'y') or skip the hunk (by pressing 'n').
This will improve clarity when and aid the navigation process for the
user.

Reported-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Abraham Samuel Adekunle <abrahamadekunle50@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:12:31 -08:00
Kristoffer Haugsbakk
3f051fc9c9 doc: patch-id: --verbatim locks in --stable
The default `--unstable` is a legacy format that predates `--stable`.
That’s why 2871f4d4 (builtin: patch-id: add --verbatim as a command mode,
2022-10-24) made `--verbatim` lock in[1] `--stable`:

    Users of --unstable mainly care about compatibility with old git
    versions, which unstripping the whitespace would break. Thus there
    isn't a usecase for the combination of --verbatim and --unstable,
    and we don't expose this so as to not add maintainence burden.

† 1: imply `--stable`, disallow `--unstable`

Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:08:37 -08:00
Kristoffer Haugsbakk
89d4f3af16 doc: patch-id: spell out the git-diff-tree(1) form
You specifically need `--patch` since the default output is a raw diff.

Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:07:22 -08:00
Kristoffer Haugsbakk
f671f5a83b doc: patch-id: use definite article for the result
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:07:21 -08:00
Kristoffer Haugsbakk
285659cc98 patch-id: use “patch ID” throughout
The “Description” section decided to introduce and use the term “patch
ID” for the ID value itself.  Let’s use the same term on the options as
well.

Also make to sure to use bare “ID” instead of “id”.

Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:07:21 -08:00
Kristoffer Haugsbakk
92a61fe44d doc: patch-id: capitalize Git version
Git versions are always capitalized.

Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:07:21 -08:00
Kristoffer Haugsbakk
3d61c1988b doc: patch-id: don’t use semicolon between bullet points
These bullet points are full-fledged paragraphs with sentences.  It’s
best to restrict semicolon-termination to the case when the bullet list
amounts to a list of items.[1]

† 1: Like “List: ... • first; ... • second; and ... • third.”

Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-09 06:07:20 -08:00
Adrian Ratiu
36d43bef82 submodule: detect conflicts with existing gitdir configs
Credit goes to Emily and Josh for testing and noticing a corner-case
which caused conflicts with existing gitdir configs to silently pass
validation, then fail later in add_submodule() with a cryptic error:

fatal: A git directory for 'nested%2fsub' is found locally with remote(s):
  origin	/.../trash directory.t7425-submodule-gitdir-path-extension/sub

This change ensures the validation step checks existing gitdirs for
conflicts. We only have to do this for submodules having gitdirs,
because those without submodule.%s.gitdir need to be migrated and
will throw an error earlier in the submodule codepath.

Quoting Josh:
 My testing setup has been as follows:
 * Using our locally-built Git with our downstream patch of [1] included:
   * create a repo "sub"
   * create a repo "super"
   * In "super":
     * mkdir nested
     * git submodule add ../sub nested/sub
     * Verify that the submodule's gitdir is .git/modules/nested%2fsub
 * Using a build of git from upstream `next` plus this series:
   * git config set --global extensions.submodulepathconfig true
   * git clone --recurse-submodules super super2
   * create a repo "nested%2fsub"
   * In "super2":
     * git submodule add ../nested%2fsub

At this point I'd expect the collision detection / encoding to take
effect, but instead I get the error listed above.
End quote

Suggested-by: Josh Steadmon <steadmon@google.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:16 +09:00
Adrian Ratiu
a612f856ff submodule: hash the submodule name for the gitdir path
If none of the previous plain-text / encoding / derivation steps work
and case 2.4 is reached, then try a hash of the submodule name to see
if that can be a valid gitdir before giving up and throwing an error.

This is a "last resort" type of measure to avoid conflicts since it
loses the human readability of the gitdir path. This logic will be
reached in rare cases, as can be seen in the test we added.

Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:16 +09:00
Adrian Ratiu
1bb1906270 submodule: fix case-folding gitdir filesystem collisions
Add a new check when extension.submodulePathConfig is enabled, to
detect and prevent case-folding filesystem colisions. When this
new check is triggered, a stricter casefolding aware URI encoding
is used to percent-encode uppercase characters.

By using this check/retry mechanism the uppercase encoding is
only applied when necessary, so case-sensitive filesystems are
not affected.

Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:16 +09:00
Adrian Ratiu
ee437749ff submodule--helper: fix filesystem collisions by encoding gitdir paths
Fix nested filesystem collisions by url-encoding gitdir paths stored
in submodule.%s.gitdir, when extensions.submodulePathConfig is enabled.

Credit goes to Junio and Patrick for coming up with this design: the
encoding is only applied when necessary, to newly added submodules.

Existing modules don't need the encoding because git already errors
out when detecting nested gitdirs before this patch.

This commit adds the basic url-encoding and some tests. Next commits
extend the encode -> validate -> retry loop to fix more conflicts.

Suggested-by: Junio C Hamano <gitster@pobox.com>
Suggested-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:15 +09:00
Adrian Ratiu
7621825c43 builtin/credential-store: move is_rfc3986_unreserved to url.[ch]
is_rfc3986_unreserved() was moved to credential-store.c and was made
static by f89854362c (credential-store: move related functions to
credential-store file, 2023-06-06) under a correct assumption, at the
time, that it was the only place using it.

However now we need it to apply URL-encoding to submodule names when
constructing gitdir paths, to avoid conflicts, so bring it back as a
public function exposed via url.h, instead of the old helper path
(strbuf), which has nothing to do with 3986 encoding/decoding anymore.

This function will be used in subsequent commits which do the encoding.

Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:15 +09:00
Adrian Ratiu
d5a4b9b73a submodule--helper: add gitdir migration command
Manually running
"git config submodule.<name>.gitdir .git/modules/<name>"
for each submodule can be impractical, so add a migration command to
submodule--helper to automatically create configs for all submodules
as required by extensions.submodulePathConfig.

The command calls create_default_gitdir_config() which validates the
gitdir paths before adding the configs.

Suggested-by: Junio C Hamano <gitster@pobox.com>
Suggested-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:15 +09:00
Adrian Ratiu
4cf8c114e3 submodule: allow runtime enabling extensions.submodulePathConfig
Add a new config `init.defaultSubmodulePathConfig` which allows
enabling `extensions.submodulePathConfig` for new submodules by
default (those created via git init or clone).

Important: setting init.defaultSubmodulePathConfig = true does
not globally enable `extensions.submodulePathConfig`. Existing
repositories will still have the extension disabled and will
require migration (for example via git submodule--helper command
added in the next commit).

Suggested-by: Patrick Steinhardt <ps@pks.im>
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:15 +09:00
Adrian Ratiu
7ad97f4bea submodule: introduce extensions.submodulePathConfig
The idea of this extension is to abstract away the submodule gitdir
path implementation: everyone is expected to use the config and not
worry about how the path is computed internally, either in git or
other implementations.

With this extension enabled, the submodule.<name>.gitdir repo config
becomes the single source of truth for all submodule gitdir paths.

The submodule.<name>.gitdir config is added automatically for all new
submodules when this extension is enabled.

Git will throw an error if the extension is enabled and a config is
missing, advising users how to migrate. Migration is manual for now.

E.g. to add a missing config entry for an existing "foo" module:
git config submodule.foo.gitdir .git/modules/foo

Suggested-by: Junio C Hamano <gitster@pobox.com>
Suggested-by: Phillip Wood <phillip.wood123@gmail.com>
Suggested-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:15 +09:00
Adrian Ratiu
376c94b167 builtin/submodule--helper: add gitdir command
This exposes the gitdir name computed by submodule_name_to_gitdir()
internally, to make it easier for users and tests to interact with it.

Next commit will add a gitdir configuration, so this helper can also be
used to easily query that config or validate any gitdir path the user
sets (submodule_name_to_git_dir now runs the validation logic, since
our previous commit).

Based-on-patch-by: Brandon Williams <bwilliams.eng@gmail.com>
Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:15 +09:00
Adrian Ratiu
a05c78d224 submodule: always validate gitdirs inside submodule_name_to_gitdir
Move the ad-hoc validation checks sprinkled across the source tree,
after calling submodule_name_to_gitdir() into the function proper,
which now always validates the gitdir before returning it.

This simplifies the API and helps to:
1. Avoid redundant validation calls after submodule_name_to_gitdir().
2. Avoid the risk of callers forgetting to validate.
3. Ensure gitdir paths provided by users via configs are always valid
   (config gitdir paths are added in a subsequent commit).

The validation function can still be called as many times as needed
outside submodule_name_to_gitdir(), for example we keep two calls
which are still required, to avoid parallel clone races by re-running
the validation in builtin/submodule-helper.c.

Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:14 +09:00
Adrian Ratiu
aac1ec71fd submodule--helper: use submodule_name_to_gitdir in add_submodule
While testing submodule gitdir path encoding, I noticed submodule--helper
is still using a hardcoded modules gitdir path leading to test failures.

Call the submodule_name_to_gitdir() helper instead, which was invented
exactly for this purpose and is already used by all the other locations
which work on gitdirs.

Also narrow the scope of the submod_gitdir_path variable which is not
used anymore in the updated "else" branch.

Signed-off-by: Adrian Ratiu <adrian.ratiu@collabora.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:05:14 +09:00
Patrick Steinhardt
57592cba24 packfile: drop repository parameter from packed_object_info()
The function `packed_object_info()` takes a packfile and offset and
returns the object info for the corresponding object. Despite these two
parameters though it also takes a repository pointer. This is redundant
information though, as `struct packed_git` already has a repository
pointer that is always populated.

Drop the redundant parameter.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:23 +09:00
Patrick Steinhardt
ca3b4933e9 packfile: skip unpacking object header for disk size requests
While most of the object info requests for a packed object require us to
unpack its headers, reading its disk size doesn't. We still unpack the
object header in that case though, which is unnecessary work.

Skip reading the header if only the disk size is requested. This leads
to a small speedup when reading disk size, only. The following benchmark
was done in the Git repository:

    Benchmark 1: ./git rev-list --disk-usage HEAD (rev = HEAD~)
      Time (mean ± σ):     105.2 ms ±   0.6 ms    [User: 91.4 ms, System: 13.3 ms]
      Range (min … max):   103.7 ms … 106.0 ms    27 runs

    Benchmark 2: ./git rev-list --disk-usage HEAD (rev = HEAD)
      Time (mean ± σ):      96.7 ms ±   0.4 ms    [User: 86.2 ms, System: 10.0 ms]
      Range (min … max):    96.2 ms …  98.1 ms    30 runs

    Summary
      ./git rev-list --disk-usage HEAD (rev = HEAD) ran
        1.09 ± 0.01 times faster than ./git rev-list --disk-usage HEAD (rev = HEAD~)

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:23 +09:00
Patrick Steinhardt
9c82ba6c32 packfile: disentangle return value of packed_object_info()
The `packed_object_info()` function returns the type of the packed
object. While we use an `enum object_type` to store the return value,
this type is not to be confused with the actual object type. It _may_
contain the object type, but it may just as well encode that the given
packed object is stored as a delta.

We have removed the only caller that relied on this returned object type
in the preceding commit, so let's simplify semantics and return either 0
on success or a negative error code otherwise.

This unblocks a small optimization where we can skip reading the object
type altogether.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:23 +09:00
Patrick Steinhardt
03d894e23c packfile: always populate pack-specific info when reading object info
When reading object information via `packed_object_info()` we may not
populate the object info's packfile-specific fields. This leads to
inconsistent object info depending on whether the info was populated via
`packfile_store_read_object_info()` or `packed_object_info()`.

Fix this inconsistency so that we can always assume the pack info to be
populated when reading object info from a pack.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:22 +09:00
Patrick Steinhardt
56be11f501 packfile: extend is_delta field to allow for "unknown" state
The `struct object_info::u::packed::is_delta` field determines whether
or not a specific object is stored as a delta. It only stores whether or
not the object is stored as delta, so it is treated as a boolean value.

This boolean is insufficient though: when reading a packed object via
`packfile_store_read_object_info()` we know to skip parsing the actual
object when the user didn't request any object-specific data. In that
case we won't read the object itself, but will only look up its position
in the packfile. Consequently, we do not know whether it is a delta or
not.

This isn't really an issue right now, as the check for an empty request
is broken. But a subsequent commit will fix it, and once we do we will
have the need to also represent an "unknown" delta state.

Prepare for this change by introducing a new enum that encodes the
object type. We don't use the "unknown" state just yet, but will start
to do so in a subsequent commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:22 +09:00
Patrick Steinhardt
0ff0f991df packfile: always declare object info to be OI_PACKED
When reading object info via a packfile we yield one of two types:

  - The object can either be OI_PACKED, which is what a caller would
    typically expect.

  - Or it can be OI_DBCACHED if it is stored in the delta base cache.

The latter really is an implementation detail though, and callers
typically don't care at all about the difference. Furthermore, the
information whether or not it is part of the delta base cache can
already be derived via the `is_delta` field, so the fact that we discern
between OI_PACKED and OI_DBCACHED only further complicates the
interface.

There aren't all that many callers that care about the `whence` field in
the first place. In fact, there's only three:

  - `packfile_store_read_object_info()` checks for `whence == OI_PACKED`
    and then populates the packfile information of the object info
    structure. We now start to do this also for deltified objects, which
    gives its callers strictly more information.

  - `repack_local_links()` wants to determine whether the object is part
    of a promisor pack and checks for `whence == OI_PACKED`. If so, it
    verifies that the packfile is a promisor pack. It's arguably wrong
    to declare that an object is not part of a promisor pack only
    because it is stored in the delta base cache.

  - `is_not_in_promisor_pack_obj()` does the same, but checks that a
    specific object is _not_ part of a promisor pack. The same reasoning
    as above applies.

Drop the OI_DBCACHED enum completely. None of the callers seem to care
about the distinction.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:22 +09:00
Patrick Steinhardt
b3b89b691c object-file: always set OI_LOOSE when reading object info
There are some early returns in `odb_source_loose_read_object_info()`
in cases where we don't have to open the loose object. These return
paths do not set `struct object_info::whence` to `OI_LOOSE` though, so
it becomes impossible for the caller to tell the format of such an
object.

The root cause of this really is that we have so many different return
paths in the function. As a consequence, it's harder than necessary to
make sure that all successful exit paths sot up the `whence` field as
expected.

Address this by refactoring the function to have a single exit path.
Like this, we can trivially set up the `whence` field when we exit
successfully from the function.

Note that we also:

  - Rename `status` to `ret` to match our usual coding style, but also
    to show that the old `status` variable is now always getting the
    expected value. Furthermore, the value is not initialized anymore,
    which has the consequence that most compilers will warn for exit
    paths where we forgot to set it.

  - Move the setup of scratch pointers closer to `parse_loose_header()`
    to show where it's needed.

  - Guard a couple of variables on cleanup so that they only get
    released in case they have been set up.

  - Reset `oi->delta_base_oid` towards the end of the function, together
    with all the other object info pointers.

Overall, all these changes result in a diff that is somewhat hard to
read. But the end result is significantly easier to read and reason
about, so I'd argue this one-time churn is worth it.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:04:22 +09:00
Ashlesh Gawande
5913fd26aa t5550: add netrc tests for http 401/403
git allows using .netrc file to supply credentials for HTTP auth.
Three test cases are added in this patch to provide missing coverage
when cloning over HTTP using .netrc file:

  - First test case checks that the git clone is successful when credentials
    are provided via .netrc file
  - Second test case checks that the git clone fails when the .netrc file
    provides invalid credentials. The HTTP server is expected to return
    401 Unauthorized in such a case. The test checks that the user is
    provided with a prompt for username/password on 401 to provide
    the valid ones.
  - Third test case checks that the git clone fails when the .netrc file
    provides credentials that are valid but do not have permission for
    this user. For example one may have multiple tokens in GitHub
    and uses the one which was not authorized for cloning this repo.
    In such a case the HTTP server returns 403 Forbidden.
    For this test, the apache.conf is modified to return a 403
    on finding a forbidden-user. No prompt for username/password is
    expected after the 403 (unlike 401). This is because prompting may wipe
    out existing credentials or conflict with custom credential helpers.

Signed-off-by: Ashlesh Gawande <git@ashlesh.me>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 11:02:39 +09:00
Junio C Hamano
f42d7c9067 Merge branch 'kh/replay-invalid-onto-advance' into ps/history
* kh/replay-invalid-onto-advance:
  t3650: add more regression tests for failure conditions
  replay: die if we cannot parse object
  replay: improve code comment and die message
  replay: die descriptively when invalid commit-ish is given
  replay: find *onto only after testing for ref name
  replay: remove dead code and rearrange
2026-01-08 11:01:22 +09:00
Paulo Casaretto
2d46e361fd lockfile: add PID file for debugging stale locks
When a lock file is held, it can be helpful to know which process owns
it, especially when debugging stale locks left behind by crashed
processes. Add an optional feature that creates a companion PID file
alongside each lock file, containing the PID of the lock holder.

For a lock file "foo.lock", the PID file is named "foo~pid.lock". The
tilde character is forbidden in refnames and allowed in Windows
filenames, which guarantees no collision with the refs namespace
(e.g., refs "foo" and "foo~pid" cannot both exist). The file contains
a single line in the format "pid <value>" followed by a newline.

The PID file is created when a lock is acquired (if enabled), and
automatically cleaned up when the lock is released (via commit or
rollback). The file is registered as a tempfile so it gets cleaned up
by signal and atexit handlers if the process terminates abnormally.

When a lock conflict occurs, the code checks for an existing PID file
and, if found, uses kill(pid, 0) to determine if the process is still
running. This allows providing context-aware error messages:

  Lock is held by process 12345. Wait for it to finish, or remove
  the lock file to continue.

Or for a stale lock:

  Lock was held by process 12345, which is no longer running.
  Remove the stale lock file to continue.

The feature is controlled via core.lockfilePid configuration (boolean).
Defaults to false. When enabled, PID files are created for all lock
operations.

Existing PID files are always read when displaying lock errors,
regardless of the core.lockfilePid setting. This ensures helpful
diagnostics even when the feature was previously enabled and later
disabled.

Signed-off-by: Paulo Casaretto <pcasaretto@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-08 09:59:36 +09:00
Elijah Newren
1df7743798 fsck: snapshot default refs before object walk
Fsck has a race when operating on live repositories; consider the
following simple script that writes new commits as fsck runs:

    #!/bin/bash
    git fsck &
    PID=$!

    while ps -p $PID >/dev/null; do
        sleep 3
        git commit -q --allow-empty -m "Another commit"
    done

Since fsck walks objects for connectivity and then reads the refs at the
end to check, this can cause fsck to get confused and think that the new
refs refer to missing commits and that new reflog entries are invalid.
Running the above script in a clone of git.git results in the following
(output ellipsized to remove additional errors of the same type):

    $ ./fsck-while-writing.sh
    Checking ref database: 100% (1/1), done.
    Checking object directories: 100% (256/256), done.
    warning in tag d6602ec5194c87b0fc87103ca4d67251c76f233a: missingTaggerEntry: invalid format - expected 'tagger' line
    Checking objects: 100% (835091/835091), done.
    error: HEAD: invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310
    error: HEAD: invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310
    error: HEAD: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68
    error: HEAD: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68
    [...]
    error: HEAD: invalid reflog entry 87c8a5c2f6b79d9afa9e941590b9a097b6f7ac09
    error: HEAD: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a
    error: HEAD: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a
    error: HEAD: invalid reflog entry 6724f2dfede88bfa9445a333e06e78536c0c6c0d
    error: refs/heads/mybranch invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310
    error: refs/heads/mybranch: invalid reflog entry 2aac9f9286e2164fbf8e4f1d1df53044ace2b310
    error: refs/heads/mybranch: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68
    error: refs/heads/mybranch: invalid reflog entry da0f5b80d61844a6f0ad2ddfd57e4fdfa246ea68
    [...]
    error: refs/heads/mybranch: invalid reflog entry 87c8a5c2f6b79d9afa9e941590b9a097b6f7ac09
    error: refs/heads/mybranch: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a
    error: refs/heads/mybranch: invalid reflog entry d80887a48865e6ad165274b152cbbbed29f8a55a
    error: refs/heads/mybranch: invalid reflog entry 6724f2dfede88bfa9445a333e06e78536c0c6c0d
    Checking connectivity: 833846, done.
    missing commit 6724f2dfede88bfa9445a333e06e78536c0c6c0d
    Verifying commits in commit graph: 100% (242243/242243), done.

We can minimize the race opportunities by taking a snapshot of refs at
program invocation, doing the connectivity check, and then checking the
snapshotted refs afterward.  This avoids races with regular refs between
fsck and adding objects to the database, though it still leaves a race
between a gc and fsck.  We are less concerned about folks simultaneously
running gc with fsck; though, if it becomes an issue, we could lock fsck
during gc.  We definitely do not want to lock fsck during operations
that may add objects to the object store; that would be problematic for
forges.

Note that refs aren't the only problem, though; reflog entries and index
entries could be problematic as well.  For now we punt on index entries
just leaving a TODO comment, and for reflogs we use a coarse solution of
taking the time at the beginning of the program and ignoring reflog
entries newer than that time.  That may be imperfect if dealing with a
network filesystem, so we leave TODO comment for those that want to
improve that handling as well.

As a high level overview:
  * In addition to fsck_handle_ref(), which now is only a few lines long
    to process a ref, there's also a snapshot_ref() which is called
    early in the program for each ref and takes all the error checking
    logic.
  * The iterating over refs that used to be in get_default_heads() plus
    a loop over the arguments now appears in shapshot_refs().
  * There's a new process_refs() as well that kind of looks like the old
    get_default_heads() though it is streamlined due to the work done by
    snapshot_refs().

This combination of changes modifies the output of running the script
(from the beginning of this commit message) to:

    $ ./fsck-while-writing.sh
    Checking ref database: 100% (1/1), done.
    Checking object directories: 100% (256/256), done.
    warning in tag d6602ec5194c87b0fc87103ca4d67251c76f233a: missingTaggerEntry: invalid format - expected 'tagger' line
    Checking objects: 100% (835091/835091), done.
    Checking connectivity: 833846, done.
    Verifying commits in commit graph: 100% (242243/242243), done.

While worries about live updates while running fsck is likely of most
interest for forge operators, it may also benefit those with
automated jobs (such as git maintenance) or even casual users who want
to do other work in their clone while fsck is running.

Originally-based-on-a-patch-by: Matthew John Cheetham <mjcheetham@outlook.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 11:37:34 +09:00
Junio C Hamano
f1ec43d4d2 Merge branch 'ps/odb-misc-fixes' into ps/packfile-store-in-odb-source
* ps/odb-misc-fixes:
  odb: properly close sources before freeing them
  builtin/gc: fix condition for whether to write commit graphs
2026-01-07 09:37:29 +09:00
Andrew Chitester
6c5c7e7071 t1420: modernize the lost-found test
This test indirectly checks that the lost-found folder has 2 files in it
and then checks that the expected two files exist. Make this more
deliberate by removing the old test -f and compare the actual ls of the
lost-found directory with the expected files.

Signed-off-by: Andrew Chitester <andchi@fastmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 09:19:41 +09:00
Patrick Steinhardt
3d09968656 odb: properly close sources before freeing them
It is possible to hit a memory leak when reading data from a submodule
via git-grep(1):

  Direct leak of 192 byte(s) in 1 object(s) allocated from:
    #0 0x55555562e726 in calloc (git+0xda726)
    #1 0x555555964734 in xcalloc ../wrapper.c:154:8
    #2 0x555555835136 in load_multi_pack_index_one ../midx.c:135:2
    #3 0x555555834fd6 in load_multi_pack_index ../midx.c:382:6
    #4 0x5555558365b6 in prepare_multi_pack_index_one ../midx.c:716:17
    #5 0x55555586c605 in packfile_store_prepare ../packfile.c:1103:3
    #6 0x55555586c90c in packfile_store_reprepare ../packfile.c:1118:2
    #7 0x5555558546b3 in odb_reprepare ../odb.c:1106:2
    #8 0x5555558539e4 in do_oid_object_info_extended ../odb.c:715:4
    #9 0x5555558533d1 in odb_read_object_info_extended ../odb.c:862:8
    #10 0x5555558540bd in odb_read_object ../odb.c:920:6
    #11 0x55555580a330 in grep_source_load_oid ../grep.c:1934:12
    #12 0x55555580a13a in grep_source_load ../grep.c:1986:10
    #13 0x555555809103 in grep_source_is_binary ../grep.c:2014:7
    #14 0x555555807574 in grep_source_1 ../grep.c:1625:8
    #15 0x555555807322 in grep_source ../grep.c:1837:10
    #16 0x5555556a5c58 in run ../builtin/grep.c:208:10
    #17 0x55555562bb42 in void* ThreadStartFunc<false>(void*) lsan_interceptors.cpp.o
    #18 0x7ffff7a9a979 in start_thread (/nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/libc.so.6+0x9a979) (BuildId: cddea92d6cba8333be952b5a02fd47d61054c5ab)
    #19 0x7ffff7b22d2b in __GI___clone3 (/nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/libc.so.6+0x122d2b) (BuildId: cddea92d6cba8333be952b5a02fd47d61054c5ab)

The root caues of this leak is the way we set up and release the
submodule:

  1. We use `repo_submodule_init()` to initialize a new repository. This
     repository is stored in `repos_to_free`.

  2. We now read data from the submodule repository.

  3. We then call `repo_clear()` on the submodule repositories.

  4. `repo_clear()` calls `odb_free()`.

  5. `odb_free()` calls `odb_free_sources()` followed by `odb_close()`.

The issue here is the 5th step: we call `odb_free_sources()` _before_ we
call `odb_close()`. But `odb_free_sources()` already frees all sources,
so the logic that closes them in `odb_close()` now becomes a no-op. As a
consequence, we never explicitly close sources at all.

Fix the leak by closing the store before we free the sources.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 09:16:51 +09:00
Patrick Steinhardt
b3449b1517 builtin/gc: fix condition for whether to write commit graphs
When performing auto-maintenance we check whether commit graphs need to
be generated by counting the number of commits that are reachable by any
reference, but not covered by a commit graph. This search is performed
by iterating through all references and then doing a depth-first search
until we have found enough commits that are not present in the commit
graph.

This logic has a memory leak though:

  Direct leak of 16 byte(s) in 1 object(s) allocated from:
      #0 0x55555562e433 in malloc (git+0xda433)
      #1 0x555555964322 in do_xmalloc ../wrapper.c:55:8
      #2 0x5555559642e6 in xmalloc ../wrapper.c:76:9
      #3 0x55555579bf29 in commit_list_append ../commit.c:1872:35
      #4 0x55555569f160 in dfs_on_ref ../builtin/gc.c:1165:4
      #5 0x5555558c33fd in do_for_each_ref_iterator ../refs/iterator.c:431:12
      #6 0x5555558af520 in do_for_each_ref ../refs.c:1828:9
      #7 0x5555558ac317 in refs_for_each_ref ../refs.c:1833:9
      #8 0x55555569e207 in should_write_commit_graph ../builtin/gc.c:1188:11
      #9 0x55555569c915 in maintenance_is_needed ../builtin/gc.c:3492:8
      #10 0x55555569b76a in cmd_maintenance ../builtin/gc.c:3542:9
      #11 0x55555575166a in run_builtin ../git.c:506:11
      #12 0x5555557502f0 in handle_builtin ../git.c:779:9
      #13 0x555555751127 in run_argv ../git.c:862:4
      #14 0x55555575007b in cmd_main ../git.c:984:19
      #15 0x5555557523aa in main ../common-main.c:9:11
      #16 0x7ffff7a2a4d7 in __libc_start_call_main (/nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/libc.so.6+0x2a4d7) (BuildId: cddea92d6cba8333be952b5a02fd47d61054c5ab)
      #17 0x7ffff7a2a59a in __libc_start_main@GLIBC_2.2.5 (/nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/libc.so.6+0x2a59a) (BuildId: cddea92d6cba8333be952b5a02fd47d61054c5ab)
      #18 0x5555555f0934 in _start (git+0x9c934)

The root cause of this memory leak is our use of `commit_list_append()`.
This function expects as parameters the item to append and the _tail_ of
the list to append. This tail will then be overwritten with the new tail
of the list so that it can be used in subsequent calls. But we call it
with `commit_list_append(parent->item, &stack)`, so we end up losing
everything but the new item.

This issue only surfaces when counting merge commits. Next to being a
memory leak, it also shows that we're in fact miscounting as we only
respect children of the last parent. All previous parents are discarded,
so their children will be disregarded unless they are hit via another
reference.

While crafting a test case for the issue I was puzzled that I couldn't
establish the proper border at which the auto-condition would be
fulfilled. As it turns out, there's another bug: if an object is at the
tip of any reference we don't mark it as seen. Consequently, if it is
the tip of or reachable via another ref, we'd count that object multiple
times.

Fix both of these bugs so that we properly count objects without leaking
any memory.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 09:16:50 +09:00
Jeff King
9e8b448dd8 cat-file: only use bitmaps when filtering
Commit 8002e8ee18 (builtin/cat-file: use bitmaps to efficiently filter
by object type, 2025-04-02) introduced a performance regression when we
are not filtering objects: it uses bitmaps even when they won't help,
incurring extra costs. For example, running the new perf tests from this
commit, which check the performance of listing objects by oid:

  $ export GIT_PERF_LARGE_REPO=/path/to/linux.git
  $ git -C "$GIT_PERF_LARGE_REPO" repack -adb
  $ GIT_SKIP_TESTS=p1006.1 ./run 8002e8ee18^ 8002e8ee18 p1006-cat-file.sh
  [...]
  Test                                  8002e8ee18^       8002e8ee18
  -------------------------------------------------------------------------------
  1006.2: list all objects (sorted)     1.48(1.44+0.04)   6.39(6.35+0.04) +331.8%
  1006.3: list all objects (unsorted)   3.01(2.97+0.04)   3.40(3.29+0.10) +13.0%
  1006.4: list blobs                    4.85(4.67+0.17)   1.68(1.58+0.10) -65.4%

An invocation that filters, like listing all blobs (1006.4), does
benefit from using the bitmaps; it now doesn't have to check the type of
each object from the pack data, so the tradeoff is worth it.

But for listing all objects in sorted idx order (1006.2), we otherwise
would never open the bitmap nor the revindex file. Worse, our sorting
step gets much worse. Normally we append into an array in pack .idx
order, and the sort step is trivial. But with bitmaps, we get the
objects in pack order, which is apparently random with respect to oid,
and have to sort the whole thing. (Note that this freshly-packed state
represents the best case for .idx sorting; if we had two packs, then
we'd have their objects one after the other and qsort would have to
interleave them).

The unsorted test in 1006.3 is interesting: there we are going in pack
order, so we load the revindex for the pack anyway. And though we don't
sort the result, we do use an oidset to check for duplicates. So we can
see in the 8002e8ee18^ timings that those two things cost ~1.5s over the
sorted case (mostly the oidset hash cost). We also incur the extra cost
to open the bitmap file as of 8002e8ee18, which seems to be ~400ms.
(This would probably be faster with a bitmap lookup table, but writing
that out is not yet the default).

So we know that bitmaps help when there's filtering to be done, but
otherwise make things worse. Let's only use them when there's a filter.

The perf script shows that we've fixed the regressions without hurting
the bitmap case:

  Test                                  8002e8ee18^       8002e8ee18                HEAD
  --------------------------------------------------------------------------------------------------------
  1006.2: list all objects (sorted)     1.56(1.53+0.03)   6.44(6.37+0.06) +312.8%   1.62(1.54+0.06) +3.8%
  1006.3: list all objects (unsorted)   3.04(2.98+0.06)   3.45(3.38+0.07) +13.5%    3.04(2.99+0.04) +0.0%
  1006.4: list blobs                    5.14(4.98+0.15)   1.76(1.68+0.06) -65.8%    1.73(1.64+0.09) -66.3%

Note that there's another related case: we might have a filter that
cannot be used with bitmaps. That check is handled already for us in
for_each_bitmapped_object(), though we'd still load the bitmap and
revindex files pointlessly in that case. I don't think it can happen in
practice for cat-file, though, since it allows only blob:none,
blob:limit, and object:type filters, all of which work with bitmaps.

It would be easy-ish to insert an extra check like:

  can_filter_bitmap(&opt->objects_filter);

into the conditional, but I didn't bother here. It would be redundant
with the call in for_each_bitmapped_object(), and the can_filter helper
function is static local in the bitmap code (so we'd have to make it
public).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 09:05:12 +09:00
Jeff King
79d301c767 t/perf/run: preserve GIT_PERF_* from environment
If you run:

  GIT_PERF_LARGE_REPO=/some/path ./p1006-cat-file.sh

it will use the repo in /some/path. But if you use the "run" helper
script to aggregate and compare results, like this:

  GIT_PERF_LARGE_REPO=/some/path ./run HEAD^ HEAD p1006-cat-file.sh

it will ignore that variable. This is because the presence of the
LARGE_REPO variable in GIT-BUILD-OPTIONS overrides what's in the
environment. This started with 4638e8806e (Makefile: use common template
for GIT-BUILD-OPTIONS, 2024-12-06), which now writes even empty
variables (though arguably it was wrong even before with a non-empty
value, as we generally prefer the environment to take precedence over
on-disk config).

We had the same problem in perf-lib.sh itself, and we hacked around it
with 32b74b9809 (perf: do allow `GIT_PERF_*` to be overridden again,
2025-04-04). That's what lets the direct invocation of "./p1006" work
above.

And in fact that was sufficient for "./run", too, until it started
loading GIT-BUILD-OPTIONS itself in 5756ccd181 (t/perf: fix benchmarks
with out-of-tree builds, 2025-04-28). Now it has the same problem: it
clobbers any incoming GIT_PERF options from the environment.

We can use the same hack here in the "run" script. It's quite ugly, but
it's just short enough that I don't think it's worth trying to factor it
out into a common shell library.

In the long run, we might consider teaching GIT-BUILD-OPTIONS to be more
gentle in overwriting existing entries. There are probably other
GIT_TEST_* variables which would need the same treatment. And if and
when we come up with a more complete solution, we can use it in both
spots.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 08:56:36 +09:00
Jeff King
aad1d1c0d5 t/perf/perf-lib: fix assignment of TEST_OUTPUT_DIRECTORY
Using the perf suite's "run" helper in a vanilla build fails like this:

  $ make && (cd t/perf && ./run p0000-perf-lib-sanity.sh)
  === Running 1 tests in this tree ===
  perf 1 - test_perf_default_repo works: 1 2 3 ok
  perf 2 - test_checkout_worktree works: 1 2 3 ok
  ok 3 - test_export works
  perf 4 - export a weird var: 1 2 3 ok
  perf 5 - éḿíẗ ńöń-ÁŚĆÍÍ ćḧáŕáćẗéŕś: 1 2 3 ok
  ok 6 - test_export works with weird vars
  perf 7 - important variables available in subshells: 1 2 3 ok
  perf 8 - test-lib-functions correctly loaded in subshells: 1 2 3 ok
  # passed all 8 test(s)
  1..8
  cannot open test-results/p0000-perf-lib-sanity.subtests: No such file or directory at ./aggregate.perl line 159.

It is trying to aggregate results written into t/perf/test-results, but
the p0000 script did not write anything there.

The "run" script looks in $TEST_OUTPUT_DIRECTORY/test-results, or if
that variable is not set, in test-results in the current working
directory (which should be t/perf itself). It pulls the value of
$TEST_OUTPUT_DIRECTORY from the GIT-BUILD-OPTIONS file.

But that doesn't quite match the setup in perf-lib.sh (which is what
scripts like p0000 use). There we do this at the top of the script:

  TEST_OUTPUT_DIRECTORY=$(pwd)

and then let test-lib.sh append "/test-results" to that. Historically,
that made the vanilla case work: we'd always use t/perf/test-results.
But when $TEST_OUTPUT_DIRECTORY was set, it would break.

Commit 5756ccd181 (t/perf: fix benchmarks with out-of-tree builds,
2025-04-28) fixed that second case by loading GIT-BUILD-OPTIONS
ourselves. But that broke the vanilla case!

Now our setting of $TEST_OUTPUT_DIRECTORY in perf-lib.sh is ignored,
because it is overwritten by GIT-BUILD-OPTIONS. And when test-lib.sh
sees that the output directory is empty, it defaults to t/test-results,
rather than t/perf/test-results.

Nobody seems to have noticed, probably for two reasons:

  1. It only matters if you're trying to aggregate results (like the
     "run" script does). Just running "./p0000-perf-lib-sanity.sh"
     manually still produces useful output; the stored result files are
     just in an unexpected place.

  2. There might be leftover files in t/perf/test-results from previous
     runs (before 5756ccd181). In particular, the ".subtests" files
     don't tend to change, and the lack of that file is what causes it
     to barf completely. So it's possible that the aggregation could
     have been showing stale results that did not match the run that
     just happened.

We can fix it by setting TEST_OUTPUT_DIRECTORY only after we've loaded
GIT-BUILD-OPTIONS, so that we override its value and not the other way
around. And we'll do so only when the variable is not set, which should
retain the fix for that case from 5756ccd181.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-07 08:56:36 +09:00
Julia Evans
555c8464e5 doc: git-reset: clarify git reset <pathspec>
From user feedback:

- Continued confusion about the terms "tree-ish" and "pathspec"
- The word "hunks" is confusing folks, use "changes" instead.
- On the part about `git restore`, there were a few comments to the
  effect of "wait, this doesn't actually update any files? What? Why?"
  Be more direct that `git reset` does not update files: there's no
  obvious reason to suggest that folks use `git reset` followed by `git
  restore`, instead suggest just using `git restore`.

Continue avoiding the use of the word "reset" to
describe what "git reset" does.

Signed-off-by: Julia Evans <julia@jvns.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 08:10:14 +09:00
Julia Evans
7fb080a790 doc: git-reset: clarify git reset [mode]
From user feedback, there was some confusion about the differences
between the modes, including:

1. Sometimes it says "index" and sometimes "index file".
   Fix by replacing "index file" with "index".
2. Many comments about not being able to understand what `--merge` does.
   Fix by mentioning obscure situations, since that seems to be what
   it's for. Most folks will use `git <cmd> --abort`.
3. Issues telling the difference between --soft and --mixed, as well as
   --keep. Leave --keep alone because I couldn't understand its use case,
   but change `--soft` / `--mixed` / `--hard` as follows:

--mixed is the default, so put it first.

Describe --soft/--mixed/--hard with the following structure:

* Start by saying what happens to the files in the working directory,
  because the thing users want to avoid most is irretrievably losing
  changes to their working directory files.
* Then describe what happens to the staging area. Right now it seems to
  frame leaving the index alone as being a sort of neutral action.
  I think this is part of what's confusing users, because in Git when
  you update HEAD, Git almost always updates the index to match HEAD.
  So leaving the index unchanged while updating HEAD is actually quite
  unusual, and it deserves to be flagged.
* Finally, give an example for --soft to explain a common use case.

Signed-off-by: Julia Evans <julia@jvns.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 08:10:14 +09:00
Julia Evans
296834217d doc: git-reset: clarify intro
From user feedback, there were several points of confusion:

- What "tree-ish", "entries", "working tree", "HEAD", and "index" mean
  ("I have no clue what the index is", "I've been using git for 20 years
  and still don't know what a tree-ish is"). Avoid using these terms
  where it makes sense.
- What "optionally modifying index and working tree to match" means
  ("to match what?" "optionally based on what?")
  Remove this from the intro, we can say it later when giving more
  details.
- One user suggested that "The <tree-ish>/<commit> defaults to HEAD
  in all forms." should be repeated later on, since it's easy to miss.
  Instead say that HEAD is the default in each case later.

Another issue is that `git reset` consistently describes the action
it does as "Reset ...", commands should not use their name to describe
themselves, and that the word "mode" is used to mean several different
things on this page.

Address these by being more clear about two use cases for `git reset`
("to undo operations" and "to update staged files"), and explaining what
the conditions are for each case instead of forcing the user to figure
out the pattern is in first form vs the other 3 forms.

Signed-off-by: Julia Evans <julia@jvns.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 08:10:14 +09:00
Julia Evans
b767867fae doc: git-reset: reorder the forms
From user feedback: three users commented that the `git reset [mode]`
form is the one that they primarily use, and that they were suprised to
see it listed last.
("I've never used git reset in any mode other than --hard").

Move it to be first, since the `git reset [mode]` form is what
"Reset current HEAD to the specified state" at the beginning refers
to, and because the `git reset [mode]` form is the only thing that
`git reset` uniquely does, the others could also be done with
`git restore`.

Signed-off-by: Julia Evans <julia@jvns.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 08:10:13 +09:00
Kristoffer Haugsbakk
56b77a687e t3650: add more regression tests for failure conditions
There isn’t much test coverage for basic failure conditions. Let’s add
a few more since these are simple to write and remove if they become
obsolete.

Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 07:30:16 +09:00
Kristoffer Haugsbakk
6f693364cc replay: die if we cannot parse object
`parse_object` can return `NULL`. That will in turn make
`repo_peel_to_type` return the same.

Let’s die fast and descriptively with the `*_or_die` variant.

Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 07:30:16 +09:00
Kristoffer Haugsbakk
f67f7ddbbd replay: improve code comment and die message
Suggested-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 07:30:16 +09:00
Kristoffer Haugsbakk
3074d08cfa replay: die descriptively when invalid commit-ish is given
Giving an invalid commit-ish to `--onto` makes git-replay(1) fail with:

    fatal: Replaying down to root commit is not supported yet!

Going backwards from this point:

1. `onto` is `NULL` from `set_up_replay_mode`;
2. that function in turn calls `peel_committish`; and
3. here we return `NULL` if `repo_get_oid` fails.

Let’s die immediately with a descriptive error message instead.

Doing this also provides us with a descriptive error if we “forget” to
provide an argument to `--onto` (but we really do unintentionally):[1]

    $ git replay --onto ^main topic1
    fatal: '^main' is not a valid commit-ish

Note that the `--advance` case won’t be triggered in practice because
of the “argument to --advance must be a reference” check (see the
previous test, and commit).

† 1: The argument to `--onto` is mandatory and the option parser accepts
     both `--onto=<name>` (stuck form) and `--onto name`. The latter
     form makes it easy to unintentionally pass something to the option
     when you really meant to pass a positional argument.

Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 07:30:16 +09:00
Kristoffer Haugsbakk
17b7965a03 replay: find *onto only after testing for ref name
We are about to make `peel_committish` die when it cannot find
a commit-ish instead of returning `NULL`. But that would make e.g.
`git replay --advance=refs/non-existent` die with a less descriptive
error message; the highest-level error message is that the name does
not exist as a ref, not that we cannot find a commit-ish based on
the name.

Let’s try to find the ref and only after that try to peel to
as a commit-ish.

Also add a regression test to protect this error order from future
modifications.

Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 07:30:16 +09:00
Kristoffer Haugsbakk
76eab50f75 replay: remove dead code and rearrange
22d99f01 (replay: add --advance or 'cherry-pick' mode, 2023-11-24) both
added `--advance` and made one of `--onto` or `--advance` mandatory.
But `determine_replay_mode` claims that there is a third alternative;
neither of `--onto` or `--advance` were given:

    if (onto_name) {
    ...
    } else if (*advance_name) {
    ...
    } else {
    ...
    }

But this is false—the fallthrough else-block is dead code.

Commit 22d99f01 was iterated upon by several people.[1] The initial
author wrote code for a sort of *guess mode*, allowing for shorter
commands when that was possible. But the next person instead made one
of the aforementioned options mandatory. In turn this code was dead on
arrival in git.git.

[1]: https://lore.kernel.org/git/CABPp-BEcJqjD4ztsZo2FTZgWT5ZOADKYEyiZtda+d0mSd1quPQ@mail.gmail.com/

Let’s remove this code. We can also join the if-block with the
condition `!*advance_name` into the `*onto` block since we do not set
`*advance_name` in this function. It only looked like we might set it
since the dead code has this line:

    *advance_name = xstrdup_or_null(last_key);

Let’s also rename the function since we do not determine the
replay mode here. We just set up `*onto` and refs to update.

Note that there might be more dead code caused by this *guess mode*.
We only concern ourselves with this function for now.

Helped-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-06 07:30:15 +09:00
Harald Nordgren
2f41b6e5e9 status: show comparison with push remote tracking branch
"git status" on a branch that follows a remote branch compares
commits on the current branch and the remote-tracking branch it
builds upon, to show "ahead", "behind", or "diverged" status.

When working on a feature branch that tracks a remote feature branch,
but you also want to track progress relative to the push destination
tracking branch (which may differ from the upstream branch), git status
now shows an additional comparison.

When the upstream tracking branch differs from the push destination
tracking branch, git status shows both the comparison with the upstream
tracking branch (as before) and an additional comparison with the push
destination tracking branch. The push branch comparison appears on a
separate line after the upstream branch status, using the same format.

Example output when tracking origin/main but push destination is
origin/feature:
    On branch feature
    Your branch and 'origin/main' have diverged,
    and have 3 and 1 different commits each, respectively.
      (use "git pull" if you want to integrate the remote branch with yours)

    Your branch is ahead of 'origin/feature' by 1 commit.
      (use "git push" to publish your local commits)

The comparison is only shown when the push destination tracking branch
differs from the upstream tracking branch, even if they are on the same
remote.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-05 11:00:11 +09:00
Harald Nordgren
da96a32b59 refactor format_branch_comparison in preparation
Refactor format_branch_comparison function in preparation for showing
comparison with push remote tracking branch.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-05 11:00:11 +09:00
Pushkar Singh
404b677229 t1300: use test helpers instead of test command
Replace `test -f` and `test -h` checks with `test_path_is_file` and
`test_path_is_symlink`. Using the test framework helpers provides
clearer diagnostics and keeps tests consistent across the suite.

Signed-off-by: Pushkar Singh <pushkarkumarsingh1970@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-05 10:58:58 +09:00
Olamide Caleb Bello
32fadb3779 environment: move "core.attributesFile" into repo-setting
When handling multiple repositories within the same process, relying on
global state for accessing the "core.attributesFile" configuration can
lead to incorrect values being used. It also makes it harder to isolate
repositories and hinders the libification of git.
The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
retrieve "core.attributesFile" via `git_attr_global_file()`
which reads from global state `git_attributes_file`.

Move the "core.attributesFile" configuration into the
`struct repo_settings` instead of relying on the global state.
A new function `repo_settings_get_attributesfile_path()` is added
and used to retrieve this setting in a repository-scoped manner.
The functions to retrieve "core.attributesFile" are replaced with
the new accessor function `repo_settings_get_attributesfile_path()`
This improves multi-repository behaviour and aligns with the goal of
libifying of Git.

Note that in `bootstrap_attr_stack()`, the `index_state` is used only
if it exists, else we default to `the_repository`.

Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 15:05:10 +09:00
Junio C Hamano
8bbe574720 SQUASH??? cocci 2026-01-04 14:33:02 +09:00
Ezekiel Newren
083774849b xdiff: move xdl_cleanup_records() from xprepare.c to xdiffi.c
Only the classic diff uses xdl_cleanup_records(). Move it,
xdl_clean_mmatch(), and the macros to xdiffi.c and call
xdl_cleanup_records() inside of xdl_do_classic_diff(). This better
organizes the code related to the classic diff.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:52 +09:00
Ezekiel Newren
efd314b8e8 xdiff: remove dependence on xdlclassifier from xdl_cleanup_records()
Disentangle xdl_cleanup_records() from the classifier so that it can be
moved from xprepare.c into xdiffi.c.

The classic diff is the only algorithm that needs to count the number
of times each line occurs in each file. Make xdl_cleanup_records()
count the number of lines instead of the classifier so it won't slow
down patience or histogram.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:52 +09:00
Ezekiel Newren
fee078ad4c xdiff: replace xdfile_t.dend with xdfenv_t.delta_end
View with --color-words. Same argument as delta_start.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:52 +09:00
Ezekiel Newren
d71dac1e23 xdiff: replace xdfile_t.dstart with xdfenv_t.delta_start
Placing delta_start in xdfenv_t instead of xdfile_t provides a more
appropriate context since this variable only makes sense with a pair
of files. View with --color-words.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:52 +09:00
Ezekiel Newren
0e67fd8b4b xdiff: cleanup xdl_trim_ends()
This patch is best viewed with a before and after of the whole
function.

Rather than using 2 pointers and walking them. Use direct indexing with
local variables of what is being compared to make it easier to follow
along.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:52 +09:00
Ezekiel Newren
fde7f6ebb1 xdiff: use xdfenv_t in xdl_trim_ends() and xdl_cleanup_records()
View with --color-words. Prepare these functions to use the fields:
delta_start, delta_end. A future patch will add these fields to
xdfenv_t.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:51 +09:00
Ezekiel Newren
319871c177 xdiff: let patience and histogram benefit from xdl_trim_ends()
The patience diff is set up the exact same way as histogram, see
xdl_do_historgram_diff() in xhistogram.c. xdl_optimize_ctxs() is
redundant now, delete it.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:51 +09:00
Ezekiel Newren
db8a50ca6b xdiff: don't waste time guessing the number of lines
All lines must be read anyway, so classify them after they're read in.
Also move the memset() into xdl_init_classifier().

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:51 +09:00
Ezekiel Newren
bcf8fd6345 xdiff: make classic diff explicit by creating xdl_do_classic_diff()
Later patches will prepare xdl_cleanup_records() to be moved into xdiffi.c
since only the classic diff uses that function.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:51 +09:00
Ezekiel Newren
7ba3a48c3d ivec: introduce the C side of ivec
Trying to use Rust's Vec in C, or git's ALLOC_GROW() macros (via
wrapper functions) in Rust is painful because:

  * C doesn't define its own vector type, and even though Rust does
    have Vec its painful to use on the C side (more on that below).
    However its still not viable to use Rust's Vec type because Git
    needs to be able to compile without Rust. So ivec was created
    expressley to be interoperable between C and Rust without needing
    Rust.
  * C doing vector things the Rust way would require wrapper functions,
    and Rust doing vector things the C way would require wrapper
    functions, so ivec was created to ensure a consistent contract
    between the 2 languages for how to manipulate a vector.
  * Currently, Rust defines its own 'Vec' type that is generic, but its
    memory allocator and struct layout weren't designed for
    interoperability with C (or any language for that matter), meaning
    that the C side cannot push to or expand a 'Vec' without defining
    wrapper functions in Rust that C can call. Without special care,
    the two languages might use different allocators (malloc/free on
    the C side, and possibly something else in Rust), which would make
    it difficult for a function in one language to free elements
    allocated by a call from a function in the other language.
  * Similarly, git defines ALLOC_GROW() and related macros in
    git-compat-util.h. While we could add functions allowing Rust to
    invoke something similar to those macros, passing three variables
    (pointer, length, allocated_size) instead of a single variable
    (vector) across the language boundary requires more cognitive
    overhead for readers to keep track of and makes it easier to make
    mistakes. Further, for low-level components that we want to
    eventually convert to pure Rust, such triplets would feel very out
    of place.

To address these issue, introduce a new type, ivec -- short for
interoperable vector. (We refer to it as 'ivec' generally, though on
the Rust side the struct is called IVec to match Rust style.)  This new
type is specifically designed for FFI purposes, so that both languages
handle the vector in the same way, though it could be used on either
side independently. This type is designed such that it can easily be
replaced by a Rust 'Vec' once interoperability is no longer a concern.

One particular item to note is that Git's macros to handle vec
operations infer the amount that a vec needs to grow from the size of
a pointer, but that makes it somewhat specific to the macros used in C.
To avoid defining every ivec function as a macro I opted to also
include an element_size field that allows concrete functions like
push() to know how much to grow the memory. This element_size also
helps in verifying that the ivec is correct when passing from C to
Rust.

Signed-off-by: Ezekiel Newren <ezekielnewren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:44:51 +09:00
Paul Tarjan
0b495cd390 t7800: fix racy "difftool --dir-diff syncs worktree" test
The "difftool --dir-diff syncs worktree without unstaged change" test
fails intermittently on Windows CI, as seen at:

  https://github.com/git/git/actions/runs/20624095002/job/59231745784#step:5:416

The root cause is that the original file content and the replacement
content have identical sizes:

  - Original: "main\ntest\na\n" = 12 bytes
  - New:      "new content\n"   = 12 bytes

When difftool's sync-back mechanism checks for changes, it compares
stat data between the temporary index and the modified files. If the
modification happens within the same timestamp granularity window and
file size stays the same, the change goes undetected.

On Windows, this is more likely to manifest because Git relies on
inode changes as a fallback when other stat fields match, but Windows
filesystems lack inodes. This is a real bug that could affect users
scripting difftool similarly, as seen at:

  https://github.com/git-for-windows/git/issues/5132

Fix the test by changing the replacement content to "modified content"
(17 bytes), ensuring the size difference is detected regardless of
timestamp resolution or platform-specific stat behavior.

Note: This fixes the test flakiness but not the underlying issue in
difftool's change detection. Other tests with same-size file patterns
(t0010-racy-git.sh, t2200-add-update.sh) are not affected because they
use normal index operations with proper racy-git detection.

Signed-off-by: Paul Tarjan <github@paulisageek.com>
Reviewed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-04 11:28:14 +09:00
Paul Tarjan
cd607431e1 t7527: fix flaky fsmonitor event tests with retry logic
The fsmonitor event tests (edit, create, delete, rename, etc.) were
flaky because there can be a race between the daemon writing events
to the trace file and the test's grep commands checking for them.

Add a retry_grep() helper function (similar to retry_until_success
in lib-git-p4.sh) that retries grep with a timeout, and use it in
all event-checking tests to wait for one expected event before
checking the rest.

Signed-off-by: Paul Tarjan <github@paulisageek.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-01 22:05:58 +09:00
Paul Tarjan
01ae1faa96 fsmonitor: implement filesystem change listener for Linux
Implement fsmonitor for Linux using the inotify API, bringing it to
feature parity with existing Windows and macOS implementations.

The Linux implementation uses inotify to monitor filesystem events.
Unlike macOS's FSEvents which can watch a single root directory,
inotify requires registering watches on every directory of interest.
The implementation carefully handles directory renames and moves
using inotify's cookie mechanism to track IN_MOVED_FROM/IN_MOVED_TO
event pairs.

Key implementation details:
- Uses inotify_init1(O_NONBLOCK) for non-blocking event monitoring
- Maintains bidirectional hashmaps between watch descriptors and paths
  for efficient event processing
- Handles directory creation, deletion, and renames dynamically
- Detects remote filesystems (NFS, CIFS, SMB, etc.) via statfs()
- Falls back to $HOME/.git-fsmonitor-* for socket when .git is remote
- Creates batches lazily (only for actual file events, not cookies)
  to avoid spurious sequence number increments

Build configuration:
- Enabled via FSMONITOR_DAEMON_BACKEND=linux and FSMONITOR_OS_SETTINGS=linux
- Requires NO_PTHREADS and NO_UNIX_SOCKETS to be unset
- Adds HAVE_LINUX_MAGIC_H for filesystem type detection

Documentation updated to note that fsmonitor.socketDir is now supported
on both Mac OS and Linux, and adds a section about inotify watch limits.

Testing performed:
- Build succeeds with standard flags and SANITIZE=address
- All t7527-builtin-fsmonitor.sh tests pass on local filesystems
- Remote filesystem detection correctly rejects network mounts

Issues addressed from PR #1352 (git/git) review comments:
- GPLv3 ME_REMOTE macro: Rewrote remote filesystem detection from
  scratch using statfs() and linux/magic.h constants (no GPLv3 code)
- Memory leak on inotify_init1 failure: Added FREE_AND_NULL cleanup
- Unsafe hashmap iteration in dtor: Collect entries first, then modify
- Missing null checks in stop_async: Added proper guard conditions
- dirname() modifying argument: Create copy with xstrdup() first
- Non-portable f_fsid.__val: Use memcmp() for fsid comparison
- Missing worktree null check: Added BUG() for null worktree
- Header updates: Use git-compat-util.h, hash_to_hex_algop()
- Code style: Use xstrdup() not xmemdupz(), proper pointer style

Issues addressed from PR #1667 (git/git) review comments:
- EINTR handling: read() now handles both EAGAIN and EINTR
- Trailing pipe in log_mask_set: Added strbuf_strip_suffix()
- Unchecked add_watch return: Now logs failure in rename_dir()
- String building: Consolidated strbuf operations with strbuf_addf()
- Translation markers: Added _() to all error_errno() messages

Based on work from https://github.com/git/git/pull/1352 by Eric DeCosta,
and https://github.com/git/git/pull/1667 by Marziyeh Esipreh, updated
to work with the current codebase and address all review feedback.

Signed-off-by: Paul Tarjan <github@paulisageek.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-01-01 22:01:45 +09:00
Troels Thomsen
57f7dd48d7 receive-pack: fix crash on out-of-namespace symref
`check_aliased_update_internal()` detects when a symbolic ref and its
target are being updated in the same push. It does this by building a
list of ref names without the optional namespace prefix. When a symbolic
ref within a namespace points to a ref outside the namespace,
`strip_namespace()` returns NULL which leads to a segfault.

A NULL check preventing this particular issue was repurposed in
ded8393610. Rather than reintroducing it, we can instead build a list of
fully qualified ref names. This prevents the crash, preserves the
consistency check from da3efdb17b, and allows updates to all symbolic
refs.

Signed-off-by: Troels Thomsen <troels@thomsen.io>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-28 14:05:41 +09:00
René Scharfe
0e445956f4 commit-reach: use commit_stack
Use commit_stack instead of open-coding it.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:29 +09:00
René Scharfe
3e456f1d8a commit-graph: use commit_stack
Replace a commit array implementation with commit_stack.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:29 +09:00
René Scharfe
958a816794 commit: add commit_stack_grow()
Add a function for increasing the capacity of a commit_stack.  It is
useful for reducing reallocations when the target size is known in
advance.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:28 +09:00
Rene Scharfe
506a7b6690 shallow: use commit_stack
Replace a commit array implementation with commit_stack.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:28 +09:00
René Scharfe
065523812f pack-bitmap-write: use commit_stack
Use commit_stack instead of open-coding it.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:28 +09:00
René Scharfe
2ebaa2b45e commit: add commit_stack_init()
Add a function for initializing a struct commit_stack, for when static
initialization is not possible or impractical.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:28 +09:00
René Scharfe
64dbeefbd2 test-reach: use commit_stack
Use commit_stack instead of open-coding it.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:28 +09:00
René Scharfe
bb3a1ce91f remote: use commit_stack for src_commits
Use commit_stack instead of open-coding it.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:28 +09:00
René Scharfe
4455d4a2ea remote: use commit_stack for sent_tips
Call commit_stack functions instead of effectively open-coding them.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:27 +09:00
René Scharfe
06e1f6467e remote: use commit_stack for local_commits
Replace a commit array implementation with commit_stack.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:27 +09:00
René Scharfe
d78039cd50 name-rev: use commit_stack
Simplify the code by using commit_stack instead of open-coding it.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:27 +09:00
René Scharfe
041c557171 midx: use commit_stack
Simplify collection commits in a callback function by passing it a
commit_stack pointer all the way from the caller, instead of using
separate variables for array and item count and a bunch of intermediate
members in struct bitmap_commit_cb.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:27 +09:00
René Scharfe
052efdd60f log: use commit_stack
Calling commit_stack_push() to add commits is simpler and more efficient
than using REALLOC_ARRAY.  Calling commit_stack_pop() to consume them in
LIFO order is also a tad simpler than calculating the array index from
the end.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:27 +09:00
René Scharfe
d8a17ef09b revision: export commit_stack
Dynamic arrays of commit pointers are used in several places.  Some of
them use a custom struct to hold array, item count and capacity, others
have them as separate variables linked by a common name part.

Pick one succinct, clean implementation -- commit_stack -- and convert
the different variants to it to reduce code duplication.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-25 08:29:27 +09:00
Christian Couder
c01bcb5d00 fetch-pack: wire up and enable auto filter logic
Previous commits have set up an infrastructure for `--filter=auto` to
automatically prepare a partial clone filter based on what the server
advertised and the client accepted.

Using that infrastructure, let's now enable the `--filter=auto` option
in `git clone` and `git fetch` by setting `allow_auto_filter` to 1.

Note that these small changes mean that when `git clone --filter=auto`
or `git fetch --filter=auto` are used, "auto" is automatically saved
as the partial clone filter for the server on the client. Therefore
subsequent calls to `git fetch` on the client will automatically use
this "auto" mode even without `--filter=auto`.

Let's also set `allow_auto_filter` to 1 in `transport.c`, as the
transport layer must be able to accept the "auto" filter spec even if
the invoking command hasn't fully parsed it yet.

When an "auto" filter is requested, let's have the "fetch-pack.c" code
in `do_fetch_pack_v2()` compute a filter and send it to the server.

In `do_fetch_pack_v2()` the logic also needs to check for the
"promisor-remote" capability and call `promisor_remote_reply()` to
parse advertised remotes and populate the list of those accepted (and
their filters).

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:06 +09:00
Christian Couder
d92ba46d5a promisor-remote: keep advertised filter in memory
Currently, advertised filters are only kept in memory temporarily
during parsing, or persisted to disk if `promisor.storeFields`
contains 'partialCloneFilter'.

In a following commit though, we will add a `--filter=auto` option.
This option will enable the client to use the filters that the server
is suggesting for the promisor remotes the client accepts.

To use them even if `promisor.storeFields` is not configured, these
filters should be stored somewhere for the current session.

Let's add an `advertised_filter` field to `struct promisor_remote`
for that purpose.

To ensure that the filters are available in all cases,
filter_promisor_remote() captures them into a temporary list and
applies them to the `promisor_remote` structs after the potential
configuration reload.

Then the accepted remotes are marked as `accepted` in the repository
state. This ensures that subsequent calls to look up accepted remotes
(like in the filter construction below) actually find them.

In a following commit, we will add a `--filter=auto` option that will
enable a client to use the filters suggested by the server for the
promisor remotes the client accepted.

To enable the client to construct a filter spec based on these filters,
let's add a `promisor_remote_construct_filter(repo)` function.

This function:

- iterates over all accepted promisor remotes in the repository,
- collects the filters advertised for them (using `advertised_filter`
  which a previous commit added to `struct promisor_remote`), and
- generates a single filter spec for them (using the
  `list_objects_filter_combine()` function added by a previous commit).

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:06 +09:00
Christian Couder
c8c941181a list-objects-filter-options: implement auto filter resolution
In a following commit, we will need to aggregate filters from multiple
accepted promisor remotes into a single filter.

For that purpose, let's add a `list_objects_filter_combine()` helper
function that takes a list of filter specifications and combines them
into a single string. If multiple filters are provided, it constructs a
"combine:..." filter, ensuring that sub-filters are properly
URL-encoded using the existing `allow_unencoded` logic.

In a following commit, we will add a `--filter=auto` option that will
enable a client to use the filters suggested by the server for the
promisor remotes the client accepted.

To simplify the filter processing related to this new feature, let's
also add a small `list_objects_filter_resolve_auto()` function.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:06 +09:00
Christian Couder
13bf8f5bcb list-objects-filter-options: support 'auto' mode for --filter
In a following commit, we are going to allow passing "auto" as a
<filterspec> to the `--filter=<filterspec>` option, but only for some
commands. Other commands that support the `--filter=<filterspec>`
option should still die() when 'auto' is passed.

Let's set up the "list-objects-filter-options.{c,h}" infrastructure to
support that:

- Add a new `unsigned int allow_auto_filter : 1;` flag to
  `struct list_objects_filter_options` which specifies if "auto" is
  accepted or not.
- Change gently_parse_list_objects_filter() to parse "auto" if it's
  accepted.
- Make sure we die() if "auto" is combined with another filter.
- Update list_objects_filter_release() to preserve the
  allow_auto_filter flag, as this function is often called (via
  opt_parse_list_objects_filter) to reset the struct before parsing a
  new value.

Let's also update `list-objects-filter.c` to recognize the new
`LOFC_AUTO` choice. Since "auto" must be resolved to a concrete filter
before filtering actually begins, initializing a filter with
`LOFC_AUTO` is invalid and will trigger a BUG().

Note that ideally combining "auto" with "auto" could be allowed, but in
practice, it's probably not worth the added code complexity. And if we
really want it, nothing prevents us to allow it in future work.

If we ever want to give a meaning to combining "auto" with a different
filter too, nothing prevents us to do that in future work either.

While at it, let's add a new "u-list-objects-filter-options.c" file for
`struct list_objects_filter_options` related unit tests. For now it
only tests gently_parse_list_objects_filter() though.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:05 +09:00
Christian Couder
0d76d0c600 doc: fetch: document --filter=<filter-spec> option
The `--filter=<filter-spec>` option is documented in most commands that
support it except `git fetch`.

Let's fix that and document that option properly in the same way as it
is already documented for `git clone`.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:05 +09:00
Christian Couder
6ac467fdbd fetch: make filter_options local to cmd_fetch()
The `struct list_objects_filter_options filter_options` variable used
in "builtin/fetch.c" to store the parsed filters specified by
`--filter=<filterspec>` is currently a static variable global to the
file.

As we are going to use it more in a following commit, it could become a
bit less easy to understand how it's managed.

To avoid that, let's make it clear that it's owned by cmd_fetch() by
moving its definition into that function and making it non-static.

This requires passing a pointer to it through the prepare_transport(),
do_fetch(), backfill_tags(), fetch_one_setup_partial(), and fetch_one()
functions, but it's quite straightforward.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:05 +09:00
Christian Couder
50cedfdf94 clone: make filter_options local to cmd_clone()
The `struct list_objects_filter_options filter_options` variable used
in "builtin/clone.c" to store the parsed filters specified by
`--filter=<filterspec>` is currently a static variable global to the
file.

As we are going to use it more in a following commit, it could become
a bit less easy to understand how it's managed.

To avoid that, let's make it clear that it's owned by cmd_clone() by
moving its definition into that function and making it non-static.

The only additional change to make this work is to pass it as an
argument to checkout(). So it's a small quite cheap cleanup anyway.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:05 +09:00
Christian Couder
09b0cefcd8 promisor-remote: allow a client to store fields
A previous commit allowed a server to pass additional fields through
the "promisor-remote" protocol capability after the "name" and "url"
fields, specifically the "partialCloneFilter" and "token" fields.

Another previous commit, c213820c51 (promisor-remote: allow a client
to check fields, 2025-09-08), has made it possible for a client to
decide if it accepts a promisor remote advertised by a server based
on these additional fields.

Often though, it would be interesting for the client to just store in
its configuration files these additional fields passed by the server,
so that it can use them when needed.

For example if a token is necessary to access a promisor remote, that
token could be updated frequently only on the server side and then
passed to all the clients through the "promisor-remote" capability,
avoiding the need to update it on all the clients manually.

Storing the token on the client side makes sure that the token is
available when the client needs to access the promisor remotes for a
lazy fetch.

In the same way, if it appears that it's better to use a different
filter to access a promisor remote, it could be helpful if the client
could automatically use it.

To allow this, let's introduce a new "promisor.storeFields"
configuration variable.

Like "promisor.checkFields" and "promisor.sendFields", it should
contain a comma or space separated list of field names. Only the
"partialCloneFilter" and "token" field names are supported for now.

When a server advertises a promisor remote, for example "foo", along
with for example "token=XXXXX" to a client, and on the client side
"promisor.storeFields" contains "token", then the client will store
XXXXX for the "remote.foo.token" variable in its configuration file
and reload its configuration so it can immediately use this new
configuration variable.

A message is emitted on stderr to warn users when the config is
changed.

Note that even if "promisor.acceptFromServer" is set to "all", a
promisor remote has to be already configured on the client side for
some of its config to be changed. In any case no new remote is
configured and no new URL is stored.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:05 +09:00
Christian Couder
81dae58f91 promisor-remote: refactor initialising field lists
In "promisor-remote.c", the fields_sent() and fields_checked()
functions serve similar purposes and contain a small amount of
duplicated code.

As we are going to add a similar function in a following commit,
let's refactor this common code into a new initialize_fields_list()
function.

Signed-off-by: Christian Couder <chriscool@tuxfamily.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-23 22:43:05 +09:00
Jean-Noël Avila
acffc5e9e5 doc: convert git-remote to synopsis style
- Switch the synopsis to a synopsis block which will automatically
  format placeholders in italics and keywords in monospace
- Use _<placeholder>_ instead of <placeholder> in the description
- Use `backticks` for keywords and more complex option
descriptions. The new rendering engine will apply synopsis rules to
these spans.
- also convert first sentences to imperative mood where applicable

Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-21 11:33:10 +09:00
Jean-Noël Avila
5b35e736dd doc: convert git stage to use synopsis block
Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-21 11:33:10 +09:00
Jean-Noël Avila
ead7aae0e4 doc: convert git-status tables to AsciiDoc format
Instead of plain text tables with hand formatting, take advantage of
asciidoc's table syntax to let the renderer do the heavy lifting and
make the tables more maintainable and translatable.

Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-21 11:33:10 +09:00
Jean-Noël Avila
20e56300d4 doc: convert git-status to synopsis style
Also convert unformatted lists to proper AsciiDoc lists.

Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-21 11:33:09 +09:00
Jean-Noël Avila
f53f133d8d doc: fix t0450-txt-doc-vs-help to select only first synopsis block
In case there are multiple synopsis blocks (declared with [synopsis]
or [verse] style) in the same file, the previous implementation was
incorrectly picking up text from all the blocks until the first empty
line. This commit modifies the sed command to stop processing upon
encountering the first empty line after the first block declaration,
thereby ensuring that only the intended block is captured.

Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-21 11:33:09 +09:00
Sam Bostock
e38352c233 worktree: use 'prune' instead of 'expire' in help text
Use 'prune' instead of 'expire' when describing the --expire option's
effect on missing worktrees, since the terminology is clearer.

Signed-off-by: Sam Bostock <sam@sambostock.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-20 15:03:32 +09:00
Sam Bostock
20684baa34 worktree: clarify --expire applies to missing worktrees
The `--expire` option for `git worktree list` and `git worktree prune`
only affects worktrees whose working directory path no longer exists.
The help text did not make this clear, and the documentation
inconsistently used "unused" for prune but "missing" for list.

This updates the help text and documentation to consistently describe
these as "missing worktrees".

Signed-off-by: Sam Bostock <sam@sambostock.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-20 15:03:32 +09:00
Sam Bostock
7796c14a1a bundle-uri: validate that bundle entries have a uri
When a bundle list config file has a typo like 'url' instead of 'uri',
or simply omits the uri field, the bundle entry is created but
bundle->uri remains NULL. This causes a segfault when copy_uri_to_file()
passes the NULL to starts_with().

Signed-off-by: Sam Bostock <sam@sambostock.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-20 14:45:27 +09:00
Junio C Hamano
00f117fafe Merge branch 'jc/object-read-stream-fix' into ps/read-object-info-improvements
* jc/object-read-stream-fix:
  odb: do not use "blank" substitute for NULL
2025-12-18 20:55:09 +09:00
Johannes Schindelin
6973e5f550 sideband: add options to allow more control sequences to be passed through
Even though control sequences that erase characters are quite juicy for
attack scenarios, where attackers are eager to hide traces of suspicious
activities, during the review of the side band sanitizing patch series
concerns were raised that there might be some legimitate scenarios where
Git server's `pre-receive` hooks use those sequences in a benign way.

Control sequences to move the cursor can likewise be used to hide tracks
by overwriting characters, and have been equally pointed out as having
legitimate users.

Let's add options to let users opt into passing through those ANSI
Escape sequences: `sideband.allowControlCharacters` now supports also
`cursor` and `erase`, and it parses the value as a comma-separated list.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 10:58:07 +09:00
Johannes Schindelin
a2d5100f7e sideband: do allow ANSI color sequences by default
The preceding two commits introduced special handling of the sideband
channel to neutralize ANSI escape sequences before sending the payload
to the terminal, and `sideband.allowControlCharacters` to override that
behavior.

However, as reported by brian m. carlson, some `pre-receive` hooks that
are actively used in practice want to color their messages and therefore
rely on the fact that Git passes them through to the terminal, even
though they have no way to determine whether the receiving side can
actually handle Escape sequences (think e.g. about the practice
recommended by Git that third-party applications wishing to use Git
functionality parse the output of Git commands).

In contrast to other ANSI escape sequences, it is highly unlikely that
coloring sequences can be essential tools in attack vectors that mislead
Git users e.g. by hiding crucial information.

Therefore we can have both: Continue to allow ANSI coloring sequences to
be passed to the terminal by default, and neutralize all other ANSI
Escape sequences.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 10:58:07 +09:00
Johannes Schindelin
740dce52b6 sideband: introduce an "escape hatch" to allow control characters
The preceding commit fixed the vulnerability whereas sideband messages
(that are under the control of the remote server) could contain ANSI
escape sequences that would be sent to the terminal verbatim.

However, this fix may not be desirable under all circumstances, e.g.
when remote servers deliberately add coloring to their messages to
increase their urgency.

To help with those use cases, give users a way to opt-out of the
protections: `sideband.allowControlCharacters`.

Suggested-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 10:58:07 +09:00
Johannes Schindelin
a7afa7a1dd sideband: mask control characters
The output of `git clone` is a vital component for understanding what
has happened when things go wrong. However, these logs are partially
under the control of the remote server (via the "sideband", which
typically contains what the remote `git pack-objects` process sends to
`stderr`), and is currently not sanitized by Git.

This makes Git susceptible to ANSI escape sequence injection (see
CWE-150, https://cwe.mitre.org/data/definitions/150.html), which allows
attackers to corrupt terminal state, to hide information, and even to
insert characters into the input buffer (i.e. as if the user had typed
those characters).

To plug this vulnerability, disallow any control character in the
sideband, replacing them instead with the common `^<letter/symbol>`
(e.g. `^[` for `\x1b`, `^A` for `\x01`).

There is likely a need for more fine-grained controls instead of using a
"heavy hammer" like this, which will be introduced subsequently.

Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 10:58:06 +09:00
Johannes Schindelin
09c6a90daa mingw: special-case index entries for symlinks with buggy size
In https://github.com/git-for-windows/git/pull/2637, we fixed a bug
where symbolic links' target path sizes were recorded incorrectly in the
index. The downside of this fix was that every user with tracked
symbolic links in their checkouts would see them as modified in `git
status`, but not in `git diff`, and only a `git add <path>` (or `git add
-u`) would "fix" this.

Let's do better than that: we can detect that situation and simply
pretend that a symbolic link with a known bad size (or a size that just
happens to be that bad size, a _very_ unlikely scenario because it would
overflow our buffers due to the trailing NUL byte) means that it needs
to be re-checked as if we had just checked it out.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:20 +09:00
Johannes Schindelin
43152a2276 mingw: emulate stat() a little more faithfully
When creating directories via `safe_create_leading_directories()`, we
might encounter an already-existing directory which is not
readable by the current user. To handle that situation, Git's code calls
`stat()` to determine whether we're looking at a directory.

In such a case, `CreateFile()` will fail, though, no matter what, and
consequently `mingw_stat()` will fail, too. But POSIX semantics seem to
still allow `stat()` to go forward.

So let's call `mingw_lstat()` to the rescue if we fail to get a file
handle due to denied permission in `mingw_stat()`, and fill the stat
info that way.

We need to be careful to not allow this to go forward in case that we're
looking at a symbolic link: to resolve the link, we would still have to
create a file handle, and we just found out that we cannot. Therefore,
`stat()` still needs to fail with `EACCES` in that case.

This fixes https://github.com/git-for-windows/git/issues/2531.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:20 +09:00
Johannes Schindelin
96b8760220 mingw: try to create symlinks without elevated permissions
As of Windows 10 Build 14972 in Developer Mode, a new flag is supported
by `CreateSymbolicLink()` to create symbolic links even when running
outside of an elevated session (which was previously required).

This new flag is called `SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE`
and has the numeric value 0x02.

Previous Windows 10 versions will not understand that flag and return
an `ERROR_INVALID_PARAMETER`, therefore we have to be careful to try
passing that flag only when the build number indicates that it is
supported.

For more information about the new flag, see this blog post:
https://blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:19 +09:00
Karsten Blees
e81cbd184f mingw: add support for symlinks to directories
Symlinks on Windows have a flag that indicates whether the target is a
file or a directory. Symlinks of wrong type simply don't work. This even
affects core Win32 APIs (e.g. `DeleteFile()` refuses to delete directory
symlinks).

However, `CreateFile()` with FILE_FLAG_BACKUP_SEMANTICS does work. Check
the target type by first creating a tentative file symlink, opening it,
and checking the type of the resulting handle. If it is a directory,
recreate the symlink with the directory flag set.

It is possible to create symlinks before the target exists (or in case
of symlinks to symlinks: before the target type is known). If this
happens, create a tentative file symlink and postpone the directory
decision: keep a list of phantom symlinks to be processed whenever a new
directory is created in `mingw_mkdir()`.

Limitations: This algorithm may fail if a link target changes from file
to directory or vice versa, or if the target directory is created in
another process. It's the best Git can do, though.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:19 +09:00
Karsten Blees
3aeb1cd126 mingw: implement basic symlink() functionality (file symlinks only)
Implement `symlink()`. This implementation always creates _file_
symlinks (remember: Windows discerns between symlinks pointing to
directories and those pointing to files). Support for directory symlinks
will be added in a subseqeuent commit.

This implementation fails with `ENOSYS` if symlinks are disabled or
unsupported.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:19 +09:00
Karsten Blees
1977e5b9de mingw: implement readlink()
Implement `readlink()` by reading NTFS reparse points via the
`read_reparse_point()` function that was introduced earlier to determine
the length of symlink targets. Works for symlinks and directory
junctions. If symlinks are disabled, fail with `ENOSYS`.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:19 +09:00
Karsten Blees
8a08d14d05 mingw: allow mingw_chdir() to change to symlink-resolved directories
If symlinks are enabled, resolve all symlinks when changing directories,
as required by POSIX.

Note: Git's `real_path()` function bases its link resolution algorithm
on this property of `chdir()`. Unfortunately, the current directory on
Windows is limited to only MAX_PATH (260) characters. Therefore using
symlinks and long paths in combination may be problematic.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:19 +09:00
Karsten Blees
c4314a6aa8 mingw: support renaming symlinks
Older MSVCRT's `_wrename()` function cannot rename symlinks over
existing files: it returns success without doing anything. Newer
MSVCR*.dll versions probably do not share this problem: according to CRT
sources, they just call `MoveFileEx()` with the `MOVEFILE_COPY_ALLOWED`
flag.

Avoid the `_wrename()` call, and go with directly calling
`MoveFileEx()`, with proper error handling of course.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:19 +09:00
Karsten Blees
47112d6f96 mingw: handle symlinks to directories in mingw_unlink()
The `_wunlink()` and `DeleteFileW()` functions refuse to delete symlinks
to directories on Windows; The error code woutl be `ERROR_ACCESS_DENIED`
in that case. Take that error code as an indicator that we need to try
`_wrmdir()` as well. In the best case, it will remove a symlink. In the
worst case, it will fail with the same error code again.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Karsten Blees
761d9427bc mingw: add symlink-specific error codes
The Win32 API calls do not set `errno`; Instead, error codes for failed
operations must be obtained via the `GetLastError()` function. Git would
not know what to do with those error values, though, which is why Git's
Windows compatibility layer translates them to `errno` values.

Let's handle a couple of symlink-related error codes that will become
relevant with the upcoming support for symlinks on Windows.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Karsten Blees
988ba47d79 mingw: change default of core.symlinks to false
Symlinks on Windows don't work the same way as on Unix systems. For
example, there are different types of symlinks for directories and
files, and unless using a recent-ish Windows version in Developer Mode,
creating symlinks requires administrative privileges.

By default, disable symlink support on Windows. That is, users
explicitly have to enable it with `git config [--system|--global]
core.symlinks true`; For convenience, `git init` (and `git clone`)
will perform a test whether the current setup allows creating symlinks
and will configure that setting in the repository config.

The test suite ignores system / global config files. Allow
testing *with* symlink support by checking if native symlinks are
enabled in MSYS2 (via setting the special environment variable
`MSYS=winsymlinks:nativestrict` to ask the MSYS2 runtime to enable
creating symlinks).

Note: This assumes that Git's test suite is run in MSYS2's Bash, which
is true for the time being (an experiment to switch to BusyBox-w32
failed due to the experimental nature of BusyBox-w32).

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Karsten Blees
fc27de047a mingw: factor out the retry logic
In several places, Git's Windows-specific code follows the pattern where
it tries to perform an operation, and retries several times when that
operation fails, sleeping an increasing amount of time, before finally
giving up and asking the user whether to rety (after, say, closing an
editor that held a handle to a file, preventing the operation from
succeeding).

This logic is a bit hard to use, and inconsistent:
`mingw_unlink()` and `mingw_rmdir()` duplicate the code to retry,
and both of them do so incompletely. They also do not restore `errno` if the
user answers 'no'.

Introduce a `retry_ask_yes_no()` helper function that handles retry with
small delay, asking the user, and restoring `errno`.

Note that in `mingw_unlink()`, we include the `_wchmod()` call in the
retry loop (which may fail if the file is locked exclusively).

In `mingw_rmdir()`, we include special error handling in the retry loop.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Bill Zissimopoulos
c08814a5c4 mingw: compute the correct size for symlinks in mingw_lstat()
POSIX specifies that upon successful return from `lstat()`: "the
value of the st_size member shall be set to the length of the pathname
contained in the symbolic link not including any terminating null byte".

Git typically doesn't trust the `stat.st_size` member of symlinks (e.g.
see `strbuf_readlink()`). Therefore, it is tempting to save on the extra
overhead of opening and reading the reparse point merely to calculate
the exact size of the link target.

This is, in fact, what Git for Windows did, from May 2015 to May 2020.
At least almost: some functions take shortcuts if `st_size` is 0 (e.g.
`diff_populate_filespec()`), hence Git for Windows hard-coded the length
of all symlinks to MAX_PATH.

This did cause problems, though, specifically in Git repositories
that were also accessed by Git for Cygwin or Git for WSL. For example,
doing `git reset --hard` using Git for Windows would update the size of
symlinks in the index to be MAX_PATH; at a later time Git for Cygwin
or Git for WSL would find that symlinks have changed size during `git
status` and update the index. And then Git for Windows would think that
the index needs to be updated. Even if the symlinks did not, in fact,
change. To avoid that, the correct size must be determined.

Signed-off-by: Bill Zissimopoulos <billziss@navimatics.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Karsten Blees
98e2eaf426 mingw: teach dirent about symlinks
Move the `S_IFLNK` detection to `file_attr_to_st_mode()`.

Implement `DT_LNK` detection in dirent.c's `readdir()` function.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Karsten Blees
8895855a44 mingw: let mingw_lstat() error early upon problems with reparse points
When obtaining lstat information for reparse points, we need to call
`FindFirstFile()` in addition to `GetFileInformationEx()` to obtain
the type of the reparse point (symlink, mount point etc.). However,
currently there is no error handling whatsoever if `FindFirstFile()`
fails.

Call `FindFirstFile()` before modifying the `stat *buf` output parameter
and error out if the call fails.

Note: The `FindFirstFile()` return value includes all the data
that we get from `GetFileAttributesEx()`, so we could replace
`GetFileAttributesEx()` with `FindFirstFile()`. We don't do that because
`GetFileAttributesEx()` is about twice as fast for single files. I.e.
we only pay the extra cost of calling `FindFirstFile()` in the rare case
that we encounter a reparse point.

Please also note that the indentation the remaining reparse point
code changed, and hence the best way to look at this diff is with
`--color-moved -w`. That code was _not_ moved because a subsequent
commit will move it to an altogether different function, anyway.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:18 +09:00
Karsten Blees
b2d9214455 mingw: drop the separate do_lstat() function
With the new `mingw_stat()` implementation, `do_lstat()` is only called
from `mingw_lstat()` (with the function parameter `follow == 0`). Remove
the extra function and the old `mingw_stat()`-specific (`follow == 1`)
logic.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:17 +09:00
Karsten Blees
25866d0c50 mingw: implement stat() with symlink support
With respect to symlinks, the current `mingw_stat()` implementation is
almost identical to `mingw_lstat()`: except for the file type (`st_mode
& S_IFMT`), it returns information about the link rather than the target.

Implement `mingw_stat()` by opening the file handle requesting minimal
permissions, and then calling `GetFileInformationByHandle()` on it. This
way, all links are resolved by the Windows file system layer.

If symlinks are disabled, use `mingw_lstat()` as before, but fail with
`ELOOP` if a symlink would have to be resolved.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:17 +09:00
Karsten Blees
e5be12952a mingw: don't call GetFileAttributes() twice in mingw_lstat()
The Win32 API function `GetFileAttributes()` cannot handle paths with
trailing dir separators. The current `mingw_stat()`/`mingw_lstat()`
implementation calls `GetFileAttributes()` twice if the path has
trailing slashes (first with the original path that was passed as
function parameter, and and a second time with a path copy with trailing
'/' removed).

With the conversion to wide Unicode, we get the length of the path for
free, and also have a (wide char) buffer that can be modified. This
makes it easy to avoid that extraneous Win32 API call.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:22:17 +09:00
Junio C Hamano
99c20c71bb Merge branch 'js/prep-symlink-windows' into js/symlink-windows
* js/prep-symlink-windows:
  trim_last_path_component(): avoid hard-coding the directory separator
  strbuf_readlink(): support link targets that exceed PATH_MAX
  strbuf_readlink(): avoid calling `readlink()` twice in corner-cases
  init: do parse _all_ core.* settings early
  mingw: do resolve symlinks in `getcwd()`
  t7800: work around the MSYS path conversion on Windows
  t6423: introduce Windows-specific handling for symlinking to /dev/null
  t1305: skip symlink tests that do not apply to Windows
  t1006: accommodate for symlink support in MSYS2
  t0600: fix incomplete prerequisite for a test case
  t0301: another fix for Windows compatibility
  t0001: handle `diff --no-index` gracefully
  mingw: special-case `open(symlink, O_CREAT | O_EXCL)`
  apply: symbolic links lack a "trustable executable bit"
  t9700: accommodate for Windows paths
2025-12-18 08:22:04 +09:00
Karsten Blees
1887b3dd06 trim_last_path_component(): avoid hard-coding the directory separator
Currently, this function hard-codes the directory separator as the
forward slash.

However, on Windows the backslash character is valid, too. And we want
to call this function in the upcoming support for symlinks on Windows
with the symlink targets (which naturally use the canonical directory
separator on Windows, which is _not_ the forward slash).

Prepare that function to be useful also in that context.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:21:07 +09:00
Karsten Blees
5f09f93726 strbuf_readlink(): support link targets that exceed PATH_MAX
The `strbuf_readlink()` function refuses to read link targets that
exceed PATH_MAX (even if a sufficient size was specified by the caller).

As some platforms (*cough* Windows *cough*) support longer paths, remove
this restriction (similar to `strbuf_getcwd()`).

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:21:07 +09:00
Karsten Blees
ac2339de65 strbuf_readlink(): avoid calling readlink() twice in corner-cases
The `strbuf_readlink()` function calls `readlink()`` twice if the hint
argument specifies the exact size of the link target (e.g. by passing
stat.st_size as returned by `lstat()`). This is necessary because
`readlink(..., hint) == hint` could mean that the buffer was too small.

Use `hint + 1` as buffer size to prevent this.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:21:06 +09:00
Johannes Schindelin
89d6c35322 init: do parse _all_ core.* settings early
In Git for Windows, `has_symlinks` is set to 0 by default. Therefore, we
need to parse the config setting `core.symlinks` to know if it has been
set to `true`. In `git init`, we must do that before copying the
templates because they might contain symbolic links.

Even if the support for symbolic links on Windows has not made it to
upstream Git yet, we really should make sure that all the `core.*`
settings are parsed before proceeding, as they might very well change
the behavior of `git init` in a way the user intended.

This fixes https://github.com/git-for-windows/git/issues/3414

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:21:06 +09:00
Johannes Schindelin
d08f0011ae mingw: do resolve symlinks in getcwd()
As pointed out in https://github.com/git-for-windows/git/issues/1676,
the `git rev-parse --is-inside-work-tree` command currently fails when
the current directory's path contains symbolic links.

The underlying reason for this bug is that `getcwd()` is supposed to
resolve symbolic links, but our `mingw_getcwd()` implementation did not.

We do have all the building blocks for that, though: the
`GetFinalPathByHandleW()` function will resolve symbolic links. However,
we only called that function if `GetLongPathNameW()` failed, for
historical reasons: the latter function was supported for a long time,
but the former API function was introduced only with Windows Vista, and
we used to support also Windows XP. With that support having been
dropped, we are free to call the symbolic link-resolving function right
away.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-18 08:21:06 +09:00
Junio C Hamano
3d86511c12 Merge branch 'js/test-symlink-windows' into js/prep-symlink-windows
* js/test-symlink-windows:
  t7800: work around the MSYS path conversion on Windows
  t6423: introduce Windows-specific handling for symlinking to /dev/null
  t1305: skip symlink tests that do not apply to Windows
  t1006: accommodate for symlink support in MSYS2
  t0600: fix incomplete prerequisite for a test case
  t0301: another fix for Windows compatibility
  t0001: handle `diff --no-index` gracefully
  mingw: special-case `open(symlink, O_CREAT | O_EXCL)`
  apply: symbolic links lack a "trustable executable bit"
  t9700: accommodate for Windows paths
2025-12-18 08:20:25 +09:00
Aaron Plattner
b9e842ecff remote-curl: Use auth for probe_rpc() requests too
If a large request requires post_rpc() to call probe_rpc(), the latter
does not use the authorization credentials used for other requests. If
this fails with an HTTP 401 error and http_auth.multistage isn't set,
then the whole request just fails.

For example, using git-credential-msal [1], the following attempt to clone a
large repository fails partway through because the initial request to download
the commit history and promisor packs succeeds, but the
subsequent request to download the blobs needed to construct the working
tree fails with a 401 error and the checkout fails.

(lines removed for brevity)

  git clone --filter=blob:none https://secure-server.example/repo
  11:03:26.855369 git.c:502               trace: built-in: git clone --filter=blob:none https://secure-server.example/repo
  Cloning into 'sw'...
  warning: templates not found in /home/aaron/share/git-core/templates
  11:03:26.857169 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo
  11:03:27.012104 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1
  11:03:27.049243 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized
  11:03:27.049270 http.c:849              <= Recv header: WWW-Authenticate: Bearer error="invalid_request", error_description="No bearer token found in the request", msal-tenant-id="<tenant>", msal-client-id="<client>"
  11:03:27.053786 run-command.c:673       trace: run_command: 'git credential-msal get'
  11:03:27.952830 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1
  11:03:27.952849 http.c:849              => Send header: Authorization: Bearer <redacted>
  11:03:27.995419 http.c:849              <= Recv header: HTTP/1.1 200 OK
  11:03:28.230039 http.c:890              == Info: Reusing existing https: connection with host secure-server.example
  11:03:28.230208 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1
  11:03:28.230216 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request
  11:03:28.230221 http.c:849              => Send header: Authorization: Bearer <redacted>
  11:03:28.269085 http.c:849              <= Recv header: HTTP/1.1 200 OK
  11:03:28.684163 http.c:890              == Info: Reusing existing https: connection with host secure-server.example
  11:03:28.684379 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1
  11:03:28.684391 http.c:849              => Send header: Accept: application/x-git-upload-pack-result
  11:03:28.684393 http.c:849              => Send header: Authorization: Bearer <redacted>
  11:03:28.869546 run-command.c:673       trace: run_command: git index-pack --stdin --fix-thin '--keep=fetch-pack 43856 on dgx-spark' --promisor
  11:06:39.861237 run-command.c:673       trace: run_command: git -c fetch.negotiationAlgorithm=noop fetch origin --no-tags --no-write-fetch-head --recurse-submodules=no --filter=blob:none --stdin
  11:06:39.865981 run-command.c:673       trace: run_command: git remote-https origin https://secure-server.example/repo
  11:06:39.868039 run-command.c:673       trace: run_command: git-remote-https origin https://secure-server.example/repo
  11:07:30.412575 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1
  11:07:30.456285 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized
  11:07:30.456318 http.c:849              <= Recv header: WWW-Authenticate: Bearer error="invalid_request", error_description="No bearer token found in the request", msal-tenant-id="<tenant>", msal-client-id="<client>"
  11:07:30.456439 run-command.c:673       trace: run_command: 'git credential-cache get'
  11:07:30.461266 http.c:849              => Send header: GET repo/info/refs?service=git-upload-pack HTTP/1.1
  11:07:30.461282 http.c:849              => Send header: Authorization: Bearer <redacted>
  11:07:30.501628 http.c:849              <= Recv header: HTTP/1.1 200 OK
  11:07:34.725262 http.c:849              => Send header: POST repo/git-upload-pack HTTP/1.1
  11:07:34.725279 http.c:849              => Send header: Content-Type: application/x-git-upload-pack-request
  11:07:34.761407 http.c:849              <= Recv header: HTTP/1.1 401 Unauthorized
  11:07:34.761443 http.c:890              == Info: Bearer authentication problem, ignoring.
  11:07:34.761453 http.c:849              <= Recv header: WWW-Authenticate: Bearer error="invalid_request", error_description="No bearer token found in the request", msal-tenant-id="<tenant>", msal-client-id="<client>"
  11:07:34.761509 http.c:890              == Info: The requested URL returned error: 401
  11:07:34.761530 http.c:890              == Info: closing connection #0
  11:07:34.761913 run-command.c:673       trace: run_command: 'git credential-cache erase'
  11:07:34.761927 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-cache erase' 'git credential-cache erase'
  11:07:34.768069 git.c:502               trace: built-in: git credential-cache erase
  11:07:34.768690 run-command.c:673       trace: run_command: 'git credential-msal erase'
  11:07:34.768713 run-command.c:765       trace: start_command: /bin/sh -c 'git credential-msal erase' 'git credential-msal erase'
  11:07:34.772742 git.c:808               trace: exec: git-credential-msal erase
  11:07:34.772783 run-command.c:673       trace: run_command: git-credential-msal erase
  11:07:34.772819 run-command.c:765       trace: start_command: /usr/bin/git-credential-msal erase
  error: RPC failed; HTTP 401 curl 22 The requested URL returned error: 401
  fatal: unable to write request to remote: Broken pipe
  fatal: could not fetch c4fff0229c9be06ecf576356a4d39a8a755b8d81 from promisor remote
  warning: Clone succeeded, but checkout failed.
  You can inspect what was checked out with 'git status'
  and retry with 'git restore --source=HEAD :/'

Fix the immediate problem by including the authorization headers in the
probe_rpc() request as well.

[1] https://github.com/Binary-Eater/git-credential-msal

Signed-off-by: Aaron Plattner <aplattner@nvidia.com>
Tested-by: Lucas De Marchi <demarchi@kernel.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-17 13:48:52 +09:00
Junio C Hamano
f1799202ea Merge branch 'ps/object-read-stream' into ps/packfile-store-in-odb-source
* ps/object-read-stream:
  streaming: drop redundant type and size pointers
  streaming: move into object database subsystem
  streaming: refactor interface to be object-database-centric
  streaming: move logic to read packed objects streams into backend
  streaming: move logic to read loose objects streams into backend
  streaming: make the `odb_read_stream` definition public
  streaming: get rid of `the_repository`
  streaming: rely on object sources to create object stream
  packfile: introduce function to read object info from a store
  streaming: move zlib stream into backends
  streaming: create structure for filtered object streams
  streaming: create structure for packed object streams
  streaming: create structure for loose object streams
  streaming: create structure for in-core object streams
  streaming: allocate stream inside the backend-specific logic
  streaming: explicitly pass packfile info when streaming a packed object
  streaming: propagate final object type via the stream
  streaming: drop the `open()` callback function
  streaming: rename `git_istream` into `odb_read_stream`
2025-12-15 17:40:31 +09:00
Lucas Seiki Oshiro
ac3e74d2b7 repo: add new flag --keys to git-repo-info
If the user wants to find what are the available keys, they need to
either check the documentation or to ask for all the key-value pairs
by using --all.

Add a new flag --keys for listing only the available keys without
listing the values.

Signed-off-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-11 14:43:17 +09:00
Lucas Seiki Oshiro
240208233c repo: add a default output format to enum output_format
Add a `FORMAT_DEFAULT` value to `enum output_format`. Change the initial
value of `format` to `FORMAT_DEFAULT` in cmd_repo_info, indicating that
the initial value hasn't been changed. Also map the string "default" to
this new value in `parse_format_cb`, allowing future patches to add
support to --format=default.

Signed-off-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-11 14:43:17 +09:00
Junio C Hamano
a92df5a0de Merge branch 'lo/repo-struct-z' into lo/repo-info-keys
* lo/repo-struct-z:
  repo: add -z as an alias for --format=nul to git-repo-structure
  repo: use [--format=... | -z] instead of [-z] in git-repo-info synopsis
  repo: remove blank line from Documentation/git-repo.adoc
2025-12-11 14:42:32 +09:00
Sam Bostock
5ae594f30b doc: fix update-ref symref-create formatting
`symref-create` should be followed `::`, not `:`. The lack of second
colon (`:`) causes it to appear as regular text (`<p>`) instead of as a
description list term (`<dt>`) in the HTML documentation.

Signed-off-by: Sam Bostock <sam@sambostock.ca>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-09 18:31:36 +09:00
Yee Cheng Chin
e39a3410c4 xdiff: re-diff shifted change groups when using histogram algorithm
After a diff algorithm has been run, the compaction phase
(xdl_change_compact()) shifts and merges change groups to produce a
cleaner output. However, this shifting could create a new matched group
where both sides now have matching lines. This results in a
wrong-looking diff output which contains redundant lines that are the
same on both files.

Fix this by detecting this situation, and re-diff the texts on each side
to find similar lines, using the fall-back Myer's diff. Only do this for
histogram diff as it's the only algorithm where this is relevant. Below
contains an example, and more details.

For an example, consider two files below:

    file1:
        A

        A
        A
        A

        A
        A
        A

    file2:
        A

        A
        x
        A

        A
        A
        A

When using Myer's diff, the algorithm finds that only the "x" has been
changed, and produces a final diff result (these are line diffs, but
using word-diff syntax for ease of presentation):

        A A[-A-]{+x+}A AAA

When using histogram diff, the algorithm first discovers the LCS "A
AAA", which it uses as anchor, then produces an intermediate diff:

        {+A Ax+}A AAA[- AAA-].

This is a longer diff than Myer's, but it's still self-consistent.
However, the compaction phase attempts to shift the first file's diff
group upwards (note that this shift crosses the anchor that histogram
had used), leading to the final results for histogram diff:

        [-A AA-]{+A Ax+}A AAA

This is a technically correct patch but looks clearly redundant to a
human as the first 3 lines should not be in the diff.

The fix would detect that a shift has caused matching to a new group,
and re-diff the "A AA" and "A Ax" parts, which results in "A A"
correctly re-marked as unchanged. This creates the now correct histogram
diff:

        A A[-A-]{+x+}A AAA

This issue is not applicable to Myer's diff algorithm as it already
generates a minimal diff, which means a shift cannot result in a smaller
diff output (the default Myer's diff in xdiff is not guaranteed to be
minimal for performance reasons, but it typically does a good enough
job).

It's also not applicable to patience diff, because it uses only unique
lines as anchor for its splits, and falls back to Myer's diff within
each split. Shifting requires both ends having the same lines, and
therefore cannot cross the unique line boundaries established by the
patience algorithm. In contrast histogram diff uses non-unique lines as
anchors, and therefore shifting can cross over them.

This issue is rare in a normal repository. Below is a table of
repositories (`git log --no-merges -p --histogram -1000`), showing how
many times a re-diff was done and how many times it resulted in finding
matching lines (therefore addressing this issue) with the fix. In
general it is fewer than 1% of diff's that exhibit this offending
behavior:

| Repo (1k commits)  | Re-diff | Found matching lines |
|--------------------|---------|----------------------|
| llvm-project       |  45     | 11                   |
| vim                | 110     |  9                   |
| git                |  18     |  2                   |
| WebKit             | 168     |  1                   |
| ripgrep            |  22     |  1                   |
| cpython            |  32     |  0                   |
| vscode             |  13     |  0                   |

Signed-off-by: Yee Cheng Chin <ychin.git@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:40:34 +09:00
Taylor Blau
1e06a72a0a midx: enable reachability bitmaps during MIDX compaction
Enable callers to generate reachability bitmaps when performing MIDX
layer compaction by combining all existing bitmaps from the compacted
layers.

Note that the because of the object/pack ordering described by the
previous commit, the pseudo-pack order for the compacted MIDX is the
same as concatenating the individual pseudo-pack orderings for each
layer in the compaction range.

As a result, the only non-test or documentation change necessary is to
treat all objects as non-preferred during compaction so as not to
disturb the object ordering.

In the future, we may want to adjust which commit(s) receive
reachability bitmaps when compacting multiple .bitmap files into one, or
even generate new bitmaps (e.g., if the references have moved
significantly since the .bitmap was generated). This commit only
implements combining all existing bitmaps in range together in order to
demonstrate and lay the groundwork for more exotic strategies.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:11 +09:00
Taylor Blau
98ce832ee6 midx: implement MIDX compaction
When managing a MIDX chain with many layers, it is convenient to combine
a sequence of adjacent layers into a single layer to prevent the chain
from growing too long.

While it is conceptually possible to "compact" a sequence of MIDX layers
together by running "git multi-pack-index write --stdin-packs", there
are a few drawbacks that make this less than desirable:

 - Preserving the MIDX chain is impossible, since there is no way to
   write a MIDX layer that contains objects or packs found in an earlier
   MIDX layer already part of the chain. So callers would have to write
   an entirely new (non-incremental) MIDX containing only the compacted
   layers, discarding all other objects/packs from the MIDX.

 - There is (currently) no way to write a MIDX layer outside of the MIDX
   chain to work around the above, such that the MIDX chain could be
   reassembled substituting the compacted layers with the MIDX that was
   written.

 - The `--stdin-packs` command-line option does not allow us to specify
   the order of packs as they appear in the MIDX. Therefore, even if
   there were workarounds for the previous two challenges, any bitmaps
   belonging to layers which come after the compacted layer(s) would no
   longer be valid.

This commit introduces a way to compact a sequence of adjacent MIDX
layers into a single layer while preserving the MIDX chain, as well as
any bitmap(s) in layers which are newer than the compacted ones.

Implementing MIDX compaction does not require a significant number of
changes to how MIDX layers are written. The main changes are as follows:

 - Instead of calling `fill_packs_from_midx()`, we call a new function
   `fill_packs_from_midx_range()`, which walks backwards along the
   portion of the MIDX chain which we are compacting, and adds packs one
   layer a time.

   In order to preserve the pseudo-pack order, the concatenated pack
   order is preserved, with the exception of preferred packs which are
   always added first.

 - After adding entries from the set of packs in the compaction range,
   `compute_sorted_entries()` must adjust the `pack_int_id`'s for all
   objects added in each fanout layer to match their original
   `pack_int_id`'s (as opposed to the index at which each pack appears
   in `ctx.info`).

 - When writing out the new 'multi-pack-index-chain' file, discard any
   layers in the compaction range, replacing them with the newly written
   layer, instead of keeping them and placing the new layer at the end
   of the chain.

This ends up being sufficient to implement MIDX compaction in such a way
that preserves bitmaps corresponding to more recent layers in the MIDX
chain.

The tests for MIDX compaction are so far fairly spartan, since the main
interesting behavior here is ensuring that the right packs/objects are
selected from each layer, and that the pack order is preserved despite
whether or not they are sorted in lexicographic order in the original
MIDX chain.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:11 +09:00
Taylor Blau
6b7d812696 t/helper/test-read-midx.c: plug memory leak when selecting layer
Though our 'read-midx' test tool is capable of printing information
about a single MIDX layer identified by its checksum, no caller in our
test suite exercises this path.

Unfortunately, there is a memory leak lurking in this (currently) unused
path that would otherwise be exposed by the following commit.

This occurs when providing a MIDX layer checksum other than the tip. As
we walk over the MIDX chain trying to find the matching layer, we drop
our reference to the top-most MIDX layer. Thus, our call to
'close_midx()' later on leaks memory between the top-most MIDX layer and
the MIDX layer immediately following the specified one.

Plug this leak by holding a reference to the tip of the MIDX chain, and
ensure that we call `close_midx()` before terminating the test tool.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:10 +09:00
Taylor Blau
477f9c1b21 midx-write.c: factor fanout layering from compute_sorted_entries()
When computing the set of objects to appear in a MIDX, we use
compute_sorted_entries(), which handles objects from various existing
sources one fanout layer at a time.

The process for computing this set is slightly different during MIDX
compaction, so factor out the existing functionality into its own
routine to prevent `compute_sorted_entries()` from becoming too
difficult to read.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:10 +09:00
Taylor Blau
cad0632e13 midx-write.c: enumerate pack_int_id values directly
Our `midx-write.c::fill_packs_from_midx()` function currently enumerates
the range [0, m->num_packs), and then shifts its index variable up by
`m->num_packs_in_base` to produce a valid `pack_int_id`.

Instead, directly enumerate the range:

    [m->num_packs_in_base, m->num_packs_in_base + m->num_packs)

, which are the original pack_int_ids themselves as opposed to the
indexes of those packs relative to the MIDX layer they are contained
within.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:09 +09:00
Taylor Blau
9ced91c83b midx-write.c: extract fill_pack_from_midx()
When filling packs from an existing MIDX, `fill_packs_from_midx()`
handles preparing a MIDX'd pack, and reading out its pack name from the
existing MIDX.

MIDX compaction will want to perform an identical operation, though the
caller will look quite different than `fill_packs_from_midx()`. To
reduce any future code duplication, extract `fill_pack_from_midx()`
from `fill_packs_from_midx()` to prepare to call our new helper function
in a future change.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:09 +09:00
Taylor Blau
363485a311 midx-write.c: introduce midx_pack_perm() helper
The `ctx->pack_perm` array can be considered as a permutation between
the original `pack_int_id` of some given pack to its position in the
`ctx->info` array containing all packs.

Today we can always index into this array with any known `pack_int_id`,
since there is never a `pack_int_id` which is greater than or equal to
the value `ctx->nr`.

That is not necessarily the case with MIDX compaction. For example,
suppose we have a MIDX chain with three layers, each containing three
packs. The base of the MIDX chain will have packs with IDs 0, 1, and 2,
the next layer 3, 4, and 5, and so on. If we are compacting the topmost
two layers, we'll have input `pack_int_id` values between [3, 8], but
`ctx->nr` will only be 6.

In that example, if we want to know where the pack whose original
`pack_int_id` value was, say, 7, we would compute `ctx->pack_perm[7]`,
leading to an uninitialized read, since there are only 6 entries
allocated in that array.

To address this, there are a couple of options:

 - We could allocate enough entries in `ctx->pack_perm` to accommodate
   the largest `orig_pack_int_id` value.

 - Or, we could internally shift the input values by the number of packs
   in the base layer of the lower end of the MIDX compaction range.

This patch prepare us to take the latter approach, since it does not
allocate more memory than strictly necessary. (In our above example, the
base of the lower end of the compaction range is the first MIDX layer
(having three packs), so we would end up indexing `ctx->pack_perm[7-3]`,
which is a valid read.)

Note that this patch does not actually implement that approach yet, but
merely performs a behavior-preserving refactoring which will make the
change easier to carry out in the future.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:08 +09:00
Taylor Blau
35364d7aba git-compat-util.h: introduce u32_add()
A future commit will want to add two 32-bit unsigned values together
while checking for overflow. Introduce a variant of the u64_add()
function for operating on 32-bit inputs.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:08 +09:00
Taylor Blau
b40e7cbca2 midx: do not require packs to be sorted in lexicographic order
The MIDX file format currently requires that pack files be identified by
the lexicographic ordering of their names (that is, a pack having a
checksum beginning with "abc" would have a numeric pack_int_id which is
smaller than the same value for a pack beginning with "bcd").

As a result, it is impossible to combine adjacent MIDX layers together
without permuting bits from bitmaps that are in more recent layer(s).

To see why, consider the following example:

          | packs       | preferred pack
  --------+-------------+---------------
  MIDX #0 | { X, Y, Z } | Y
  MIDX #1 | { A, B, C } | B
  MIDX #2 | { D, E, F } | D

, where MIDX #2's base MIDX is MIDX #1, and so on. Suppose that we want
to combine MIDX layers #0 and #1, to create a new layer #0' containing
the packs from both layers. With the original three MIDX layers, objects
are laid out in the bitmap in the order they appear in their source
pack, and the packs themselves are arranged according to the pseudo-pack
order. In this case, that ordering is Y, X, Z, B, A, C.

But recall that the pseudo-pack ordering is defined by the order that
packs appear in the MIDX, with the exception of the preferred pack,
which sorts ahead of all other packs regardless of its position within
the MIDX. In the above example, that means that pack 'Y' could be placed
anywhere (so long as it is designated as preferred), however, all other
packs must be placed in the location listed above.

Because that ordering isn't sorted lexicographically, it is impossible
to compact MIDX layers in the above configuration without permuting the
object-to-bit-position mapping. Changing this mapping would affect all
bitmaps belonging to newer layers, rendering the bitmaps associated with
MIDX #2 unreadable.

One of the goals of MIDX compaction is that we are able to shrink the
length of the MIDX chain *without* invalidating bitmaps that belong to
newer layers, and the lexicographic ordering constraint is at odds with
this goal.

However, packs do not *need* to be lexicographically ordered within the
MIDX. As far as I can gather, the only reason they are sorted lexically
is to make it possible to perform a binary search over the pack names in
a MIDX, necessary to make `midx_contains_pack()`'s performance
logarithmic in the number of packs rather than linear.

Relax this constraint by allowing MIDX writes to proceed with packs that
are not arranged in lexicographic order. `midx_contains_pack()` will
lazily instantiate a `pack_names_sorted` array on the MIDX, which will
be used to implement the binary search over pack names.

Note that this produces MIDXs which may be incompatible with earlier
versions of Git that have stricter requirements on the layout of packs
within a MIDX. This patch does *not* modify the version number of the
MIDX format, since existing versions of Git already know to gracefully
ignore a MIDX with packs that appear out-of-order.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:08 +09:00
Taylor Blau
a7c1d30f29 midx-write.c: introduce struct write_midx_opts
In the MIDX writing code, there are four functions which perform some
sort of MIDX write operation. They are:

 - write_midx_file()
 - write_midx_file_only()
 - expire_midx_packs()
 - midx_repack()

All of these functions are thin wrappers over `write_midx_internal()`,
which implements the bulk of these routines. As a result, the
`write_midx_internal()` function takes six arguments.

Future commits in this series will want to add additional arguments, and
in general this function's signature will be the union of parameters
among *all* possible ways to write a MIDX.

Instead of adding yet more arguments to this function to support MIDX
compaction, introduce a `struct write_midx_opts`, which has the same
struct members as `write_midx_internal()`'s arguments.

Adding additional fields to the `write_midx_opts` struct is preferable
to adding additional arguments to `write_midx_internal()`. This is
because the callers below all zero-initialize the struct, so each time
we add a new piece of information, we do not have to pass the zero value
for it in all other call-sites that do not care about it.

For now, no functional changes are included in this patch.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:07 +09:00
Taylor Blau
9df4325f15 midx-write.c: don't use pack_perm when assigning bitmap_pos
In midx_pack_order(), we compute for each bitampped pack the first bit
to correspond to an object in that pack, along with how many bits were
assigned to object(s) in that pack.

Initially, each bitmap_nr value is set to zero, and each bitmap_pos
value is set to the sentinel BITMAP_POS_UNKNOWN. This is done to ensure
that there are no packs who have an unknown bit position but a somehow
non-zero number of objects (cf. `write_midx_bitmapped_packs()` in
midx-write.c).

Once the pack order is fully determined, midx_pack_order() sets the
bitmap_pos field for any bitmapped packs to zero if they are still
listed as BITMAP_POS_UNKNOWN.

However, we enumerate the bitmapped packs in order of `ctx->pack_perm`.
This is fine for existing cases, since the only time the
`ctx->pack_perm` array holds a value outside of the addressable range of
`ctx->info` is when there are expired packs, which only occurs via 'git
multi-pack-index expire', which does not support writing MIDX bitmaps.
As a result, the range of ctx->pack_perm covers all values in [0,
`ctx->nr`), so enumerating in this order isn't an issue.

A future change necessary for compaction will complicate this further by
introducing a wrapper around the `ctx->pack_perm` array, which turns the
given `pack_int_id` into one that is relative to the lower end of the
compaction range. As a result, indexing into `ctx->pack_perm` through
this helper, say, with "0" will produce a crash when the lower end of
the compaction range has >0 pack(s) in its base layer, since the
subtraction will wrap around the 32-bit unsigned range, resulting in an
uninitialized read.

But the process is completely unnecessary in the first place: we are
enumerating all values of `ctx->info`, and there is no reason to process
them in a different order than they appear in memory. Index `ctx->info`
directly to reflect that.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:07 +09:00
Taylor Blau
3dfc5b5c83 t/t5319-multi-pack-index.sh: fix copy-and-paste error in t5319.39
Commit d4bf1d88b90 (multi-pack-index: verify missing pack, 2018-09-13)
adds a new test to the MIDX test script to test how we handle missing
packs.

While the commit itself describes the test as "verify missing pack[s]",
the test itself is actually called "verify packnames out of order",
despite that not being what it tests.

Likely this was a copy-and-paste of the test immediately above it of the
same name. Correct this by renaming the test to match the commit
message.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:06 +09:00
Taylor Blau
ae3770a76e git-multi-pack-index(1): align SYNOPSIS with 'git multi-pack-index -h'
Since c39fffc1c90 (tests: start asserting that *.txt SYNOPSIS matches -h
output, 2022-10-13), the manual page for 'git multi-pack-index' has a
SYNOPSIS section which differs from 'git multi-pack-index -h'.

Correct this while also documenting additional options accepted by the
'write' sub-command.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:06 +09:00
Taylor Blau
10292d39ac git-multi-pack-index(1): remove non-existent incompatibility
Since fcb2205b774 (midx: implement support for writing incremental MIDX
chains, 2024-08-06), the command-line options '--incremental' and
'--bitmap' were declared to be incompatible with one another when
running 'git multi-pack-index write'.

However, since 27afc272c49 (midx: implement writing incremental MIDX
bitmaps, 2025-03-20), that incompatibility no longer exists, despite the
documentation saying so. Correct this by removing the stale reference to
their incompatibility.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:05 +09:00
Taylor Blau
13980c8225 builtin/multi-pack-index.c: make '--progress' a common option
All multi-pack-index sub-commands (write, verify, repack, and expire)
support a '--progress' command-line option, despite not listing it as
one of the common options in `common_opts`.

As a result each sub-command declares its own `OPT_BIT()` for a
"--progress" command-line option. Centralize this within the
`common_opts` to avoid re-declaring it in each sub-command.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:05 +09:00
Taylor Blau
fcf3d688d2 midx: split get_midx_checksum() by adding get_midx_hash()
When trying to print out, say, the hexadecimal representation of a
MIDX's hash, our code will do something like:

    hash_to_hex_algop(get_midx_checksum(m),
                      m->source->odb->repo->hash_algo);

, which is both cumbersome and repetitive. In fact, all but a handful of
callers to `get_midx_checksum()` do exactly the above. Reduce the
repetitive nature of calling `get_midx_checksum()` by having it return a
pointer into a static buffer containing the above result.

For the handful of callers that do need to compare the raw bytes and
don't want to deal with an encoded copy (e.g., because they are passing
it to hasheq() or similar), introduce `get_midx_hash()` which returns
the raw bytes.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:05 +09:00
Taylor Blau
3eea3c8023 midx: mark get_midx_checksum() arguments as const
To make clear that the fucntion `get_midx_checksum()` does not do
anything to modify its argument, mark the MIDX pointer as const.

The following commit will rename this function altogether to make clear
that it returns the raw bytes of the checksum, not a hex-encoded copy of
it.

Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:38:04 +09:00
Patrick Steinhardt
84071a6dea gitattributes: disable blank-at-eof errors for clar test expectations
The clar unit testing framework carries a couple of files that contain
expected output for its self-tests. Some of these files expectedly end
with a blank line at the end of the file, which Git would consider to be
a whitespace error by default.

Teach our gitattributes to ignore those errors.

Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:25:16 +09:00
Patrick Steinhardt
2e53d29f53 t/unit-tests: demonstrate use of integer comparison assertions
The clar project has introduced a couple of new assertions that perform
relative integer comparisons, like "greater than" or "less or equal".
Adapt the reftable-record unit tests to demonstrate their usage.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:25:16 +09:00
Patrick Steinhardt
d5e4aef358 t/unit-tests: update clar to 39f11fe
Update clar to commit 39f11fe (Merge pull request #131 from
pks-gitlab/pks-integer-double-evaluation, 2025-12-05). This commit
includes the following changes relevant to Git:

  - There are now typesafe integer comparison functions. Furthermore,
    the range of comparison functions has been included to also have
    relative comparisons, like "greater than".

  - There is a new `cl_failf()` macro that allows the caller to specify
    an error message with formatting directives.

  - The TAP format has been fixed to correctly terminate YAML blocks
    with "...\n" instead of "---\n".

Note that we already had a `cl_failf()` function declared in our own
sources. This function is equivalent to the upstreamed function, so we
can simply drop it now.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-07 07:25:15 +09:00
Junio C Hamano
ecde85d238 Merge branch 'ps/object-source-management' into ps/odb-misc-fixes
* ps/object-source-management:
  odb: handle recreation of quarantine directories
  odb: handle changing a repository's commondir
  chdir-notify: add function to unregister listeners
  odb: handle initialization of sources in `odb_new()`
  http-push: stop setting up `the_repository` for each reference
  t/helper: stop setting up `the_repository` repeatedly
  builtin/index-pack: fix deferred fsck outside repos
  oidset: introduce `oidset_equal()`
  odb: move logic to disable ref updates into repo
  odb: refactor `odb_clear()` to `odb_free()`
  odb: adopt logic to close object databases
  setup: convert `set_git_dir()` to have file scope
  path: move `enter_repo()` into "setup.c"
2025-12-05 21:23:32 +09:00
Karthik Nayak
7ccec5a1f6 refs: add GIT_REF_URI to specify reference backend and directory
Git allows setting a different object directory via
'GIT_OBJECT_DIRECTORY', but provides no equivalent for references.
This asymmetry makes it difficult to test different reference backends
or use alternative reference storage locations without modifying the
repository structure.

Add a new environment variable 'GIT_REF_URI' that specifies both the
reference backend and directory path using a URI format:

    <ref_backend>://<URI-for-resource>

When set, this variable is used to obtain the main reference store for
all Git commands. The variable is checked in `get_main_ref_store()`
when lazily assigning `repo->refs_private`. We cannot initialize this
earlier in `repo_set_gitdir()` because the repository's hash algorithm
isn't known at that point, and the reftable backend requires this
information during initialization.

When used with worktrees, the specified directory is treated as the
reference directory for all worktree operations.

Add a new test file 't1423-ref-backend.sh' to test this environment
variable.

Helped-by: Jean-Noël Avila <jn.avila@free.fr>
Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-01 16:23:51 -08:00
Karthik Nayak
cc4dcc855b refs: support obtaining ref_store for given dir
The refs subsystem uses the `get_main_ref_store()` to obtain the main
ref_store for a given repository. In the upcoming patches we also want
to create a ref_store for any given reference directory, which may exist
in arbitrary paths. For the files backend and the reftable backend, the
reference directory is generally the $GIT_DIR.

To support such behavior, extract out the core logic for creating out
the ref_store from `get_main_ref_store()` into a new function
`get_ref_store_for_dir()` which can provide the ref_store for a
given (repository, directory, reference format) combination.

Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-12-01 16:23:51 -08:00
Jeff King
247c6598a8 fsck: use parse_unsigned_from_buf() for parsing timestamp
In 5a993593b2 (fsck: avoid parse_timestamp() on buffer that isn't
NUL-terminated, 2025-11-18), we added a wrapper that copies the
timestamp into a buffer before calling parse_timestamp().

Now that we have a more robust helper for parsing from a buffer, we can
drop our wrapper and switch to that. We could just do so inline, but the
choice of "unsigned" vs "signed" depends on the typedef of timestamp_t.
So we'll wrap that in a macro that is defined alongside the rest of the
timestamp abstraction.

The resulting function is almost a drop-in replacement, but the new
interface means we need to hold the result in a separate timestamp_t,
rather than returning it directly from one function into the parameter
of another. The old one did still detect overflow errors by returning
TIME_MAX, since date_overflows() checks for that, but now we'll see it
more directly from the return of parse_timestamp_from_buf(). The
behavior should be the same.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-30 10:03:43 -08:00
Jeff King
7cf9c96646 cache-tree: use parse_int_from_buf()
In c4c9089584 (cache-tree: avoid strtol() on non-string buffer,
2025-11-18) we wrote an ad-hoc integer parser which did not detect
overflow. This wasn't too big a problem, since the original use of
strtol() did not do so either. But now that we have a more robust
parsing function, let's use that. It reduces the amount of code and
should catch more cases of malformed entries.

I kept our local parse_int() wrapper here, since it handles management
of our ptr/len pair (rather than doing it inline in the entry parser of
read_one()).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-30 10:03:43 -08:00
Jeff King
b5b6c11a70 parse: add functions for parsing from non-string buffers
If you have a buffer that is not NUL-terminated but want to parse an
integer, there aren't many good options. If you use strtol() and
friends, you risk running off the end of the buffer if there is no
non-digit terminating character. And even if you carefully make sure
that there is such a character, ASan's strict-string-check mode will
still complain.

You can copy bytes into a temporary buffer, terminate it, and then call
strtol(), but doing so adds some pitfalls (like making sure you soak up
whitespace and leading +/- signs, and reporting overflow for overly long
input). Or you can hand-parse the digits, but then you need to take some
care to handle overflow (and again, whitespace and +/- signs).

These things aren't impossible to do right, but it's error-prone to have
to do them in every spot that wants to do such parsing. So let's add
some functions which can be used across the code base.

There are a few choices regarding the interface and the implementation.

First, the implementation:

  - I went with with parsing the digits (rather than buffering and
    passing to libc functions). It ends up being a similar amount of
    code because we have to do some parsing either way. And likewise
    overflow detection depends on the exact type the caller wants, so we
    either have to do it by hand or write a separate wrapper for
    strtol(), strtoumax(), and so on.

  - Unsigned overflow detection is done using the same techniques as in
    unsigned_add_overflows(), etc. We can't use those macros directly
    because our core function is type-agnostic (so the caller passes in
    the max value, rather than us deriving it on the fly). This is
    similar to how git_parse_int(), etc, work.

  - Signed overflow detection assumes that we can express a negative
    value with magnitude one larger than our maximum positive value
    (e.g., -128..127 for a signed 8-bit value). I doubt this is
    guaranteed by the standard, but it should hold in practice, and we
    make the same assumption in git_parse_int(), etc. The nice thing
    about this is that we can derive the range from the number of bits
    in the type. For ints, you obviously could use INT_MIN..INT_MAX, but
    for an arbitrary type, we can use maximum_signed_value_of_type().

  - I didn't bother with handling bases other than 10. It would
    complicate the code, and I suspect it won't be needed. We could
    probably retro-fit it later without too much work, if need be.

For the interface:

  - What do we call it? We have git_parse_int() and friends, which aim
    to make parsing less error-prone. And in some ways, these are just
    buffer (rather than string) versions of those functions. But not
    entirely. Those functions are aimed at parsing a single user-facing
    value. So they accept a unit prefix (e.g., "10k"), which we won't
    always want. And they insist that the whole string is consumed
    (rather than passing back an "end" pointer).

    We also have strtol_i() and strtoul_ui() wrappers, which try to make
    error handling simpler (especially around overflow), but mostly
    behave like their libc counterparts. These also don't pass out an
    end pointer, though.

    So I started a new namespace, "parse_<type>_from_buf".

  - Like those other functions above, we use an out-parameter to store
    the result, which lets us return an error code directly. This avoids
    the complicated errno dance for detecting overflow that you get with
    strtol().

    What should the error code look like? git_parse_int() uses a bool
    for success/failure. But strtol_ui() uses the syscall-like "0 is
    success, -1 is error" convention.

    I went with the bool approach here. Since the names are closest to
    those functions, I thought it would cause the least confusion.

  - Unlike git_parse_signed() and friends, we do not insist that the
    entire buffer be consumed. For parsing a specific standalone string
    that makes sense, but within an unterminated buffer you are much
    more likely to be parsing multiple fields from a larger data set.

    We pass out an "end" pointer the same way strtol() does. Another
    option is to accept the input as an in-out parameter and advance the
    pointer ourselves (and likewise shrink the length pointer). That
    would let you do something like:

       if (!parse_int_from_buf(&p, &len, &out))
               return error(...);
       /* "p" and "len" were adjusted automatically */
       if (!len || *p++ != ' ')
               return error(...);

    That saves a few lines of code in some spots, but requires a few
    more in others (depending on whether the caller has a length in the
    first place or is using an end pointer). Of the two callers I intend
    to immediately convert, we have one of each type!

    I went with the strtol() approach as flexible and time-tested.

  - We could likewise take the input buffer as two pointers (start and
    end) rather than a pointer and a length. That again makes life
    easier for some callers and harder for others. I stuck with pointer
    and length as the more usual interface.

  - What happens when a caller passes in a NULL end pointer? This is
    allowed by strtol(). But I think it's often a sign of a lurking bug,
    because there's no way to know how much was consumed (and even if a
    caller wants to assume everything is consumed, you have no way to
    verify it). So it is simply an error in this interface (you'd get a
    segfault).

    I am tempted to say that if the end pointer is NULL the functions
    could confirm that the entire buffer was consumed, as a convenience.
    But that felt a bit magical and surprising.

Like git_parse_*(), there is a generic signed/unsigned helper, and then
we can add type-specific helpers on top. I've added an int helper here
to start, and we'll add more as we convert callers.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-30 10:03:43 -08:00
Jeff King
fa4fe12fa0 parse: prefer bool to int for boolean returns
All of the integer parsing functions in parse.[ch] return an int that is
"0" for failure or "1" for success. Since most of the other functions in
Git use "0" for success and "-1" for failure, this can be confusing.
Let's switch the return types to bool to make it clear that we are using
this other convention. Callers should not need to update at all.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-30 10:03:42 -08:00
Junio C Hamano
9841b05cbc Merge branch 'jk/asan-bonanza' into jk/parse-int
* jk/asan-bonanza:
  t: enable ASan's strict_string_checks option
  fsck: avoid parse_timestamp() on buffer that isn't NUL-terminated
  fsck: remove redundant date timestamp check
  fsck: avoid strcspn() in fsck_ident()
  fsck: assert newline presence in fsck_ident()
  cache-tree: avoid strtol() on non-string buffer
  Makefile: turn on NO_MMAP when building with ASan
  pack-bitmap: handle name-hash lookups in incremental bitmaps
  compat/mmap: mark unused argument in git_munmap()
2025-11-30 10:03:37 -08:00
Junio C Hamano
925a5befa6 fixup! last-modified: document option --max-depth 2025-11-26 11:06:33 -08:00
Toon Claes
cd0f572477 last-modified: document how depth is handled better
By default git-last-modified(1) only shows information about paths at
the root level. This can be confusing. Clarify the command's behavior in
the documentation.

Signed-off-by: Toon Claes <toon@iotcl.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-26 10:14:38 -08:00
Wiktor Mis
87466656fa completion: complete "git -<TAB>" with short options
"git" itself has completion for its long options and subcommands,
but not for its short options.  Add support for them.

Signed-off-by: Wiktor Mis <mwiktor023@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-26 09:55:55 -08:00
Toon Claes
e8c09f2af4 last-modified: document option --max-depth
Option --max-depth is supported by git-last-modified(1), because it was
added to the diff machinery in a1dfa5448d (diff: teach tree-diff a
max-depth parameter, 2025-08-07).

This option is useful for everyday use of the git-last-modified(1)
command, so document it's existence in the man page and `-h` output.

Signed-off-by: Toon Claes <toon@iotcl.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-26 08:46:55 -08:00
Toon Claes
f73f951464 last-modified: handle and document NUL termination
When option `-z` is provided to git-last-modified(1), each line is
separated with a NUL instead of a newline. Document this properly and
handle parsing of the option in the builtin itself.

Signed-off-by: Toon Claes <toon@iotcl.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-26 08:46:54 -08:00
Samo Pogačnik
2026440bab shallow: set borders which are all reachable after clone shallow since
When shallow cloning based on a date, it happens that not all
shallow border commits are reachable.

Original implementation of a generic shallow boundary finder
based on rev-list sets a commit (from the initial list of border
commit candidates) to be the border commit as soon as it finds one
of its parentis that wasn't on the list of initial candidates. This
results in a successful shallow clone, where some of its declared
border commits may not be reachable and they would not actually exist
in the cloned repository. Thus the result may contradict existing
comment in the code, which correctly states that such commmit should
not be considered border.

One can inspect such case by running the added test scenario:
- 'clone shallow since all borders reachable'

The modified implementation of a generic shallow boundary finder
based on rev-list ensures that all shallow border commits are reachable
also after being grafted. This is achieved by inspecting all parents
of each initial border commit candidate. The border commit candidate
is set border only when all its parents wern't on the initial list of
candidates. Otherwise the border commit candidate is not set as border
however its parents that weren't on the list of candidates are set as
borders.

Signed-off-by: Samo Pogačnik <samo_pogacnik@t-2.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-23 21:35:17 -08:00
Delilah Ashley Wu
f141ee7d7b config: keep bailing on unreadable global files
The expected behaviour for `git config list` is:
  A. Without `--global`, it should not bail on unreadable/non-existent
     global config files.

  B. With `--global`, it should bail when both `$HOME/.gitconfig` and
     `$XDG_CONFIG_HOME/git/config` are unreadable. It should not bail
     when one or more of them is readable.

The previous patch, config: read global scope via config_sequence,
introduced a regression in scenario B. When both global config files are
unreadable, running `git config list --global` would not fail. For
example, `GIT_CONFIG_GLOBAL=does-not-exist git config list --global`
exits with status code 0.

Assuming that `config_source->scope == CONFIG_SCOPE_GLOBAL` iff the
`--global` argument is specified, use this to determine whether to bail.
When reading only the global scope and both config files are unreadable,
then adjust the return code to be non-zero.

Note: When bailing, the exit code is not determined by sum of the return
codes of the underlying operations. Instead, the exit code is modified
via a single decrement. If this is undesirable, we can change it to sum
the return codes of the underlying operations instead.

Lastly, modify the tests to remove the known breakage/regression. The
tests for scenario B will now pass.

Helped-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>
Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-19 07:14:27 -08:00
Delilah Ashley Wu
eb3a952ca1 config: read global scope via config_sequence
The output of `git config list --global` should include both the home
(`$HOME/.gitconfig`) and XDG (`$XDG_CONFIG_HOME/git/config`) configs,
but it only reads from the former.

We assumed each config scope corresponds to a single config file. Under
this assumption, `git config list --global` reads the global config by
calling `git_config_from_file_with_options(...,"~/.gitconfig", ...)`.
This function usage restricts us to a single config file. Because the
global scope includes two files, we should read the configs via another
method.

The output of `git config list --show-scope --show-origin` (without
`--global`) correctly includes both the home and XDG config files. So
there's existing code that respects both locations, namely the
`do_git_config_sequence()` function which reads from all scopes.
Introduce flags to make it possible to ignore all but the global scope
(i.e. ignore system, local, worktree, and cmdline). Then, reuse the
function to read only the global scope when `--global` is specified.
This was the suggested solution in the bug report:
https://lore.kernel.org/git/kl6ly1oze7wb.fsf@chooglen-macbookpro.roam.corp.google.com.

Then, modify the tests to check that `git config list --global` includes
both home and XDG configs.

This patch introduces a regression. If both global config files are
unreadable, then `git config list --global` should exit non-zero. This
is no longer the case, so mark the corresponding test as a "TODO known
breakage" and address the issue in the next patch, config: keep bailing
on unreadable global files.

Implementation notes:
  1. The `ignore_global` flag is not set anywhere, so the
     `if (!opts->ignore_global)` condition is always met. We can remove
     this flag if desired.

  2. I've assumed that `config_source->scope == CONFIG_SCOPE_GLOBAL` iff
     `--global` is specified. This comparison determines whether to call
     `do_git_config_sequence()` for the global scope, or to keep calling
     `git_config_from_file_with_options()` for other scopes.

  3. Keep populating `opts->source.file` in `builtin/config.c` because
     it is used as the destination config file for write operations.
     The proposed changes could convolute the code because there is no
     single source of truth for the config file locations in the global
     scope. Add a comment to help clarify this. Please let me know if
     it's unclear.

Reported-by: Jade Lovelace <lists@jade.fyi>
Suggested-by: Glen Choo <glencbz@gmail.com>
Helped-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>
Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-19 07:14:27 -08:00
Delilah Ashley Wu
eea016940e config: test home and xdg files in list --global
The `git config list --global` output includes `$HOME/.gitconfig` (home
config), but ignores `$XDG_CONFIG_HOME/git/config` (XDG config). It
should include both files.

Modify tests to check the following and expect a failure:
  - `git config list --global` should include contents from both the
     home and XDG config locations (assuming they are readable), not
     just the former.

  - `--show-origin` should print correct paths to both config files,
    assuming they exist.

Also, add tests to ensure subsequent patches do not introduce
regressions to `git config list`. Specifically, check that:
  - The home config should take precedence over the XDG config.

  - Without `--global`, it should not bail on unreadable/non-existent
    global config files.

  - With `--global`, it should bail when both `$HOME/.gitconfig` and
    `$XDG_CONFIG_HOME/git/config` are unreadable. It should not bail if
    at least one of them is readable.

The next patch, config: read global scope via config_sequence, will
implement a fix to include both config files when `--global` is
specified.

Reported-by: Jade Lovelace <lists@jade.fyi>
Helped-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>
Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-19 07:14:26 -08:00
Delilah Ashley Wu
dfd1063876 cleanup_path: force forward slashes on Windows
Git prefers forward slashes as directory separators across all
platforms. On Windows, the backslash is the native directory separator,
but all Windows versions supported by Git also accept the forward slash
in all but rare circumstances. Our tests expect forward slashes. Git
generates relative paths with forward slashes. Forward slashes are more
convenient to use in shell scripts.

For these reasons, we enforced forward slashes in `interpolate_path()`
in 5ca6b7bb47b (config --show-origin: report paths with forward slashes,
2016-03-23). However, other code paths may generate paths containing
backslashes. For example, `config --show-origin` prints the XDG config
path with mixed slashes on Windows:

$ git config --list --show-origin
file:C:/Program Files/Git/etc/gitconfig         system.foo=bar
file:"C:\\Users\\delilah/.config/git/config"    xdg.foo=bar
file:C:/Users/delilah/.gitconfig                home.foo=bar
file:.git/config                                local.foo=bar

Let's enforce forward slashes in all code paths that directly or
indirectly call `cleanup_path()` by modifying it to use
`convert_slashes()` on Windows. Since `convert_slashes()` modifies the
path in-place, change the argument and return type of `cleanup_path()`
from `const char *` to `char *`. All existing callers of
`cleanup_path()` pass `char *` anyways, so this change is compatible.

The next patch, config: test home and xdg files in `list --global`, will
assert that the XDG config path uses forward slashes.

Suggested-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Delilah Ashley Wu <delilahwu@microsoft.com>
Reviewed-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-19 07:14:26 -08:00
brian m. carlson
68aace560b object-file-convert: always make sure object ID algo is valid
In some cases, we zero-initialize our object IDs, which sets the algo
member to zero as well, which is not a valid algorithm number.  This is
a bad practice, but we typically paper over it in many cases by simply
substituting the repository's hash algorithm.

However, our new Rust loose object map code doesn't handle this
gracefully and can't find object IDs when the algorithm is zero because
they don't compare equal to those with the correct algo field.  In
addition, the comparison code doesn't have any knowledge of what the
main algorithm is because that's global state, so we can't adjust the
comparison.

To make our code function properly and to avoid propagating these bad
entries, if we get a source object ID with a zero algo, just make a copy
of it with the fixed algorithm.  This has the benefit of also fixing the
object IDs if we're in a single algorithm mode as well.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:16 -08:00
brian m. carlson
44ed7c1886 rust: add a small wrapper around the hashfile code
Our new binary object map code avoids needing to be intimately involved
with file handling by simply writing data to an object implement Write.
This makes it very easy to test by writing to a Cursor wrapping a Vec
for tests, and thus decouples it from intimate knowledge about how we
handle files.

However, we will actually want to write our data to an actual file,
since that's the most practical way to persist data.  Implement a
wrapper around the hashfile code that implements the Write trait so that
we can write our object map into a file.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:16 -08:00
brian m. carlson
ca05bfbae0 rust: add a new binary object map format
Our current loose object format has a few problems.  First, it is not
efficient: the list of object IDs is not sorted and even if it were,
there would not be an efficient way to look up objects in both
algorithms.

Second, we need to store mappings for things which are not technically
loose objects but are not packed objects, either, and so cannot be
stored in a pack index.  These kinds of things include shallows, their
parents, and their trees, as well as submodules. Yet we also need to
implement a sensible way to store the kind of object so that we can
prune unneeded entries.  For instance, if the user has updated the
shallows, we can remove the old values.

For these reasons, introduce a new binary object map format.  The
careful reader will notice that it resembles very closely the pack index
v3 format.  Add an in-memory object map as well, and allow writing to a
batched map, which can then be written later as one of the binary object
maps.  Include several tests for round tripping and data lookup across
algorithms.

Note that the use of this code elsewhere in Git will involve some C code
and some C-compatible code in Rust that will be introduced in a future
commit.  Thus, for example, we ignore the fact that if there is no
current batch and the caller asks for data to be written, this code does
nothing, mostly because this code also does not involve itself with
opening or manipulating files.  The C code that we will add later will
implement this functionality at a higher level and take care of this,
since the code which is necessary for writing to the object store is
deeply involved with our C abstractions and it would require extensive
work (which would not be especially valuable at this point) to port
those to Rust.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:16 -08:00
brian m. carlson
f15c9f93d5 rust: add functionality to hash an object
In a future commit, we'll want to hash some data when dealing with an
object map.  Let's make this easy by creating a structure to hash
objects and calling into the C functions as necessary to perform the
hashing.  For now, we only implement safe hashing, but in the future we
could add unsafe hashing if we want.  Implement Clone and Drop to
appropriately manage our memory.  Additionally implement Write to make
it easy to use with other formats that implement this trait.

While we're at it, add some tests for the various hashing cases.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:16 -08:00
brian m. carlson
ddeec7a34f rust: add a build.rs script for tests
Cargo uses the build.rs script to determine how to compile and link a
binary.  The only binary we're generating, however, is for our tests,
but in a future commit, we're going to link against libgit.a for some
functionality and we'll need to make sure the test binaries are
complete.

Add a build.rs file for this case and specify the files we're going to
be linking against.  Because we cannot specify different dependencies
when building our static library versus our tests, update the Makefile
to specify these dependencies for our static library to avoid race
conditions during build.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:15 -08:00
brian m. carlson
f29070cb25 hash: expose hash context functions to Rust
We'd like to be able to hash our data in Rust using the same contexts as
in C.  However, we need our helper functions to not be inline so they
can be linked into the binary appropriately.  In addition, to avoid
managing memory manually and since we don't know the size of the hash
context structure, we want to have simple alloc and free functions we
can use to make sure a context can be easily dynamically created.

Expose the helper functions and create alloc, free, and init functions
we can call.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:15 -08:00
brian m. carlson
f00c4ace1e write-or-die: add an fsync component for the object map
We'll soon be writing out an object map using the hashfile code. Add an
fsync component to allow us to handle fsyncing it correctly.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:15 -08:00
brian m. carlson
0bbba5f98f csum-file: define hashwrite's count as a uint32_t
We want to call this code from Rust and ensure that the types are the
same for compatibility, which is easiest to do if the type is a fixed
size.  Since unsigned int is 32 bits on all the platforms we care about,
define it as a uint32_t instead.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:15 -08:00
brian m. carlson
a8dfbc133a rust: add additional helpers for ObjectID
Right now, users can internally access the contents of the ObjectID
struct, which can lead to data that is not valid, such as invalid
algorithms or non-zero-padded hash values.  These can cause problems
down the line as we use them more.

Add a constructor for ObjectID that allows us to set these values and
also provide an accessor for the algorithm so that we can access it.  In
addition, provide useful Display and Debug implementations that can
format our data in a useful way.

Now that we have the ability to work with these various components in a
nice way, add some tests as well to make sure that ObjectID and
HashAlgorithm work together as expected.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:15 -08:00
brian m. carlson
eae08d8bf7 hash: add a function to look up hash algo structs
In C, it's easy for us to look up a hash algorithm structure by its
offset by simply indexing the hash_algos array.  However, in Rust, we
sometimes need a pointer to pass to a C function, but we have our own
hash algorithm abstraction.

To get one from the other, let's provide a simple function that looks up
the C structure from the offset and expose it in Rust.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:14 -08:00
brian m. carlson
76ee08578e rust: add a hash algorithm abstraction
This works very similarly to the existing one in C except that it
doesn't provide any functionality to hash an object.  We don't currently
need that right now, but the use of those function pointers do make it
substantially more difficult to write a bit-for-bit identical structure
across the C/Rust interface, so omit them for now.

Instead of the more customary "&self", use "self", because the former is
the size of a pointer and the latter is the size of an integer on most
systems.  Don't define an unknown value but use an Option for that
instead.

Update the object ID structure to allow slicing the data appropriately
for the algorithm.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:14 -08:00
brian m. carlson
0404613aa0 rust: add a ObjectID struct
We'd like to be able to write some Rust code that can work with object
IDs.  Add a structure here that's identical to struct object_id in C,
for easy use in sharing across the FFI boundary.  We will use this
structure in several places in hot paths, such as index-pack or
pack-objects when converting between algorithms, so prioritize efficient
interchange over a more idiomatic Rust approach.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:14 -08:00
brian m. carlson
f97c49398b hash: use uint32_t for object_id algorithm
We currently use an int for this value, but we'll define this structure
from Rust in a future commit and we want to ensure that our data types
are exactly identical.  To make that possible, use a uint32_t for the
hash algorithm.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:14 -08:00
brian m. carlson
447480a5a6 conversion: don't crash when no destination algo
When we set up a repository that doesn't have a compatibility hash
algorithm, we set the destination algorithm object to NULL.  In such a
case, we want to silently do nothing instead of crashing, so simply
treat the operation as a no-op and copy the object ID.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:14 -08:00
brian m. carlson
3a1fd7b09c repository: require Rust support for interoperability
We'll be implementing some of our interoperability code, like the loose
object map, in Rust.  While the code currently compiles with the old
loose object map format, which is written entirely in C, we'll soon
replace that with the Rust-based implementation.

Require the use of Rust for compatibility mode and die if it is not
supported.  Because the repo argument is not used when Rust is missing,
cast it to void to silence the compiler warning, which we do not care
about.

Add a prerequisite in our tests, RUST, that checks if Rust functionality
is available and use it in the tests that handle interoperability.

This is technically a regression in functionality compared to our
existing state, but pack index v3 is not yet implemented and thus the
functionality is mostly quite broken, which is why we've recently marked
this functionality as experimental.  We don't believe anyone is getting
useful use out of the interoperability code in its current state, so no
actual users should be negatively impacted by this change.

Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-17 14:24:13 -08:00
Li Chen
036e2d476c rebase: support --trailer
Implement a new `--trailer <text>` option for `git rebase`
(support merge backend only now), which appends arbitrary
trailer lines to each rebased commit message.

Reject it if the user passes an option that requires the
apply backend (git am) since it lacks message‑filter/trailer
hook. otherwise we can just use the merge backend.

Automatically set REBASE_FORCE when any trailer is supplied.

And reject invalid input before user edits the interactive file.

Signed-off-by: Li Chen <chenl311@chinatelecom.cn>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-06 09:45:00 -08:00
Li Chen
534a87d6f4 trailer: append trailers in-process and drop the fork to interpret-trailers
Route all trailer insertion through trailer_process() and make
builtin/interpret-trailers just do file I/O before calling into it.
amend_file_with_trailers() now shares the same code path.

This removes the fork/exec and tempfile juggling, cutting overhead and
simplifying error handling. No functional change. It also
centralizes logic to prepare for follow-up rebase --trailer patch.

Signed-off-by: Li Chen <chenl311@chinatelecom.cn>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-06 09:45:00 -08:00
Li Chen
7aeb71a516 trailer: move process_trailers to trailer.h
This function would be used by trailer_process
in following commits.

Signed-off-by: Li Chen <chenl311@chinatelecom.cn>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-06 09:45:00 -08:00
Li Chen
6145e03295 interpret-trailers: factor out buffer-based processing to process_trailers()
Extracted trailer processing into a helper that accumulates output in
a strbuf before writing.

Updated interpret_trailers() to reuse the helper, buffer output, and
clean up both input and output buffers after writing.

Signed-off-by: Li Chen <chenl311@chinatelecom.cn>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-06 09:45:00 -08:00
Junio C Hamano
97660b2cfe dir.c: do not be fooled by :(exclude) pathspec elements
When exclude_matches_pathspec() tries to determine if an otherwise
excluded item matches the pathspec given, it goes through each
pathspec element and declares a hit, without checking if the element
is a negative ":(exclude)" element.  Fix it be applying the usual "a
path matches if it matches any one of positive pathspec element, and
if it matches none of negative pathspec elements" rule in the
function.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-11-04 14:12:24 -08:00
Michal Suchanek
b0380a8ebc doc: git-worktree: Add side by side branch checkout example
Signed-off-by: Michal Suchanek <msuchanek@suse.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-10-10 11:20:26 -07:00
Michal Suchanek
dd4271e1c5 doc: git-worktree: Link to examples
Also add advice to put new worktrees outside of existing ones.

Signed-off-by: Michal Suchanek <msuchanek@suse.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2025-10-10 11:20:25 -07:00
244 changed files with 12578 additions and 2884 deletions

1
.gitattributes vendored
View File

@ -17,3 +17,4 @@ CODE_OF_CONDUCT.md -whitespace
/Documentation/gitk.adoc conflict-marker-size=32
/Documentation/user-manual.adoc conflict-marker-size=32
/t/t????-*.sh conflict-marker-size=32
/t/unit-tests/clar/test/expected/* whitespace=-blank-at-eof

1
.gitignore vendored
View File

@ -79,6 +79,7 @@
/git-grep
/git-hash-object
/git-help
/git-history
/git-hook
/git-http-backend
/git-http-fetch

View File

@ -1,105 +1,105 @@
-b::
`-b`::
Show blank SHA-1 for boundary commits. This can also
be controlled via the `blame.blankBoundary` config option.
--root::
`--root`::
Do not treat root commits as boundaries. This can also be
controlled via the `blame.showRoot` config option.
--show-stats::
`--show-stats`::
Include additional statistics at the end of blame output.
-L <start>,<end>::
-L :<funcname>::
Annotate only the line range given by '<start>,<end>',
or by the function name regex '<funcname>'.
`-L <start>,<end>`::
`-L :<funcname>`::
Annotate only the line range given by `<start>,<end>`,
or by the function name regex _<funcname>_.
May be specified multiple times. Overlapping ranges are allowed.
+
'<start>' and '<end>' are optional. `-L <start>` or `-L <start>,` spans from
'<start>' to end of file. `-L ,<end>` spans from start of file to '<end>'.
_<start>_ and _<end>_ are optional. `-L <start>` or `-L <start>,` spans from
_<start>_ to end of file. `-L ,<end>` spans from start of file to _<end>_.
+
include::line-range-format.adoc[]
-l::
`-l`::
Show long rev (Default: off).
-t::
`-t`::
Show raw timestamp (Default: off).
-S <revs-file>::
Use revisions from revs-file instead of calling linkgit:git-rev-list[1].
`-S <revs-file>`::
Use revisions from _<revs-file>_ instead of calling
linkgit:git-rev-list[1].
--reverse <rev>..<rev>::
`--reverse <start>..<end>`::
Walk history forward instead of backward. Instead of showing
the revision in which a line appeared, this shows the last
revision in which a line has existed. This requires a range of
revision like START..END where the path to blame exists in
START. `git blame --reverse START` is taken as `git blame
--reverse START..HEAD` for convenience.
revision like `<start>..<end>` where the path to blame exists in
_<start>_. `git blame --reverse <start>` is taken as `git blame
--reverse <start>..HEAD` for convenience.
--first-parent::
`--first-parent`::
Follow only the first parent commit upon seeing a merge
commit. This option can be used to determine when a line
was introduced to a particular integration branch, rather
than when it was introduced to the history overall.
-p::
--porcelain::
`-p`::
`--porcelain`::
Show in a format designed for machine consumption.
--line-porcelain::
`--line-porcelain`::
Show the porcelain format, but output commit information for
each line, not just the first time a commit is referenced.
Implies --porcelain.
Implies `--porcelain`.
--incremental::
`--incremental`::
Show the result incrementally in a format designed for
machine consumption.
--encoding=<encoding>::
Specifies the encoding used to output author names
`--encoding=<encoding>`::
Specify the encoding used to output author names
and commit summaries. Setting it to `none` makes blame
output unconverted data. For more information see the
discussion about encoding in the linkgit:git-log[1]
manual page.
--contents <file>::
Annotate using the contents from the named file, starting from <rev>
if it is specified, and HEAD otherwise. You may specify '-' to make
`--contents <file>`::
Annotate using the contents from _<file>_, starting from _<rev>_
if it is specified, and `HEAD` otherwise. You may specify `-` to make
the command read from the standard input for the file contents.
--date <format>::
Specifies the format used to output dates. If --date is not
provided, the value of the blame.date config variable is
used. If the blame.date config variable is also not set, the
`--date <format>`::
Specify the format used to output dates. If `--date` is not
provided, the value of the `blame.date` config variable is
used. If the `blame.date` config variable is also not set, the
iso format is used. For supported values, see the discussion
of the --date option at linkgit:git-log[1].
of the `--date` option at linkgit:git-log[1].
--progress::
--no-progress::
Progress status is reported on the standard error stream
by default when it is attached to a terminal. This flag
enables progress reporting even if not attached to a
terminal. Can't use `--progress` together with `--porcelain`
or `--incremental`.
`--progress`::
`--no-progress`::
Enable progress reporting on the standard error stream even if
not attached to a terminal. By default, progress status is
reported only when it is attached. You can't use `--progress`
together with `--porcelain` or `--incremental`.
-M[<num>]::
`-M[<num>]`::
Detect moved or copied lines within a file. When a commit
moves or copies a block of lines (e.g. the original file
has A and then B, and the commit changes it to B and then
A), the traditional 'blame' algorithm notices only half of
has _A_ and then _B_, and the commit changes it to _B_ and then
_A_), the traditional `blame` algorithm notices only half of
the movement and typically blames the lines that were moved
up (i.e. B) to the parent and assigns blame to the lines that
were moved down (i.e. A) to the child commit. With this
up (i.e. _B_) to the parent and assigns blame to the lines that
were moved down (i.e. _A_) to the child commit. With this
option, both groups of lines are blamed on the parent by
running extra passes of inspection.
+
<num> is optional but it is the lower bound on the number of
_<num>_ is optional, but it is the lower bound on the number of
alphanumeric characters that Git must detect as moving/copying
within a file for it to associate those lines with the parent
commit. The default value is 20.
-C[<num>]::
`-C[<num>]`::
In addition to `-M`, detect lines moved or copied from other
files that were modified in the same commit. This is
useful when you reorganize your program and move code
@ -109,14 +109,14 @@ commit. The default value is 20.
option is given three times, the command additionally
looks for copies from other files in any commit.
+
<num> is optional but it is the lower bound on the number of
_<num>_ is optional, but it is the lower bound on the number of
alphanumeric characters that Git must detect as moving/copying
between files for it to associate those lines with the parent
commit. And the default value is 40. If there are more than one
`-C` options given, the <num> argument of the last `-C` will
`-C` options given, the _<num>_ argument of the last `-C` will
take effect.
--ignore-rev <rev>::
`--ignore-rev <rev>`::
Ignore changes made by the revision when assigning blame, as if the
change never happened. Lines that were changed or added by an ignored
commit will be blamed on the previous commit that changed that line or
@ -126,26 +126,26 @@ take effect.
another commit will be marked with a `?` in the blame output. If the
`blame.markUnblamableLines` config option is set, then those lines touched
by an ignored commit that we could not attribute to another revision are
marked with a '*'. In the porcelain modes, we print 'ignored' and
'unblamable' on a newline respectively.
marked with a `*`. In the porcelain modes, we print `ignored` and
`unblamable` on a newline respectively.
--ignore-revs-file <file>::
Ignore revisions listed in `file`, which must be in the same format as an
`--ignore-revs-file <file>`::
Ignore revisions listed in _<file>_, which must be in the same format as an
`fsck.skipList`. This option may be repeated, and these files will be
processed after any files specified with the `blame.ignoreRevsFile` config
option. An empty file name, `""`, will clear the list of revs from
previously processed files.
--color-lines::
`--color-lines`::
Color line annotations in the default format differently if they come from
the same commit as the preceding line. This makes it easier to distinguish
code blocks introduced by different commits. The color defaults to cyan and
can be adjusted using the `color.blame.repeatedLines` config option.
--color-by-age::
Color line annotations depending on the age of the line in the default format.
The `color.blame.highlightRecent` config option controls what color is used for
each range of age.
`--color-by-age`::
Color line annotations depending on the age of the line in
the default format. The `color.blame.highlightRecent` config
option controls what color is used for each range of age.
-h::
`-h`::
Show help message.

View File

@ -523,6 +523,8 @@ include::config/sequencer.adoc[]
include::config/showbranch.adoc[]
include::config/sideband.adoc[]
include::config/sparse.adoc[]
include::config/splitindex.adoc[]

View File

@ -348,6 +348,17 @@ confusion unless you know what you are doing (e.g. you are creating a
read-only snapshot of the same index to a location different from the
repository's usual working tree).
core.lockfilePid::
If true, Git will create a PID file alongside lock files. When a
lock acquisition fails and a PID file exists, Git can provide
additional diagnostic information about the process holding the
lock, including whether it is still running. Defaults to `false`.
+
The PID file is named by inserting `~pid` before the `.lock` suffix.
For example, if the lock file is `index.lock`, the PID file will be
`index~pid.lock`. The file contains a single line in the format
`pid <value>` followed by a newline.
core.logAllRefUpdates::
Enable the reflog. Updates to a ref <ref> is logged to the file
"`$GIT_DIR/logs/<ref>`", by appending the new and old

View File

@ -73,6 +73,35 @@ relativeWorktrees:::
repaired with either the `--relative-paths` option or with the
`worktree.useRelativePaths` config set to `true`.
submodulePathConfig:::
This extension is for the minority of users who:
+
--
* Encounter errors like `refusing to create ... in another submodule's git dir`
due to a number of reasons, like case-insensitive filesystem conflicts when
creating modules named `foo` and `Foo`.
* Require more flexible submodule layouts, for example due to nested names like
`foo`, `foo/bar` and `foo/baz` not supported by the default gitdir mechanism
which uses `.git/modules/<plain-name>` locations, causing further conflicts.
--
+
When `extensions.submodulePathConfig` is enabled, the `submodule.<name>.gitdir`
config becomes the single source of truth for all submodule gitdir paths and is
automatically set for all new submodules both during clone and init operations.
+
Git will error out if a module does not have a corresponding
`submodule.<name>.gitdir` set.
+
Existing (pre-extension) submodules need to be migrated by adding the missing
config entries. This can be done manually, e.g. for each submodule:
`git config submodule.<name>.gitdir .git/modules/<name>`, or via the
`git submodule--helper migrate-gitdir-configs` command which iterates over all
submodules and attempts to migrate them.
+
The extension can be enabled automatically for new repositories by setting
`init.defaultSubmodulePathConfig` to `true`, for example by running
`git config --global init.defaultSubmodulePathConfig true`.
worktreeConfig:::
If enabled, then worktrees will load config settings from the
`$GIT_DIR/config.worktree` file in addition to the

View File

@ -4,8 +4,8 @@ fsmonitor.allowRemote::
behavior. Only respected when `core.fsmonitor` is set to `true`.
fsmonitor.socketDir::
This Mac OS-specific option, if set, specifies the directory in
This Mac OS and Linux-specific option, if set, specifies the directory in
which to create the Unix domain socket used for communication
between the fsmonitor daemon and various Git commands. The directory must
reside on a native Mac OS filesystem. Only respected when `core.fsmonitor`
reside on a native filesystem. Only respected when `core.fsmonitor`
is set to `true`.

View File

@ -18,3 +18,9 @@ endif::[]
See `--ref-format=` in linkgit:git-init[1]. Both the command line
option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
precedence over this config.
init.defaultSubmodulePathConfig::
A boolean that specifies if `git init` and `git clone` should
automatically set `extensions.submodulePathConfig` to `true`. This
allows all new repositories to automatically use the submodule path
extension. Defaults to `false` when unset.

View File

@ -89,3 +89,36 @@ variable. The fields are checked only if the
`promisor.acceptFromServer` config variable is not set to "None". If
set to "None", this config variable has no effect. See
linkgit:gitprotocol-v2[5].
promisor.storeFields::
A comma or space separated list of additional remote related
field names. If a client accepts an advertised remote, the
client will store the values associated with these field names
taken from the remote advertisement into its configuration,
and then reload its remote configuration. Currently,
"partialCloneFilter" and "token" are the only supported field
names.
+
For example if a server advertises "partialCloneFilter=blob:limit=20k"
for remote "foo", and that remote is accepted, then "blob:limit=20k"
will be stored for the "remote.foo.partialCloneFilter" configuration
variable.
+
If the new field value from an advertised remote is the same as the
existing field value for that remote on the client side, then no
change is made to the client configuration though.
+
When a new value is stored, a message is printed to standard error to
let users know about this.
+
Note that for security reasons, if the remote is not already
configured on the client side, nothing will be stored for that
remote. In any case, no new remote will be created and no URL will be
stored.
+
Before storing a partial clone filter, it's parsed to check it's
valid. If it's not, a warning is emitted and it's not stored.
+
Before storing a token, a check is performed to ensure it contains no
control character. If the check fails, a warning is emitted and it's
not stored.

View File

@ -0,0 +1,24 @@
sideband.allowControlCharacters::
By default, control characters that are delivered via the sideband
are masked, except ANSI color sequences. This prevents potentially
unwanted ANSI escape sequences from being sent to the terminal. Use
this config setting to override this behavior (the value can be
a comma-separated list of the following keywords):
+
--
default::
color::
Allow ANSI color sequences, line feeds and horizontal tabs,
but mask all other control characters. This is the default.
cursor::
Allow control sequences that move the cursor. This is
disabled by default.
erase::
Allow control sequences that erase charactrs. This is
disabled by default.
false::
Mask all control characters other than line feeds and
horizontal tabs.
true::
Allow all control characters to be sent to the terminal.
--

View File

@ -52,6 +52,13 @@ submodule.<name>.active::
submodule.active config option. See linkgit:gitsubmodules[7] for
details.
submodule.<name>.gitdir::
This sets the gitdir path for submodule <name>. This configuration is
respected when `extensions.submodulePathConfig` is enabled, otherwise it
has no effect. When enabled, this config becomes the single source of
truth for submodule gitdir paths and Git will error if it is missing.
See linkgit:git-config[1] for details.
submodule.active::
A repeated field which contains a pathspec used to match against a
submodule's path to determine if the submodule is of interest to git

View File

@ -88,6 +88,25 @@ linkgit:git-config[1].
This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes
precedence over the `fetch.output` config option.
--filter=<filter-spec>::
Use the partial clone feature and request that the server sends
a subset of reachable objects according to a given object filter.
When using `--filter`, the supplied _<filter-spec>_ is used for
the partial fetch.
+
If `--filter=auto` is used, the filter specification is determined
automatically by combining the filter specifications advertised by
the server for the promisor remotes that the client accepts (see
linkgit:gitprotocol-v2[5] and the `promisor.acceptFromServer`
configuration option in linkgit:git-config[1]).
+
For details on all other available filter specifications, see the
`--filter=<filter-spec>` option in linkgit:git-rev-list[1].
+
For example, `--filter=blob:none` will filter out all blobs (file
contents) until needed by Git. Also, `--filter=blob:limit=<size>` will
filter out all blobs of size at least _<size>_.
ifndef::git-pull[]
`--write-fetch-head`::
`--no-write-fetch-head`::

View File

@ -13,6 +13,9 @@
`badGpgsig`::
(ERROR) A tag contains a bad (truncated) signature (e.g., `gpgsig`) header.
`badHeadTarget`::
(ERROR) The `HEAD` ref is a symref that does not refer to a branch.
`badHeaderContinuation`::
(ERROR) A continuation header (such as for `gpgsig`) is unexpectedly truncated.
@ -41,6 +44,9 @@
`badRefName`::
(ERROR) A ref has an invalid format.
`badRefOid`::
(ERROR) A ref points to an invalid object ID.
`badReferentName`::
(ERROR) The referent name of a symref is invalid.

View File

@ -7,8 +7,8 @@ git-blame - Show what revision and author last modified each line of a file
SYNOPSIS
--------
[verse]
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
[synopsis]
git blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
[--ignore-rev <rev>] [--ignore-revs-file <file>]
[--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
@ -30,7 +30,7 @@ lines that were copied and pasted from another file, etc., see the
`-C` and `-M` options.
The report does not tell you anything about lines which have been deleted or
replaced; you need to use a tool such as 'git diff' or the "pickaxe"
replaced; you need to use a tool such as `git diff` or the "pickaxe"
interface briefly mentioned in the following paragraph.
Apart from supporting file annotation, Git also supports searching the
@ -50,47 +50,47 @@ OPTIONS
-------
include::blame-options.adoc[]
-c::
`-c`::
Use the same output mode as linkgit:git-annotate[1] (Default: off).
--score-debug::
`--score-debug`::
Include debugging information related to the movement of
lines between files (see `-C`) and lines moved within a
file (see `-M`). The first number listed is the score.
This is the number of alphanumeric characters detected
as having been moved between or within files. This must be above
a certain threshold for 'git blame' to consider those lines
a certain threshold for `git blame` to consider those lines
of code to have been moved.
-f::
--show-name::
`-f`::
`--show-name`::
Show the filename in the original commit. By default
the filename is shown if there is any line that came from a
file with a different name, due to rename detection.
-n::
--show-number::
`-n`::
`--show-number`::
Show the line number in the original commit (Default: off).
-s::
`-s`::
Suppress the author name and timestamp from the output.
-e::
--show-email::
`-e`::
`--show-email`::
Show the author email instead of the author name (Default: off).
This can also be controlled via the `blame.showEmail` config
option.
-w::
`-w`::
Ignore whitespace when comparing the parent's version and
the child's to find where the lines came from.
include::diff-algorithm-option.adoc[]
--abbrev=<n>::
Instead of using the default 7+1 hexadecimal digits as the
abbreviated object name, use <m>+1 digits, where <m> is at
least <n> but ensures the commit object names are unique.
`--abbrev=<n>`::
Instead of using the default _7+1_ hexadecimal digits as the
abbreviated object name, use _<m>+1_ digits, where _<m>_ is at
least _<n>_ but ensures the commit object names are unique.
Note that 1 column
is used for a caret to mark the boundary commit.
@ -124,21 +124,21 @@ header at the minimum has the first line which has:
This header line is followed by the following information
at least once for each commit:
- the author name ("author"), email ("author-mail"), time
("author-time"), and time zone ("author-tz"); similarly
- the author name (`author`), email (`author-mail`), time
(`author-time`), and time zone (`author-tz`); similarly
for committer.
- the filename in the commit that the line is attributed to.
- the first line of the commit log message ("summary").
- the first line of the commit log message (`summary`).
The contents of the actual line are output after the above
header, prefixed by a TAB. This is to allow adding more
header, prefixed by a _TAB_. This is to allow adding more
header elements later.
The porcelain format generally suppresses commit information that has
already been seen. For example, two lines that are blamed to the same
commit will both be shown, but the details for that commit will be shown
only once. Information which is specific to individual lines will not be
grouped together, like revs to be marked 'ignored' or 'unblamable'. This
grouped together, like revs to be marked `ignored` or `unblamable`. This
is more efficient, but may require more state be kept by the reader. The
`--line-porcelain` option can be used to output full commit information
for each line, allowing simpler (but less efficient) usage like:
@ -152,7 +152,7 @@ for each line, allowing simpler (but less efficient) usage like:
SPECIFYING RANGES
-----------------
Unlike 'git blame' and 'git annotate' in older versions of git, the extent
Unlike `git blame` and `git annotate` in older versions of git, the extent
of the annotation can be limited to both line ranges and revision
ranges. The `-L` option, which limits annotation to a range of lines, may be
specified multiple times.
@ -173,7 +173,7 @@ which limits the annotation to the body of the `hello` subroutine.
When you are not interested in changes older than version
v2.6.18, or changes older than 3 weeks, you can use revision
range specifiers similar to 'git rev-list':
range specifiers similar to `git rev-list`:
git blame v2.6.18.. -- foo
git blame --since=3.weeks -- foo
@ -212,7 +212,8 @@ does not contain the actual lines from the file that is being
annotated.
. Each blame entry always starts with a line of:
+
[synopsis]
<40-byte-hex-sha1> <sourceline> <resultline> <num-lines>
+
Line numbers count from 1.
@ -224,16 +225,17 @@ Line numbers count from 1.
. Unlike the Porcelain format, the filename information is always
given and terminates the entry:
"filename" <whitespace-quoted-filename-goes-here>
+
[synopsis]
filename <whitespace-quoted-filename-goes-here>
+
and thus it is really quite easy to parse for some line- and word-oriented
parser (which should be quite natural for most scripting languages).
+
[NOTE]
For people who do parsing: to make it more robust, just ignore any
lines between the first and last one ("<sha1>" and "filename" lines)
where you do not recognize the tag words (or care about that particular
lines between the first and last one (_<40-byte-hex-sha1>_ and `filename`
lines) where you do not recognize the tag words (or care about that particular
one) at the beginning of the "extended information" lines. That way, if
there is ever added information (like the commit encoding or extended
commit commentary), a blame viewer will not care.

View File

@ -187,11 +187,26 @@ objects from the source repository into a pack in the cloned repository.
Use the partial clone feature and request that the server sends
a subset of reachable objects according to a given object filter.
When using `--filter`, the supplied _<filter-spec>_ is used for
the partial clone filter. For example, `--filter=blob:none` will
filter out all blobs (file contents) until needed by Git. Also,
`--filter=blob:limit=<size>` will filter out all blobs of size
at least _<size>_. For more details on filter specifications, see
the `--filter` option in linkgit:git-rev-list[1].
the partial clone filter.
+
If `--filter=auto` is used the filter specification is determined
automatically through the 'promisor-remote' protocol (see
linkgit:gitprotocol-v2[5]) by combining the filter specifications
advertised by the server for the promisor remotes that the client
accepts (see the `promisor.acceptFromServer` configuration option in
linkgit:git-config[1]). This allows the server to suggest the optimal
filter for the available promisor remotes.
+
As with other filter specifications, the "auto" value is persisted in
the configuration. This ensures that future fetches will continue to
adapt to the server's current recommendation.
+
For details on all other available filter specifications, see the
`--filter=<filter-spec>` option in linkgit:git-rev-list[1].
+
For example, `--filter=blob:none` will filter out all blobs (file
contents) until needed by Git. Also, `--filter=blob:limit=<size>` will
filter out all blobs of size at least _<size>_.
`--also-filter-submodules`::
Also apply the partial clone filter to any submodules in the repository.

View File

@ -76,9 +76,9 @@ repositories; this may be overridden by setting `fsmonitor.allowRemote` to
correctly with all network-mounted repositories, so such use is considered
experimental.
On Mac OS, the inter-process communication (IPC) between various Git
On Mac OS and Linux, the inter-process communication (IPC) between various Git
commands and the fsmonitor daemon is done via a Unix domain socket (UDS) -- a
special type of file -- which is supported by native Mac OS filesystems,
special type of file -- which is supported by native Mac OS and Linux filesystems,
but not on network-mounted filesystems, NTFS, or FAT32. Other filesystems
may or may not have the needed support; the fsmonitor daemon is not guaranteed
to work with these filesystems and such use is considered experimental.
@ -87,13 +87,33 @@ By default, the socket is created in the `.git` directory. However, if the
`.git` directory is on a network-mounted filesystem, it will instead be
created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a
network-mounted filesystem, in which case you must set the configuration
variable `fsmonitor.socketDir` to the path of a directory on a Mac OS native
variable `fsmonitor.socketDir` to the path of a directory on a native
filesystem in which to create the socket file.
If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)
is on a native Mac OS file filesystem the fsmonitor daemon will report an
is on a native filesystem the fsmonitor daemon will report an
error that will cause the daemon and the currently running command to exit.
LINUX CAVEATS
~~~~~~~~~~~~~
On Linux, the fsmonitor daemon uses inotify to monitor filesystem events.
The inotify system has per-user limits on the number of watches that can
be created. The default limit is typically 8192 watches per user.
For large repositories with many directories, you may need to increase
this limit. Check the current limit with:
cat /proc/sys/fs/inotify/max_user_watches
To temporarily increase the limit:
sudo sysctl fs.inotify.max_user_watches=65536
To make the change permanent, add to `/etc/sysctl.conf`:
fs.inotify.max_user_watches=65536
CONFIGURATION
-------------

View File

@ -0,0 +1,73 @@
git-history(1)
==============
NAME
----
git-history - EXPERIMENTAL: Rewrite history
SYNOPSIS
--------
[synopsis]
git history reword <commit> [--ref-action=(branches|head|print)]
DESCRIPTION
-----------
Rewrite history by rearranging or modifying specific commits in the
history.
THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
This command is related to linkgit:git-rebase[1] in that both commands can be
used to rewrite history. There are a couple of major differences though:
* linkgit:git-history[1] can work in a bare repository as it does not need to
touch either the index or the worktree.
* linkgit:git-history[1] does not execute any linkgit:githooks[5] at the
current point in time. This may change in the future.
* linkgit:git-history[1] by default updates all branches that are descendants
of the original commit to point to the rewritten commit.
Overall, linkgit:git-history[1] aims to provide a more opinionated way to modify
your commit history that is simpler to use compared to linkgit:git-rebase[1] in
general.
Use linkgit:git-rebase[1] if you want to reapply a range of commits onto a
different base, or interactive rebases if you want to edit a range of commits
at once.
LIMITATIONS
-----------
This command does not (yet) work with histories that contain merges. You
should use linkgit:git-rebase[1] with the `--rebase-merges` flag instead.
Furthermore, the command does not support operations that can result in merge
conflicts. This limitation is by design as history rewrites are not intended to
be stateful operations. The limitation can be lifted once (if) Git learns about
first-class conflicts.
COMMANDS
--------
Several commands are available to rewrite history in different ways:
`reword <commit>`::
Rewrite the commit message of the specified commit. All the other
details of this commit remain unchanged. This command will spawn an
editor with the current message of that commit.
OPTIONS
-------
`--ref-action=(branches|head|print)`::
Control which references will be updated by the command, if any. With
`branches`, all local branches that point to commits which are
decendants of the original commit will be rewritten. With `head`, only
the current `HEAD` reference will be rewritten. With `print`, all
updates as they would be performed with `branches` are printed in a
format that can be consumed by linkgit:git-update-ref[1].
GIT
---
Part of the linkgit:git[1] suite

View File

@ -9,7 +9,8 @@ git-last-modified - EXPERIMENTAL: Show when files were last modified
SYNOPSIS
--------
[synopsis]
git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...]
git last-modified [--recursive] [--show-trees] [--max-depth=<depth>] [-z]
[<revision-range>] [[--] <path>...]
DESCRIPTION
-----------
@ -26,12 +27,23 @@ OPTIONS
`--recursive`::
Instead of showing tree entries, step into subtrees and show all entries
inside them recursively.
See the section "NOTES ABOUT DEPTH" below for more details.
`-t`::
`--show-trees`::
Show tree entries even when recursing into them. It has no effect
without `--recursive`.
`--max-depth=<depth>`::
For each pathspec given on the command line, descend at most `<depth>`
levels of directories. A negative value means no limit.
Setting a positive value implies `--recursive`.
Cannot be combined with wildcards in the pathspec.
See the section "NOTES ABOUT DEPTH" below for more details.
`-z`::
Terminate each line with a _NUL_ rather than a newline.
`<revision-range>`::
Only traverse commits in the specified revision range. When no
`<revision-range>` is specified, it defaults to `HEAD` (i.e. the whole
@ -44,6 +56,63 @@ OPTIONS
Without an optional path parameter, all files and subdirectories
in path traversal the are included in the output.
OUTPUT
------
The output is in the format:
------------
<oid> TAB <path> LF
------------
If a path contains any special characters, the path is C-style quoted. To
avoid quoting, pass option `-z` to terminate each line with a NUL.
------------
<oid> TAB <path> NUL
------------
NOTES ABOUT DEPTH
-----------------
By default this command only shows information about paths at the root level.
When a path that lives in a subtree is provided, information about the top-level
subtree is printed. For example:
------------
$ git last-modified -- sub/file
abcd1234abcd1234abcd1234abcd1234abcd1234 sub
------------
To get details about the exact path in a subtree, add option `--recursive`:
------------
$ git last-modified --recursive -- sub/file
5678abca5678abca5678abca5678abca5678abca sub/file
------------
This comes with a downside. When the path provided is a tree itself, with
option `--recursive` all paths in that subtree are printed too:
------------
$ git last-modified --recursive -- sub/subsub
1234cdef1234cdef1234cdef1234cdef1234cdef sub/subsub/a
3456cdef3456cdef3456cdef3456cdef3456cdef sub/subsub/b
5678abcd5678abcd5678abcd5678abcd5678abcd sub/subsub/c
------------
To stop this command from traversing deeper into trees, add option
`--max-depth=0`:
------------
$ git last-modified --recursive --max-depth=0 -- sub/subsub
3456def3456def3456def3456def3456def3456b sub/subsub
------------
SEE ALSO
--------
linkgit:git-blame[1],

View File

@ -9,7 +9,14 @@ git-multi-pack-index - Write and verify multi-pack-indexes
SYNOPSIS
--------
[verse]
'git multi-pack-index' [--object-dir=<dir>] [--[no-]bitmap] <sub-command>
'git multi-pack-index' [<options>] write [--preferred-pack=<pack>]
[--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs]
[--refs-snapshot=<path>]
'git multi-pack-index' [<options>] compact [--[no-]incremental]
[--[no-]bitmap] <from> <to>
'git multi-pack-index' [<options>] verify
'git multi-pack-index' [<options>] expire
'git multi-pack-index' [<options>] repack [--batch-size=<size>]
DESCRIPTION
-----------
@ -18,6 +25,8 @@ Write or verify a multi-pack-index (MIDX) file.
OPTIONS
-------
The following command-line options are applicable to all sub-commands:
--object-dir=<dir>::
Use given directory for the location of Git objects. We check
`<dir>/packs/multi-pack-index` for the current MIDX file, and
@ -73,7 +82,18 @@ marker).
Write an incremental MIDX file containing only objects
and packs not present in an existing MIDX layer.
Migrates non-incremental MIDXs to incremental ones when
necessary. Incompatible with `--bitmap`.
necessary.
--
compact::
Write a new MIDX layer containing only objects and packs present
in the range `<from>` to `<to>`, where both arguments are
checksums of existing layers in the MIDX chain.
+
--
--incremental::
Write the result to a MIDX chain instead of writing a
stand-alone MIDX. Incompatible with `--bitmap`.
--
verify::

View File

@ -21,7 +21,7 @@ the same time also reasonably unique, i.e., two patches that have the same
The main usecase for this command is to look for likely duplicate commits.
When dealing with `git diff-tree` output, it takes advantage of
When dealing with `git diff-tree --patch` output, it takes advantage of
the fact that the patch is prefixed with the object name of the
commit, and outputs two 40-byte hexadecimal strings. The first
string is the patch ID, and the second string is the commit ID.
@ -31,8 +31,8 @@ OPTIONS
-------
`--verbatim`::
Calculate the patch-id of the input as it is given, do not strip
any whitespace.
Calculate the patch ID of the input as it is given, do not strip
any whitespace. Implies `--stable` and forbids `--unstable`.
+
This is the default if `patchid.verbatim` is `true`.
@ -45,24 +45,24 @@ This is the default if `patchid.verbatim` is `true`.
with two different settings for `-O<orderfile>` result in the same
patch ID signature, thereby allowing the computed result to be used
as a key to index some meta-information about the change between
the two trees;
the two trees.
- Result is different from the value produced by git 1.9 and older
- The result is different from the value produced by Git 1.9 and older
or produced when an "unstable" hash (see `--unstable` below) is
configured - even when used on a diff output taken without any use
of `-O<orderfile>`, thereby making existing databases storing such
"unstable" or historical patch-ids unusable.
"unstable" or historical patch IDs unusable.
- All whitespace within the patch is ignored and does not affect the id.
- All whitespace within the patch is ignored and does not affect the ID.
--
+
This is the default if `patchid.stable` is set to `true`.
`--unstable`::
Use an "unstable" hash as the patch ID. With this option,
the result produced is compatible with the patch-id value produced
by git 1.9 and older and whitespace is ignored. Users with pre-existing
databases storing patch-ids produced by git 1.9 and older (who do not deal
the result produced is compatible with the patch ID value produced
by Git 1.9 and older and whitespace is ignored. Users with pre-existing
databases storing patch IDs produced by Git 1.9 and older (who do not deal
with reordered patches) may want to use this option.
+
This is the default.

View File

@ -494,9 +494,16 @@ See also INCOMPATIBLE OPTIONS below.
Add a `Signed-off-by` trailer to all the rebased commits. Note
that if `--interactive` is given then only commits marked to be
picked, edited or reworded will have the trailer added.
+
See also INCOMPATIBLE OPTIONS below.
--trailer=<trailer>::
Append the given trailer line(s) to every rebased commit
message, processed via linkgit:git-interpret-trailers[1].
When this option is present *rebase automatically implies*
`--force-rebase` so that fastforwarded commits are also
rewritten.
-i::
--interactive::
Make a list of the commits which are about to be rebased. Let the

View File

@ -8,20 +8,20 @@ git-remote - Manage set of tracked repositories
SYNOPSIS
--------
[verse]
'git remote' [-v | --verbose]
'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>
'git remote rename' [--[no-]progress] <old> <new>
'git remote remove' <name>
'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>)
'git remote set-branches' [--add] <name> <branch>...
'git remote get-url' [--push] [--all] <name>
'git remote set-url' [--push] <name> <newurl> [<oldurl>]
'git remote set-url --add' [--push] <name> <newurl>
'git remote set-url --delete' [--push] <name> <URL>
'git remote' [-v | --verbose] 'show' [-n] <name>...
'git remote prune' [-n | --dry-run] <name>...
'git remote' [-v | --verbose] 'update' [-p | --prune] [(<group> | <remote>)...]
[synopsis]
git remote [-v | --verbose]
git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>
git remote rename [--[no-]progress] <old> <new>
git remote remove <name>
git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
git remote set-branches [--add] <name> <branch>...
git remote get-url [--push] [--all] <name>
git remote set-url [--push] <name> <newurl> [<oldurl>]
git remote set-url --add [--push] <name> <newurl>
git remote set-url --delete [--push] <name> <URL>
git remote [-v | --verbose] show [-n] <name>...
git remote prune [-n | --dry-run] <name>...
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
DESCRIPTION
-----------
@ -32,8 +32,8 @@ Manage the set of repositories ("remotes") whose branches you track.
OPTIONS
-------
-v::
--verbose::
`-v`::
`--verbose`::
Be a little more verbose and show remote url after name.
For promisor remotes, also show which filters (`blob:none` etc.)
are configured.
@ -43,14 +43,14 @@ OPTIONS
COMMANDS
--------
With no arguments, shows a list of existing remotes. Several
With no arguments, show a list of existing remotes. Several
subcommands are available to perform operations on the remotes.
'add'::
`add`::
Add a remote named <name> for the repository at
<URL>. The command `git fetch <name>` can then be used to create and
update remote-tracking branches <name>/<branch>.
Add a remote named _<name>_ for the repository at
_<URL>_. The command `git fetch <name>` can then be used to create and
update remote-tracking branches `<name>/<branch>`.
+
With `-f` option, `git fetch <name>` is run immediately after
the remote information is set up.
@ -66,40 +66,40 @@ By default, only tags on fetched branches are imported
+
With `-t <branch>` option, instead of the default glob
refspec for the remote to track all branches under
the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>`
the `refs/remotes/<name>/` namespace, a refspec to track only _<branch>_
is created. You can give more than one `-t <branch>` to track
multiple branches without grabbing all branches.
+
With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
up to point at remote's `<master>` branch. See also the set-head command.
up to point at remote's _<master>_ branch. See also the set-head command.
+
When a fetch mirror is created with `--mirror=fetch`, the refs will not
be stored in the 'refs/remotes/' namespace, but rather everything in
'refs/' on the remote will be directly mirrored into 'refs/' in the
be stored in the `refs/remotes/` namespace, but rather everything in
`refs/` on the remote will be directly mirrored into `refs/` in the
local repository. This option only makes sense in bare repositories,
because a fetch would overwrite any local commits.
+
When a push mirror is created with `--mirror=push`, then `git push`
will always behave as if `--mirror` was passed.
'rename'::
`rename`::
Rename the remote named <old> to <new>. All remote-tracking branches and
Rename the remote named _<old>_ to _<new>_. All remote-tracking branches and
configuration settings for the remote are updated.
+
In case <old> and <new> are the same, and <old> is a file under
In case _<old>_ and _<new>_ are the same, and _<old>_ is a file under
`$GIT_DIR/remotes` or `$GIT_DIR/branches`, the remote is converted to
the configuration file format.
'remove'::
'rm'::
`remove`::
`rm`::
Remove the remote named <name>. All remote-tracking branches and
Remove the remote named _<name>_. All remote-tracking branches and
configuration settings for the remote are removed.
'set-head'::
`set-head`::
Sets or deletes the default branch (i.e. the target of the
Set or delete the default branch (i.e. the target of the
symbolic-ref `refs/remotes/<name>/HEAD`) for
the named remote. Having a default branch for a remote is not required,
but allows the name of the remote to be specified in lieu of a specific
@ -116,15 +116,15 @@ the symbolic-ref `refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This
only work if `refs/remotes/origin/next` already exists; if not it must be
fetched first.
+
Use `<branch>` to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., `git
Use _<branch>_ to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., `git
remote set-head origin master` will set the symbolic-ref `refs/remotes/origin/HEAD` to
`refs/remotes/origin/master`. This will only work if
`refs/remotes/origin/master` already exists; if not it must be fetched first.
+
'set-branches'::
`set-branches`::
Changes the list of branches tracked by the named remote.
Change the list of branches tracked by the named remote.
This can be used to track a subset of the available remote branches
after the initial setup for a remote.
+
@ -134,7 +134,7 @@ The named branches will be interpreted as if specified with the
With `--add`, instead of replacing the list of currently tracked
branches, adds to that list.
'get-url'::
`get-url`::
Retrieves the URLs for a remote. Configurations for `insteadOf` and
`pushInsteadOf` are expanded here. By default, only the first URL is listed.
@ -143,18 +143,18 @@ With `--push`, push URLs are queried rather than fetch URLs.
+
With `--all`, all URLs for the remote will be listed.
'set-url'::
`set-url`::
Changes URLs for the remote. Sets first URL for remote <name> that matches
regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If
<oldurl> doesn't match any URL, an error occurs and nothing is changed.
Change URLs for the remote. Sets first URL for remote _<name>_ that matches
regex _<oldurl>_ (first URL if no _<oldurl>_ is given) to _<newurl>_. If
_<oldurl>_ doesn't match any URL, an error occurs and nothing is changed.
+
With `--push`, push URLs are manipulated instead of fetch URLs.
+
With `--add`, instead of changing existing URLs, new URL is added.
+
With `--delete`, instead of changing existing URLs, all URLs matching
regex <URL> are deleted for remote <name>. Trying to delete all
regex _<URL>_ are deleted for remote _<name>_. Trying to delete all
non-push URLs is an error.
+
Note that the push URL and the fetch URL, even though they can
@ -165,17 +165,17 @@ fetch from one place (e.g. your upstream) and push to another (e.g.
your publishing repository), use two separate remotes.
'show'::
`show`::
Gives some information about the remote <name>.
Give some information about the remote _<name>_.
+
With `-n` option, the remote heads are not queried first with
`git ls-remote <name>`; cached information is used instead.
'prune'::
`prune`::
Deletes stale references associated with <name>. By default, stale
remote-tracking branches under <name> are deleted, but depending on
Delete stale references associated with _<name>_. By default, stale
remote-tracking branches under _<name>_ are deleted, but depending on
global configuration and the configuration of the remote we might even
prune local tags that haven't been pushed there. Equivalent to `git
fetch --prune <name>`, except that no new references will be fetched.
@ -186,13 +186,13 @@ depending on various configuration.
With `--dry-run` option, report what branches would be pruned, but do not
actually prune them.
'update'::
`update`::
Fetch updates for remotes or remote groups in the repository as defined by
`remotes.<group>`. If neither group nor remote is specified on the command line,
the configuration parameter remotes.default will be used; if
remotes.default is not defined, all remotes which do not have the
configuration parameter `remote.<name>.skipDefaultUpdate` set to true will
the configuration parameter `remotes.default` will be used; if
`remotes.default` is not defined, all remotes which do not have the
configuration parameter `remote.<name>.skipDefaultUpdate` set to `true` will
be updated. (See linkgit:git-config[1]).
+
With `--prune` option, run pruning against all the remotes that are updated.
@ -210,7 +210,7 @@ EXIT STATUS
On success, the exit status is `0`.
When subcommands such as 'add', 'rename', and 'remove' can't find the
When subcommands such as `add`, `rename`, and `remove` can't find the
remote in question, the exit status is `2`. When the remote already
exists, the exit status is `3`.
@ -247,7 +247,7 @@ $ git switch -c staging staging/master
...
------------
* Imitate 'git clone' but track only selected branches
* Imitate `git clone` but track only selected branches
+
------------
$ mkdir project.git

View File

@ -62,7 +62,9 @@ The default mode can be configured via the `replay.refAction` configuration vari
Range of commits to replay; see "Specifying Ranges" in
linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
range should have a single tip, so that it's clear to which tip the
advanced <branch> should point.
advanced <branch> should point. Any commits in the range whose
changes are already present in the branch the commits are being
replayed onto will be dropped.
include::rev-list-options.adoc[]

View File

@ -9,6 +9,7 @@ SYNOPSIS
--------
[synopsis]
git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]
git repo info --keys [--format=(default|nul) | -z]
git repo structure [--format=(table|keyvalue|nul) | -z]
DESCRIPTION
@ -44,6 +45,16 @@ supported:
+
`-z` is an alias for `--format=nul`.
`info --keys [--format=(default|nul) | -z]`::
List all the available keys, one per line. The output format can be chosen
through the flag `--format`. The following formats are supported:
+
`default`:::
output the keys one per line.
`nul`:::
similar to `default`, but using a NUL character after each value.
`structure [--format=(table|keyvalue|nul) | -z]`::
Retrieve statistics about the current repository structure. The
following kinds of information are reported:

View File

@ -3,86 +3,67 @@ git-reset(1)
NAME
----
git-reset - Reset current HEAD to the specified state
git-reset - Set `HEAD` or the index to a known state
SYNOPSIS
--------
[synopsis]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
git reset [-q] [<tree-ish>] [--] <pathspec>...
git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
DESCRIPTION
-----------
In the first three forms, copy entries from _<tree-ish>_ to the index.
In the last form, set the current branch head (`HEAD`) to _<commit>_,
optionally modifying index and working tree to match.
The _<tree-ish>_/_<commit>_ defaults to `HEAD` in all forms.
`git reset` does either of the following:
`git reset [-q] [<tree-ish>] [--] <pathspec>...`::
`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`::
These forms reset the index entries for all paths that match the
_<pathspec>_ to their state at _<tree-ish>_. (It does not affect
the working tree or the current branch.)
+
This means that `git reset <pathspec>` is the opposite of `git add
<pathspec>`. This command is equivalent to
`git restore [--source=<tree-ish>] --staged <pathspec>...`.
+
After running `git reset <pathspec>` to update the index entry, you can
use linkgit:git-restore[1] to check the contents out of the index to
the working tree. Alternatively, using linkgit:git-restore[1]
and specifying a commit with `--source`, you
can copy the contents of a path out of a commit to the index and to the
working tree in one go.
`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`::
Interactively select hunks in the difference between the index
and _<tree-ish>_ (defaults to `HEAD`). The chosen hunks are applied
in reverse to the index.
+
This means that `git reset -p` is the opposite of `git add -p`, i.e.
you can use it to selectively reset hunks. See the "Interactive Mode"
section of linkgit:git-add[1] to learn how to operate the `--patch` mode.
1. `git reset [<mode>] <commit>` changes which commit `HEAD` points to. This
makes it possible to undo various Git operations, for example commit, merge,
rebase, and pull.
2. When you specify files or directories or pass `--patch`, `git reset` updates
the staged version of the specified files.
`git reset [<mode>] [<commit>]`::
This form resets the current branch head to _<commit>_ and
possibly updates the index (resetting it to the tree of _<commit>_) and
the working tree depending on _<mode>_. Before the operation, `ORIG_HEAD`
is set to the tip of the current branch. If _<mode>_ is omitted,
defaults to `--mixed`. The _<mode>_ must be one of the following:
Set the current branch head (`HEAD`) to point at _<commit>_.
Depending on _<mode>_, also update the working directory and/or index
to match the contents of _<commit>_.
_<commit>_ defaults to `HEAD`.
Before the operation, `ORIG_HEAD` is set to the tip of the current branch.
+
The _<mode>_ must be one of the following (default `--mixed`):
+
--
`--soft`::
Does not touch the index file or the working tree at all (but
resets the head to _<commit>_, just like all modes do). This leaves
all your changed files "Changes to be committed", as `git status`
would put it.
--
`--mixed`::
Resets the index but not the working tree (i.e., the changed files
are preserved but not marked for commit) and reports what has not
been updated. This is the default action.
Leave your working directory unchanged.
Update the index to match the new `HEAD`, so nothing will be staged.
+
If `-N` is specified, removed paths are marked as intent-to-add (see
If `-N` is specified, mark removed paths as intent-to-add (see
linkgit:git-add[1]).
`--soft`::
Leave your working tree files and the index unchanged.
For example, if you have no staged changes, you can use
`git reset --soft HEAD~5; git commit`
to combine the last 5 commits into 1 commit. This works even with
changes in the working tree, which are left untouched, but such usage
can lead to confusion.
`--hard`::
Resets the index and working tree. Any changes to tracked files in the
working tree since _<commit>_ are discarded. Any untracked files or
directories in the way of writing any tracked files are simply deleted.
Overwrite all files and directories with the version from _<commit>_,
and may overwrite untracked files. Tracked files not in _<commit>_ are
removed so that the working tree matches _<commit>_.
Update the index to match the new `HEAD`, so nothing will be staged.
`--merge`::
Resets the index and updates the files in the working tree that are
different between _<commit>_ and `HEAD`, but keeps those which are
Reset the index and update the files in the working tree that are
different between _<commit>_ and `HEAD`, but keep those which are
different between the index and working tree (i.e. which have changes
which have not been added).
Mainly exists to reset unmerged index entries, like those left behind by
`git am -3` or `git switch -m` in certain situations.
If a file that is different between _<commit>_ and the index has
unstaged changes, reset is aborted.
+
In other words, `--merge` does something like a `git read-tree -u -m <commit>`,
but carries forward unmerged index entries.
`--keep`::
Resets index entries and updates files in the working tree that are
@ -98,6 +79,28 @@ but carries forward unmerged index entries.
the submodules' `HEAD` to be detached at that commit.
--
`git reset [-q] [<tree-ish>] [--] <pathspec>...`::
`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`::
For all specified files or directories, set the staged version to
the version from the given commit or tree (which defaults to `HEAD`).
+
This means that `git reset <pathspec>` is the opposite of `git add
<pathspec>`: it unstages all changes to the specified file(s) or
directories. This is equivalent to `git restore --staged <pathspec>...`.
+
In this mode, `git reset` updates only the index (without updating the `HEAD` or
working tree files). If you want to update the files as well as the index
entries, use linkgit:git-restore[1].
`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`::
Interactively select changes from the difference between the index
and the specified commit or tree (which defaults to `HEAD`).
The index is modified using the chosen changes.
+
This means that `git reset -p` is the opposite of `git add -p`, i.e.
you can use it to selectively unstage changes. See the "Interactive Mode"
section of linkgit:git-add[1] to learn how to use the `--patch` option.
See "Reset, restore and revert" in linkgit:git[1] for the differences
between the three commands.

View File

@ -8,8 +8,8 @@ git-stage - Add file contents to the staging area
SYNOPSIS
--------
[verse]
'git stage' <arg>...
[synopsis]
git stage <arg>...
DESCRIPTION

View File

@ -8,8 +8,9 @@ git-status - Show the working tree status
SYNOPSIS
--------
[verse]
'git status' [<options>] [--] [<pathspec>...]
[synopsis]
git status [<options>] [--] [<pathspec>...]
DESCRIPTION
-----------
@ -18,57 +19,57 @@ current HEAD commit, paths that have differences between the working
tree and the index file, and paths in the working tree that are not
tracked by Git (and are not ignored by linkgit:gitignore[5]). The first
are what you _would_ commit by running `git commit`; the second and
third are what you _could_ commit by running 'git add' before running
third are what you _could_ commit by running `git add` before running
`git commit`.
OPTIONS
-------
-s::
--short::
`-s`::
`--short`::
Give the output in the short-format.
-b::
--branch::
`-b`::
`--branch`::
Show the branch and tracking info even in short-format.
--show-stash::
`--show-stash`::
Show the number of entries currently stashed away.
--porcelain[=<version>]::
`--porcelain[=<version>]`::
Give the output in an easy-to-parse format for scripts.
This is similar to the short output, but will remain stable
across Git versions and regardless of user configuration. See
below for details.
+
The version parameter is used to specify the format version.
This is optional and defaults to the original version 'v1' format.
The _<version>_ parameter is used to specify the format version.
This is optional and defaults to the original version `v1` format.
--long::
`--long`::
Give the output in the long-format. This is the default.
-v::
--verbose::
`-v`::
`--verbose`::
In addition to the names of files that have been changed, also
show the textual changes that are staged to be committed
(i.e., like the output of `git diff --cached`). If `-v` is specified
twice, then also show the changes in the working tree that
have not yet been staged (i.e., like the output of `git diff`).
-u[<mode>]::
--untracked-files[=<mode>]::
`-u[<mode>]`::
`--untracked-files[=<mode>]`::
Show untracked files.
+
--
The mode parameter is used to specify the handling of untracked files.
It is optional: it defaults to 'all', and if specified, it must be
It is optional: it defaults to `all`, and if specified, it must be
stuck to the option (e.g. `-uno`, but not `-u no`).
The possible options are:
- 'no' - Show no untracked files.
- 'normal' - Shows untracked files and directories.
- 'all' - Also shows individual files in untracked directories.
`no`:: Show no untracked files.
`normal`:: Show untracked files and directories.
`all`:: Also show individual files in untracked directories.
When `-u` option is not used, untracked files and directories are
shown (i.e. the same as specifying `normal`), to help you avoid
@ -82,76 +83,78 @@ return more quickly without showing untracked files.
All usual spellings for Boolean value `true` are taken as `normal`
and `false` as `no`.
The default can be changed using the status.showUntrackedFiles
The default can be changed using the `status.showUntrackedFiles`
configuration variable documented in linkgit:git-config[1].
--
--ignore-submodules[=<when>]::
Ignore changes to submodules when looking for changes. <when> can be
either "none", "untracked", "dirty" or "all", which is the default.
Using "none" will consider the submodule modified when it either contains
`--ignore-submodules[=<when>]`::
Ignore changes to submodules when looking for changes. _<when>_ can be
either `none`, `untracked`, `dirty` or `all`, which is the default.
`none`;; will consider the submodule modified when it either contains
untracked or modified files or its HEAD differs from the commit recorded
in the superproject and can be used to override any settings of the
'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When
"untracked" is used submodules are not considered dirty when they only
`ignore` option in linkgit:git-config[1] or linkgit:gitmodules[5].
`untracked`;; submodules are not considered dirty when they only
contain untracked content (but they are still scanned for modified
content). Using "dirty" ignores all changes to the work tree of submodules,
content).
`dirty`;; ignore all changes to the work tree of submodules,
only changes to the commits stored in the superproject are shown (this was
the behavior before 1.7.0). Using "all" hides all changes to submodules
the behavior before 1.7.0).
`all`;; hide all changes to submodules
(and suppresses the output of submodule summaries when the config option
`status.submoduleSummary` is set).
--ignored[=<mode>]::
`--ignored[=<mode>]`::
Show ignored files as well.
+
--
The mode parameter is used to specify the handling of ignored files.
It is optional: it defaults to 'traditional'.
It is optional: it defaults to `traditional`.
The possible options are:
- 'traditional' - Shows ignored files and directories, unless
--untracked-files=all is specified, in which case
`traditional`:: Show ignored files and directories, unless
`--untracked-files=all` is specified, in which case
individual files in ignored directories are
displayed.
- 'no' - Show no ignored files.
- 'matching' - Shows ignored files and directories matching an
`no`:: Show no ignored files.
`matching`:: Show ignored files and directories matching an
ignore pattern.
When 'matching' mode is specified, paths that explicitly match an
+
Paths that explicitly match an
ignored pattern are shown. If a directory matches an ignore pattern,
then it is shown, but not paths contained in the ignored directory. If
a directory does not match an ignore pattern, but all contents are
ignored, then the directory is not shown, but all contents are shown.
--
-z::
Terminate entries with NUL, instead of LF. This implies
`-z`::
Terminate entries with _NUL_, instead of _LF_. This implies
the `--porcelain=v1` output format if no other format is given.
--column[=<options>]::
--no-column::
`--column[=<options>]`::
`--no-column`::
Display untracked files in columns. See configuration variable
`column.status` for option syntax. `--column` and `--no-column`
without options are equivalent to 'always' and 'never'
without options are equivalent to `always` and `never`
respectively.
--ahead-behind::
--no-ahead-behind::
`--ahead-behind`::
`--no-ahead-behind`::
Display or do not display detailed ahead/behind counts for the
branch relative to its upstream branch. Defaults to true.
branch relative to its upstream branch. Defaults to `true`.
--renames::
--no-renames::
`--renames`::
`--no-renames`::
Turn on/off rename detection regardless of user configuration.
See also linkgit:git-diff[1] `--no-renames`.
--find-renames[=<n>]::
`--find-renames[=<n>]`::
Turn on rename detection, optionally setting the similarity
threshold.
See also linkgit:git-diff[1] `--find-renames`.
<pathspec>...::
`<pathspec>...`::
See the 'pathspec' entry in linkgit:gitglossary[7].
OUTPUT
@ -173,12 +176,12 @@ Short Format
In the short-format, the status of each path is shown as one of these
forms
XY PATH
XY ORIG_PATH -> PATH
<xy> <path>
<xy> <orig-path> -> <path>
where `ORIG_PATH` is where the renamed/copied contents came
from. `ORIG_PATH` is only shown when the entry is renamed or
copied. The `XY` is a two-letter status code.
where _<orig-path>_ is where the renamed/copied contents came
from. _<orig-path>_ is only shown when the entry is renamed or
copied. The _<xy>_ is a two-letter status code `XY`.
The fields (including the `->`) are separated from each other by a
single space. If a filename contains whitespace or other nonprintable
@ -187,7 +190,7 @@ literal: surrounded by ASCII double quote (34) characters, and with
interior special characters backslash-escaped.
There are three different types of states that are shown using this format, and
each one uses the `XY` syntax differently:
each one uses the _<xy>_ syntax differently:
* When a merge is occurring and the merge was successful, or outside of a merge
situation, `X` shows the status of the index and `Y` shows the status of the
@ -207,60 +210,59 @@ In the following table, these three classes are shown in separate sections, and
these characters are used for `X` and `Y` fields for the first two sections that
show tracked paths:
* ' ' = unmodified
* 'M' = modified
* 'T' = file type changed (regular file, symbolic link or submodule)
* 'A' = added
* 'D' = deleted
* 'R' = renamed
* 'C' = copied (if config option status.renames is set to "copies")
* 'U' = updated but unmerged
' ':: unmodified
`M`:: modified
`T`:: file type changed (regular file, symbolic link or submodule)
`A`:: added
`D`:: deleted
`R`:: renamed
`C`:: copied (if config option status.renames is set to "copies")
`U`:: updated but unmerged
....
X Y Meaning
-------------------------------------------------
[AMD] not updated
M [ MTD] updated in index
T [ MTD] type changed in index
A [ MTD] added to index
D deleted from index
R [ MTD] renamed in index
C [ MTD] copied in index
[MTARC] index and work tree matches
[ MTARC] M work tree changed since index
[ MTARC] T type changed in work tree since index
[ MTARC] D deleted in work tree
R renamed in work tree
C copied in work tree
-------------------------------------------------
D D unmerged, both deleted
A U unmerged, added by us
U D unmerged, deleted by them
U A unmerged, added by them
D U unmerged, deleted by us
A A unmerged, both added
U U unmerged, both modified
-------------------------------------------------
? ? untracked
! ! ignored
-------------------------------------------------
....
[cols="^1m,^1m,<2",options="header"]
|===
|X | Y |Meaning
| |[AMD] |not updated
|M |[ MTD] |updated in index
|T |[ MTD] |type changed in index
|A |[ MTD] |added to index
|D | |deleted from index
|R |[ MTD] |renamed in index
|C |[ MTD] |copied in index
|[MTARC] | |index and work tree matches
|[ MTARC] |M |work tree changed since index
|[ MTARC] |T |type changed in work tree since index
|[ MTARC] |D |deleted in work tree
| |R |renamed in work tree
| |C |copied in work tree
|D |D |unmerged, both deleted
|A |U |unmerged, added by us
|U |D |unmerged, deleted by them
|U |A |unmerged, added by them
|D |U |unmerged, deleted by us
|A |A |unmerged, both added
|U |U |unmerged, both modified
|? |? |untracked
|! |! |ignored
|===
Submodules have more state and instead report
* 'M' = the submodule has a different HEAD than recorded in the index
* 'm' = the submodule has modified content
* '?' = the submodule has untracked files
`M`:: the submodule has a different HEAD than recorded in the index
`m`:: the submodule has modified content
`?`:: the submodule has untracked files
This is since modified content or untracked files in a submodule cannot be added
via `git add` in the superproject to prepare a commit.
'm' and '?' are applied recursively. For example if a nested submodule
in a submodule contains an untracked file, this is reported as '?' as well.
`m` and `?` are applied recursively. For example if a nested submodule
in a submodule contains an untracked file, this is reported as `?` as well.
If -b is used the short-format status is preceded by a line
If `-b` is used the short-format status is preceded by a line
[synopsis]
{empty}## <branchname> <tracking-info>
## branchname tracking info
Porcelain Format Version 1
~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -271,16 +273,16 @@ based on user configuration. This makes it ideal for parsing by scripts.
The description of the short format above also describes the porcelain
format, with a few exceptions:
1. The user's color.status configuration is not respected; color will
1. The user's `color.status` configuration is not respected; color will
always be off.
2. The user's status.relativePaths configuration is not respected; paths
2. The user's `status.relativePaths` configuration is not respected; paths
shown will always be relative to the repository root.
There is also an alternate -z format recommended for machine parsing. In
There is also an alternate `-z` format recommended for machine parsing. In
that format, the status field is the same, but some other things
change. First, the '\->' is omitted from rename entries and the field
order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
change. First, the `->` is omitted from rename entries and the field
order is reversed (e.g `from -> to` becomes `to from`). Second, a _NUL_
(ASCII 0) follows each filename, replacing space as a field separator
and the terminating newline (but a space still separates the status
field from the first filename). Third, filenames containing special
@ -296,7 +298,7 @@ Version 2 format adds more detailed information about the state of
the worktree and changed items. Version 2 also defines an extensible
set of easy to parse optional headers.
Header lines start with "#" and are added in response to specific
Header lines start with `#` and are added in response to specific
command line arguments. Parsers should ignore headers they
don't recognize.
@ -306,16 +308,15 @@ Branch Headers
If `--branch` is given, a series of header lines are printed with
information about the current branch.
....
Line Notes
------------------------------------------------------------
# branch.oid <commit> | (initial) Current commit.
# branch.head <branch> | (detached) Current branch.
# branch.upstream <upstream-branch> If upstream is set.
# branch.ab +<ahead> -<behind> If upstream is set and
[cols="<1,<1",options="header"]
|===
|Line |Notes
|`# branch.oid <commit> \| (initial)` |Current commit.
|`# branch.head <branch> \| (detached)` |Current branch.
|`# branch.upstream <upstream-branch>` |If upstream is set.
|`# branch.ab +<ahead> -<behind>` |If upstream is set and
the commit is present.
------------------------------------------------------------
....
|===
Stash Information
^^^^^^^^^^^^^^^^^
@ -336,66 +337,73 @@ line types in any order.
Ordinary changed entries have the following format:
[synopsis]
1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
Renamed or copied entries have the following format:
[synopsis]
2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
....
Field Meaning
--------------------------------------------------------
<XY> A 2 character field containing the staged and
[cols="<1,<1a",options="header"]
|===
|Field | Meaning
|_<XY>_
|A 2 character field containing the staged and
unstaged XY values described in the short format,
with unchanged indicated by a "." rather than
a space.
<sub> A 4 character field describing the submodule state.
|_<sub>_
|A 4 character field describing the submodule state.
"N..." when the entry is not a submodule.
"S<c><m><u>" when the entry is a submodule.
<c> is "C" if the commit changed; otherwise ".".
<m> is "M" if it has tracked changes; otherwise ".".
<u> is "U" if there are untracked changes; otherwise ".".
<mH> The octal file mode in HEAD.
<mI> The octal file mode in the index.
<mW> The octal file mode in the worktree.
<hH> The object name in HEAD.
<hI> The object name in the index.
<X><score> The rename or copy score (denoting the percentage
`S<c><m><u>` when the entry is a submodule.
* _<c>_ is "C" if the commit changed; otherwise ".".
* _<m>_ is "M" if it has tracked changes; otherwise ".".
* _<u>_ is "U" if there are untracked changes; otherwise ".".
|_<mH>_ |The octal file mode in HEAD.
|_<mI>_ |The octal file mode in the index.
|_<mW>_ |The octal file mode in the worktree.
|_<hH>_ |The object name in HEAD.
|_<hI>_ |The object name in the index.
|_<X><score>_ |The rename or copy score (denoting the percentage
of similarity between the source and target of the
move or copy). For example "R100" or "C75".
<path> The pathname. In a renamed/copied entry, this
is the target path.
<sep> When the `-z` option is used, the 2 pathnames are separated
with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
|_<path>_
|The pathname. In a renamed/copied entry, this is the target path.
|_<sep>_
|When the `-z` option is used, the 2 pathnames are separated
with a _NUL_ (ASCII 0x00) byte; otherwise, a _TAB_ (ASCII 0x09)
byte separates them.
<origPath> The pathname in the commit at HEAD or in the index.
|_<origPath>_
|The pathname in the commit at HEAD or in the index.
This is only present in a renamed/copied entry, and
tells where the renamed/copied contents came from.
--------------------------------------------------------
....
|===
Unmerged entries have the following format; the first character is
a "u" to distinguish from ordinary changed entries.
[synopsis]
u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
....
Field Meaning
--------------------------------------------------------
<XY> A 2 character field describing the conflict type
[cols="<1,<1a",options="header"]
|===
|Field |Meaning
|_<XY>_ |A 2 character field describing the conflict type
as described in the short format.
<sub> A 4 character field describing the submodule state
|_<sub>_ |A 4 character field describing the submodule state
as described above.
<m1> The octal file mode in stage 1.
<m2> The octal file mode in stage 2.
<m3> The octal file mode in stage 3.
<mW> The octal file mode in the worktree.
<h1> The object name in stage 1.
<h2> The object name in stage 2.
<h3> The object name in stage 3.
<path> The pathname.
--------------------------------------------------------
....
|_<m1>_ |The octal file mode in stage 1.
|_<m2>_ |The octal file mode in stage 2.
|_<m3>_ |The octal file mode in stage 3.
|_<mW>_ |The octal file mode in the worktree.
|_<h1>_ |The object name in stage 1.
|_<h2>_ |The object name in stage 2.
|_<h3>_ |The object name in stage 3.
|_<path>_ |The pathname.
|===
Other Items
^^^^^^^^^^^
@ -416,7 +424,7 @@ Pathname Format Notes and -z
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the `-z` option is given, pathnames are printed as is and
without any quoting and lines are terminated with a NUL (ASCII 0x00)
without any quoting and lines are terminated with a _NUL_ (ASCII 0x00)
byte.
Without the `-z` option, pathnames with "unusual" characters are
@ -439,11 +447,11 @@ directory.
If `status.submoduleSummary` is set to a non zero number or true (identical
to -1 or an unlimited number), the submodule summary will be enabled for
the long format and a summary of commits for modified submodules will be
shown (see --summary-limit option of linkgit:git-submodule[1]). Please note
shown (see `--summary-limit` option of linkgit:git-submodule[1]). Please note
that the summary output from the status command will be suppressed for all
submodules when `diff.ignoreSubmodules` is set to 'all' or only for those
submodules when `diff.ignoreSubmodules` is set to `all` or only for those
submodules where `submodule.<name>.ignore=all`. To also view the summary for
ignored submodules you can either use the --ignore-submodules=dirty command
ignored submodules you can either use the `--ignore-submodules=dirty` command
line option or the 'git submodule summary' command, which shows a similar
output but does not honor these settings.
@ -484,7 +492,7 @@ results, so it could be faster on subsequent runs.
setting this variable to `false` disables the warning message
given when enumerating untracked files takes more than 2
seconds. In a large project, it may take longer and the user
may have already accepted the trade off (e.g. using "-uno" may
may have already accepted the trade off (e.g. using `-uno` may
not be an acceptable option for the user), in which case, there
is no point issuing the warning message, and in such a case,
disabling the warning may be the best.

View File

@ -119,7 +119,7 @@ verify::
Verify <ref> against <old-oid> but do not change it. If
<old-oid> is zero or missing, the ref must not exist.
symref-create:
symref-create::
Create symbolic ref <ref> with <new-target> after verifying that
it does not exist.

View File

@ -79,6 +79,9 @@ with a matching name, treat as equivalent to:
$ git worktree add --track -b <branch> <path> <remote>/<branch>
------------
+
For best results it is advised to specify _<path>_ outside of the repository
and existing worktrees - see <<EXAMPLES,EXAMPLES>>
+
If the branch exists in multiple remotes and one of them is named by
the `checkout.defaultRemote` configuration variable, we'll use that
one for the purposes of disambiguation, even if the _<branch>_ isn't
@ -271,7 +274,7 @@ mismatch, even if the links are correct.
With `list`, output additional information about worktrees (see below).
`--expire <time>`::
With `prune`, only expire unused worktrees older than _<time>_.
With `prune`, only prune missing worktrees if older than _<time>_.
+
With `list`, annotate missing worktrees as prunable if they are older than
_<time>_.
@ -502,6 +505,7 @@ locked "reason\nwhy is locked"
...
------------
[[EXAMPLES]]
EXAMPLES
--------
You are in the middle of a refactoring session and your boss comes in and
@ -522,6 +526,16 @@ $ popd
$ git worktree remove ../temp
------------
Side by side branch checkouts for a repository using multiple worktrees
------------
mkdir some-repository
cd some-repository
git clone --bare gitforge@someforge.example.com:some-org/some-repository some-repository.git
git --git-dir=some-repository.git worktree add some-branch
git --git-dir=some-repository.git worktree add another-branch
------------
CONFIGURATION
-------------

View File

@ -584,6 +584,14 @@ double-quotes and respecting backslash escapes. E.g., the value
repositories will be set to this value. The default is "files".
See `--ref-format` in linkgit:git-init[1].
`GIT_REF_URI`::
Specify which reference backend to be used along with its URI. Reference
backends like the files, reftable backend use the $GIT_DIR as their URI.
+
Expects the format `<ref_backend>://<URI-for-resource>`, where the
_<ref_backend>_ specifies the reference backend and the _<URI-for-resource>_
specifies the URI used by the backend.
Git Commits
~~~~~~~~~~~
`GIT_AUTHOR_NAME`::

View File

@ -233,14 +233,30 @@ of refs, such that both sides end up with different commits on a branch that
the other doesn't have. This can result in important objects becoming
unreferenced and possibly pruned by `git gc`, causing data loss.
+
Therefore, it's better to push your work to either the other system or a central
server using the normal push and pull mechanism. However, this doesn't always
preserve important data, like stashes, so some people prefer to share a working
tree across systems.
Therefore, it's better to push your work to either the other system or a
central server using the normal push and pull mechanism. In Git 2.51, Git
learned to import and export stashes, so it's possible to synchronize the state
of the working tree by stashing it with `git stash`, then exporting either all
stashes with `git stash export --to-ref refs/heads/stashes` (assuming you want
to export to the `stashes` branch) or selecting stashes by adding their numbers
to the end of that command. It's also possible to include untracked files by
using the `--include-untracked` argument when stashing the data in the first
place, but be careful not to do this if any of these contain sensitive
information.
+
If you do this, the recommended approach is to use `rsync -a --delete-after`
(ideally with an encrypted connection such as with `ssh`) on the root of
repository. You should ensure several things when you do this:
You can then push the `stashes` branch (or whatever branch you've exported to),
fetch them to the local system (such as with `git fetch origin
+stashes:stashes`), and import the stashes on the other system with `git stash
import stashes` (again, changing the name as necessary). Applying the changes
to the working tree can be done with `git stash pop` or `git stash apply`.
This is the approach that is most robust and most likely to avoid unintended
problems.
+
Having said that, there are some cases where people nevertheless prefer to
share a working tree across systems. If you do this, the recommended approach
is to use `rsync -a --delete-after` (ideally with an encrypted connection such
as with `ssh`) on the root of repository. You should ensure several things
when you do this:
+
* If you have additional worktrees or a separate Git directory, they must be
synced at the same time as the main working tree and repository.
@ -251,10 +267,11 @@ repository. You should ensure several things when you do this:
any sort are taking place on it, including background operations like `git
gc` and operations invoked by your editor).
+
Be aware that even with these recommendations, syncing in this way has some risk
since it bypasses Git's normal integrity checking for repositories, so having
backups is advised. You may also wish to do a `git fsck` to verify the
integrity of your data on the destination system after syncing.
Be aware that even with these recommendations, syncing working trees in this
way has some risk since it bypasses Git's normal integrity checking for
repositories, so having backups is advised. You may also wish to do a `git
fsck` to verify the integrity of your data on the destination system after
syncing.
Common Issues
-------------

View File

@ -10,6 +10,7 @@ SYNOPSIS
--------
[verse]
$GIT_DIR/objects/[0-9a-f][0-9a-f]/*
$GIT_DIR/objects/object-map/map-*.map
DESCRIPTION
-----------
@ -48,6 +49,83 @@ stored under
Similarly, a blob containing the contents `abc` would have the uncompressed
data of `blob 3\0abc`.
== Loose object mapping
When the `compatObjectFormat` option is used, Git needs to store a mapping
between the repository's main algorithm and the compatibility algorithm for
loose objects as well as some auxiliary information.
The mapping consists of a set of files under `$GIT_DIR/objects/object-map`
ending in `.map`. The portion of the filename before the extension is that of
the main hash checksum (that is, the one specified in
`extensions.objectformat`) in hex format.
`git gc` will repack existing entries into one file, removing any unnecessary
objects, such as obsolete shallow entries or loose objects that have been
packed.
The file format is as follows. All values are in network byte order and all
4-byte and 8-byte values must be 4-byte aligned in the file, so the NUL padding
may be required in some cases. Git always uses the smallest number of NUL
bytes (including zero) that is required for the padding in order to make
writing files deterministic.
- A header appears at the beginning and consists of the following:
* A 4-byte mapping signature: `LMAP`
* 4-byte version number: 1
* 4-byte length of the header section (including reserved entries but
excluding any NUL padding).
* 4-byte number of objects declared in this map file.
* 4-byte number of object formats declared in this map file.
* For each object format:
** 4-byte format identifier (e.g., `sha1` for SHA-1)
** 4-byte length in bytes of shortened object names (that is, prefixes of
the full object names). This is the shortest possible length needed to
make names in the shortened object name table unambiguous.
** 8-byte integer, recording where tables relating to this format
are stored in this index file, as an offset from the beginning.
* 8-byte offset to the trailer from the beginning of this file.
* The remainder of the header section is reserved for future use.
Readers must ignore unrecognized data here.
- Zero or more NUL bytes. These are used to improve the alignment of the
4-byte quantities below.
- Tables for the first object format:
* A sorted table of shortened object names. These are prefixes of the names
of all objects in this file, packed together to reduce the cache footprint
of the binary search for a specific object name.
* A sorted table of full object names.
* A table of 4-byte metadata values.
- Zero or more NUL bytes.
- Tables for subsequent object formats:
* A sorted table of shortened object names. These are prefixes of the names
of all objects in this file, packed together without offset values to
reduce the cache footprint of the binary search for a specific object name.
* A table of full object names in the order specified by the first object format.
* A table of 4-byte values mapping object name order to the order of the
first object format. For an object in the table of sorted shortened object
names, the value at the corresponding index in this table is the index in
the previous table for that same object.
* Zero or more NUL bytes.
- The trailer consists of the following:
* Hash checksum of all of the above using the main hash.
The lower six bits of each metadata table contain a type field indicating the
reason that this object is stored:
0::
Reserved.
1::
This object is stored as a loose object in the repository.
2::
This object is a shallow entry. The mapping refers to a shallow value
returned by a remote server.
3::
This object is a submodule entry. The mapping refers to the commit stored
representing a submodule.
Other data may be stored in this field in the future. Bits that are not used
must be zero.
GIT
---
Part of the linkgit:git[1] suite

View File

@ -812,10 +812,15 @@ MUST appear first in each pr-fields, in that order.
After these mandatory fields, the server MAY advertise the following
optional fields in any order:
`partialCloneFilter`:: The filter specification used by the remote.
`partialCloneFilter`:: The filter specification for the remote. It
corresponds to the "remote.<name>.partialCloneFilter" config setting.
Clients can use this to determine if the remote's filtering strategy
is compatible with their needs (e.g., checking if both use "blob:none").
It corresponds to the "remote.<name>.partialCloneFilter" config setting.
is compatible with their needs (e.g., checking if both use
"blob:none"). Additionally they can use this through the
`--filter=auto` option in linkgit:git-clone[1]. With that option, the
filter specification of the clone will be automatically computed by
combining the filter specifications of the promisor remotes the client
accepts.
`token`:: An authentication token that clients can use when
connecting to the remote. It corresponds to the "remote.<name>.token"
@ -826,9 +831,11 @@ are case-sensitive and MUST be transmitted exactly as specified
above. Clients MUST ignore fields they don't recognize to allow for
future protocol extensions.
For now, the client can only use information transmitted through these
fields to decide if it accepts the advertised promisor remote. In the
future that information might be used for other purposes though.
The client can use information transmitted through these fields to
decide if it accepts the advertised promisor remote. Also, the client
can be configured to store the values of these fields or use them
to automatically configure the repository (see "promisor.storeFields"
in linkgit:git-config[1] and `--filter=auto` in linkgit:git-clone[1]).
Field values MUST be urlencoded.
@ -856,8 +863,9 @@ the server advertised, the client shouldn't advertise the
On the server side, the "promisor.advertise" and "promisor.sendFields"
configuration options can be used to control what it advertises. On
the client side, the "promisor.acceptFromServer" configuration option
can be used to control what it accepts. See the documentation of these
configuration options for more information.
can be used to control what it accepts, and the "promisor.storeFields"
option, to control what it stores. See the documentation of these
configuration options in linkgit:git-config[1] for more information.
Note that in the future it would be nice if the "promisor-remote"
protocol capability could be used by the server, when responding to

View File

@ -64,6 +64,7 @@ manpages = {
'git-gui.adoc' : 1,
'git-hash-object.adoc' : 1,
'git-help.adoc' : 1,
'git-history.adoc' : 1,
'git-hook.adoc' : 1,
'git-http-backend.adoc' : 1,
'git-http-fetch.adoc' : 1,

View File

@ -1116,6 +1116,7 @@ LIB_OBJS += commit-reach.o
LIB_OBJS += commit.o
LIB_OBJS += common-exit.o
LIB_OBJS += common-init.o
LIB_OBJS += compat/ivec.o
LIB_OBJS += compat/nonblock.o
LIB_OBJS += compat/obstack.o
LIB_OBJS += compat/open.o
@ -1285,6 +1286,7 @@ LIB_OBJS += repack-geometry.o
LIB_OBJS += repack-midx.o
LIB_OBJS += repack-promisor.o
LIB_OBJS += replace-object.o
LIB_OBJS += replay.o
LIB_OBJS += repo-settings.o
LIB_OBJS += repository.o
LIB_OBJS += rerere.o
@ -1417,6 +1419,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o
BUILTIN_OBJS += builtin/grep.o
BUILTIN_OBJS += builtin/hash-object.o
BUILTIN_OBJS += builtin/help.o
BUILTIN_OBJS += builtin/history.o
BUILTIN_OBJS += builtin/hook.o
BUILTIN_OBJS += builtin/index-pack.o
BUILTIN_OBJS += builtin/init-db.o
@ -1516,10 +1519,12 @@ CLAR_TEST_SUITES += u-dir
CLAR_TEST_SUITES += u-example-decorate
CLAR_TEST_SUITES += u-hash
CLAR_TEST_SUITES += u-hashmap
CLAR_TEST_SUITES += u-list-objects-filter-options
CLAR_TEST_SUITES += u-mem-pool
CLAR_TEST_SUITES += u-oid-array
CLAR_TEST_SUITES += u-oidmap
CLAR_TEST_SUITES += u-oidtree
CLAR_TEST_SUITES += u-parse-int
CLAR_TEST_SUITES += u-prio-queue
CLAR_TEST_SUITES += u-reftable-basics
CLAR_TEST_SUITES += u-reftable-block
@ -1545,7 +1550,10 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
RUST_SOURCES += src/csum_file.rs
RUST_SOURCES += src/hash.rs
RUST_SOURCES += src/lib.rs
RUST_SOURCES += src/loose.rs
RUST_SOURCES += src/varint.rs
GIT-VERSION-FILE: FORCE
@ -1692,6 +1700,7 @@ ifeq ($(uname_S),Darwin)
ifeq ($(shell test -d /opt/local/lib && echo y),y)
BASIC_CFLAGS += -I/opt/local/include
BASIC_LDFLAGS += -L/opt/local/lib
HAS_GOOD_LIBICONV = Yes
endif
endif
ifndef NO_APPLE_COMMON_CRYPTO
@ -1714,6 +1723,7 @@ endif
ifdef USE_HOMEBREW_LIBICONV
ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/libiconv && echo y),y)
ICONVDIR ?= $(HOMEBREW_PREFIX)/opt/libiconv
HAS_GOOD_LIBICONV = Yes
endif
endif
endif
@ -1859,6 +1869,11 @@ ifndef NO_ICONV
endif
EXTLIBS += $(ICONV_LINK) -liconv
endif
ifdef NEEDS_GOOD_LIBICONV
ifndef HAS_GOOD_LIBICONV
BASIC_CFLAGS += -DICONV_RESTART_RESET
endif
endif
endif
ifdef ICONV_OMITS_BOM
BASIC_CFLAGS += -DICONV_OMITS_BOM
@ -2991,7 +3006,7 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
$(LIB_FILE): $(LIB_OBJS)
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
$(RUST_LIB): Cargo.toml $(RUST_SOURCES)
$(RUST_LIB): Cargo.toml $(RUST_SOURCES) $(LIB_FILE)
$(QUIET_CARGO)cargo build $(CARGO_ARGS)
.PHONY: rust

View File

@ -42,10 +42,10 @@ static struct patch_mode patch_mode_add = {
.apply_args = { "--cached", NULL },
.apply_check_args = { "--cached", NULL },
.prompt_mode = {
N_("Stage mode change [y,n,q,a,d%s,?]? "),
N_("Stage deletion [y,n,q,a,d%s,?]? "),
N_("Stage addition [y,n,q,a,d%s,?]? "),
N_("Stage this hunk [y,n,q,a,d%s,?]? ")
N_("Stage mode change%s [y,n,q,a,d%s,?]? "),
N_("Stage deletion%s [y,n,q,a,d%s,?]? "),
N_("Stage addition%s [y,n,q,a,d%s,?]? "),
N_("Stage this hunk%s [y,n,q,a,d%s,?]? ")
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for staging."),
@ -64,10 +64,10 @@ static struct patch_mode patch_mode_stash = {
.apply_args = { "--cached", NULL },
.apply_check_args = { "--cached", NULL },
.prompt_mode = {
N_("Stash mode change [y,n,q,a,d%s,?]? "),
N_("Stash deletion [y,n,q,a,d%s,?]? "),
N_("Stash addition [y,n,q,a,d%s,?]? "),
N_("Stash this hunk [y,n,q,a,d%s,?]? "),
N_("Stash mode change%s [y,n,q,a,d%s,?]? "),
N_("Stash deletion%s [y,n,q,a,d%s,?]? "),
N_("Stash addition%s [y,n,q,a,d%s,?]? "),
N_("Stash this hunk%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for stashing."),
@ -88,10 +88,10 @@ static struct patch_mode patch_mode_reset_head = {
.is_reverse = 1,
.index_only = 1,
.prompt_mode = {
N_("Unstage mode change [y,n,q,a,d%s,?]? "),
N_("Unstage deletion [y,n,q,a,d%s,?]? "),
N_("Unstage addition [y,n,q,a,d%s,?]? "),
N_("Unstage this hunk [y,n,q,a,d%s,?]? "),
N_("Unstage mode change%s [y,n,q,a,d%s,?]? "),
N_("Unstage deletion%s [y,n,q,a,d%s,?]? "),
N_("Unstage addition%s [y,n,q,a,d%s,?]? "),
N_("Unstage this hunk%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for unstaging."),
@ -111,10 +111,10 @@ static struct patch_mode patch_mode_reset_nothead = {
.apply_check_args = { "--cached", NULL },
.index_only = 1,
.prompt_mode = {
N_("Apply mode change to index [y,n,q,a,d%s,?]? "),
N_("Apply deletion to index [y,n,q,a,d%s,?]? "),
N_("Apply addition to index [y,n,q,a,d%s,?]? "),
N_("Apply this hunk to index [y,n,q,a,d%s,?]? "),
N_("Apply mode change to index%s [y,n,q,a,d%s,?]? "),
N_("Apply deletion to index%s [y,n,q,a,d%s,?]? "),
N_("Apply addition to index%s [y,n,q,a,d%s,?]? "),
N_("Apply this hunk to index%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for applying."),
@ -134,10 +134,10 @@ static struct patch_mode patch_mode_checkout_index = {
.apply_check_args = { "-R", NULL },
.is_reverse = 1,
.prompt_mode = {
N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
N_("Discard mode change from worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard this hunk from worktree%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for discarding."),
@ -157,10 +157,10 @@ static struct patch_mode patch_mode_checkout_head = {
.apply_check_args = { "-R", NULL },
.is_reverse = 1,
.prompt_mode = {
N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
N_("Discard mode change from index and worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard deletion from index and worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard addition from index and worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard this hunk from index and worktree%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for discarding."),
@ -179,10 +179,10 @@ static struct patch_mode patch_mode_checkout_nothead = {
.apply_for_checkout = 1,
.apply_check_args = { NULL },
.prompt_mode = {
N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
N_("Apply mode change to index and worktree%s [y,n,q,a,d%s,?]? "),
N_("Apply deletion to index and worktree%s [y,n,q,a,d%s,?]? "),
N_("Apply addition to index and worktree%s [y,n,q,a,d%s,?]? "),
N_("Apply this hunk to index and worktree%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for applying."),
@ -202,10 +202,10 @@ static struct patch_mode patch_mode_worktree_head = {
.apply_check_args = { "-R", NULL },
.is_reverse = 1,
.prompt_mode = {
N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
N_("Discard mode change from worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "),
N_("Discard this hunk from worktree%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for discarding."),
@ -224,10 +224,10 @@ static struct patch_mode patch_mode_worktree_nothead = {
.apply_args = { NULL },
.apply_check_args = { NULL },
.prompt_mode = {
N_("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
N_("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
N_("Apply addition to worktree [y,n,q,a,d%s,?]? "),
N_("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
N_("Apply mode change to worktree%s [y,n,q,a,d%s,?]? "),
N_("Apply deletion to worktree%s [y,n,q,a,d%s,?]? "),
N_("Apply addition to worktree%s [y,n,q,a,d%s,?]? "),
N_("Apply this hunk to worktree%s [y,n,q,a,d%s,?]? "),
},
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
"will immediately be marked for applying."),
@ -1460,6 +1460,7 @@ static int patch_update_file(struct add_p_state *s,
render_diff_header(s, file_diff, colored, &s->buf);
fputs(s->buf.buf, stdout);
for (;;) {
const char *hunk_use_decision = "";
enum {
ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
@ -1564,8 +1565,14 @@ static int patch_update_file(struct add_p_state *s,
(uintmax_t)(file_diff->hunk_nr
? file_diff->hunk_nr
: 1));
if (hunk->use != UNDECIDED_HUNK) {
if (hunk->use == USE_HUNK)
hunk_use_decision = _(" (was: y)");
else
hunk_use_decision = _(" (was: n)");
}
printf(_(s->mode->prompt_mode[prompt_mode_type]),
s->buf.buf);
hunk_use_decision, s->buf.buf);
if (*s->s.reset_color_interactive)
fputs(s->s.reset_color_interactive, stdout);
fflush(stdout);

19
attr.c
View File

@ -879,14 +879,6 @@ const char *git_attr_system_file(void)
return system_wide;
}
const char *git_attr_global_file(void)
{
if (!git_attributes_file)
git_attributes_file = xdg_config_home("attributes");
return git_attributes_file;
}
int git_attr_system_is_enabled(void)
{
return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
@ -912,6 +904,8 @@ static void bootstrap_attr_stack(struct index_state *istate,
{
struct attr_stack *e;
unsigned flags = READ_ATTR_MACRO_OK;
const char *attributes_file_path;
struct repository *repo;
if (*stack)
return;
@ -927,8 +921,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
}
/* home directory */
if (git_attr_global_file()) {
e = read_attr_from_file(git_attr_global_file(), flags);
if (istate && istate->repo)
repo = istate->repo;
else
repo = the_repository;
attributes_file_path = repo_settings_get_attributesfile_path(repo);
if (attributes_file_path) {
e = read_attr_from_file(attributes_file_path, flags);
push_stack(stack, e, NULL, 0);
}

3
attr.h
View File

@ -232,9 +232,6 @@ void attr_start(void);
/* Return the system gitattributes file. */
const char *git_attr_system_file(void);
/* Return the global gitattributes file, if any. */
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);

17
build.rs Normal file
View File

@ -0,0 +1,17 @@
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation: version 2 of the License, dated June 1991.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <https://www.gnu.org/licenses/>.
fn main() {
println!("cargo:rustc-link-search=.");
println!("cargo:rustc-link-lib=git");
println!("cargo:rustc-link-lib=z");
}

View File

@ -17,7 +17,8 @@
* . Define the implementation of the built-in command `foo` with
* signature:
*
* int cmd_foo(int argc, const char **argv, const char *prefix);
* int cmd_foo(int argc, const char **argv,
* const char *prefix, struct repository *repo);
*
* . Add the external declaration for the function to `builtin.h`.
*
@ -29,12 +30,14 @@
* where options is the bitwise-or of:
*
* `RUN_SETUP`:
*
* If there is not a Git directory to work on, abort. If there
* is a work tree, chdir to the top of it if the command was
* invoked in a subdirectory. If there is no work tree, no
* chdir() is done.
*
* `RUN_SETUP_GENTLY`:
*
* If there is a Git directory, chdir as per RUN_SETUP, otherwise,
* don't chdir anywhere.
*
@ -57,6 +60,12 @@
* more informed decision, e.g., by ignoring `pager.<cmd>` for
* certain subcommands.
*
* `NO_PARSEOPT`:
*
* Most Git builtins use the parseopt library for parsing options.
* This flag indicates that a custom parser is used and thus the
* builtin would not appear in 'git --list-cmds=parseopt'.
*
* . Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`.
*
* Additionally, if `foo` is a new command, there are 4 more things to do:
@ -69,6 +78,21 @@
*
* . Add an entry for `/git-foo` to `.gitignore`.
*
* As you work on implementing your builtin, be mindful that the
* following tests will check different aspects of the builtin's
* readiness and adherence to matching the documentation:
*
* * t0012-help.sh checks that the builtin can handle -h, which comes
* automatically with the parseopt API.
*
* * t0450-txt-doc-vs-help.sh checks that the -h help output matches the
* SYNOPSIS in the documentation for the builtin.
*
* * t1517-outside-repo.sh checks that the builtin can handle -h when
* run outside of the context of a repository. Note that this test
* requires that the usage has a space after the builtin name, so some
* minimum description of options is required.
*
*
* How a built-in is called
* ------------------------
@ -172,6 +196,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix, struc
int cmd_grep(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_hash_object(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_help(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_history(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_hook(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo);
int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);

View File

@ -487,8 +487,7 @@ static void batch_object_write(const char *obj_name,
data->info.sizep = &data->size;
if (pack)
ret = packed_object_info(the_repository, pack,
offset, &data->info);
ret = packed_object_info(pack, offset, &data->info);
else
ret = odb_read_object_info_extended(the_repository->objects,
&data->oid, &data->info,
@ -846,11 +845,13 @@ static void batch_each_object(struct batch_options *opt,
.callback = callback,
.payload = _payload,
};
struct bitmap_index *bitmap = prepare_bitmap_git(the_repository);
struct bitmap_index *bitmap = NULL;
for_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0);
if (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter,
if (opt->objects_filter.choice != LOFC_DISABLED &&
(bitmap = prepare_bitmap_git(the_repository)) &&
!for_each_bitmapped_object(bitmap, &opt->objects_filter,
batch_one_object_bitmapped, &payload)) {
struct packed_git *pack;

View File

@ -77,7 +77,6 @@ static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
static int max_jobs = -1;
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
static int config_filter_submodules = -1; /* unspecified */
static int option_remote_submodules;
@ -634,7 +633,9 @@ static int git_sparse_checkout_init(const char *repo)
return result;
}
static int checkout(int submodule_progress, int filter_submodules,
static int checkout(int submodule_progress,
struct list_objects_filter_options *filter_options,
int filter_submodules,
enum ref_storage_format ref_storage_format)
{
struct object_id oid;
@ -723,9 +724,9 @@ static int checkout(int submodule_progress, int filter_submodules,
strvec_pushf(&cmd.args, "--ref-format=%s",
ref_storage_format_to_name(ref_storage_format));
if (filter_submodules && filter_options.choice)
if (filter_submodules && filter_options->choice)
strvec_pushf(&cmd.args, "--filter=%s",
expand_list_objects_filter_spec(&filter_options));
expand_list_objects_filter_spec(filter_options));
if (option_single_branch >= 0)
strvec_push(&cmd.args, option_single_branch ?
@ -903,6 +904,7 @@ int cmd_clone(int argc,
enum transport_family family = TRANSPORT_FAMILY_ALL;
struct string_list option_config = STRING_LIST_INIT_DUP;
int option_dissociate = 0;
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
int option_filter_submodules = -1; /* unspecified */
struct string_list server_options = STRING_LIST_INIT_NODUP;
const char *bundle_uri = NULL;
@ -999,6 +1001,8 @@ int cmd_clone(int argc,
NULL
};
filter_options.allow_auto_filter = 1;
packet_trace_identity("clone");
repo_config(the_repository, git_clone_config, NULL);
@ -1625,9 +1629,13 @@ int cmd_clone(int argc,
return 1;
junk_mode = JUNK_LEAVE_REPO;
err = checkout(submodule_progress, filter_submodules,
err = checkout(submodule_progress,
&filter_options,
filter_submodules,
ref_storage_format);
list_objects_filter_release(&filter_options);
string_list_clear(&option_not, 0);
string_list_clear(&option_config, 0);
string_list_clear(&server_options, 0);

View File

@ -539,8 +539,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
path = repo_git_path(the_repository, "next-index-%"PRIuMAX,
(uintmax_t) getpid());
hold_lock_file_for_update(&false_lock, path,
LOCK_DIE_ON_ERROR);
hold_lock_file_for_update(&false_lock, path, LOCK_DIE_ON_ERROR);
create_base_index(current_head);
add_remove_files(&partial);
@ -1719,7 +1718,7 @@ int cmd_commit(int argc,
OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG),
OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, parse_opt_strvec),
OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),

View File

@ -803,6 +803,18 @@ static void location_options_init(struct config_location_options *opts,
}
if (opts->use_global_config) {
/*
* Since global config is sourced from more than one location,
* use `config.c#do_git_config_sequence()` with `opts->options`
* to read it. However, writing global config should point to a
* single destination, set in `opts->source.file`.
*/
opts->options.ignore_repo = 1;
opts->options.ignore_cmdline= 1;
opts->options.ignore_worktree = 1;
opts->options.ignore_system = 1;
opts->source.scope = CONFIG_SCOPE_GLOBAL;
opts->source.file = opts->file_to_free = git_global_config();
if (!opts->source.file)
/*

View File

@ -7,6 +7,7 @@
#include "path.h"
#include "string-list.h"
#include "parse-options.h"
#include "url.h"
#include "write-or-die.h"
static struct lock_file credential_lock;
@ -76,12 +77,6 @@ static void rewrite_credential_file(const char *fn, struct credential *c,
die_errno("unable to write credential store");
}
static int is_rfc3986_unreserved(char ch)
{
return isalnum(ch) ||
ch == '-' || ch == '_' || ch == '.' || ch == '~';
}
static int is_rfc3986_reserved_or_unreserved(char ch)
{
if (is_rfc3986_unreserved(ch))

View File

@ -900,7 +900,7 @@ static void end_packfile(void)
idx_name = keep_pack(create_index());
/* Register the packfile with core git's machinery. */
new_p = packfile_store_load_pack(pack_data->repo->objects->packfiles,
new_p = packfile_store_load_pack(pack_data->repo->objects->sources->packfiles,
idx_name, 1);
if (!new_p)
die(_("core Git rejected index %s"), idx_name);
@ -955,7 +955,7 @@ static int store_object(
struct object_id *oidout,
uintmax_t mark)
{
struct packfile_store *packs = the_repository->objects->packfiles;
struct odb_source *source;
void *out, *delta;
struct object_entry *e;
unsigned char hdr[96];
@ -979,7 +979,11 @@ static int store_object(
if (e->idx.offset) {
duplicate_count_by_type[type]++;
return 1;
} else if (packfile_list_find_oid(packfile_store_get_packs(packs), &oid)) {
}
for (source = the_repository->objects->sources; source; source = source->next) {
if (!packfile_list_find_oid(packfile_store_get_packs(source->packfiles), &oid))
continue;
e->type = type;
e->pack_id = MAX_PACK_ID;
e->idx.offset = 1; /* just not zero! */
@ -1096,10 +1100,10 @@ static void truncate_pack(struct hashfile_checkpoint *checkpoint)
static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
{
struct packfile_store *packs = the_repository->objects->packfiles;
size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
unsigned char *in_buf = xmalloc(in_sz);
unsigned char *out_buf = xmalloc(out_sz);
struct odb_source *source;
struct object_entry *e;
struct object_id oid;
unsigned long hdrlen;
@ -1179,15 +1183,20 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
if (e->idx.offset) {
duplicate_count_by_type[OBJ_BLOB]++;
truncate_pack(&checkpoint);
goto out;
}
} else if (packfile_list_find_oid(packfile_store_get_packs(packs), &oid)) {
for (source = the_repository->objects->sources; source; source = source->next) {
if (!packfile_list_find_oid(packfile_store_get_packs(source->packfiles), &oid))
continue;
e->type = OBJ_BLOB;
e->pack_id = MAX_PACK_ID;
e->idx.offset = 1; /* just not zero! */
duplicate_count_by_type[OBJ_BLOB]++;
truncate_pack(&checkpoint);
goto out;
}
} else {
e->depth = 0;
e->type = OBJ_BLOB;
e->pack_id = pack_id;
@ -1195,8 +1204,8 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
e->idx.crc32 = crc32_end(pack_file);
object_count++;
object_count_by_type[OBJ_BLOB]++;
}
out:
free(in_buf);
free(out_buf);
}

View File

@ -97,7 +97,6 @@ static struct strbuf default_rla = STRBUF_INIT;
static struct transport *gtransport;
static struct transport *gsecondary;
static struct refspec refmap = REFSPEC_INIT_FETCH;
static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
static struct string_list server_options = STRING_LIST_INIT_DUP;
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
@ -1449,7 +1448,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
smart_options->negotiation_tips = oids;
}
static struct transport *prepare_transport(struct remote *remote, int deepen)
static struct transport *prepare_transport(struct remote *remote, int deepen,
struct list_objects_filter_options *filter_options)
{
struct transport *transport;
@ -1473,9 +1473,9 @@ static struct transport *prepare_transport(struct remote *remote, int deepen)
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
if (refetch)
set_option(transport, TRANS_OPT_REFETCH, "yes");
if (filter_options.choice) {
if (filter_options->choice) {
const char *spec =
expand_list_objects_filter_spec(&filter_options);
expand_list_objects_filter_spec(filter_options);
set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec);
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
}
@ -1493,7 +1493,8 @@ static int backfill_tags(struct display_state *display_state,
struct ref_transaction *transaction,
struct ref *ref_map,
struct fetch_head *fetch_head,
const struct fetch_config *config)
const struct fetch_config *config,
struct list_objects_filter_options *filter_options)
{
int retcode, cannot_reuse;
@ -1507,7 +1508,7 @@ static int backfill_tags(struct display_state *display_state,
cannot_reuse = transport->cannot_reuse ||
deepen_since || deepen_not.nr;
if (cannot_reuse) {
gsecondary = prepare_transport(transport->remote, 0);
gsecondary = prepare_transport(transport->remote, 0, filter_options);
transport = gsecondary;
}
@ -1713,7 +1714,8 @@ out:
static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config)
const struct fetch_config *config,
struct list_objects_filter_options *filter_options)
{
struct ref_transaction *transaction = NULL;
struct ref *ref_map = NULL;
@ -1873,7 +1875,7 @@ static int do_fetch(struct transport *transport,
* the transaction and don't commit anything.
*/
if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
&fetch_head, config))
&fetch_head, config, filter_options))
retcode = 1;
}
@ -2198,20 +2200,21 @@ static int fetch_multiple(struct string_list *list, int max_children,
* Fetching from the promisor remote should use the given filter-spec
* or inherit the default filter-spec from the config.
*/
static inline void fetch_one_setup_partial(struct remote *remote)
static inline void fetch_one_setup_partial(struct remote *remote,
struct list_objects_filter_options *filter_options)
{
/*
* Explicit --no-filter argument overrides everything, regardless
* of any prior partial clones and fetches.
*/
if (filter_options.no_filter)
if (filter_options->no_filter)
return;
/*
* If no prior partial clone/fetch and the current fetch DID NOT
* request a partial-fetch, do a normal fetch.
*/
if (!repo_has_promisor_remote(the_repository) && !filter_options.choice)
if (!repo_has_promisor_remote(the_repository) && !filter_options->choice)
return;
/*
@ -2220,8 +2223,8 @@ static inline void fetch_one_setup_partial(struct remote *remote)
* filter-spec as the default for subsequent fetches to this
* remote if there is currently no default filter-spec.
*/
if (filter_options.choice) {
partial_clone_register(remote->name, &filter_options);
if (filter_options->choice) {
partial_clone_register(remote->name, filter_options);
return;
}
@ -2230,14 +2233,15 @@ static inline void fetch_one_setup_partial(struct remote *remote)
* explicitly given filter-spec or inherit the filter-spec from
* the config.
*/
if (!filter_options.choice)
partial_clone_get_default_filter_spec(&filter_options, remote->name);
if (!filter_options->choice)
partial_clone_get_default_filter_spec(filter_options, remote->name);
return;
}
static int fetch_one(struct remote *remote, int argc, const char **argv,
int prune_tags_ok, int use_stdin_refspecs,
const struct fetch_config *config)
const struct fetch_config *config,
struct list_objects_filter_options *filter_options)
{
struct refspec rs = REFSPEC_INIT_FETCH;
int i;
@ -2249,7 +2253,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
die(_("no remote repository specified; please specify either a URL or a\n"
"remote name from which new revisions should be fetched"));
gtransport = prepare_transport(remote, 1);
gtransport = prepare_transport(remote, 1, filter_options);
if (prune < 0) {
/* no command line request */
@ -2304,7 +2308,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,
sigchain_push_common(unlock_pack_on_signal);
atexit(unlock_pack_atexit);
sigchain_push(SIGPIPE, SIG_IGN);
exit_code = do_fetch(gtransport, &rs, config);
exit_code = do_fetch(gtransport, &rs, config, filter_options);
sigchain_pop(SIGPIPE);
refspec_clear(&rs);
transport_disconnect(gtransport);
@ -2329,6 +2333,7 @@ int cmd_fetch(int argc,
const char *submodule_prefix = "";
const char *bundle_uri;
struct string_list list = STRING_LIST_INIT_DUP;
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
struct remote *remote = NULL;
int all = -1, multiple = 0;
int result = 0;
@ -2434,6 +2439,8 @@ int cmd_fetch(int argc,
OPT_END()
};
filter_options.allow_auto_filter = 1;
packet_trace_identity("fetch");
/* Record the command line for the reflog */
@ -2594,7 +2601,7 @@ int cmd_fetch(int argc,
trace2_region_enter("fetch", "negotiate-only", the_repository);
if (!remote)
die(_("must supply remote when using --negotiate-only"));
gtransport = prepare_transport(remote, 1);
gtransport = prepare_transport(remote, 1, &filter_options);
if (gtransport->smart_options) {
gtransport->smart_options->acked_commits = &acked_commits;
} else {
@ -2616,12 +2623,12 @@ int cmd_fetch(int argc,
} else if (remote) {
if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
trace2_region_enter("fetch", "setup-partial", the_repository);
fetch_one_setup_partial(remote);
fetch_one_setup_partial(remote, &filter_options);
trace2_region_leave("fetch", "setup-partial", the_repository);
}
trace2_region_enter("fetch", "fetch-one", the_repository);
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
&config);
&config, &filter_options);
trace2_region_leave("fetch", "fetch-one", the_repository);
} else {
int max_children = max_jobs;
@ -2727,5 +2734,6 @@ int cmd_fetch(int argc,
cleanup:
string_list_clear(&list, 0);
list_objects_filter_release(&filter_options);
return result;
}

View File

@ -51,6 +51,7 @@ static int show_progress = -1;
static int show_dangling = 1;
static int name_objects;
static int check_references = 1;
static timestamp_t now;
#define ERROR_OBJECT 01
#define ERROR_REACHABLE 02
#define ERROR_PACK 04
@ -510,6 +511,9 @@ static int fsck_handle_reflog_ent(const char *refname,
timestamp_t timestamp, int tz UNUSED,
const char *message UNUSED, void *cb_data UNUSED)
{
if (now && timestamp > now)
return 0;
if (verbose)
fprintf_ln(stderr, _("Checking reflog %s->%s"),
oid_to_hex(ooid), oid_to_hex(noid));
@ -531,8 +535,22 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)
return 0;
}
static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
struct ref_snapshot {
char *refname;
struct object_id oid;
/* TODO: Maybe supplement with latest reflog entry info too? */
};
struct snapshot {
size_t nr;
size_t alloc;
struct ref_snapshot *ref;
/* TODO: Consider also snapshotting the index of each worktree. */
};
static int snapshot_ref(const struct reference *ref, void *cb_data)
{
struct snapshot *snap = cb_data;
struct object *obj;
obj = parse_object(the_repository, ref->oid);
@ -556,6 +574,20 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
errors_found |= ERROR_REFS;
}
default_refs++;
ALLOC_GROW(snap->ref, snap->nr + 1, snap->alloc);
snap->ref[snap->nr].refname = xstrdup(ref->name);
oidcpy(&snap->ref[snap->nr].oid, ref->oid);
snap->nr++;
return 0;
}
static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
{
struct object *obj;
obj = parse_object(the_repository, ref->oid);
obj->flags |= USED;
fsck_put_object_name(&fsck_walk_options,
ref->oid, "%s", ref->name);
@ -564,18 +596,35 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
return 0;
}
static int fsck_head_link(const char *head_ref_name,
const char **head_points_at,
struct object_id *head_oid);
static void get_default_heads(void)
static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
{
struct worktree **worktrees, **p;
const char *head_points_at;
struct object_id head_oid;
for (int i = 0; i < argc; i++) {
const char *arg = argv[i];
struct object_id oid;
if (!repo_get_oid(the_repository, arg, &oid)) {
struct reference ref = {
.name = arg,
.oid = &oid,
};
snapshot_ref(&ref, snap);
continue;
}
error(_("invalid parameter: expected sha1, got '%s'"), arg);
errors_found |= ERROR_OBJECT;
}
if (argc) {
include_reflogs = 0;
return;
}
refs_for_each_rawref(get_main_ref_store(the_repository),
fsck_handle_ref, NULL);
snapshot_ref, snap);
worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
@ -583,22 +632,62 @@ static void get_default_heads(void)
struct strbuf refname = STRBUF_INIT;
strbuf_worktree_ref(wt, &refname, "HEAD");
fsck_head_link(refname.buf, &head_points_at, &head_oid);
head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
refname.buf, 0, &head_oid, NULL);
if (head_points_at && !is_null_oid(&head_oid)) {
struct reference ref = {
.name = refname.buf,
.oid = &head_oid,
};
fsck_handle_ref(&ref, NULL);
snapshot_ref(&ref, snap);
}
strbuf_release(&refname);
if (include_reflogs)
/*
* TODO: Could use refs_for_each_reflog(...) to find
* latest entry instead of using a global 'now' for that
* purpose.
*/
}
free_worktrees(worktrees);
/* Ignore reflogs newer than now */
now = time(NULL);
}
static void free_snapshot_refs(struct snapshot *snap)
{
for (size_t i = 0; i < snap->nr; i++)
free(snap->ref[i].refname);
free(snap->ref);
}
static void process_refs(struct snapshot *snap)
{
struct worktree **worktrees, **p;
for (size_t i = 0; i < snap->nr; i++) {
struct reference ref = {
.name = snap->ref[i].refname,
.oid = &snap->ref[i].oid,
};
fsck_handle_ref(&ref, NULL);
}
if (include_reflogs) {
worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
struct worktree *wt = *p;
refs_for_each_reflog(get_worktree_ref_store(wt),
fsck_handle_reflog, wt);
}
free_worktrees(worktrees);
}
/*
* Not having any default heads isn't really fatal, but
@ -713,43 +802,6 @@ static void fsck_source(struct odb_source *source)
stop_progress(&progress);
}
static int fsck_head_link(const char *head_ref_name,
const char **head_points_at,
struct object_id *head_oid)
{
int null_is_error = 0;
if (verbose)
fprintf_ln(stderr, _("Checking %s link"), head_ref_name);
*head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
head_ref_name, 0, head_oid,
NULL);
if (!*head_points_at) {
errors_found |= ERROR_REFS;
return error(_("invalid %s"), head_ref_name);
}
if (!strcmp(*head_points_at, head_ref_name))
/* detached HEAD */
null_is_error = 1;
else if (!starts_with(*head_points_at, "refs/heads/")) {
errors_found |= ERROR_REFS;
return error(_("%s points to something strange (%s)"),
head_ref_name, *head_points_at);
}
if (is_null_oid(head_oid)) {
if (null_is_error) {
errors_found |= ERROR_REFS;
return error(_("%s: detached HEAD points at nothing"),
head_ref_name);
}
fprintf_ln(stderr,
_("notice: %s points to an unborn branch (%s)"),
head_ref_name, *head_points_at + 11);
}
return 0;
}
static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
{
int i;
@ -963,8 +1015,12 @@ int cmd_fsck(int argc,
const char *prefix,
struct repository *repo UNUSED)
{
int i;
struct odb_source *source;
struct snapshot snap = {
.nr = 0,
.alloc = 0,
.ref = NULL
};
/* fsck knows how to handle missing promisor objects */
fetch_if_missing = 0;
@ -1000,6 +1056,17 @@ int cmd_fsck(int argc,
if (check_references)
fsck_refs(the_repository);
/*
* Take a snapshot of the refs before walking objects to avoid looking
* at a set of refs that may be changed by the user while we are walking
* objects. We can still walk over new objects that are added during the
* execution of fsck but won't miss any objects that were reachable.
*/
snapshot_refs(&snap, argc, argv);
/* Ensure we get a "fresh" view of the odb */
odb_reprepare(the_repository->objects);
if (connectivity_only) {
for_each_loose_object(the_repository->objects,
mark_loose_for_connectivity, NULL, 0);
@ -1041,42 +1108,18 @@ int cmd_fsck(int argc,
errors_found |= ERROR_OBJECT;
}
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
struct object_id oid;
if (!repo_get_oid(the_repository, arg, &oid)) {
struct object *obj = lookup_object(the_repository,
&oid);
/* Process the snapshotted refs and the reflogs. */
process_refs(&snap);
if (!obj || !(obj->flags & HAS_OBJ)) {
if (is_promisor_object(the_repository, &oid))
continue;
error(_("%s: object missing"), oid_to_hex(&oid));
errors_found |= ERROR_OBJECT;
continue;
}
obj->flags |= USED;
fsck_put_object_name(&fsck_walk_options, &oid,
"%s", arg);
mark_object_reachable(obj);
continue;
}
error(_("invalid parameter: expected sha1, got '%s'"), arg);
errors_found |= ERROR_OBJECT;
}
/*
* If we've not been given any explicit head information, do the
* default ones from .git/refs. We also consider the index file
* in this case (ie this implies --cache).
*/
if (!argc) {
get_default_heads();
/* If not given any explicit objects, process index files too. */
if (!argc)
keep_cache_objects = 1;
}
if (keep_cache_objects) {
/*
* TODO: Consider first walking these indexes in snapshot_refs,
* to snapshot where the index entries used to point, and then
* check those snapshotted locations here.
*/
struct worktree **worktrees, **p;
verify_index_checksum = 1;
@ -1149,5 +1192,6 @@ int cmd_fsck(int argc,
}
}
free_snapshot_refs(&snap);
return errors_found;
}

View File

@ -671,7 +671,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
const struct fsmonitor_batch *batch;
struct fsmonitor_batch *remainder = NULL;
intmax_t count = 0, duplicates = 0;
kh_str_t *shown;
kh_str_t *shown = NULL;
int hash_ret;
int do_trivial = 0;
int do_flush = 0;
@ -909,8 +909,6 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
total_response_len += payload.len;
}
kh_release_str(shown);
pthread_mutex_lock(&state->main_lock);
if (token_data->client_ref_count > 0)
@ -954,6 +952,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates);
cleanup:
kh_destroy_str(shown);
strbuf_release(&response_token);
strbuf_release(&requested_token_id);
strbuf_release(&payload);
@ -1405,6 +1404,7 @@ static int fsmonitor_run_daemon(void)
done:
pthread_cond_destroy(&state.cookies_cond);
pthread_mutex_destroy(&state.main_lock);
hashmap_clear(&state.cookies);
fsm_listen__dtor(&state);
fsm_health__dtor(&state);

View File

@ -748,8 +748,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
xsnprintf(my_host, sizeof(my_host), "unknown");
pidfile_path = repo_git_path(the_repository, "gc.pid");
fd = hold_lock_file_for_update(&lock, pidfile_path,
LOCK_DIE_ON_ERROR);
fd = hold_lock_file_for_update(&lock, pidfile_path, LOCK_DIE_ON_ERROR);
if (!force) {
static char locking_host[HOST_NAME_MAX + 1];
static char *scan_fmt;
@ -1016,8 +1015,7 @@ int cmd_gc(int argc,
if (daemonized) {
char *path = repo_git_path(the_repository, "gc.log");
hold_lock_file_for_update(&log_lock, path,
LOCK_DIE_ON_ERROR);
hold_lock_file_for_update(&log_lock, path, LOCK_DIE_ON_ERROR);
dup2(get_lock_file_fd(&log_lock), 2);
atexit(process_log_file_at_exit);
free(path);
@ -1130,8 +1128,10 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)
return 0;
commit = lookup_commit(the_repository, maybe_peeled);
if (!commit)
if (!commit || commit->object.flags & SEEN)
return 0;
commit->object.flags |= SEEN;
if (repo_parse_commit(the_repository, commit) ||
commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
return 0;
@ -1141,7 +1141,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)
if (data->num_not_in_graph >= data->limit)
return 1;
commit_list_append(commit, &stack);
commit_list_insert(commit, &stack);
while (!result && stack) {
struct commit_list *parent;
@ -1162,7 +1162,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)
break;
}
commit_list_append(parent->item, &stack);
commit_list_insert(parent->item, &stack);
}
}

View File

@ -1213,8 +1213,14 @@ int cmd_grep(int argc,
*/
if (recurse_submodules)
repo_read_gitmodules(the_repository, 1);
if (startup_info->have_repository)
packfile_store_prepare(the_repository->objects->packfiles);
if (startup_info->have_repository) {
struct odb_source *source;
odb_prepare_alternates(the_repository->objects);
for (source = the_repository->objects->sources; source; source = source->next)
packfile_store_prepare(source->packfiles);
}
start_threads(&opt);
} else {

404
builtin/history.c Normal file
View File

@ -0,0 +1,404 @@
#define USE_THE_REPOSITORY_VARIABLE
#include "builtin.h"
#include "commit.h"
#include "commit-reach.h"
#include "config.h"
#include "editor.h"
#include "environment.h"
#include "gettext.h"
#include "hex.h"
#include "parse-options.h"
#include "refs.h"
#include "replay.h"
#include "revision.h"
#include "sequencer.h"
#include "strvec.h"
#include "tree.h"
#include "wt-status.h"
#define GIT_HISTORY_REWORD_USAGE \
N_("git history reword <commit> [--ref-action=(branches|head|print)]")
static void change_data_free(void *util, const char *str UNUSED)
{
struct wt_status_change_data *d = util;
free(d->rename_source);
free(d);
}
static int fill_commit_message(struct repository *repo,
const struct object_id *old_tree,
const struct object_id *new_tree,
const char *default_message,
const char *action,
struct strbuf *out)
{
const char *path = git_path_commit_editmsg();
const char *hint =
_("Please enter the commit message for the %s changes."
" Lines starting\nwith '%s' will be ignored, and an"
" empty message aborts the commit.\n");
struct wt_status s;
strbuf_addstr(out, default_message);
strbuf_addch(out, '\n');
strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str);
write_file_buf(path, out->buf, out->len);
wt_status_prepare(repo, &s);
FREE_AND_NULL(s.branch);
s.ahead_behind_flags = AHEAD_BEHIND_QUICK;
s.commit_template = 1;
s.colopts = 0;
s.display_comment_prefix = 1;
s.hints = 0;
s.use_color = 0;
s.whence = FROM_COMMIT;
s.committable = 1;
s.fp = fopen(git_path_commit_editmsg(), "a");
if (!s.fp)
return error_errno(_("could not open '%s'"), git_path_commit_editmsg());
wt_status_collect_changes_trees(&s, old_tree, new_tree);
wt_status_print(&s);
wt_status_collect_free_buffers(&s);
string_list_clear_func(&s.change, change_data_free);
strbuf_reset(out);
if (launch_editor(path, out, NULL)) {
fprintf(stderr, _("Aborting commit as launching the editor failed.\n"));
return -1;
}
strbuf_stripspace(out, comment_line_str);
cleanup_message(out, COMMIT_MSG_CLEANUP_ALL, 0);
if (!out->len) {
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
return -1;
}
return 0;
}
static int commit_tree_with_edited_message(struct repository *repo,
const char *action,
struct commit *original,
struct commit **out)
{
const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
const char *original_message, *original_body, *ptr;
struct commit_extra_header *original_extra_headers = NULL;
struct strbuf commit_message = STRBUF_INIT;
struct object_id rewritten_commit_oid;
struct object_id original_tree_oid;
struct object_id parent_tree_oid;
char *original_author = NULL;
struct commit *parent;
size_t len;
int ret;
original_tree_oid = repo_get_commit_tree(repo, original)->object.oid;
parent = original->parents ? original->parents->item : NULL;
if (parent) {
if (repo_parse_commit(repo, parent)) {
ret = error(_("unable to parse parent commit %s"),
oid_to_hex(&parent->object.oid));
goto out;
}
parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid;
} else {
oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree);
}
/* We retain authorship of the original commit. */
original_message = repo_logmsg_reencode(repo, original, NULL, NULL);
ptr = find_commit_header(original_message, "author", &len);
if (ptr)
original_author = xmemdupz(ptr, len);
find_commit_subject(original_message, &original_body);
ret = fill_commit_message(repo, &parent_tree_oid, &original_tree_oid,
original_body, action, &commit_message);
if (ret < 0)
goto out;
original_extra_headers = read_commit_extra_headers(original, exclude_gpgsig);
ret = commit_tree_extended(commit_message.buf, commit_message.len, &original_tree_oid,
original->parents, &rewritten_commit_oid, original_author,
NULL, NULL, original_extra_headers);
if (ret < 0)
goto out;
*out = lookup_commit_or_die(&rewritten_commit_oid, "rewritten commit");
out:
free_commit_extra_headers(original_extra_headers);
strbuf_release(&commit_message);
free(original_author);
return ret;
}
enum ref_action {
REF_ACTION_DEFAULT,
REF_ACTION_BRANCHES,
REF_ACTION_HEAD,
REF_ACTION_PRINT,
};
static int parse_ref_action(const struct option *opt, const char *value, int unset)
{
enum ref_action *action = opt->value;
BUG_ON_OPT_NEG_NOARG(unset, value);
if (!strcmp(value, "branches")) {
*action = REF_ACTION_BRANCHES;
} else if (!strcmp(value, "head")) {
*action = REF_ACTION_HEAD;
} else if (!strcmp(value, "print")) {
*action = REF_ACTION_PRINT;
} else {
return error(_("%s expects one of 'branches', 'head' or 'print'"),
opt->long_name);
}
return 0;
}
static int handle_reference_updates(enum ref_action action,
struct repository *repo,
struct commit *original,
struct commit *rewritten,
const char *reflog_msg)
{
const struct name_decoration *decoration;
struct replay_revisions_options opts = { 0 };
struct replay_result result = {
.final_oid = rewritten->object.oid,
};
struct ref_transaction *transaction = NULL;
struct strvec args = STRVEC_INIT;
struct strbuf err = STRBUF_INIT;
struct commit *head = NULL;
struct rev_info revs;
char hex[GIT_MAX_HEXSZ + 1];
int ret;
repo_init_revisions(repo, &revs, NULL);
strvec_push(&args, "ignored");
strvec_push(&args, "--reverse");
strvec_push(&args, "--topo-order");
strvec_push(&args, "--full-history");
/* We only want to see commits that are descendants of the old commit. */
strvec_pushf(&args, "--ancestry-path=%s",
oid_to_hex(&original->object.oid));
/*
* Ancestry path may also show ancestors of the old commit, but we
* don't want to see those, either.
*/
strvec_pushf(&args, "^%s", oid_to_hex(&original->object.oid));
/*
* When we're asked to update HEAD we need to verify that the commit
* that we want to rewrite is actually an ancestor of it and, if so,
* update it. Otherwise we'll update (or print) all descendant
* branches.
*/
if (action == REF_ACTION_HEAD) {
struct commit_list *from_list = NULL;
head = lookup_commit_reference_by_name("HEAD");
if (!head) {
ret = error(_("cannot look up HEAD"));
goto out;
}
commit_list_insert(original, &from_list);
ret = repo_is_descendant_of(repo, head, from_list);
free_commit_list(from_list);
if (ret < 0) {
ret = error(_("cannot determine descendance"));
goto out;
} else if (!ret) {
ret = error(_("rewritten commit must be an ancestor "
"of HEAD when using --ref-action=head"));
goto out;
}
strvec_push(&args, oid_to_hex(&head->object.oid));
} else {
strvec_push(&args, "--branches");
}
setup_revisions_from_strvec(&args, &revs, NULL);
if (revs.nr)
BUG("revisions were set up with invalid argument '%s'", args.v[0]);
opts.onto = oid_to_hex_r(hex, &rewritten->object.oid);
ret = replay_revisions(repo, &revs, &opts, &result);
if (ret)
goto out;
switch (action) {
case REF_ACTION_DEFAULT:
case REF_ACTION_BRANCHES:
transaction = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err);
if (!transaction) {
ret = error(_("failed to begin ref transaction: %s"), err.buf);
goto out;
}
for (size_t i = 0; i < result.updates_nr; i++) {
ret = ref_transaction_update(transaction,
result.updates[i].refname,
&result.updates[i].new_oid,
&result.updates[i].old_oid,
NULL, NULL, 0, reflog_msg, &err);
if (ret) {
ret = error(_("failed to update ref '%s': %s"),
result.updates[i].refname, err.buf);
goto out;
}
}
/*
* `replay_revisions()` only updates references that are
* ancestors of `rewritten`, so we need to manually
* handle updating references that point to `original`.
*/
for (decoration = get_name_decoration(&original->object);
decoration;
decoration = decoration->next)
{
if (decoration->type != DECORATION_REF_LOCAL)
continue;
ret = ref_transaction_update(transaction,
decoration->name,
&rewritten->object.oid,
&original->object.oid,
NULL, NULL, 0, reflog_msg, &err);
if (ret) {
ret = error(_("failed to update ref '%s': %s"),
decoration->name, err.buf);
goto out;
}
}
if (ref_transaction_commit(transaction, &err)) {
ret = error(_("failed to commit ref transaction: %s"), err.buf);
goto out;
}
break;
case REF_ACTION_HEAD:
ret = refs_update_ref(get_main_ref_store(repo), reflog_msg, "HEAD",
&result.final_oid, &head->object.oid, 0,
UPDATE_REFS_MSG_ON_ERR);
if (ret)
goto out;
break;
case REF_ACTION_PRINT:
for (size_t i = 0; i < result.updates_nr; i++)
printf("update %s %s %s\n",
result.updates[i].refname,
oid_to_hex(&result.updates[i].new_oid),
oid_to_hex(&result.updates[i].old_oid));
break;
default:
BUG("unsupported ref action %d", action);
}
ret = 0;
out:
ref_transaction_free(transaction);
replay_result_release(&result);
release_revisions(&revs);
strbuf_release(&err);
strvec_clear(&args);
return ret;
}
static int cmd_history_reword(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_REWORD_USAGE,
NULL,
};
enum ref_action action = REF_ACTION_DEFAULT;
struct option options[] = {
OPT_CALLBACK_F(0, "ref-action", &action, N_("<action>"),
N_("control ref update behavior (branches|head|print)"),
PARSE_OPT_NONEG, parse_ref_action),
OPT_END(),
};
struct strbuf reflog_msg = STRBUF_INIT;
struct commit *original, *rewritten;
int ret;
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (argc != 1) {
ret = error(_("command expects a single revision"));
goto out;
}
repo_config(repo, git_default_config, NULL);
original = lookup_commit_reference_by_name(argv[0]);
if (!original) {
ret = error(_("commit cannot be found: %s"), argv[0]);
goto out;
}
ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten);
if (ret < 0) {
ret = error(_("failed writing reworded commit"));
goto out;
}
strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
ret = handle_reference_updates(action, repo, original, rewritten,
reflog_msg.buf);
if (ret < 0) {
ret = error(_("failed replaying descendants"));
goto out;
}
ret = 0;
out:
strbuf_release(&reflog_msg);
return ret;
}
int cmd_history(int argc,
const char **argv,
const char *prefix,
struct repository *repo)
{
const char * const usage[] = {
GIT_HISTORY_REWORD_USAGE,
NULL,
};
parse_opt_subcommand_fn *fn = NULL;
struct option options[] = {
OPT_SUBCOMMAND("reword", &fn, cmd_history_reword),
OPT_END(),
};
argc = parse_options(argc, argv, prefix, options, usage, 0);
return fn(argc, argv, prefix, repo);
}

View File

@ -1638,7 +1638,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
hash, "idx", 1);
if (do_fsck_object && startup_info->have_repository)
packfile_store_load_pack(the_repository->objects->packfiles,
packfile_store_load_pack(the_repository->objects->sources->packfiles,
final_index_name, 0);
if (!from_stdin) {

View File

@ -10,7 +10,6 @@
#include "gettext.h"
#include "parse-options.h"
#include "string-list.h"
#include "tempfile.h"
#include "trailer.h"
#include "config.h"
@ -93,37 +92,6 @@ static int parse_opt_parse(const struct option *opt, const char *arg,
return 0;
}
static struct tempfile *trailers_tempfile;
static FILE *create_in_place_tempfile(const char *file)
{
struct stat st;
struct strbuf filename_template = STRBUF_INIT;
const char *tail;
FILE *outfile;
if (stat(file, &st))
die_errno(_("could not stat %s"), file);
if (!S_ISREG(st.st_mode))
die(_("file %s is not a regular file"), file);
if (!(st.st_mode & S_IWUSR))
die(_("file %s is not writable by user"), file);
/* Create temporary file in the same directory as the original */
tail = strrchr(file, '/');
if (tail)
strbuf_add(&filename_template, file, tail - file + 1);
strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX");
trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode);
strbuf_release(&filename_template);
outfile = fdopen_tempfile(trailers_tempfile, "w");
if (!outfile)
die_errno(_("could not open temporary file"));
return outfile;
}
static void read_input_file(struct strbuf *sb, const char *file)
{
if (file) {
@ -140,55 +108,20 @@ static void interpret_trailers(const struct process_trailer_options *opts,
struct list_head *new_trailer_head,
const char *file)
{
LIST_HEAD(head);
struct strbuf sb = STRBUF_INIT;
struct strbuf trailer_block_sb = STRBUF_INIT;
struct trailer_block *trailer_block;
FILE *outfile = stdout;
trailer_config_init();
struct strbuf out = STRBUF_INIT;
read_input_file(&sb, file);
if (opts->in_place)
outfile = create_in_place_tempfile(file);
trailer_block = parse_trailers(opts, sb.buf, &head);
/* Print the lines before the trailer block */
if (!opts->only_trailers)
fwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile);
if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))
fprintf(outfile, "\n");
if (!opts->only_input) {
LIST_HEAD(config_head);
LIST_HEAD(arg_head);
parse_trailers_from_config(&config_head);
parse_trailers_from_command_line_args(&arg_head, new_trailer_head);
list_splice(&config_head, &arg_head);
process_trailers_lists(&head, &arg_head);
}
/* Print trailer block. */
format_trailers(opts, &head, &trailer_block_sb);
free_trailers(&head);
fwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile);
strbuf_release(&trailer_block_sb);
/* Print the lines after the trailer block as is. */
if (!opts->only_trailers)
fwrite(sb.buf + trailer_block_end(trailer_block), 1,
sb.len - trailer_block_end(trailer_block), outfile);
trailer_block_release(trailer_block);
process_trailers(opts, new_trailer_head, &sb, &out);
if (opts->in_place)
if (rename_tempfile(&trailers_tempfile, file))
die_errno(_("could not rename temporary file to %s"), file);
write_file_buf(file, out.buf, out.len);
else
strbuf_write(&out, stdout);
strbuf_release(&sb);
strbuf_release(&out);
}
int cmd_interpret_trailers(int argc,
@ -232,6 +165,8 @@ int cmd_interpret_trailers(int argc,
git_interpret_trailers_usage,
options);
trailer_config_init();
if (argc) {
int i;
for (i = 0; i < argc; i++)

View File

@ -23,6 +23,11 @@
#define PARENT1 (1u<<16) /* used instead of SEEN */
#define PARENT2 (1u<<17) /* used instead of BOTTOM, BOUNDARY */
#define LAST_MODIFIED_INIT { \
.line_termination = '\n', \
.max_depth = -1, \
}
struct last_modified_entry {
struct hashmap_entry hashent;
struct object_id oid;
@ -55,6 +60,8 @@ struct last_modified {
struct rev_info rev;
bool recursive;
bool show_trees;
int line_termination;
int max_depth;
const char **all_paths;
size_t all_paths_nr;
@ -165,7 +172,7 @@ static void last_modified_emit(struct last_modified *lm,
putchar('^');
printf("%s\t", oid_to_hex(&commit->object.oid));
if (lm->rev.diffopt.line_termination)
if (lm->line_termination)
write_name_quoted(path, stdout, '\n');
else
printf("%s%c", path, '\0');
@ -482,6 +489,12 @@ static int last_modified_init(struct last_modified *lm, struct repository *r,
lm->rev.diffopt.flags.recursive = lm->recursive;
lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees;
if (lm->max_depth >= 0) {
lm->rev.diffopt.flags.recursive = 1;
lm->rev.diffopt.max_depth = lm->max_depth;
lm->rev.diffopt.max_depth_valid = 1;
}
argc = setup_revisions(argc, argv, &lm->rev, NULL);
if (argc > 1) {
error(_("unknown last-modified argument: %s"), argv[1]);
@ -507,10 +520,10 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix,
struct repository *repo)
{
int ret;
struct last_modified lm = { 0 };
struct last_modified lm = LAST_MODIFIED_INIT;
const char * const last_modified_usage[] = {
N_("git last-modified [--recursive] [--show-trees] "
N_("git last-modified [--recursive] [--show-trees] [--max-depth=<depth>] [-z]\n"
" [<revision-range>] [[--] <path>...]"),
NULL
};
@ -520,6 +533,10 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix,
N_("recurse into subtrees")),
OPT_BOOL('t', "show-trees", &lm.show_trees,
N_("show tree entries when recursing into subtrees")),
OPT_INTEGER_F(0, "max-depth", &lm.max_depth,
N_("maximum tree depth to recurse"), PARSE_OPT_NONEG),
OPT_SET_INT('z', NULL, &lm.line_termination,
N_("lines are separated with NUL character"), '\0'),
OPT_END()
};

View File

@ -1896,11 +1896,11 @@ int cmd_format_patch(int argc,
{
struct format_config cfg;
struct commit *commit;
struct commit **list = NULL;
struct commit_stack list = COMMIT_STACK_INIT;
struct rev_info rev;
char *to_free = NULL;
struct setup_revision_opt s_r_opt;
size_t nr = 0, total, i;
size_t total, i;
int use_stdout = 0;
int start_number = -1;
int just_numbers = 0;
@ -2283,14 +2283,12 @@ int cmd_format_patch(int argc,
if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids))
continue;
nr++;
REALLOC_ARRAY(list, nr);
list[nr - 1] = commit;
commit_stack_push(&list, commit);
}
if (nr == 0)
if (!list.nr)
/* nothing to do */
goto done;
total = nr;
total = list.nr;
if (cover_letter == -1) {
if (cfg.config_cover_letter == COVER_AUTO)
cover_letter = (total > 1);
@ -2308,7 +2306,7 @@ int cmd_format_patch(int argc,
if (!cover_letter && total != 1)
die(_("--interdiff requires --cover-letter or single patch"));
rev.idiff_oid1 = &idiff_prev.oid[idiff_prev.nr - 1];
rev.idiff_oid2 = get_commit_tree_oid(list[0]);
rev.idiff_oid2 = get_commit_tree_oid(list.items[0]);
rev.idiff_title = diff_title(&idiff_title, reroll_count,
_("Interdiff:"),
_("Interdiff against v%d:"));
@ -2324,7 +2322,7 @@ int cmd_format_patch(int argc,
die(_("--range-diff requires --cover-letter or single patch"));
infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev,
origin, list[0]);
origin, list.items[0]);
rev.rdiff1 = rdiff1.buf;
rev.rdiff2 = rdiff2.buf;
rev.creation_factor = creation_factor;
@ -2360,11 +2358,11 @@ int cmd_format_patch(int argc,
}
memset(&bases, 0, sizeof(bases));
base = get_base_commit(&cfg, list, nr);
base = get_base_commit(&cfg, list.items, list.nr);
if (base) {
reset_revision_walk();
clear_object_flags(the_repository, UNINTERESTING);
prepare_bases(&bases, base, list, nr);
prepare_bases(&bases, base, list.items, list.nr);
}
if (in_reply_to || cfg.thread || cover_letter) {
@ -2381,7 +2379,8 @@ int cmd_format_patch(int argc,
if (cfg.thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, !!output_directory,
origin, nr, list, description_file, branch_name, quiet, &cfg);
origin, list.nr, list.items,
description_file, branch_name, quiet, &cfg);
print_bases(&bases, rev.diffopt.file);
print_signature(signature, rev.diffopt.file);
total++;
@ -2395,12 +2394,12 @@ int cmd_format_patch(int argc,
if (show_progress)
progress = start_delayed_progress(the_repository,
_("Generating patches"), total);
for (i = 0; i < nr; i++) {
size_t idx = nr - i - 1;
while (list.nr) {
size_t idx = list.nr - 1;
int shown;
display_progress(progress, total - idx);
commit = list[idx];
commit = commit_stack_pop(&list);
rev.nr = total - idx + (start_number - 1);
/* Make the second and subsequent mails replies to the first */
@ -2469,7 +2468,7 @@ int cmd_format_patch(int argc,
}
}
stop_progress(&progress);
free(list);
commit_stack_clear(&list);
if (ignore_if_in_upstream)
free_patch_ids(&ids);

View File

@ -13,9 +13,14 @@
#include "repository.h"
#define BUILTIN_MIDX_WRITE_USAGE \
N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]" \
N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]\n" \
" [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs]\n" \
" [--refs-snapshot=<path>]")
#define BUILTIN_MIDX_COMPACT_USAGE \
N_("git multi-pack-index [<options>] compact [--[no-]incremental]\n" \
" [--[no-]bitmap] <from> <to>")
#define BUILTIN_MIDX_VERIFY_USAGE \
N_("git multi-pack-index [<options>] verify")
@ -29,6 +34,10 @@ static char const * const builtin_multi_pack_index_write_usage[] = {
BUILTIN_MIDX_WRITE_USAGE,
NULL
};
static char const * const builtin_multi_pack_index_compact_usage[] = {
BUILTIN_MIDX_COMPACT_USAGE,
NULL
};
static char const * const builtin_multi_pack_index_verify_usage[] = {
BUILTIN_MIDX_VERIFY_USAGE,
NULL
@ -43,6 +52,7 @@ static char const * const builtin_multi_pack_index_repack_usage[] = {
};
static char const * const builtin_multi_pack_index_usage[] = {
BUILTIN_MIDX_WRITE_USAGE,
BUILTIN_MIDX_COMPACT_USAGE,
BUILTIN_MIDX_VERIFY_USAGE,
BUILTIN_MIDX_EXPIRE_USAGE,
BUILTIN_MIDX_REPACK_USAGE,
@ -84,6 +94,8 @@ static struct option common_opts[] = {
N_("directory"),
N_("object directory containing set of packfile and pack-index pairs"),
parse_object_dir),
OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"),
MIDX_PROGRESS),
OPT_END(),
};
@ -138,8 +150,6 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
N_("pack for reuse when computing a multi-pack bitmap")),
OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX),
OPT_BIT(0, "progress", &opts.flags,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_BIT(0, "incremental", &opts.flags,
N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL),
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
@ -194,14 +204,71 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,
return ret;
}
static int cmd_multi_pack_index_compact(int argc, const char **argv,
const char *prefix,
struct repository *repo)
{
struct multi_pack_index *m, *cur;
struct multi_pack_index *from_midx = NULL;
struct multi_pack_index *to_midx = NULL;
struct odb_source *source;
int ret;
struct option *options;
static struct option builtin_multi_pack_index_compact_options[] = {
OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX),
OPT_BIT(0, "incremental", &opts.flags,
N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL),
OPT_END(),
};
repo_config(repo, git_multi_pack_index_write_config, NULL);
options = add_common_options(builtin_multi_pack_index_compact_options);
trace2_cmd_mode(argv[0]);
if (isatty(2))
opts.flags |= MIDX_PROGRESS;
argc = parse_options(argc, argv, prefix,
options, builtin_multi_pack_index_compact_usage,
0);
if (argc != 2)
usage_with_options(builtin_multi_pack_index_compact_usage,
options);
source = handle_object_dir_option(the_repository);
FREE_AND_NULL(options);
m = get_multi_pack_index(source);
for (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) {
const char *midx_csum = get_midx_checksum(cur);
if (!from_midx && !strcmp(midx_csum, argv[0]))
from_midx = cur;
if (!to_midx && !strcmp(midx_csum, argv[1]))
to_midx = cur;
}
if (!from_midx)
die(_("could not find MIDX 'from': %s"), argv[0]);
if (!to_midx)
die(_("could not find MIDX 'to': %s"), argv[1]);
ret = write_midx_file_compact(source, from_midx, to_midx, opts.flags);
return ret;
}
static int cmd_multi_pack_index_verify(int argc, const char **argv,
const char *prefix,
struct repository *repo UNUSED)
{
struct option *options;
static struct option builtin_multi_pack_index_verify_options[] = {
OPT_BIT(0, "progress", &opts.flags,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
struct odb_source *source;
@ -231,8 +298,6 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
{
struct option *options;
static struct option builtin_multi_pack_index_expire_options[] = {
OPT_BIT(0, "progress", &opts.flags,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
struct odb_source *source;
@ -264,8 +329,6 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv,
static struct option builtin_multi_pack_index_repack_options[] = {
OPT_UNSIGNED(0, "batch-size", &opts.batch_size,
N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")),
OPT_BIT(0, "progress", &opts.flags,
N_("force progress reporting"), MIDX_PROGRESS),
OPT_END(),
};
struct odb_source *source;
@ -300,6 +363,7 @@ int cmd_multi_pack_index(int argc,
struct option builtin_multi_pack_index_options[] = {
OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack),
OPT_SUBCOMMAND("write", &fn, cmd_multi_pack_index_write),
OPT_SUBCOMMAND("compact", &fn, cmd_multi_pack_index_compact),
OPT_SUBCOMMAND("verify", &fn, cmd_multi_pack_index_verify),
OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire),
OPT_END(),

View File

@ -180,8 +180,7 @@ static void name_rev(struct commit *start_commit,
{
struct prio_queue queue;
struct commit *commit;
struct commit **parents_to_queue = NULL;
size_t parents_to_queue_nr, parents_to_queue_alloc = 0;
struct commit_stack parents_to_queue = COMMIT_STACK_INIT;
struct rev_name *start_name;
repo_parse_commit(the_repository, start_commit);
@ -206,7 +205,7 @@ static void name_rev(struct commit *start_commit,
struct commit_list *parents;
int parent_number = 1;
parents_to_queue_nr = 0;
parents_to_queue.nr = 0;
for (parents = commit->parents;
parents;
@ -238,22 +237,18 @@ static void name_rev(struct commit *start_commit,
string_pool);
else
parent_name->tip_name = name->tip_name;
ALLOC_GROW(parents_to_queue,
parents_to_queue_nr + 1,
parents_to_queue_alloc);
parents_to_queue[parents_to_queue_nr] = parent;
parents_to_queue_nr++;
commit_stack_push(&parents_to_queue, parent);
}
}
/* The first parent must come out first from the prio_queue */
while (parents_to_queue_nr)
while (parents_to_queue.nr)
prio_queue_put(&queue,
parents_to_queue[--parents_to_queue_nr]);
commit_stack_pop(&parents_to_queue));
}
clear_prio_queue(&queue);
free(parents_to_queue);
commit_stack_clear(&parents_to_queue);
}
static int subpath_matches(const char *path, const char *filter)

View File

@ -1529,9 +1529,12 @@ static int want_cruft_object_mtime(struct repository *r,
const struct object_id *oid,
unsigned flags, uint32_t mtime)
{
struct packed_git **cache;
struct odb_source *source;
for (cache = kept_pack_cache(r, flags); *cache; cache++) {
for (source = r->objects->sources; source; source = source->next) {
struct packed_git **cache = packfile_store_get_kept_pack_cache(source->packfiles, flags);
for (; *cache; cache++) {
struct packed_git *p = *cache;
off_t ofs;
uint32_t candidate_mtime;
@ -1573,6 +1576,7 @@ static int want_cruft_object_mtime(struct repository *r,
if (mtime <= candidate_mtime)
return 0;
}
}
return -1;
}
@ -1624,9 +1628,9 @@ static int want_found_object(const struct object_id *oid, int exclude,
*/
unsigned flags = 0;
if (ignore_packed_keep_on_disk)
flags |= ON_DISK_KEEP_PACKS;
flags |= KEPT_PACK_ON_DISK;
if (ignore_packed_keep_in_core)
flags |= IN_CORE_KEEP_PACKS;
flags |= KEPT_PACK_IN_CORE;
/*
* If the object is in a pack that we want to ignore, *and* we
@ -1749,14 +1753,16 @@ static int want_object_in_pack_mtime(const struct object_id *oid,
}
}
for (e = the_repository->objects->packfiles->packs.head; e; e = e->next) {
for (source = the_repository->objects->sources; source; source = source->next) {
for (e = source->packfiles->packs.head; e; e = e->next) {
struct packed_git *p = e->pack;
want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
if (!exclude && want > 0)
packfile_list_prepend(&the_repository->objects->packfiles->packs, p);
packfile_list_prepend(&source->packfiles->packs, p);
if (want != -1)
return want;
}
}
if (uri_protocols.nr) {
struct configured_exclusion *ex =
@ -2411,7 +2417,7 @@ static void drop_reused_delta(struct object_entry *entry)
oi.sizep = &size;
oi.typep = &type;
if (packed_object_info(the_repository, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {
if (packed_object_info(IN_PACK(entry), entry->in_pack_offset, &oi) < 0) {
/*
* We failed to get the info from this pack for some reason;
* fall back to odb_read_object_info, which may find another copy.
@ -3748,7 +3754,7 @@ static int add_object_entry_from_pack(const struct object_id *oid,
struct object_info oi = OBJECT_INFO_INIT;
oi.typep = &type;
if (packed_object_info(the_repository, p, ofs, &oi) < 0) {
if (packed_object_info(p, ofs, &oi) < 0) {
die(_("could not get type of object %s in pack %s"),
oid_to_hex(oid), p->pack_name);
} else if (type == OBJ_COMMIT) {
@ -3931,7 +3937,7 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked)
* an optimization during delta selection.
*/
revs.no_kept_objects = 1;
revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS;
revs.keep_pack_cache_flags |= KEPT_PACK_IN_CORE;
revs.blob_objects = 1;
revs.tree_objects = 1;
revs.tag_objects = 1;
@ -4030,7 +4036,7 @@ static void show_cruft_commit(struct commit *commit, void *data)
static int cruft_include_check_obj(struct object *obj, void *data UNUSED)
{
return !has_object_kept_pack(to_pack.repo, &obj->oid, IN_CORE_KEEP_PACKS);
return !has_object_kept_pack(to_pack.repo, &obj->oid, KEPT_PACK_IN_CORE);
}
static int cruft_include_check(struct commit *commit, void *data)

View File

@ -228,9 +228,9 @@ int cmd_patch_id(int argc,
int opts = 0;
struct option builtin_patch_id_options[] = {
OPT_CMDMODE(0, "unstable", &opts,
N_("use the unstable patch-id algorithm"), 1),
N_("use the unstable patch ID algorithm"), 1),
OPT_CMDMODE(0, "stable", &opts,
N_("use the stable patch-id algorithm"), 2),
N_("use the stable patch ID algorithm"), 2),
OPT_CMDMODE(0, "verbatim", &opts,
N_("don't strip whitespace from the patch"), 3),
OPT_END()

View File

@ -36,6 +36,7 @@
#include "reset.h"
#include "trace2.h"
#include "hook.h"
#include "trailer.h"
static char const * const builtin_rebase_usage[] = {
N_("git rebase [-i] [options] [--exec <cmd>] "
@ -113,6 +114,7 @@ struct rebase_options {
enum action action;
char *reflog_action;
int signoff;
struct strvec trailer_args;
int allow_rerere_autoupdate;
int keep_empty;
int autosquash;
@ -143,6 +145,7 @@ struct rebase_options {
.flags = REBASE_NO_QUIET, \
.git_am_opts = STRVEC_INIT, \
.exec = STRING_LIST_INIT_NODUP, \
.trailer_args = STRVEC_INIT, \
.git_format_patch_opt = STRBUF_INIT, \
.fork_point = -1, \
.reapply_cherry_picks = -1, \
@ -166,6 +169,7 @@ static void rebase_options_release(struct rebase_options *opts)
free(opts->strategy);
string_list_clear(&opts->strategy_opts, 0);
strbuf_release(&opts->git_format_patch_opt);
strvec_clear(&opts->trailer_args);
}
static struct replay_opts get_replay_opts(const struct rebase_options *opts)
@ -177,6 +181,10 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
sequencer_init_config(&replay);
replay.signoff = opts->signoff;
for (size_t i = 0; i < opts->trailer_args.nr; i++)
strvec_push(&replay.trailer_args, opts->trailer_args.v[i]);
replay.allow_ff = !(opts->flags & REBASE_FORCE);
if (opts->allow_rerere_autoupdate)
replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
@ -500,6 +508,23 @@ static int read_basic_state(struct rebase_options *opts)
opts->gpg_sign_opt = xstrdup(buf.buf);
}
strbuf_reset(&buf);
if (strbuf_read_file(&buf, state_dir_path("trailer", opts), 0) >= 0) {
const char *p = buf.buf, *end = buf.buf + buf.len;
while (p < end) {
char *nl = memchr(p, '\n', end - p);
if (!nl)
die("nl shouldn't be NULL");
*nl = '\0';
if (*p)
strvec_push(&opts->trailer_args, p);
p = nl + 1;
}
}
strbuf_release(&buf);
return 0;
@ -528,6 +553,21 @@ static int rebase_write_basic_state(struct rebase_options *opts)
if (opts->signoff)
write_file(state_dir_path("signoff", opts), "--signoff");
/*
* save opts->trailer_args into state_dir/trailer
*/
if (opts->trailer_args.nr) {
struct strbuf buf = STRBUF_INIT;
for (size_t i = 0; i < opts->trailer_args.nr; i++) {
strbuf_addstr(&buf, opts->trailer_args.v[i]);
strbuf_addch(&buf, '\n');
}
write_file(state_dir_path("trailer", opts),
"%s", buf.buf);
strbuf_release(&buf);
}
return 0;
}
@ -1132,6 +1172,8 @@ int cmd_rebase(int argc,
.flags = PARSE_OPT_NOARG,
.defval = REBASE_DIFFSTAT,
},
OPT_STRVEC(0, "trailer", &options.trailer_args, N_("trailer"),
N_("add custom trailer(s)")),
OPT_BOOL(0, "signoff", &options.signoff,
N_("add a Signed-off-by trailer to each commit")),
OPT_BOOL(0, "committer-date-is-author-date",
@ -1285,6 +1327,11 @@ int cmd_rebase(int argc,
builtin_rebase_options,
builtin_rebase_usage, 0);
if (options.trailer_args.nr) {
validate_trailer_args_after_config(&options.trailer_args);
options.flags |= REBASE_FORCE;
}
if (preserve_merges_selected)
die(_("--preserve-merges was replaced by --rebase-merges\n"
"Note: Your `pull.rebase` configuration may also be set to 'preserve',\n"
@ -1542,6 +1589,9 @@ int cmd_rebase(int argc,
if (options.root && !options.onto_name)
imply_merge(&options, "--root without --onto");
if (options.trailer_args.nr)
imply_merge(&options, "--trailer");
if (isatty(2) && options.flags & REBASE_NO_QUIET)
strbuf_addstr(&options.git_format_patch_opt, " --progress");

View File

@ -1645,7 +1645,6 @@ static void check_aliased_update_internal(struct command *cmd,
cmd->error_string = "broken symref";
return;
}
dst_name = strip_namespace(dst_name);
if (!(item = string_list_lookup(list, dst_name)))
return;
@ -1690,10 +1689,13 @@ static void check_aliased_updates(struct command *commands)
{
struct command *cmd;
struct string_list ref_list = STRING_LIST_INIT_NODUP;
struct strbuf ref_name = STRBUF_INIT;
for (cmd = commands; cmd; cmd = cmd->next) {
struct string_list_item *item =
string_list_append(&ref_list, cmd->ref_name);
struct string_list_item *item;
strbuf_reset(&ref_name);
strbuf_addf(&ref_name, "%s%s", get_git_namespace(), cmd->ref_name);
item = string_list_append(&ref_list, ref_name.buf);
item->util = (void *)cmd;
}
string_list_sort(&ref_list);
@ -1703,6 +1705,7 @@ static void check_aliased_updates(struct command *commands)
check_aliased_update(cmd, &ref_list);
}
strbuf_release(&ref_name);
string_list_clear(&ref_list, 0);
}

View File

@ -2,294 +2,22 @@
* "git replay" builtin command
*/
#define USE_THE_REPOSITORY_VARIABLE
#define DISABLE_SIGN_COMPARE_WARNINGS
#include "git-compat-util.h"
#include "builtin.h"
#include "config.h"
#include "environment.h"
#include "hex.h"
#include "lockfile.h"
#include "merge-ort.h"
#include "object-name.h"
#include "parse-options.h"
#include "refs.h"
#include "replay.h"
#include "revision.h"
#include "strmap.h"
#include <oidset.h>
#include <tree.h>
enum ref_action_mode {
REF_ACTION_UPDATE,
REF_ACTION_PRINT,
};
static const char *short_commit_name(struct repository *repo,
struct commit *commit)
{
return repo_find_unique_abbrev(repo, &commit->object.oid,
DEFAULT_ABBREV);
}
static struct commit *peel_committish(struct repository *repo, const char *name)
{
struct object *obj;
struct object_id oid;
if (repo_get_oid(repo, name, &oid))
return NULL;
obj = parse_object(repo, &oid);
return (struct commit *)repo_peel_to_type(repo, name, 0, obj,
OBJ_COMMIT);
}
static char *get_author(const char *message)
{
size_t len;
const char *a;
a = find_commit_header(message, "author", &len);
if (a)
return xmemdupz(a, len);
return NULL;
}
static struct commit *create_commit(struct repository *repo,
struct tree *tree,
struct commit *based_on,
struct commit *parent)
{
struct object_id ret;
struct object *obj = NULL;
struct commit_list *parents = NULL;
char *author;
char *sign_commit = NULL; /* FIXME: cli users might want to sign again */
struct commit_extra_header *extra = NULL;
struct strbuf msg = STRBUF_INIT;
const char *out_enc = get_commit_output_encoding();
const char *message = repo_logmsg_reencode(repo, based_on,
NULL, out_enc);
const char *orig_message = NULL;
const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
commit_list_insert(parent, &parents);
extra = read_commit_extra_headers(based_on, exclude_gpgsig);
find_commit_subject(message, &orig_message);
strbuf_addstr(&msg, orig_message);
author = get_author(message);
reset_ident_date();
if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,
&ret, author, NULL, sign_commit, extra)) {
error(_("failed to write commit object"));
goto out;
}
obj = parse_object(repo, &ret);
out:
repo_unuse_commit_buffer(the_repository, based_on, message);
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&msg);
free(author);
return (struct commit *)obj;
}
struct ref_info {
struct commit *onto;
struct strset positive_refs;
struct strset negative_refs;
int positive_refexprs;
int negative_refexprs;
};
static void get_ref_information(struct repository *repo,
struct rev_cmdline_info *cmd_info,
struct ref_info *ref_info)
{
int i;
ref_info->onto = NULL;
strset_init(&ref_info->positive_refs);
strset_init(&ref_info->negative_refs);
ref_info->positive_refexprs = 0;
ref_info->negative_refexprs = 0;
/*
* When the user specifies e.g.
* git replay origin/main..mybranch
* git replay ^origin/next mybranch1 mybranch2
* we want to be able to determine where to replay the commits. In
* these examples, the branches are probably based on an old version
* of either origin/main or origin/next, so we want to replay on the
* newest version of that branch. In contrast we would want to error
* out if they ran
* git replay ^origin/master ^origin/next mybranch
* git replay mybranch~2..mybranch
* the first of those because there's no unique base to choose, and
* the second because they'd likely just be replaying commits on top
* of the same commit and not making any difference.
*/
for (i = 0; i < cmd_info->nr; i++) {
struct rev_cmdline_entry *e = cmd_info->rev + i;
struct object_id oid;
const char *refexpr = e->name;
char *fullname = NULL;
int can_uniquely_dwim = 1;
if (*refexpr == '^')
refexpr++;
if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1)
can_uniquely_dwim = 0;
if (e->flags & BOTTOM) {
if (can_uniquely_dwim)
strset_add(&ref_info->negative_refs, fullname);
if (!ref_info->negative_refexprs)
ref_info->onto = lookup_commit_reference_gently(repo,
&e->item->oid, 1);
ref_info->negative_refexprs++;
} else {
if (can_uniquely_dwim)
strset_add(&ref_info->positive_refs, fullname);
ref_info->positive_refexprs++;
}
free(fullname);
}
}
static void determine_replay_mode(struct repository *repo,
struct rev_cmdline_info *cmd_info,
const char *onto_name,
char **advance_name,
struct commit **onto,
struct strset **update_refs)
{
struct ref_info rinfo;
get_ref_information(repo, cmd_info, &rinfo);
if (!rinfo.positive_refexprs)
die(_("need some commits to replay"));
die_for_incompatible_opt2(!!onto_name, "--onto",
!!*advance_name, "--advance");
if (onto_name) {
*onto = peel_committish(repo, onto_name);
if (rinfo.positive_refexprs <
strset_get_size(&rinfo.positive_refs))
die(_("all positive revisions given must be references"));
} else if (*advance_name) {
struct object_id oid;
char *fullname = NULL;
*onto = peel_committish(repo, *advance_name);
if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name),
&oid, &fullname, 0) == 1) {
free(*advance_name);
*advance_name = fullname;
} else {
die(_("argument to --advance must be a reference"));
}
if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple sources because ordering would be ill-defined"));
} else {
int positive_refs_complete = (
rinfo.positive_refexprs ==
strset_get_size(&rinfo.positive_refs));
int negative_refs_complete = (
rinfo.negative_refexprs ==
strset_get_size(&rinfo.negative_refs));
/*
* We need either positive_refs_complete or
* negative_refs_complete, but not both.
*/
if (rinfo.negative_refexprs > 0 &&
positive_refs_complete == negative_refs_complete)
die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
if (negative_refs_complete) {
struct hashmap_iter iter;
struct strmap_entry *entry;
const char *last_key = NULL;
if (rinfo.negative_refexprs == 0)
die(_("all positive revisions given must be references"));
else if (rinfo.negative_refexprs > 1)
die(_("cannot implicitly determine whether this is an --advance or --onto operation"));
else if (rinfo.positive_refexprs > 1)
die(_("cannot advance target with multiple source branches because ordering would be ill-defined"));
/* Only one entry, but we have to loop to get it */
strset_for_each_entry(&rinfo.negative_refs,
&iter, entry) {
last_key = entry->key;
}
free(*advance_name);
*advance_name = xstrdup_or_null(last_key);
} else { /* positive_refs_complete */
if (rinfo.negative_refexprs > 1)
die(_("cannot implicitly determine correct base for --onto"));
if (rinfo.negative_refexprs == 1)
*onto = rinfo.onto;
}
}
if (!*advance_name) {
*update_refs = xcalloc(1, sizeof(**update_refs));
**update_refs = rinfo.positive_refs;
memset(&rinfo.positive_refs, 0, sizeof(**update_refs));
}
strset_clear(&rinfo.negative_refs);
strset_clear(&rinfo.positive_refs);
}
static struct commit *mapped_commit(kh_oid_map_t *replayed_commits,
struct commit *commit,
struct commit *fallback)
{
khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid);
if (pos == kh_end(replayed_commits))
return fallback;
return kh_value(replayed_commits, pos);
}
static struct commit *pick_regular_commit(struct repository *repo,
struct commit *pickme,
kh_oid_map_t *replayed_commits,
struct commit *onto,
struct merge_options *merge_opt,
struct merge_result *result)
{
struct commit *base, *replayed_base;
struct tree *pickme_tree, *base_tree;
base = pickme->parents->item;
replayed_base = mapped_commit(replayed_commits, base, onto);
result->tree = repo_get_commit_tree(repo, replayed_base);
pickme_tree = repo_get_commit_tree(repo, pickme);
base_tree = repo_get_commit_tree(repo, base);
merge_opt->branch1 = short_commit_name(repo, replayed_base);
merge_opt->branch2 = short_commit_name(repo, pickme);
merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2);
merge_incore_nonrecursive(merge_opt,
base_tree,
result->tree,
pickme_tree,
result);
free((char*)merge_opt->ancestor);
merge_opt->ancestor = NULL;
if (!result->clean)
return NULL;
return create_commit(repo, result->tree, pickme, replayed_base);
}
static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source)
{
if (!ref_action || !strcmp(ref_action, "update"))
@ -343,21 +71,11 @@ int cmd_replay(int argc,
const char *prefix,
struct repository *repo)
{
const char *advance_name_opt = NULL;
char *advance_name = NULL;
struct commit *onto = NULL;
const char *onto_name = NULL;
int contained = 0;
struct replay_revisions_options opts = { 0 };
struct replay_result result = { 0 };
const char *ref_action = NULL;
enum ref_action_mode ref_mode;
struct rev_info revs;
struct commit *last_commit = NULL;
struct commit *commit;
struct merge_options merge_opt;
struct merge_result result;
struct strset *update_refs = NULL;
kh_oid_map_t *replayed_commits;
struct ref_transaction *transaction = NULL;
struct strbuf transaction_err = STRBUF_INIT;
struct strbuf reflog_msg = STRBUF_INIT;
@ -370,13 +88,13 @@ int cmd_replay(int argc,
NULL
};
struct option replay_options[] = {
OPT_STRING(0, "advance", &advance_name_opt,
OPT_STRING(0, "advance", &opts.advance,
N_("branch"),
N_("make replay advance given branch")),
OPT_STRING(0, "onto", &onto_name,
OPT_STRING(0, "onto", &opts.onto,
N_("revision"),
N_("replay onto given commit")),
OPT_BOOL(0, "contained", &contained,
OPT_BOOL(0, "contained", &opts.contained,
N_("update all branches that point at commits in <revision-range>")),
OPT_STRING(0, "ref-action", &ref_action,
N_("mode"),
@ -387,19 +105,17 @@ int cmd_replay(int argc,
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
if (!onto_name && !advance_name_opt) {
if (!opts.onto && !opts.advance) {
error(_("option --onto or --advance is mandatory"));
usage_with_options(replay_usage, replay_options);
}
die_for_incompatible_opt2(!!advance_name_opt, "--advance",
contained, "--contained");
die_for_incompatible_opt2(!!opts.advance, "--advance",
opts.contained, "--contained");
/* Parse ref action mode from command line or config */
ref_mode = get_ref_action_mode(repo, ref_action);
advance_name = xstrdup_or_null(advance_name_opt);
repo_init_revisions(repo, &revs, prefix);
/*
@ -451,18 +167,19 @@ int cmd_replay(int argc,
revs.simplify_history = 0;
}
determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
&onto, &update_refs);
if (!onto) /* FIXME: Should handle replaying down to root commit */
die("Replaying down to root commit is not supported yet!");
ret = replay_revisions(repo, &revs, &opts, &result);
if (ret)
goto cleanup;
/* Build reflog message */
if (advance_name_opt)
strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt);
else
strbuf_addf(&reflog_msg, "replay --onto %s",
oid_to_hex(&onto->object.oid));
if (opts.advance) {
strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
} else {
struct object_id oid;
if (repo_get_oid_committish(repo, opts.onto, &oid))
BUG("--onto commit should have been resolved beforehand already");
strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
}
/* Initialize ref transaction if using update mode */
if (ref_mode == REF_ACTION_UPDATE) {
@ -475,78 +192,19 @@ int cmd_replay(int argc,
}
}
if (prepare_revision_walk(&revs) < 0) {
ret = error(_("error preparing revisions"));
goto cleanup;
}
init_basic_merge_options(&merge_opt, repo);
memset(&result, 0, sizeof(result));
merge_opt.show_rename_progress = 0;
last_commit = onto;
replayed_commits = kh_init_oid_map();
while ((commit = get_revision(&revs))) {
const struct name_decoration *decoration;
khint_t pos;
int hr;
if (!commit->parents)
die(_("replaying down to root commit is not supported yet!"));
if (commit->parents->next)
die(_("replaying merge commits is not supported yet!"));
last_commit = pick_regular_commit(repo, commit, replayed_commits,
onto, &merge_opt, &result);
if (!last_commit)
break;
/* Record commit -> last_commit mapping */
pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
if (hr == 0)
BUG("Duplicate rewritten commit: %s\n",
oid_to_hex(&commit->object.oid));
kh_value(replayed_commits, pos) = last_commit;
/* Update any necessary branches */
if (advance_name)
continue;
decoration = get_name_decoration(&commit->object);
if (!decoration)
continue;
while (decoration) {
if (decoration->type == DECORATION_REF_LOCAL &&
(contained || strset_contains(update_refs,
decoration->name))) {
if (handle_ref_update(ref_mode, transaction,
decoration->name,
&last_commit->object.oid,
&commit->object.oid,
reflog_msg.buf,
&transaction_err) < 0) {
for (size_t i = 0; i < result.updates_nr; i++) {
ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
&result.updates[i].new_oid, &result.updates[i].old_oid,
reflog_msg.buf, &transaction_err);
if (ret) {
ret = error(_("failed to update ref '%s': %s"),
decoration->name, transaction_err.buf);
goto cleanup;
}
}
decoration = decoration->next;
}
}
/* In --advance mode, advance the target ref */
if (result.clean == 1 && advance_name) {
if (handle_ref_update(ref_mode, transaction, advance_name,
&last_commit->object.oid,
&onto->object.oid,
reflog_msg.buf,
&transaction_err) < 0) {
ret = error(_("failed to update ref '%s': %s"),
advance_name, transaction_err.buf);
result.updates[i].refname, transaction_err.buf);
goto cleanup;
}
}
/* Commit the ref transaction if we have one */
if (transaction && result.clean == 1) {
if (transaction) {
if (ref_transaction_commit(transaction, &transaction_err)) {
ret = error(_("failed to commit ref transaction: %s"),
transaction_err.buf);
@ -554,24 +212,19 @@ int cmd_replay(int argc,
}
}
merge_finalize(&merge_opt, &result);
kh_destroy_oid_map(replayed_commits);
if (update_refs) {
strset_clear(update_refs);
free(update_refs);
}
ret = result.clean;
cleanup:
if (transaction)
ref_transaction_free(transaction);
replay_result_release(&result);
strbuf_release(&transaction_err);
strbuf_release(&reflog_msg);
release_revisions(&revs);
free(advance_name);
/* Return */
if (ret < 0)
exit(128);
return ret ? 0 : 1;
if (ret) {
if (result.merge_conflict)
return 1;
return 128;
}
return 0;
}

View File

@ -18,6 +18,7 @@
static const char *const repo_usage[] = {
"git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]",
"git repo info --keys [--format=(default|nul) | -z]",
"git repo structure [--format=(table|keyvalue|nul) | -z]",
NULL
};
@ -25,6 +26,7 @@ static const char *const repo_usage[] = {
typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
enum output_format {
FORMAT_DEFAULT,
FORMAT_TABLE,
FORMAT_KEYVALUE,
FORMAT_NUL_TERMINATED,
@ -148,6 +150,29 @@ static int print_all_fields(struct repository *repo,
return 0;
}
static int print_keys(enum output_format format)
{
char sep;
switch (format) {
case FORMAT_DEFAULT:
sep = '\n';
break;
case FORMAT_NUL_TERMINATED:
sep = '\0';
break;
default:
die(_("--keys can only be used with --format=default or --format=nul"));
}
for (size_t i = 0; i < ARRAY_SIZE(repo_info_fields); i++) {
const struct field *field = &repo_info_fields[i];
printf("%s%c", field->key, sep);
}
return 0;
}
static int parse_format_cb(const struct option *opt,
const char *arg, int unset UNUSED)
{
@ -161,6 +186,8 @@ static int parse_format_cb(const struct option *opt,
*format = FORMAT_KEYVALUE;
else if (!strcmp(arg, "table"))
*format = FORMAT_TABLE;
else if (!strcmp(arg, "default"))
*format = FORMAT_DEFAULT;
else
die(_("invalid format '%s'"), arg);
@ -170,8 +197,9 @@ static int parse_format_cb(const struct option *opt,
static int cmd_repo_info(int argc, const char **argv, const char *prefix,
struct repository *repo)
{
enum output_format format = FORMAT_KEYVALUE;
enum output_format format = FORMAT_DEFAULT;
int all_keys = 0;
int show_keys = 0;
struct option options[] = {
OPT_CALLBACK_F(0, "format", &format, N_("format"),
N_("output format"),
@ -181,10 +209,21 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
parse_format_cb),
OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
OPT_BOOL(0, "keys", &show_keys, N_("show keys")),
OPT_END()
};
argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
if (show_keys && (all_keys || argc))
die(_("--keys cannot be used with a <key> or --all"));
if (show_keys)
return print_keys(format);
if (format == FORMAT_DEFAULT)
format = FORMAT_KEYVALUE;
if (format != FORMAT_KEYVALUE && format != FORMAT_NUL_TERMINATED)
die(_("unsupported output format"));

View File

@ -34,6 +34,7 @@
#include "list-objects-filter-options.h"
#include "wildmatch.h"
#include "strbuf.h"
#include "url.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@ -435,6 +436,102 @@ struct init_cb {
};
#define INIT_CB_INIT { 0 }
static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path,
const char *submodule_name)
{
const char *value;
char *key;
if (validate_submodule_git_dir(gitdir_path->buf, submodule_name))
return -1;
key = xstrfmt("submodule.%s.gitdir", submodule_name);
/* Nothing to do if the config already exists. */
if (!repo_config_get_string_tmp(the_repository, key, &value)) {
free(key);
return 0;
}
if (repo_config_set_gently(the_repository, key, gitdir_path->buf)) {
free(key);
return -1;
}
free(key);
return 0;
}
static void create_default_gitdir_config(const char *submodule_name)
{
struct strbuf gitdir_path = STRBUF_INIT;
struct git_hash_ctx ctx;
char hex_name_hash[GIT_MAX_HEXSZ + 1], header[128];
unsigned char raw_name_hash[GIT_MAX_RAWSZ];
int header_len;
/* Case 1: try the plain module name */
repo_git_path_append(the_repository, &gitdir_path, "modules/%s", submodule_name);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
strbuf_release(&gitdir_path);
return;
}
/* Case 2.1: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */
strbuf_reset(&gitdir_path);
repo_git_path_append(the_repository, &gitdir_path, "modules/");
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
strbuf_release(&gitdir_path);
return;
}
/* Case 2.2: Try extended uppercase URI (RFC3986) encoding, to fix case-folding */
strbuf_reset(&gitdir_path);
repo_git_path_append(the_repository, &gitdir_path, "modules/");
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
return;
/* Case 2.3: Try some derived gitdir names, see if one sticks */
for (char c = '0'; c <= '9'; c++) {
strbuf_reset(&gitdir_path);
repo_git_path_append(the_repository, &gitdir_path, "modules/");
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved);
strbuf_addch(&gitdir_path, c);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
return;
strbuf_reset(&gitdir_path);
repo_git_path_append(the_repository, &gitdir_path, "modules/");
strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved);
strbuf_addch(&gitdir_path, c);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name))
return;
}
/* Case 2.4: If all the above failed, try a hash of the name as a last resort */
header_len = snprintf(header, sizeof(header), "blob %zu", strlen(submodule_name));
the_hash_algo->init_fn(&ctx);
the_hash_algo->update_fn(&ctx, header, header_len);
the_hash_algo->update_fn(&ctx, "\0", 1);
the_hash_algo->update_fn(&ctx, submodule_name, strlen(submodule_name));
the_hash_algo->final_fn(raw_name_hash, &ctx);
hash_to_hex_algop_r(hex_name_hash, raw_name_hash, the_hash_algo);
strbuf_reset(&gitdir_path);
repo_git_path_append(the_repository, &gitdir_path, "modules/%s", hex_name_hash);
if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) {
strbuf_release(&gitdir_path);
return;
}
/* Case 3: nothing worked, error out */
die(_("failed to set a valid default config for 'submodule.%s.gitdir'. "
"Please ensure it is set, for example by running something like: "
"'git config submodule.%s.gitdir .git/modules/%s'"),
submodule_name, submodule_name, submodule_name);
}
static void init_submodule(const char *path, const char *prefix,
const char *super_prefix,
unsigned int flags)
@ -511,6 +608,10 @@ static void init_submodule(const char *path, const char *prefix,
if (repo_config_set_gently(the_repository, sb.buf, upd))
die(_("Failed to register update mode for submodule path '%s'"), displaypath);
}
if (the_repository->repository_format_submodule_path_cfg)
create_default_gitdir_config(sub->name);
strbuf_release(&sb);
free(displaypath);
free(url);
@ -1204,6 +1305,82 @@ static int module_summary(int argc, const char **argv, const char *prefix,
return ret;
}
static int module_gitdir(int argc, const char **argv, const char *prefix UNUSED,
struct repository *repo)
{
struct strbuf gitdir = STRBUF_INIT;
if (argc != 2)
usage(_("git submodule--helper gitdir <name>"));
submodule_name_to_gitdir(&gitdir, repo, argv[1]);
printf("%s\n", gitdir.buf);
strbuf_release(&gitdir);
return 0;
}
static int module_migrate(int argc UNUSED, const char **argv UNUSED,
const char *prefix UNUSED, struct repository *repo)
{
struct strbuf module_dir = STRBUF_INIT;
DIR *dir;
struct dirent *de;
int repo_version = 0;
repo_git_path_append(repo, &module_dir, "modules/");
dir = opendir(module_dir.buf);
if (!dir)
die(_("could not open '%s'"), module_dir.buf);
while ((de = readdir(dir))) {
struct strbuf gitdir_path = STRBUF_INIT;
char *key;
const char *value;
if (is_dot_or_dotdot(de->d_name))
continue;
strbuf_addf(&gitdir_path, "%s/%s", module_dir.buf, de->d_name);
if (!is_git_directory(gitdir_path.buf)) {
strbuf_release(&gitdir_path);
continue;
}
strbuf_release(&gitdir_path);
key = xstrfmt("submodule.%s.gitdir", de->d_name);
if (!repo_config_get_string_tmp(repo, key, &value)) {
/* Already has a gitdir config, nothing to do. */
free(key);
continue;
}
free(key);
create_default_gitdir_config(de->d_name);
}
closedir(dir);
strbuf_release(&module_dir);
repo_config_get_int(the_repository, "core.repositoryformatversion", &repo_version);
if (repo_version == 0 &&
repo_config_set_gently(repo, "core.repositoryformatversion", "1"))
die(_("could not set core.repositoryformatversion to 1. "
"Please set it for migration to work, for example: "
"git config core.repositoryformatversion 1"));
if (repo_config_set_gently(repo, "extensions.submodulePathConfig", "true"))
die(_("could not enable submodulePathConfig extension. It is required "
"for migration to work. Please enable it in the root repo: "
"git config extensions.submodulePathConfig true"));
repo->repository_format_submodule_path_cfg = 1;
return 0;
}
struct sync_cb {
const char *prefix;
const char *super_prefix;
@ -1699,10 +1876,6 @@ static int clone_submodule(const struct module_clone_data *clone_data,
clone_data_path = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository),
clone_data->path);
if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0)
die(_("refusing to create/use '%s' in another submodule's "
"git dir"), sm_gitdir);
if (!file_exists(sm_gitdir)) {
if (clone_data->require_init && !stat(clone_data_path, &st) &&
!is_empty_dir(clone_data_path))
@ -1789,8 +1962,9 @@ static int clone_submodule(const struct module_clone_data *clone_data,
char *head = xstrfmt("%s/HEAD", sm_gitdir);
unlink(head);
free(head);
die(_("refusing to create/use '%s' in another submodule's "
"git dir"), sm_gitdir);
die(_("refusing to create/use '%s' in another submodule's git dir. "
"Enabling extensions.submodulePathConfig should fix this."),
sm_gitdir);
}
connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
@ -3190,13 +3364,13 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path)
static int add_submodule(const struct add_data *add_data)
{
char *submod_gitdir_path;
struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
struct string_list reference = STRING_LIST_INIT_NODUP;
int ret = -1;
/* perhaps the path already exists and is already a git repo, else clone it */
if (is_directory(add_data->sm_path)) {
char *submod_gitdir_path;
struct strbuf sm_path = STRBUF_INIT;
strbuf_addstr(&sm_path, add_data->sm_path);
submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path);
@ -3210,10 +3384,11 @@ static int add_submodule(const struct add_data *add_data)
free(submod_gitdir_path);
} else {
struct child_process cp = CHILD_PROCESS_INIT;
struct strbuf submod_gitdir = STRBUF_INIT;
submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name);
submodule_name_to_gitdir(&submod_gitdir, the_repository, add_data->sm_name);
if (is_directory(submod_gitdir_path)) {
if (is_directory(submod_gitdir.buf)) {
if (!add_data->force) {
struct strbuf msg = STRBUF_INIT;
char *die_msg;
@ -3222,8 +3397,8 @@ static int add_submodule(const struct add_data *add_data)
"locally with remote(s):\n"),
add_data->sm_name);
append_fetch_remotes(&msg, submod_gitdir_path);
free(submod_gitdir_path);
append_fetch_remotes(&msg, submod_gitdir.buf);
strbuf_release(&submod_gitdir);
strbuf_addf(&msg, _("If you want to reuse this local git "
"directory instead of cloning again from\n"
@ -3241,7 +3416,7 @@ static int add_submodule(const struct add_data *add_data)
"submodule '%s'\n"), add_data->sm_name);
}
}
free(submod_gitdir_path);
strbuf_release(&submod_gitdir);
clone_data.prefix = add_data->prefix;
clone_data.path = add_data->sm_path;
@ -3569,6 +3744,9 @@ static int module_add(int argc, const char **argv, const char *prefix,
add_data.progress = !!progress;
add_data.dissociate = !!dissociate;
if (the_repository->repository_format_submodule_path_cfg)
create_default_gitdir_config(add_data.sm_name);
if (add_submodule(&add_data))
goto cleanup;
configure_added_submodule(&add_data);
@ -3594,6 +3772,8 @@ int cmd_submodule__helper(int argc,
NULL
};
struct option options[] = {
OPT_SUBCOMMAND("migrate-gitdir-configs", &fn, module_migrate),
OPT_SUBCOMMAND("gitdir", &fn, module_gitdir),
OPT_SUBCOMMAND("clone", &fn, module_clone),
OPT_SUBCOMMAND("add", &fn, module_add),
OPT_SUBCOMMAND("update", &fn, module_update),

View File

@ -499,8 +499,7 @@ int cmd_tag(int argc,
OPT_CALLBACK_F('m', "message", &msg, N_("message"),
N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"),
N_("add custom trailer(s)"), PARSE_OPT_NONEG),
OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, parse_opt_strvec),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
OPT_CLEANUP(&cleanup_arg),

View File

@ -72,7 +72,7 @@ static char *git_attr_val_system(int ident_flag UNUSED)
static char *git_attr_val_global(int ident_flag UNUSED)
{
char *file = xstrdup_or_null(git_attr_global_file());
char *file = xstrdup_or_null(repo_settings_get_attributesfile_path(the_repository));
if (file) {
normalize_path_copy(file, file);
return file;

View File

@ -252,7 +252,7 @@ static int prune(int ac, const char **av, const char *prefix,
OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
OPT__VERBOSE(&verbose, N_("report pruned working trees")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("expire working trees older than <time>")),
N_("prune missing working trees older than <time>")),
OPT_END()
};
@ -1070,7 +1070,7 @@ static int list(int ac, const char **av, const char *prefix,
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
OPT_EXPIRY_DATE(0, "expire", &expire,
N_("add 'prunable' annotation to worktrees older than <time>")),
N_("add 'prunable' annotation to missing worktrees older than <time>")),
OPT_SET_INT('z', NULL, &line_terminator,
N_("terminate records with a NUL character"), '\0'),
OPT_END()

View File

@ -89,7 +89,10 @@ static int summarize_bundle(struct remote_bundle_info *info, void *data)
{
FILE *fp = data;
fprintf(fp, "[bundle \"%s\"]\n", info->id);
if (info->uri)
fprintf(fp, "\turi = %s\n", info->uri);
else
fprintf(fp, "\t# uri = (missing)\n");
if (info->creationToken)
fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken);
@ -267,6 +270,19 @@ int bundle_uri_parse_config_format(const char *uri,
result = 1;
}
if (!result) {
struct hashmap_iter iter;
struct remote_bundle_info *bundle;
hashmap_for_each_entry(&list->bundles, &iter, bundle, ent) {
if (!bundle->uri) {
error(_("bundle list at '%s': bundle '%s' has no uri"),
uri, bundle->id ? bundle->id : "<unknown>");
result = 1;
}
}
}
return result;
}
@ -751,6 +767,12 @@ static int fetch_bundle_uri_internal(struct repository *r,
return -1;
}
if (!bundle->uri) {
error(_("bundle '%s' has no uri"),
bundle->id ? bundle->id : "<unknown>");
return -1;
}
if (!bundle->file &&
!(bundle->file = find_temp_filename())) {
result = -1;

View File

@ -16,6 +16,7 @@
#include "promisor-remote.h"
#include "trace.h"
#include "trace2.h"
#include "parse.h"
#ifndef DEBUG_CACHE_TREE
#define DEBUG_CACHE_TREE 0
@ -550,32 +551,13 @@ void cache_tree_write(struct strbuf *sb, struct cache_tree *root)
static int parse_int(const char **ptr, unsigned long *len_p, int *out)
{
const char *s = *ptr;
unsigned long len = *len_p;
int ret = 0;
int sign = 1;
const char *ep;
while (len && *s == '-') {
sign *= -1;
s++;
len--;
}
while (len) {
if (!isdigit(*s))
break;
ret *= 10;
ret += *s - '0';
s++;
len--;
}
if (s == *ptr)
if (!parse_int_from_buf(*ptr, *len_p, &ep, out))
return -1;
*ptr = s;
*len_p = len;
*out = sign * ret;
*len_p -= ep - *ptr;
*ptr = ep;
return 0;
}

View File

@ -115,6 +115,7 @@ git-grep mainporcelain info
git-gui mainporcelain
git-hash-object plumbingmanipulators
git-help ancillaryinterrogators complete
git-history mainporcelain history
git-hook purehelpers
git-http-backend synchingrepositories
git-http-fetch synchelpers

View File

@ -1127,18 +1127,12 @@ struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit
return get_commit_tree_in_graph_one(r->objects->commit_graph, c);
}
struct packed_commit_list {
struct commit **list;
size_t nr;
size_t alloc;
};
struct write_commit_graph_context {
struct repository *r;
struct odb_source *odb_source;
char *graph_name;
struct oid_array oids;
struct packed_commit_list commits;
struct commit_stack commits;
int num_extra_edges;
int num_generation_data_overflows;
unsigned long approx_nr_objects;
@ -1180,7 +1174,7 @@ static int write_graph_chunk_fanout(struct hashfile *f,
{
struct write_commit_graph_context *ctx = data;
int i, count = 0;
struct commit **list = ctx->commits.list;
struct commit **list = ctx->commits.items;
/*
* Write the first-level table (the list is sorted,
@ -1206,7 +1200,7 @@ static int write_graph_chunk_oids(struct hashfile *f,
void *data)
{
struct write_commit_graph_context *ctx = data;
struct commit **list = ctx->commits.list;
struct commit **list = ctx->commits.items;
int count;
for (count = 0; count < ctx->commits.nr; count++, list++) {
display_progress(ctx->progress, ++ctx->progress_cnt);
@ -1226,8 +1220,8 @@ static int write_graph_chunk_data(struct hashfile *f,
void *data)
{
struct write_commit_graph_context *ctx = data;
struct commit **list = ctx->commits.list;
struct commit **last = ctx->commits.list + ctx->commits.nr;
struct commit **list = ctx->commits.items;
struct commit **last = ctx->commits.items + ctx->commits.nr;
uint32_t num_extra_edges = 0;
while (list < last) {
@ -1249,7 +1243,7 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value = GRAPH_PARENT_NONE;
else {
edge_value = oid_pos(&parent->item->object.oid,
ctx->commits.list,
ctx->commits.items,
ctx->commits.nr,
commit_to_oid);
@ -1280,7 +1274,7 @@ static int write_graph_chunk_data(struct hashfile *f,
edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges;
else {
edge_value = oid_pos(&parent->item->object.oid,
ctx->commits.list,
ctx->commits.items,
ctx->commits.nr,
commit_to_oid);
@ -1332,7 +1326,7 @@ static int write_graph_chunk_generation_data(struct hashfile *f,
int i, num_generation_data_overflows = 0;
for (i = 0; i < ctx->commits.nr; i++) {
struct commit *c = ctx->commits.list[i];
struct commit *c = ctx->commits.items[i];
timestamp_t offset;
repo_parse_commit(ctx->r, c);
offset = commit_graph_data_at(c)->generation - c->date;
@ -1355,7 +1349,7 @@ static int write_graph_chunk_generation_data_overflow(struct hashfile *f,
struct write_commit_graph_context *ctx = data;
int i;
for (i = 0; i < ctx->commits.nr; i++) {
struct commit *c = ctx->commits.list[i];
struct commit *c = ctx->commits.items[i];
timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
display_progress(ctx->progress, ++ctx->progress_cnt);
@ -1372,8 +1366,8 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
void *data)
{
struct write_commit_graph_context *ctx = data;
struct commit **list = ctx->commits.list;
struct commit **last = ctx->commits.list + ctx->commits.nr;
struct commit **list = ctx->commits.items;
struct commit **last = ctx->commits.items + ctx->commits.nr;
struct commit_list *parent;
while (list < last) {
@ -1393,7 +1387,7 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
/* Since num_parents > 2, this initializer is safe. */
for (parent = (*list)->parents->next; parent; parent = parent->next) {
int edge_value = oid_pos(&parent->item->object.oid,
ctx->commits.list,
ctx->commits.items,
ctx->commits.nr,
commit_to_oid);
@ -1427,8 +1421,8 @@ static int write_graph_chunk_bloom_indexes(struct hashfile *f,
void *data)
{
struct write_commit_graph_context *ctx = data;
struct commit **list = ctx->commits.list;
struct commit **last = ctx->commits.list + ctx->commits.nr;
struct commit **list = ctx->commits.items;
struct commit **last = ctx->commits.items + ctx->commits.nr;
uint32_t cur_pos = 0;
while (list < last) {
@ -1463,8 +1457,8 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,
void *data)
{
struct write_commit_graph_context *ctx = data;
struct commit **list = ctx->commits.list;
struct commit **last = ctx->commits.list + ctx->commits.nr;
struct commit **list = ctx->commits.items;
struct commit **last = ctx->commits.items + ctx->commits.nr;
trace2_bloom_filter_settings(ctx);
@ -1499,7 +1493,7 @@ static int add_packed_commits(const struct object_id *oid,
display_progress(ctx->progress, ++ctx->progress_done);
oi.typep = &type;
if (packed_object_info(ctx->r, pack, offset, &oi) < 0)
if (packed_object_info(pack, offset, &oi) < 0)
die(_("unable to get type of object %s"), oid_to_hex(oid));
if (type != OBJ_COMMIT)
@ -1585,7 +1579,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
struct compute_generation_info {
struct repository *r;
struct packed_commit_list *commits;
struct commit_stack *commits;
struct progress *progress;
int progress_cnt;
@ -1622,7 +1616,7 @@ static void compute_reachable_generation_numbers(
struct commit_list *list = NULL;
for (i = 0; i < info->commits->nr; i++) {
struct commit *c = info->commits->list[i];
struct commit *c = info->commits->items[i];
timestamp_t gen;
repo_parse_commit(info->r, c);
gen = info->get_generation(c, info->data);
@ -1729,7 +1723,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
if (!ctx->trust_generation_numbers) {
for (i = 0; i < ctx->commits.nr; i++) {
struct commit *c = ctx->commits.list[i];
struct commit *c = ctx->commits.items[i];
repo_parse_commit(ctx->r, c);
commit_graph_data_at(c)->generation = GENERATION_NUMBER_ZERO;
}
@ -1738,7 +1732,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx)
compute_reachable_generation_numbers(&info, 2);
for (i = 0; i < ctx->commits.nr; i++) {
struct commit *c = ctx->commits.list[i];
struct commit *c = ctx->commits.items[i];
timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
if (offset > GENERATION_NUMBER_V2_OFFSET_MAX)
ctx->num_generation_data_overflows++;
@ -1760,8 +1754,8 @@ void ensure_generations_valid(struct repository *r,
struct commit **commits, size_t nr)
{
int generation_version = get_configured_generation_version(r);
struct packed_commit_list list = {
.list = commits,
struct commit_stack list = {
.items = commits,
.alloc = nr,
.nr = nr,
};
@ -1804,7 +1798,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
_("Computing commit changed paths Bloom filters"),
ctx->commits.nr);
DUP_ARRAY(sorted_commits, ctx->commits.list, ctx->commits.nr);
DUP_ARRAY(sorted_commits, ctx->commits.items, ctx->commits.nr);
if (ctx->order_by_pack)
QSORT(sorted_commits, ctx->commits.nr, commit_pos_cmp);
@ -1992,26 +1986,26 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx)
oid_array_sort(&ctx->oids);
for (i = 0; i < ctx->oids.nr; i = oid_array_next_unique(&ctx->oids, i)) {
unsigned int num_parents;
struct commit *commit;
display_progress(ctx->progress, i + 1);
ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.oid[i]);
commit = lookup_commit(ctx->r, &ctx->oids.oid[i]);
if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE &&
commit_graph_position(ctx->commits.list[ctx->commits.nr]) != COMMIT_NOT_FROM_GRAPH)
commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
continue;
if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE)
repo_parse_commit(ctx->r, ctx->commits.list[ctx->commits.nr]);
repo_parse_commit(ctx->r, commit);
else
repo_parse_commit_no_graph(ctx->r, ctx->commits.list[ctx->commits.nr]);
repo_parse_commit_no_graph(ctx->r, commit);
num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents);
num_parents = commit_list_count(commit->parents);
if (num_parents > 2)
ctx->num_extra_edges += num_parents - 1;
ctx->commits.nr++;
commit_stack_push(&ctx->commits, commit);
}
stop_progress(&ctx->progress);
}
@ -2330,7 +2324,7 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx,
oid_to_hex(&g->oid),
(uintmax_t)st_add(ctx->commits.nr, g->num_commits));
ALLOC_GROW(ctx->commits.list, ctx->commits.nr + g->num_commits, ctx->commits.alloc);
commit_stack_grow(&ctx->commits, g->num_commits);
for (i = 0; i < g->num_commits; i++) {
struct object_id oid;
@ -2343,10 +2337,8 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx,
/* only add commits if they still exist in the repo */
result = lookup_commit_reference_gently(ctx->r, &oid, 1);
if (result) {
ctx->commits.list[ctx->commits.nr] = result;
ctx->commits.nr++;
}
if (result)
commit_stack_push(&ctx->commits, result);
}
}
@ -2367,14 +2359,14 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
_("Scanning merged commits"),
ctx->commits.nr);
QSORT(ctx->commits.list, ctx->commits.nr, commit_compare);
QSORT(ctx->commits.items, ctx->commits.nr, commit_compare);
ctx->num_extra_edges = 0;
for (i = 0; i < ctx->commits.nr; i++) {
display_progress(ctx->progress, i + 1);
if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
&ctx->commits.list[i]->object.oid)) {
if (i && oideq(&ctx->commits.items[i - 1]->object.oid,
&ctx->commits.items[i]->object.oid)) {
/*
* Silently ignore duplicates. These were likely
* created due to a commit appearing in multiple
@ -2385,10 +2377,10 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
} else {
unsigned int num_parents;
ctx->commits.list[dedup_i] = ctx->commits.list[i];
ctx->commits.items[dedup_i] = ctx->commits.items[i];
dedup_i++;
num_parents = commit_list_count(ctx->commits.list[i]->parents);
num_parents = commit_list_count(ctx->commits.items[i]->parents);
if (num_parents > 2)
ctx->num_extra_edges += num_parents - 1;
}
@ -2666,7 +2658,7 @@ int write_commit_graph(struct odb_source *source,
cleanup:
free(ctx.graph_name);
free(ctx.base_graph_name);
free(ctx.commits.list);
commit_stack_clear(&ctx.commits);
oid_array_clear(&ctx.oids);
clear_topo_level_slab(&topo_levels);

View File

@ -283,8 +283,8 @@ static int remove_redundant_with_gen(struct repository *r,
{
size_t i, count_non_stale = 0, count_still_independent = cnt;
timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
struct commit **walk_start, **sorted;
size_t walk_start_nr = 0, walk_start_alloc = cnt;
struct commit **sorted;
struct commit_stack walk_start = COMMIT_STACK_INIT;
size_t min_gen_pos = 0;
/*
@ -298,7 +298,7 @@ static int remove_redundant_with_gen(struct repository *r,
QSORT(sorted, cnt, compare_commits_by_gen);
min_generation = commit_graph_generation(sorted[0]);
ALLOC_ARRAY(walk_start, walk_start_alloc);
commit_stack_grow(&walk_start, cnt);
/* Mark all parents of the input as STALE */
for (i = 0; i < cnt; i++) {
@ -312,18 +312,17 @@ static int remove_redundant_with_gen(struct repository *r,
repo_parse_commit(r, parents->item);
if (!(parents->item->object.flags & STALE)) {
parents->item->object.flags |= STALE;
ALLOC_GROW(walk_start, walk_start_nr + 1, walk_start_alloc);
walk_start[walk_start_nr++] = parents->item;
commit_stack_push(&walk_start, parents->item);
}
parents = parents->next;
}
}
QSORT(walk_start, walk_start_nr, compare_commits_by_gen);
QSORT(walk_start.items, walk_start.nr, compare_commits_by_gen);
/* remove STALE bit for now to allow walking through parents */
for (i = 0; i < walk_start_nr; i++)
walk_start[i]->object.flags &= ~STALE;
for (i = 0; i < walk_start.nr; i++)
walk_start.items[i]->object.flags &= ~STALE;
/*
* Start walking from the highest generation. Hopefully, it will
@ -331,12 +330,12 @@ static int remove_redundant_with_gen(struct repository *r,
* terminate early. Otherwise, we will do the same amount of work
* as before.
*/
for (i = walk_start_nr; i && count_still_independent > 1; i--) {
for (i = walk_start.nr; i && count_still_independent > 1; i--) {
/* push the STALE bits up to min generation */
struct commit_list *stack = NULL;
commit_list_insert(walk_start[i - 1], &stack);
walk_start[i - 1]->object.flags |= STALE;
commit_list_insert(walk_start.items[i - 1], &stack);
walk_start.items[i - 1]->object.flags |= STALE;
while (stack) {
struct commit_list *parents;
@ -390,8 +389,8 @@ static int remove_redundant_with_gen(struct repository *r,
}
/* clear marks */
clear_commit_marks_many(walk_start_nr, walk_start, STALE);
free(walk_start);
clear_commit_marks_many(walk_start.nr, walk_start.items, STALE);
commit_stack_clear(&walk_start);
*dedup_cnt = count_non_stale;
return 0;

View File

@ -1984,3 +1984,31 @@ int run_commit_hook(int editor_is_used, const char *index_file,
opt.invoked_hook = invoked_hook;
return run_hooks_opt(the_repository, name, &opt);
}
void commit_stack_init(struct commit_stack *stack)
{
stack->items = NULL;
stack->nr = stack->alloc = 0;
}
void commit_stack_grow(struct commit_stack *stack, size_t extra)
{
ALLOC_GROW(stack->items, st_add(stack->nr, extra), stack->alloc);
}
void commit_stack_push(struct commit_stack *stack, struct commit *commit)
{
commit_stack_grow(stack, 1);
stack->items[stack->nr++] = commit;
}
struct commit *commit_stack_pop(struct commit_stack *stack)
{
return stack->nr ? stack->items[--stack->nr] : NULL;
}
void commit_stack_clear(struct commit_stack *stack)
{
free(stack->items);
commit_stack_init(stack);
}

View File

@ -381,4 +381,16 @@ int parse_buffer_signed_by_header(const char *buffer,
const struct git_hash_algo *algop);
int add_header_signature(struct strbuf *buf, struct strbuf *sig, const struct git_hash_algo *algo);
struct commit_stack {
struct commit **items;
size_t nr, alloc;
};
#define COMMIT_STACK_INIT { 0 }
void commit_stack_init(struct commit_stack *);
void commit_stack_grow(struct commit_stack *, size_t);
void commit_stack_push(struct commit_stack *, struct commit *);
struct commit *commit_stack_pop(struct commit_stack *);
void commit_stack_clear(struct commit_stack *);
#endif /* COMMIT_H */

View File

@ -0,0 +1,33 @@
#include "git-compat-util.h"
#include "config.h"
#include "fsmonitor-ll.h"
#include "fsm-health.h"
#include "fsmonitor--daemon.h"
/*
* The Linux fsmonitor implementation uses inotify which has its own
* mechanisms for detecting filesystem unmount and other events that
* would require the daemon to shutdown. Therefore, we don't need
* a separate health thread like Windows does.
*
* These stub functions satisfy the interface requirements.
*/
int fsm_health__ctor(struct fsmonitor_daemon_state *state UNUSED)
{
return 0;
}
void fsm_health__dtor(struct fsmonitor_daemon_state *state UNUSED)
{
return;
}
void fsm_health__loop(struct fsmonitor_daemon_state *state UNUSED)
{
return;
}
void fsm_health__stop_async(struct fsmonitor_daemon_state *state UNUSED)
{
}

View File

@ -0,0 +1,61 @@
#define USE_THE_REPOSITORY_VARIABLE
#include "git-compat-util.h"
#include "config.h"
#include "gettext.h"
#include "hex.h"
#include "path.h"
#include "repository.h"
#include "strbuf.h"
#include "fsmonitor-ll.h"
#include "fsmonitor-ipc.h"
#include "fsmonitor-path-utils.h"
static GIT_PATH_FUNC(fsmonitor_ipc__get_default_path, "fsmonitor--daemon.ipc")
const char *fsmonitor_ipc__get_path(struct repository *r)
{
static const char *ipc_path = NULL;
git_SHA_CTX sha1ctx;
char *sock_dir = NULL;
struct strbuf ipc_file = STRBUF_INIT;
unsigned char hash[GIT_SHA1_RAWSZ];
if (!r)
BUG("No repository passed into fsmonitor_ipc__get_path");
if (ipc_path)
return ipc_path;
/* By default the socket file is created in the .git directory */
if (fsmonitor__is_fs_remote(r->gitdir) < 1) {
ipc_path = fsmonitor_ipc__get_default_path();
return ipc_path;
}
if (!r->worktree)
BUG("repository has no worktree");
git_SHA1_Init(&sha1ctx);
git_SHA1_Update(&sha1ctx, r->worktree, strlen(r->worktree));
git_SHA1_Final(hash, &sha1ctx);
repo_config_get_string(r, "fsmonitor.socketdir", &sock_dir);
/* Create the socket file in either socketDir or $HOME */
if (sock_dir && *sock_dir) {
strbuf_addf(&ipc_file, "%s/.git-fsmonitor-%s",
sock_dir, hash_to_hex_algop(hash, &hash_algos[GIT_HASH_SHA1]));
} else {
strbuf_addf(&ipc_file, "~/.git-fsmonitor-%s",
hash_to_hex_algop(hash, &hash_algos[GIT_HASH_SHA1]));
}
free(sock_dir);
ipc_path = interpolate_path(ipc_file.buf, 1);
if (!ipc_path)
die(_("Invalid path: %s"), ipc_file.buf);
strbuf_release(&ipc_file);
return ipc_path;
}

View File

@ -0,0 +1,740 @@
#include "git-compat-util.h"
#include "dir.h"
#include "fsmonitor-ll.h"
#include "fsm-listen.h"
#include "fsmonitor--daemon.h"
#include "fsmonitor-path-utils.h"
#include "gettext.h"
#include "simple-ipc.h"
#include "string-list.h"
#include "trace.h"
#include <dirent.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/inotify.h>
#include <sys/stat.h>
/*
* Safe value to bitwise OR with rest of mask for
* kernels that do not support IN_MASK_CREATE
*/
#ifndef IN_MASK_CREATE
#define IN_MASK_CREATE 0x00000000
#endif
enum shutdown_reason {
SHUTDOWN_CONTINUE = 0,
SHUTDOWN_STOP,
SHUTDOWN_ERROR,
SHUTDOWN_FORCE
};
struct watch_entry {
struct hashmap_entry ent;
int wd;
uint32_t cookie;
const char *dir;
};
struct rename_entry {
struct hashmap_entry ent;
time_t whence;
uint32_t cookie;
const char *dir;
};
struct fsm_listen_data {
int fd_inotify;
enum shutdown_reason shutdown;
struct hashmap watches;
struct hashmap renames;
struct hashmap revwatches;
};
static int watch_entry_cmp(const void *cmp_data UNUSED,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
const void *keydata UNUSED)
{
const struct watch_entry *e1, *e2;
e1 = container_of(eptr, const struct watch_entry, ent);
e2 = container_of(entry_or_key, const struct watch_entry, ent);
return e1->wd != e2->wd;
}
static int revwatches_entry_cmp(const void *cmp_data UNUSED,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
const void *keydata UNUSED)
{
const struct watch_entry *e1, *e2;
e1 = container_of(eptr, const struct watch_entry, ent);
e2 = container_of(entry_or_key, const struct watch_entry, ent);
return strcmp(e1->dir, e2->dir);
}
static int rename_entry_cmp(const void *cmp_data UNUSED,
const struct hashmap_entry *eptr,
const struct hashmap_entry *entry_or_key,
const void *keydata UNUSED)
{
const struct rename_entry *e1, *e2;
e1 = container_of(eptr, const struct rename_entry, ent);
e2 = container_of(entry_or_key, const struct rename_entry, ent);
return e1->cookie != e2->cookie;
}
/*
* Register an inotify watch, add watch descriptor to path mapping
* and the reverse mapping.
*/
static int add_watch(const char *path, struct fsm_listen_data *data)
{
const char *interned = strintern(path);
struct watch_entry *w1, *w2;
/* add the inotify watch, don't allow watches to be modified */
int wd = inotify_add_watch(data->fd_inotify, interned,
(IN_ALL_EVENTS | IN_ONLYDIR | IN_MASK_CREATE)
^ IN_ACCESS ^ IN_CLOSE ^ IN_OPEN);
if (wd < 0) {
if (errno == ENOENT || errno == ENOTDIR)
return 0; /* directory was deleted or is not a directory */
if (errno == EEXIST)
return 0; /* watch already exists, no action needed */
return error_errno(_("inotify_add_watch('%s') failed"), interned);
}
/* add watch descriptor -> directory mapping */
CALLOC_ARRAY(w1, 1);
w1->wd = wd;
w1->dir = interned;
hashmap_entry_init(&w1->ent, memhash(&w1->wd, sizeof(int)));
hashmap_add(&data->watches, &w1->ent);
/* add directory -> watch descriptor mapping */
CALLOC_ARRAY(w2, 1);
w2->wd = wd;
w2->dir = interned;
hashmap_entry_init(&w2->ent, strhash(w2->dir));
hashmap_add(&data->revwatches, &w2->ent);
return 0;
}
/*
* Remove the inotify watch, the watch descriptor to path mapping
* and the reverse mapping.
*/
static void remove_watch(struct watch_entry *w, struct fsm_listen_data *data)
{
struct watch_entry k1, k2, *w1, *w2;
/* remove watch, ignore error if kernel already did it */
if (inotify_rm_watch(data->fd_inotify, w->wd) && errno != EINVAL)
error_errno(_("inotify_rm_watch() failed"));
k1.wd = w->wd;
hashmap_entry_init(&k1.ent, memhash(&k1.wd, sizeof(int)));
w1 = hashmap_remove_entry(&data->watches, &k1, ent, NULL);
if (!w1)
BUG("Double remove of watch for '%s'", w->dir);
if (w1->cookie)
BUG("Removing watch for '%s' which has a pending rename", w1->dir);
k2.dir = w->dir;
hashmap_entry_init(&k2.ent, strhash(k2.dir));
w2 = hashmap_remove_entry(&data->revwatches, &k2, ent, NULL);
if (!w2)
BUG("Double remove of reverse watch for '%s'", w->dir);
/* w1->dir and w2->dir are interned strings, we don't own them */
free(w1);
free(w2);
}
/*
* Check for stale directory renames.
*
* https://man7.org/linux/man-pages/man7/inotify.7.html
*
* Allow for some small timeout to account for the fact that insertion of the
* IN_MOVED_FROM+IN_MOVED_TO event pair is not atomic, and the possibility that
* there may not be any IN_MOVED_TO event.
*
* If the IN_MOVED_TO event is not received within the timeout then events have
* been missed and the monitor is in an inconsistent state with respect to the
* filesystem.
*/
static int check_stale_dir_renames(struct hashmap *renames, time_t max_age)
{
struct rename_entry *re;
struct hashmap_iter iter;
hashmap_for_each_entry(renames, &iter, re, ent) {
if (re->whence <= max_age)
return -1;
}
return 0;
}
/*
* Track pending renames.
*
* Tracking is done via an event cookie to watch descriptor mapping.
*
* A rename is not complete until matching an IN_MOVED_TO event is received
* for a corresponding IN_MOVED_FROM event.
*/
static void add_dir_rename(uint32_t cookie, const char *path,
struct fsm_listen_data *data)
{
struct watch_entry k, *w;
struct rename_entry *re;
/* lookup the watch descriptor for the given path */
k.dir = path;
hashmap_entry_init(&k.ent, strhash(path));
w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
if (!w) {
/*
* This can happen in rare cases where the directory was
* moved before we had a chance to add a watch on it.
* Just ignore this rename.
*/
trace_printf_key(&trace_fsmonitor,
"no watch found for rename from '%s'", path);
return;
}
w->cookie = cookie;
/* add the pending rename to match against later */
CALLOC_ARRAY(re, 1);
re->dir = w->dir;
re->cookie = w->cookie;
re->whence = time(NULL);
hashmap_entry_init(&re->ent, memhash(&re->cookie, sizeof(uint32_t)));
hashmap_add(&data->renames, &re->ent);
}
/*
* Handle directory renames
*
* Once an IN_MOVED_TO event is received, lookup the rename tracking information
* via the event cookie and use this information to update the watch.
*/
static void rename_dir(uint32_t cookie, const char *path,
struct fsm_listen_data *data)
{
struct rename_entry rek, *re;
struct watch_entry k, *w;
/* lookup a pending rename to match */
rek.cookie = cookie;
hashmap_entry_init(&rek.ent, memhash(&rek.cookie, sizeof(uint32_t)));
re = hashmap_get_entry(&data->renames, &rek, ent, NULL);
if (re) {
k.dir = re->dir;
hashmap_entry_init(&k.ent, strhash(k.dir));
w = hashmap_get_entry(&data->revwatches, &k, ent, NULL);
if (w) {
w->cookie = 0; /* rename handled */
remove_watch(w, data);
if (add_watch(path, data))
trace_printf_key(&trace_fsmonitor,
"failed to add watch for renamed dir '%s'",
path);
} else {
/* Directory was moved out of watch tree */
trace_printf_key(&trace_fsmonitor,
"No matching watch for rename to '%s'", path);
}
hashmap_remove_entry(&data->renames, &rek, ent, NULL);
free(re);
} else {
/* Directory was moved from outside the watch tree */
trace_printf_key(&trace_fsmonitor,
"No matching cookie for rename to '%s'", path);
}
}
/*
* Recursively add watches to every directory under path
*/
static int register_inotify(const char *path,
struct fsmonitor_daemon_state *state,
struct fsmonitor_batch *batch)
{
DIR *dir;
const char *rel;
struct strbuf current = STRBUF_INIT;
struct dirent *de;
struct stat fs;
int ret = -1;
dir = opendir(path);
if (!dir) {
if (errno == ENOENT || errno == ENOTDIR)
return 0; /* directory was deleted */
return error_errno(_("opendir('%s') failed"), path);
}
while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
strbuf_reset(&current);
strbuf_addf(&current, "%s/%s", path, de->d_name);
if (lstat(current.buf, &fs)) {
if (errno == ENOENT)
continue; /* file was deleted */
error_errno(_("lstat('%s') failed"), current.buf);
goto failed;
}
/* recurse into directory */
if (S_ISDIR(fs.st_mode)) {
if (add_watch(current.buf, state->listen_data))
goto failed;
if (register_inotify(current.buf, state, batch))
goto failed;
} else if (batch) {
rel = current.buf + state->path_worktree_watch.len + 1;
trace_printf_key(&trace_fsmonitor, "explicitly adding '%s'", rel);
fsmonitor_batch__add_path(batch, rel);
}
}
ret = 0;
failed:
strbuf_release(&current);
if (closedir(dir) < 0)
return error_errno(_("closedir('%s') failed"), path);
return ret;
}
static int em_rename_dir_from(uint32_t mask)
{
return ((mask & IN_ISDIR) && (mask & IN_MOVED_FROM));
}
static int em_rename_dir_to(uint32_t mask)
{
return ((mask & IN_ISDIR) && (mask & IN_MOVED_TO));
}
static int em_remove_watch(uint32_t mask)
{
return (mask & IN_DELETE_SELF);
}
static int em_dir_renamed(uint32_t mask)
{
return ((mask & IN_ISDIR) && (mask & IN_MOVE));
}
static int em_dir_created(uint32_t mask)
{
return ((mask & IN_ISDIR) && (mask & IN_CREATE));
}
static int em_dir_deleted(uint32_t mask)
{
return ((mask & IN_ISDIR) && (mask & IN_DELETE));
}
static int em_force_shutdown(uint32_t mask)
{
return (mask & IN_UNMOUNT) || (mask & IN_Q_OVERFLOW);
}
static int em_ignore(uint32_t mask)
{
return (mask & IN_IGNORED) || (mask & IN_MOVE_SELF);
}
static void log_mask_set(const char *path, uint32_t mask)
{
struct strbuf msg = STRBUF_INIT;
if (mask & IN_ACCESS)
strbuf_addstr(&msg, "IN_ACCESS|");
if (mask & IN_MODIFY)
strbuf_addstr(&msg, "IN_MODIFY|");
if (mask & IN_ATTRIB)
strbuf_addstr(&msg, "IN_ATTRIB|");
if (mask & IN_CLOSE_WRITE)
strbuf_addstr(&msg, "IN_CLOSE_WRITE|");
if (mask & IN_CLOSE_NOWRITE)
strbuf_addstr(&msg, "IN_CLOSE_NOWRITE|");
if (mask & IN_OPEN)
strbuf_addstr(&msg, "IN_OPEN|");
if (mask & IN_MOVED_FROM)
strbuf_addstr(&msg, "IN_MOVED_FROM|");
if (mask & IN_MOVED_TO)
strbuf_addstr(&msg, "IN_MOVED_TO|");
if (mask & IN_CREATE)
strbuf_addstr(&msg, "IN_CREATE|");
if (mask & IN_DELETE)
strbuf_addstr(&msg, "IN_DELETE|");
if (mask & IN_DELETE_SELF)
strbuf_addstr(&msg, "IN_DELETE_SELF|");
if (mask & IN_MOVE_SELF)
strbuf_addstr(&msg, "IN_MOVE_SELF|");
if (mask & IN_UNMOUNT)
strbuf_addstr(&msg, "IN_UNMOUNT|");
if (mask & IN_Q_OVERFLOW)
strbuf_addstr(&msg, "IN_Q_OVERFLOW|");
if (mask & IN_IGNORED)
strbuf_addstr(&msg, "IN_IGNORED|");
if (mask & IN_ISDIR)
strbuf_addstr(&msg, "IN_ISDIR|");
strbuf_strip_suffix(&msg, "|");
trace_printf_key(&trace_fsmonitor, "inotify_event: '%s', mask=%#8.8x %s",
path, mask, msg.buf);
strbuf_release(&msg);
}
int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
{
int fd;
int ret = 0;
struct fsm_listen_data *data;
CALLOC_ARRAY(data, 1);
state->listen_data = data;
state->listen_error_code = -1;
data->shutdown = SHUTDOWN_ERROR;
fd = inotify_init1(O_NONBLOCK);
if (fd < 0) {
FREE_AND_NULL(state->listen_data);
return error_errno(_("inotify_init1() failed"));
}
data->fd_inotify = fd;
hashmap_init(&data->watches, watch_entry_cmp, NULL, 0);
hashmap_init(&data->renames, rename_entry_cmp, NULL, 0);
hashmap_init(&data->revwatches, revwatches_entry_cmp, NULL, 0);
if (add_watch(state->path_worktree_watch.buf, data))
ret = -1;
else if (register_inotify(state->path_worktree_watch.buf, state, NULL))
ret = -1;
else if (state->nr_paths_watching > 1) {
if (add_watch(state->path_gitdir_watch.buf, data))
ret = -1;
else if (register_inotify(state->path_gitdir_watch.buf, state, NULL))
ret = -1;
}
if (!ret) {
state->listen_error_code = 0;
data->shutdown = SHUTDOWN_CONTINUE;
}
return ret;
}
void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
{
struct fsm_listen_data *data;
struct hashmap_iter iter;
struct watch_entry *w;
struct watch_entry **to_remove;
size_t nr_to_remove = 0, alloc_to_remove = 0;
size_t i;
int fd;
if (!state || !state->listen_data)
return;
data = state->listen_data;
fd = data->fd_inotify;
/*
* Collect all entries first, then remove them.
* We can't modify the hashmap while iterating over it.
*/
to_remove = NULL;
hashmap_for_each_entry(&data->watches, &iter, w, ent) {
ALLOC_GROW(to_remove, nr_to_remove + 1, alloc_to_remove);
to_remove[nr_to_remove++] = w;
}
for (i = 0; i < nr_to_remove; i++) {
to_remove[i]->cookie = 0; /* ignore any pending renames */
remove_watch(to_remove[i], data);
}
free(to_remove);
hashmap_clear(&data->watches);
hashmap_clear(&data->revwatches); /* remove_watch freed the entries */
hashmap_clear_and_free(&data->renames, struct rename_entry, ent);
FREE_AND_NULL(state->listen_data);
if (fd && (close(fd) < 0))
error_errno(_("closing inotify file descriptor failed"));
}
void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
{
if (state && state->listen_data &&
state->listen_data->shutdown == SHUTDOWN_CONTINUE)
state->listen_data->shutdown = SHUTDOWN_STOP;
}
/*
* Process a single inotify event and queue for publication.
*/
static int process_event(const char *path,
const struct inotify_event *event,
struct fsmonitor_batch **batch,
struct string_list *cookie_list,
struct fsmonitor_daemon_state *state)
{
const char *rel;
const char *last_sep;
switch (fsmonitor_classify_path_absolute(state, path)) {
case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
/* Use just the filename of the cookie file. */
last_sep = find_last_dir_sep(path);
string_list_append(cookie_list,
last_sep ? last_sep + 1 : path);
break;
case IS_INSIDE_DOT_GIT:
case IS_INSIDE_GITDIR:
break;
case IS_DOT_GIT:
case IS_GITDIR:
/*
* If .git directory is deleted or renamed away,
* we have to quit.
*/
if (em_dir_deleted(event->mask)) {
trace_printf_key(&trace_fsmonitor,
"event: gitdir removed");
state->listen_data->shutdown = SHUTDOWN_FORCE;
goto done;
}
if (em_dir_renamed(event->mask)) {
trace_printf_key(&trace_fsmonitor,
"event: gitdir renamed");
state->listen_data->shutdown = SHUTDOWN_FORCE;
goto done;
}
break;
case IS_WORKDIR_PATH:
/* normal events in the working directory */
if (trace_pass_fl(&trace_fsmonitor))
log_mask_set(path, event->mask);
if (!*batch)
*batch = fsmonitor_batch__new();
rel = path + state->path_worktree_watch.len + 1;
fsmonitor_batch__add_path(*batch, rel);
if (em_dir_deleted(event->mask))
break;
/* received IN_MOVE_FROM, add tracking for expected IN_MOVE_TO */
if (em_rename_dir_from(event->mask))
add_dir_rename(event->cookie, path, state->listen_data);
/* received IN_MOVE_TO, update watch to reflect new path */
if (em_rename_dir_to(event->mask)) {
rename_dir(event->cookie, path, state->listen_data);
if (register_inotify(path, state, *batch)) {
state->listen_data->shutdown = SHUTDOWN_ERROR;
goto done;
}
}
if (em_dir_created(event->mask)) {
if (add_watch(path, state->listen_data)) {
state->listen_data->shutdown = SHUTDOWN_ERROR;
goto done;
}
if (register_inotify(path, state, *batch)) {
state->listen_data->shutdown = SHUTDOWN_ERROR;
goto done;
}
}
break;
case IS_OUTSIDE_CONE:
default:
trace_printf_key(&trace_fsmonitor,
"ignoring '%s'", path);
break;
}
return 0;
done:
return -1;
}
/*
* Read the inotify event stream and pre-process events before further
* processing and eventual publishing.
*/
static void handle_events(struct fsmonitor_daemon_state *state)
{
/* See https://man7.org/linux/man-pages/man7/inotify.7.html */
char buf[4096]
__attribute__ ((aligned(__alignof__(struct inotify_event))));
struct hashmap *watches = &state->listen_data->watches;
struct fsmonitor_batch *batch = NULL;
struct string_list cookie_list = STRING_LIST_INIT_DUP;
struct watch_entry k, *w;
struct strbuf path = STRBUF_INIT;
const struct inotify_event *event;
int fd = state->listen_data->fd_inotify;
ssize_t len;
char *ptr, *p;
for (;;) {
len = read(fd, buf, sizeof(buf));
if (len == -1) {
if (errno == EAGAIN || errno == EINTR)
goto done;
error_errno(_("reading inotify message stream failed"));
state->listen_data->shutdown = SHUTDOWN_ERROR;
goto done;
}
/* nothing to read */
if (len == 0)
goto done;
/* Loop over all events in the buffer. */
for (ptr = buf; ptr < buf + len;
ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event *)ptr;
if (em_ignore(event->mask))
continue;
/* File system was unmounted or event queue overflowed */
if (em_force_shutdown(event->mask)) {
if (trace_pass_fl(&trace_fsmonitor))
log_mask_set("Forcing shutdown", event->mask);
state->listen_data->shutdown = SHUTDOWN_FORCE;
goto done;
}
k.wd = event->wd;
hashmap_entry_init(&k.ent, memhash(&k.wd, sizeof(int)));
w = hashmap_get_entry(watches, &k, ent, NULL);
if (!w) {
/* Watch was removed, skip event */
continue;
}
/* directory watch was removed */
if (em_remove_watch(event->mask)) {
remove_watch(w, state->listen_data);
continue;
}
strbuf_reset(&path);
strbuf_addf(&path, "%s/%s", w->dir, event->name);
p = fsmonitor__resolve_alias(path.buf, &state->alias);
if (!p)
p = strbuf_detach(&path, NULL);
if (process_event(p, event, &batch, &cookie_list, state)) {
free(p);
goto done;
}
free(p);
}
strbuf_reset(&path);
fsmonitor_publish(state, batch, &cookie_list);
string_list_clear(&cookie_list, 0);
batch = NULL;
}
done:
strbuf_release(&path);
fsmonitor_batch__free_list(batch);
string_list_clear(&cookie_list, 0);
}
/*
* Non-blocking read of the inotify events stream. The inotify fd is polled
* frequently to help minimize the number of queue overflows.
*/
void fsm_listen__loop(struct fsmonitor_daemon_state *state)
{
int poll_num;
const int interval = 1000;
time_t checked = time(NULL);
struct pollfd fds[1];
fds[0].fd = state->listen_data->fd_inotify;
fds[0].events = POLLIN;
/*
* Our fs event listener is now running, so it's safe to start
* serving client requests.
*/
ipc_server_start_async(state->ipc_server_data);
for (;;) {
switch (state->listen_data->shutdown) {
case SHUTDOWN_CONTINUE:
poll_num = poll(fds, 1, 1);
if (poll_num == -1) {
if (errno == EINTR)
continue;
error_errno(_("polling inotify message stream failed"));
state->listen_data->shutdown = SHUTDOWN_ERROR;
continue;
}
if ((time(NULL) - checked) >= interval) {
checked = time(NULL);
if (check_stale_dir_renames(&state->listen_data->renames,
checked - interval)) {
trace_printf_key(&trace_fsmonitor,
"Missed IN_MOVED_TO events, forcing shutdown");
state->listen_data->shutdown = SHUTDOWN_FORCE;
continue;
}
}
if (poll_num > 0 && (fds[0].revents & POLLIN))
handle_events(state);
continue;
case SHUTDOWN_ERROR:
state->listen_error_code = -1;
ipc_server_stop_async(state->ipc_server_data);
break;
case SHUTDOWN_FORCE:
state->listen_error_code = 0;
ipc_server_stop_async(state->ipc_server_data);
break;
case SHUTDOWN_STOP:
default:
state->listen_error_code = 0;
break;
}
return;
}
}

View File

@ -0,0 +1,223 @@
#include "git-compat-util.h"
#include "fsmonitor-ll.h"
#include "fsmonitor-path-utils.h"
#include "gettext.h"
#include "trace.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/statfs.h>
#ifdef HAVE_LINUX_MAGIC_H
#include <linux/magic.h>
#endif
/*
* Filesystem magic numbers for remote filesystems.
* Defined here if not available in linux/magic.h.
*/
#ifndef CIFS_SUPER_MAGIC
#define CIFS_SUPER_MAGIC 0xff534d42
#endif
#ifndef SMB_SUPER_MAGIC
#define SMB_SUPER_MAGIC 0x517b
#endif
#ifndef SMB2_SUPER_MAGIC
#define SMB2_SUPER_MAGIC 0xfe534d42
#endif
#ifndef NFS_SUPER_MAGIC
#define NFS_SUPER_MAGIC 0x6969
#endif
#ifndef AFS_SUPER_MAGIC
#define AFS_SUPER_MAGIC 0x5346414f
#endif
#ifndef CODA_SUPER_MAGIC
#define CODA_SUPER_MAGIC 0x73757245
#endif
#ifndef V9FS_MAGIC
#define V9FS_MAGIC 0x01021997
#endif
#ifndef FUSE_SUPER_MAGIC
#define FUSE_SUPER_MAGIC 0x65735546
#endif
/*
* Check if filesystem type is a remote filesystem.
*/
static int is_remote_fs(unsigned long f_type)
{
switch (f_type) {
case CIFS_SUPER_MAGIC:
case SMB_SUPER_MAGIC:
case SMB2_SUPER_MAGIC:
case NFS_SUPER_MAGIC:
case AFS_SUPER_MAGIC:
case CODA_SUPER_MAGIC:
case FUSE_SUPER_MAGIC:
return 1;
default:
return 0;
}
}
/*
* Get the filesystem type name for logging purposes.
*/
static const char *get_fs_typename(unsigned long f_type)
{
switch (f_type) {
case CIFS_SUPER_MAGIC:
return "cifs";
case SMB_SUPER_MAGIC:
return "smb";
case SMB2_SUPER_MAGIC:
return "smb2";
case NFS_SUPER_MAGIC:
return "nfs";
case AFS_SUPER_MAGIC:
return "afs";
case CODA_SUPER_MAGIC:
return "coda";
case V9FS_MAGIC:
return "9p";
case FUSE_SUPER_MAGIC:
return "fuse";
default:
return "unknown";
}
}
/*
* Find the mount point for a given path by reading /proc/mounts.
* Returns the filesystem type for the longest matching mount point.
*/
static char *find_mount(const char *path, struct statfs *fs)
{
FILE *fp;
struct strbuf line = STRBUF_INIT;
struct strbuf match = STRBUF_INIT;
struct strbuf fstype = STRBUF_INIT;
char *result = NULL;
struct statfs path_fs;
if (statfs(path, &path_fs) < 0)
return NULL;
fp = fopen("/proc/mounts", "r");
if (!fp)
return NULL;
while (strbuf_getline(&line, fp) != EOF) {
char *fields[6];
char *p = line.buf;
int i;
/* Parse mount entry: device mountpoint fstype options dump pass */
for (i = 0; i < 6 && p; i++) {
fields[i] = p;
p = strchr(p, ' ');
if (p)
*p++ = '\0';
}
if (i >= 3) {
const char *mountpoint = fields[1];
const char *type = fields[2];
struct statfs mount_fs;
/* Check if this mount point is a prefix of our path */
if (starts_with(path, mountpoint) &&
(path[strlen(mountpoint)] == '/' ||
path[strlen(mountpoint)] == '\0')) {
/* Check if filesystem ID matches */
if (statfs(mountpoint, &mount_fs) == 0 &&
!memcmp(&mount_fs.f_fsid, &path_fs.f_fsid,
sizeof(mount_fs.f_fsid))) {
/* Keep the longest matching mount point */
if (strlen(mountpoint) > match.len) {
strbuf_reset(&match);
strbuf_addstr(&match, mountpoint);
strbuf_reset(&fstype);
strbuf_addstr(&fstype, type);
*fs = mount_fs;
}
}
}
}
}
fclose(fp);
strbuf_release(&line);
strbuf_release(&match);
if (fstype.len)
result = strbuf_detach(&fstype, NULL);
else
strbuf_release(&fstype);
return result;
}
int fsmonitor__get_fs_info(const char *path, struct fs_info *fs_info)
{
struct statfs fs;
if (statfs(path, &fs) == -1) {
int saved_errno = errno;
trace_printf_key(&trace_fsmonitor, "statfs('%s') failed: %s",
path, strerror(saved_errno));
errno = saved_errno;
return -1;
}
trace_printf_key(&trace_fsmonitor,
"statfs('%s') [type 0x%08lx]",
path, (unsigned long)fs.f_type);
fs_info->is_remote = is_remote_fs(fs.f_type);
/*
* Try to get filesystem type from /proc/mounts for a more
* descriptive name.
*/
fs_info->typename = find_mount(path, &fs);
if (!fs_info->typename)
fs_info->typename = xstrdup(get_fs_typename(fs.f_type));
trace_printf_key(&trace_fsmonitor,
"'%s' is_remote: %d, typename: %s",
path, fs_info->is_remote, fs_info->typename);
return 0;
}
int fsmonitor__is_fs_remote(const char *path)
{
struct fs_info fs;
if (fsmonitor__get_fs_info(path, &fs))
return -1;
free(fs.typename);
return fs.is_remote;
}
/*
* No-op for Linux - we don't have firmlinks like macOS.
*/
int fsmonitor__get_alias(const char *path UNUSED,
struct alias_info *info UNUSED)
{
return 0;
}
/*
* No-op for Linux - we don't have firmlinks like macOS.
*/
char *fsmonitor__resolve_alias(const char *path UNUSED,
const struct alias_info *info UNUSED)
{
return NULL;
}

View File

@ -0,0 +1,71 @@
#include "git-compat-util.h"
#include "config.h"
#include "fsmonitor-ll.h"
#include "fsmonitor-ipc.h"
#include "fsmonitor-settings.h"
#include "fsmonitor-path-utils.h"
#include <libgen.h>
/*
* For the builtin FSMonitor, we create the Unix domain socket for the
* IPC in the .git directory. If the working directory is remote,
* then the socket will be created on the remote file system. This
* can fail if the remote file system does not support UDS file types
* (e.g. smbfs to a Windows server) or if the remote kernel does not
* allow a non-local process to bind() the socket. (These problems
* could be fixed by moving the UDS out of the .git directory and to a
* well-known local directory on the client machine, but care should
* be taken to ensure that $HOME is actually local and not a managed
* file share.)
*
* FAT32 and NTFS working directories are problematic too.
*
* The builtin FSMonitor uses a Unix domain socket in the .git
* directory for IPC. These Windows drive formats do not support
* Unix domain sockets, so mark them as incompatible for the daemon.
*/
static enum fsmonitor_reason check_uds_volume(struct repository *r)
{
struct fs_info fs;
const char *ipc_path = fsmonitor_ipc__get_path(r);
char *path;
char *dir;
/*
* Create a copy for dirname() since it may modify its argument.
*/
path = xstrdup(ipc_path);
dir = dirname(path);
if (fsmonitor__get_fs_info(dir, &fs) == -1) {
free(path);
return FSMONITOR_REASON_ERROR;
}
free(path);
if (fs.is_remote ||
!strcmp(fs.typename, "msdos") ||
!strcmp(fs.typename, "ntfs") ||
!strcmp(fs.typename, "vfat")) {
free(fs.typename);
return FSMONITOR_REASON_NOSOCKETS;
}
free(fs.typename);
return FSMONITOR_REASON_OK;
}
enum fsmonitor_reason fsm_os__incompatible(struct repository *r, int ipc)
{
enum fsmonitor_reason reason;
if (ipc) {
reason = check_uds_volume(r);
if (reason != FSMONITOR_REASON_OK)
return reason;
}
return FSMONITOR_REASON_OK;
}

111
compat/ivec.c Normal file
View File

@ -0,0 +1,111 @@
#include "ivec.h"
struct IVec_c_void {
void *ptr;
size_t length;
size_t capacity;
size_t element_size;
};
static void _set_capacity(void *self_, size_t new_capacity)
{
struct IVec_c_void *self = self_;
if (new_capacity == self->capacity) {
return;
}
if (new_capacity == 0) {
FREE_AND_NULL(self->ptr);
} else {
self->ptr = realloc(self->ptr, new_capacity * self->element_size);
}
self->capacity = new_capacity;
}
void ivec_init(void *self_, size_t element_size)
{
struct IVec_c_void *self = self_;
self->ptr = NULL;
self->length = 0;
self->capacity = 0;
self->element_size = element_size;
}
void ivec_zero(void *self_, size_t capacity)
{
struct IVec_c_void *self = self_;
self->ptr = calloc(capacity, self->element_size);
self->length = capacity;
self->capacity = capacity;
// DO NOT MODIFY element_size!!!
}
void ivec_reserve_exact(void *self_, size_t additional)
{
struct IVec_c_void *self = self_;
_set_capacity(self, self->capacity + additional);
}
void ivec_reserve(void *self_, size_t additional)
{
struct IVec_c_void *self = self_;
size_t growby = 128;
if (self->capacity > growby)
growby = self->capacity;
if (additional > growby)
growby = additional;
_set_capacity(self, self->capacity + growby);
}
void ivec_shrink_to_fit(void *self_)
{
struct IVec_c_void *self = self_;
_set_capacity(self, self->length);
}
void ivec_push(void *self_, const void *value)
{
struct IVec_c_void *self = self_;
void *dst = NULL;
if (self->length == self->capacity)
ivec_reserve(self, 1);
dst = (uint8_t*)self->ptr + self->length * self->element_size;
memcpy(dst, value, self->element_size);
self->length++;
}
void ivec_free(void *self_)
{
struct IVec_c_void *self = self_;
FREE_AND_NULL(self->ptr);
self->length = 0;
self->capacity = 0;
// DO NOT MODIFY element_size!!!
}
void ivec_move(void *src_, void *dst_)
{
struct IVec_c_void *src = src_;
struct IVec_c_void *dst = dst_;
ivec_free(dst);
dst->ptr = src->ptr;
dst->length = src->length;
dst->capacity = src->capacity;
// DO NOT MODIFY element_size!!!
src->ptr = NULL;
src->length = 0;
src->capacity = 0;
// DO NOT MODIFY element_size!!!
}

52
compat/ivec.h Normal file
View File

@ -0,0 +1,52 @@
#ifndef IVEC_H
#define IVEC_H
#include <git-compat-util.h>
#define IVEC_INIT(variable) ivec_init(&(variable), sizeof(*(variable).ptr))
#ifndef CBINDGEN
#define DEFINE_IVEC_TYPE(type, suffix) \
struct IVec_##suffix { \
type* ptr; \
size_t length; \
size_t capacity; \
size_t element_size; \
}
DEFINE_IVEC_TYPE(bool, bool);
DEFINE_IVEC_TYPE(uint8_t, u8);
DEFINE_IVEC_TYPE(uint16_t, u16);
DEFINE_IVEC_TYPE(uint32_t, u32);
DEFINE_IVEC_TYPE(uint64_t, u64);
DEFINE_IVEC_TYPE(int8_t, i8);
DEFINE_IVEC_TYPE(int16_t, i16);
DEFINE_IVEC_TYPE(int32_t, i32);
DEFINE_IVEC_TYPE(int64_t, i64);
DEFINE_IVEC_TYPE(float, f32);
DEFINE_IVEC_TYPE(double, f64);
DEFINE_IVEC_TYPE(size_t, usize);
DEFINE_IVEC_TYPE(ssize_t, isize);
#endif
void ivec_init(void *self_, size_t element_size);
void ivec_zero(void *self_, size_t capacity);
void ivec_reserve_exact(void *self_, size_t additional);
void ivec_reserve(void *self_, size_t additional);
void ivec_shrink_to_fit(void *self_);
void ivec_push(void *self_, const void *value);
void ivec_free(void *self_);
void ivec_move(void *src, void *dst);
#endif /* IVEC_H */

View File

@ -121,10 +121,6 @@ struct utsname {
* trivial stubs
*/
static inline int readlink(const char *path UNUSED, char *buf UNUSED, size_t bufsiz UNUSED)
{ errno = ENOSYS; return -1; }
static inline int symlink(const char *oldpath UNUSED, const char *newpath UNUSED)
{ errno = ENOSYS; return -1; }
static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED)
{ errno = ENOSYS; return -1; }
#ifndef __MINGW64_VERSION_MAJOR
@ -197,6 +193,8 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);
int uname(struct utsname *buf);
int symlink(const char *target, const char *link);
int readlink(const char *path, char *buf, size_t bufsiz);
/*
* replacements of existing functions

View File

@ -21,14 +21,13 @@
#define SECURITY_WIN32
#include <sspi.h>
#include <wchar.h>
#include <winioctl.h>
#include <winternl.h>
#define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
#define HCAST(type, handle) ((type)(intptr_t)handle)
static const int delay[] = { 0, 1, 10, 20, 40 };
void open_in_gdb(void)
{
static struct child_process cp = CHILD_PROCESS_INIT;
@ -103,6 +102,7 @@ int err_win_to_posix(DWORD winerr)
case ERROR_INVALID_PARAMETER: error = EINVAL; break;
case ERROR_INVALID_PASSWORD: error = EPERM; break;
case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
case ERROR_INVALID_REPARSE_DATA: error = EINVAL; break;
case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
case ERROR_INVALID_WORKSTATION: error = EACCES; break;
@ -117,6 +117,7 @@ int err_win_to_posix(DWORD winerr)
case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
case ERROR_NOACCESS: error = EFAULT; break;
case ERROR_NONE_MAPPED: error = EINVAL; break;
case ERROR_NOT_A_REPARSE_POINT: error = EINVAL; break;
case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
case ERROR_NOT_READY: error = EAGAIN; break;
case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
@ -137,6 +138,9 @@ int err_win_to_posix(DWORD winerr)
case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
case ERROR_READ_FAULT: error = EIO; break;
case ERROR_REPARSE_ATTRIBUTE_CONFLICT: error = EINVAL; break;
case ERROR_REPARSE_TAG_INVALID: error = EINVAL; break;
case ERROR_REPARSE_TAG_MISMATCH: error = EINVAL; break;
case ERROR_SEEK: error = EIO; break;
case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
@ -204,15 +208,12 @@ static int read_yes_no_answer(void)
return -1;
}
static int ask_yes_no_if_possible(const char *format, ...)
static int ask_yes_no_if_possible(const char *format, va_list args)
{
char question[4096];
const char *retry_hook;
va_list args;
va_start(args, format);
vsnprintf(question, sizeof(question), format, args);
va_end(args);
retry_hook = mingw_getenv("GIT_ASK_YESNO");
if (retry_hook) {
@ -237,6 +238,31 @@ static int ask_yes_no_if_possible(const char *format, ...)
}
}
static int retry_ask_yes_no(int *tries, const char *format, ...)
{
static const int delay[] = { 0, 1, 10, 20, 40 };
va_list args;
int result, saved_errno = errno;
if ((*tries) < ARRAY_SIZE(delay)) {
/*
* We assume that some other process had the file open at the wrong
* moment and retry. In order to give the other process a higher
* chance to complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
*/
Sleep(delay[*tries]);
(*tries)++;
return 1;
}
va_start(args, format);
result = ask_yes_no_if_possible(format, args);
va_end(args);
errno = saved_errno;
return result;
}
/* Windows only */
enum hide_dotfiles_type {
HIDE_DOTFILES_FALSE = 0,
@ -270,6 +296,134 @@ int mingw_core_config(const char *var, const char *value,
return 0;
}
static inline int is_wdir_sep(wchar_t wchar)
{
return wchar == L'/' || wchar == L'\\';
}
static const wchar_t *make_relative_to(const wchar_t *path,
const wchar_t *relative_to, wchar_t *out,
size_t size)
{
size_t i = wcslen(relative_to), len;
/* Is `path` already absolute? */
if (is_wdir_sep(path[0]) ||
(iswalpha(path[0]) && path[1] == L':' && is_wdir_sep(path[2])))
return path;
while (i > 0 && !is_wdir_sep(relative_to[i - 1]))
i--;
/* Is `relative_to` in the current directory? */
if (!i)
return path;
len = wcslen(path);
if (i + len + 1 > size) {
error("Could not make '%ls' relative to '%ls' (too large)",
path, relative_to);
return NULL;
}
memcpy(out, relative_to, i * sizeof(wchar_t));
wcscpy(out + i, path);
return out;
}
static DWORD symlink_file_flags = 0, symlink_directory_flags = 1;
enum phantom_symlink_result {
PHANTOM_SYMLINK_RETRY,
PHANTOM_SYMLINK_DONE,
PHANTOM_SYMLINK_DIRECTORY
};
/*
* Changes a file symlink to a directory symlink if the target exists and is a
* directory.
*/
static enum phantom_symlink_result
process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
{
HANDLE hnd;
BY_HANDLE_FILE_INFORMATION fdata;
wchar_t relative[MAX_PATH];
const wchar_t *rel;
/* check that wlink is still a file symlink */
if ((GetFileAttributesW(wlink)
& (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
!= FILE_ATTRIBUTE_REPARSE_POINT)
return PHANTOM_SYMLINK_DONE;
/* make it relative, if necessary */
rel = make_relative_to(wtarget, wlink, relative, ARRAY_SIZE(relative));
if (!rel)
return PHANTOM_SYMLINK_DONE;
/* let Windows resolve the link by opening it */
hnd = CreateFileW(rel, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hnd == INVALID_HANDLE_VALUE) {
errno = err_win_to_posix(GetLastError());
return PHANTOM_SYMLINK_RETRY;
}
if (!GetFileInformationByHandle(hnd, &fdata)) {
errno = err_win_to_posix(GetLastError());
CloseHandle(hnd);
return PHANTOM_SYMLINK_RETRY;
}
CloseHandle(hnd);
/* if target exists and is a file, we're done */
if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
return PHANTOM_SYMLINK_DONE;
/* otherwise recreate the symlink with directory flag */
if (DeleteFileW(wlink) &&
CreateSymbolicLinkW(wlink, wtarget, symlink_directory_flags))
return PHANTOM_SYMLINK_DIRECTORY;
errno = err_win_to_posix(GetLastError());
return PHANTOM_SYMLINK_RETRY;
}
/* keep track of newly created symlinks to non-existing targets */
struct phantom_symlink_info {
struct phantom_symlink_info *next;
wchar_t *wlink;
wchar_t *wtarget;
};
static struct phantom_symlink_info *phantom_symlinks = NULL;
static CRITICAL_SECTION phantom_symlinks_cs;
static void process_phantom_symlinks(void)
{
struct phantom_symlink_info *current, **psi;
EnterCriticalSection(&phantom_symlinks_cs);
/* process phantom symlinks list */
psi = &phantom_symlinks;
while ((current = *psi)) {
enum phantom_symlink_result result = process_phantom_symlink(
current->wtarget, current->wlink);
if (result == PHANTOM_SYMLINK_RETRY) {
psi = &current->next;
} else {
/* symlink was processed, remove from list */
*psi = current->next;
free(current);
/* if symlink was a directory, start over */
if (result == PHANTOM_SYMLINK_DIRECTORY)
psi = &phantom_symlinks;
}
}
LeaveCriticalSection(&phantom_symlinks_cs);
}
/* Normalizes NT paths as returned by some low-level APIs. */
static wchar_t *normalize_ntpath(wchar_t *wbuf)
{
@ -297,7 +451,7 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
int mingw_unlink(const char *pathname, int handle_in_use_error)
{
int ret, tries = 0;
int tries = 0;
wchar_t wpathname[MAX_PATH];
if (xutftowcs_path(wpathname, pathname) < 0)
return -1;
@ -305,29 +459,26 @@ int mingw_unlink(const char *pathname, int handle_in_use_error)
if (DeleteFileW(wpathname))
return 0;
do {
/* read-only files cannot be removed */
_wchmod(wpathname, 0666);
while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
if (!_wunlink(wpathname))
return 0;
if (!is_file_in_use_error(GetLastError()))
break;
if (!handle_in_use_error)
return ret;
/*
* We assume that some other process had the source or
* destination file open at the wrong moment and retry.
* In order to give the other process a higher chance to
* complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
* _wunlink() / DeleteFileW() for directory symlinks fails with
* ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the
* same error we get if a file is in use (already checked above).
*/
Sleep(delay[tries]);
tries++;
}
while (ret == -1 && is_file_in_use_error(GetLastError()) &&
ask_yes_no_if_possible("Unlink of file '%s' failed. "
"Should I try again?", pathname))
ret = _wunlink(wpathname);
return ret;
if (!_wrmdir(wpathname))
return 0;
if (!handle_in_use_error)
return -1;
} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
"Should I try again?", pathname));
return -1;
}
static int is_dir_empty(const wchar_t *wpath)
@ -354,7 +505,7 @@ static int is_dir_empty(const wchar_t *wpath)
int mingw_rmdir(const char *pathname)
{
int ret, tries = 0;
int tries = 0;
wchar_t wpathname[MAX_PATH];
struct stat st;
@ -380,7 +531,11 @@ int mingw_rmdir(const char *pathname)
if (xutftowcs_path(wpathname, pathname) < 0)
return -1;
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
do {
if (!_wrmdir(wpathname)) {
invalidate_lstat_cache();
return 0;
}
if (!is_file_in_use_error(GetLastError()))
errno = err_win_to_posix(GetLastError());
if (errno != EACCES)
@ -389,23 +544,9 @@ int mingw_rmdir(const char *pathname)
errno = ENOTEMPTY;
break;
}
/*
* We assume that some other process had the source or
* destination file open at the wrong moment and retry.
* In order to give the other process a higher chance to
* complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
*/
Sleep(delay[tries]);
tries++;
}
while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
ask_yes_no_if_possible("Deletion of directory '%s' failed. "
"Should I try again?", pathname))
ret = _wrmdir(wpathname);
if (!ret)
invalidate_lstat_cache();
return ret;
} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
"Should I try again?", pathname));
return -1;
}
static inline int needs_hiding(const char *path)
@ -466,6 +607,8 @@ int mingw_mkdir(const char *path, int mode UNUSED)
if (xutftowcs_path(wpath, path) < 0)
return -1;
ret = _wmkdir(wpath);
if (!ret)
process_phantom_symlinks();
if (!ret && needs_hiding(path))
return set_hidden_flag(wpath, 1);
return ret;
@ -853,9 +996,27 @@ int mingw_access(const char *filename, int mode)
int mingw_chdir(const char *dirname)
{
wchar_t wdirname[MAX_PATH];
if (xutftowcs_path(wdirname, dirname) < 0)
return -1;
return _wchdir(wdirname);
if (has_symlinks) {
HANDLE hnd = CreateFileW(wdirname, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hnd == INVALID_HANDLE_VALUE) {
errno = err_win_to_posix(GetLastError());
return -1;
}
if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) {
errno = err_win_to_posix(GetLastError());
CloseHandle(hnd);
return -1;
}
CloseHandle(hnd);
}
return _wchdir(normalize_ntpath(wdirname));
}
int mingw_chmod(const char *filename, int mode)
@ -917,53 +1078,139 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
return 1;
}
/* We keep the do_lstat code in a separate function to avoid recursion.
* When a path ends with a slash, the stat will fail with ENOENT. In
* this case, we strip the trailing slashes and stat again.
*
* If follow is true then act like stat() and report on the link
* target. Otherwise report on the link itself.
#ifndef _WINNT_H
/*
* The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in
* ntifs.h) and in MSYS1's winnt.h (which defines _WINNT_H). So define
* it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_).
*/
static int do_lstat(int follow, const char *file_name, struct stat *buf)
typedef struct _REPARSE_DATA_BUFFER {
DWORD ReparseTag;
WORD ReparseDataLength;
WORD Reserved;
#ifndef _MSC_VER
_ANONYMOUS_UNION
#endif
union {
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct {
WORD SubstituteNameOffset;
WORD SubstituteNameLength;
WORD PrintNameOffset;
WORD PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
BYTE DataBuffer[1];
} GenericReparseBuffer;
} DUMMYUNIONNAME;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#endif
static int read_reparse_point(const WCHAR *wpath, BOOL fail_on_unknown_tag,
char *tmpbuf, int *plen, DWORD *ptag)
{
HANDLE handle;
WCHAR *wbuf;
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
DWORD dummy;
/* read reparse point data */
handle = CreateFileW(wpath, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
if (handle == INVALID_HANDLE_VALUE) {
errno = err_win_to_posix(GetLastError());
return -1;
}
if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b,
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) {
errno = err_win_to_posix(GetLastError());
CloseHandle(handle);
return -1;
}
CloseHandle(handle);
/* get target path for symlinks or mount points (aka 'junctions') */
switch ((*ptag = b->ReparseTag)) {
case IO_REPARSE_TAG_SYMLINK:
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
*(WCHAR*) (((char*) wbuf)
+ b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0;
break;
case IO_REPARSE_TAG_MOUNT_POINT:
wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer)
+ b->MountPointReparseBuffer.SubstituteNameOffset);
*(WCHAR*) (((char*) wbuf)
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
break;
default:
if (fail_on_unknown_tag) {
errno = EINVAL;
return -1;
} else {
*plen = MAX_PATH;
return 0;
}
}
if ((*plen =
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_PATH)) < 0)
return -1;
return 0;
}
int mingw_lstat(const char *file_name, struct stat *buf)
{
WIN32_FILE_ATTRIBUTE_DATA fdata;
DWORD reparse_tag = 0;
int link_len = 0;
wchar_t wfilename[MAX_PATH];
if (xutftowcs_path(wfilename, file_name) < 0)
int wlen = xutftowcs_path(wfilename, file_name);
if (wlen < 0)
return -1;
/* strip trailing '/', or GetFileAttributes will fail */
while (wlen && is_dir_sep(wfilename[wlen - 1]))
wfilename[--wlen] = 0;
if (!wlen) {
errno = ENOENT;
return -1;
}
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
/* for reparse points, get the link tag and length */
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
char tmpbuf[MAX_PATH];
if (read_reparse_point(wfilename, FALSE, tmpbuf,
&link_len, &reparse_tag) < 0)
return -1;
}
buf->st_ino = 0;
buf->st_gid = 0;
buf->st_uid = 0;
buf->st_nlink = 1;
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
buf->st_size = fdata.nFileSizeLow |
(((off_t)fdata.nFileSizeHigh)<<32);
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
reparse_tag);
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
WIN32_FIND_DATAW findbuf;
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
if (handle != INVALID_HANDLE_VALUE) {
if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
if (follow) {
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
} else {
buf->st_mode = S_IFLNK;
}
buf->st_mode |= S_IREAD;
if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
buf->st_mode |= S_IWRITE;
}
FindClose(handle);
}
}
return 0;
}
switch (GetLastError()) {
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
@ -990,39 +1237,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
return -1;
}
/* We provide our own lstat/fstat functions, since the provided
* lstat/fstat functions are so slow. These stat functions are
* tailored for Git's usage (read: fast), and are not meant to be
* complete. Note that Git stat()s are redirected to mingw_lstat()
* too, since Windows doesn't really handle symlinks that well.
*/
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
{
size_t namelen;
char alt_name[PATH_MAX];
if (!do_lstat(follow, file_name, buf))
return 0;
/* if file_name ended in a '/', Windows returned ENOENT;
* try again without trailing slashes
*/
if (errno != ENOENT)
return -1;
namelen = strlen(file_name);
if (namelen && file_name[namelen-1] != '/')
return -1;
while (namelen && file_name[namelen-1] == '/')
--namelen;
if (!namelen || namelen >= PATH_MAX)
return -1;
memcpy(alt_name, file_name, namelen);
alt_name[namelen] = 0;
return do_lstat(follow, alt_name, buf);
}
static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
{
BY_HANDLE_FILE_INFORMATION fdata;
@ -1036,7 +1250,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
buf->st_gid = 0;
buf->st_uid = 0;
buf->st_nlink = 1;
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0);
buf->st_size = fdata.nFileSizeLow |
(((off_t)fdata.nFileSizeHigh)<<32);
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
@ -1046,13 +1260,37 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
return 0;
}
int mingw_lstat(const char *file_name, struct stat *buf)
{
return do_stat_internal(0, file_name, buf);
}
int mingw_stat(const char *file_name, struct stat *buf)
{
return do_stat_internal(1, file_name, buf);
wchar_t wfile_name[MAX_PATH];
HANDLE hnd;
int result;
/* open the file and let Windows resolve the links */
if (xutftowcs_path(wfile_name, file_name) < 0)
return -1;
hnd = CreateFileW(wfile_name, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hnd == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
if (err == ERROR_ACCESS_DENIED &&
!mingw_lstat(file_name, buf) &&
!S_ISLNK(buf->st_mode))
/*
* POSIX semantics state to still try to fill
* information, even if permission is denied to create
* a file handle.
*/
return 0;
errno = err_win_to_posix(err);
return -1;
}
result = get_file_info_by_handle(hnd, buf);
CloseHandle(hnd);
return result;
}
int mingw_fstat(int fd, struct stat *buf)
@ -1239,18 +1477,16 @@ char *mingw_getcwd(char *pointer, int len)
{
wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
HANDLE hnd;
if (!ret || ret >= ARRAY_SIZE(cwd)) {
errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
return NULL;
}
ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
HANDLE hnd = CreateFileW(cwd, 0,
hnd = CreateFileW(cwd, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hnd == INVALID_HANDLE_VALUE)
return NULL;
if (hnd != INVALID_HANDLE_VALUE) {
ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
CloseHandle(hnd);
if (!ret || ret >= ARRAY_SIZE(wpointer))
@ -1259,13 +1495,11 @@ char *mingw_getcwd(char *pointer, int len)
return NULL;
return pointer;
}
if (!ret || ret >= ARRAY_SIZE(wpointer))
return NULL;
if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) {
if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) {
errno = ENOENT;
return NULL;
}
if (xwcstoutf(pointer, wpointer, len) < 0)
if (xwcstoutf(pointer, cwd, len) < 0)
return NULL;
convert_slashes(pointer);
return pointer;
@ -1986,6 +2220,16 @@ int mingw_kill(pid_t pid, int sig)
CloseHandle(h);
return 0;
}
/*
* OpenProcess returns ERROR_INVALID_PARAMETER for
* non-existent PIDs. Map this to ESRCH for POSIX
* compatibility with kill(pid, 0).
*/
if (GetLastError() == ERROR_INVALID_PARAMETER)
errno = ESRCH;
else
errno = err_win_to_posix(GetLastError());
return -1;
}
errno = EINVAL;
@ -2201,7 +2445,7 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
int mingw_rename(const char *pold, const char *pnew)
{
static int supports_file_rename_info_ex = 1;
DWORD attrs, gle;
DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
int tries = 0;
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
int wpnew_len;
@ -2212,15 +2456,6 @@ int mingw_rename(const char *pold, const char *pnew)
if (wpnew_len < 0)
return -1;
/*
* Try native rename() first to get errno right.
* It is based on MoveFile(), which cannot overwrite existing files.
*/
if (!_wrename(wpold, wpnew))
return 0;
if (errno != EEXIST)
return -1;
repeat:
if (supports_file_rename_info_ex) {
/*
@ -2296,13 +2531,22 @@ repeat:
* to retry.
*/
} else {
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
if (MoveFileExW(wpold, wpnew,
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
return 0;
gle = GetLastError();
}
/* TODO: translate more errors */
if (gle == ERROR_ACCESS_DENIED &&
/* revert file attributes on failure */
if (attrs != INVALID_FILE_ATTRIBUTES)
SetFileAttributesW(wpnew, attrs);
if (!is_file_in_use_error(gle)) {
errno = err_win_to_posix(gle);
return -1;
}
if (attrs == INVALID_FILE_ATTRIBUTES &&
(attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
DWORD attrsold = GetFileAttributesW(wpold);
@ -2314,28 +2558,10 @@ repeat:
return -1;
}
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
return 0;
gle = GetLastError();
/* revert file attributes on failure */
SetFileAttributesW(wpnew, attrs);
}
}
if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
/*
* We assume that some other process had the source or
* destination file open at the wrong moment and retry.
* In order to give the other process a higher chance to
* complete its operation, we give up our time slice now.
* If we have to retry again, we do sleep a bit.
*/
Sleep(delay[tries]);
tries++;
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
goto repeat;
}
if (gle == ERROR_ACCESS_DENIED &&
ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
"Should I try again?", pold, pnew))
goto repeat;
@ -2624,6 +2850,94 @@ int link(const char *oldpath, const char *newpath)
return 0;
}
int symlink(const char *target, const char *link)
{
wchar_t wtarget[MAX_PATH], wlink[MAX_PATH];
int len;
/* fail if symlinks are disabled or API is not supported (WinXP) */
if (!has_symlinks) {
errno = ENOSYS;
return -1;
}
if ((len = xutftowcs_path(wtarget, target)) < 0
|| xutftowcs_path(wlink, link) < 0)
return -1;
/* convert target dir separators to backslashes */
while (len--)
if (wtarget[len] == '/')
wtarget[len] = '\\';
/* create file symlink */
if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) {
errno = err_win_to_posix(GetLastError());
return -1;
}
/* convert to directory symlink if target exists */
switch (process_phantom_symlink(wtarget, wlink)) {
case PHANTOM_SYMLINK_RETRY: {
/* if target doesn't exist, add to phantom symlinks list */
wchar_t wfullpath[MAX_PATH];
struct phantom_symlink_info *psi;
/* convert to absolute path to be independent of cwd */
len = GetFullPathNameW(wlink, MAX_PATH, wfullpath, NULL);
if (!len || len >= MAX_PATH) {
errno = err_win_to_posix(GetLastError());
return -1;
}
/* over-allocate and fill phantom_symlink_info structure */
psi = xmalloc(sizeof(struct phantom_symlink_info)
+ sizeof(wchar_t) * (len + wcslen(wtarget) + 2));
psi->wlink = (wchar_t *)(psi + 1);
wcscpy(psi->wlink, wfullpath);
psi->wtarget = psi->wlink + len + 1;
wcscpy(psi->wtarget, wtarget);
EnterCriticalSection(&phantom_symlinks_cs);
psi->next = phantom_symlinks;
phantom_symlinks = psi;
LeaveCriticalSection(&phantom_symlinks_cs);
break;
}
case PHANTOM_SYMLINK_DIRECTORY:
/* if we created a dir symlink, process other phantom symlinks */
process_phantom_symlinks();
break;
default:
break;
}
return 0;
}
int readlink(const char *path, char *buf, size_t bufsiz)
{
WCHAR wpath[MAX_PATH];
char tmpbuf[MAX_PATH];
int len;
DWORD tag;
if (xutftowcs_path(wpath, path) < 0)
return -1;
if (read_reparse_point(wpath, TRUE, tmpbuf, &len, &tag) < 0)
return -1;
/*
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
* condition. There is no conversion function that produces invalid UTF-8,
* so convert to a (hopefully large enough) temporary buffer, then memcpy
* the requested number of bytes (including '\0' for robustness).
*/
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
return min(bufsiz, len);
}
pid_t waitpid(pid_t pid, int *status, int options)
{
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
@ -2812,6 +3126,15 @@ static void setup_windows_environment(void)
if (!tmp && (tmp = getenv("USERPROFILE")))
setenv("HOME", tmp, 1);
}
/*
* Change 'core.symlinks' default to false, unless native symlinks are
* enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
* run the test suite (which doesn't obey config files) with or without
* symlink support.
*/
if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
has_symlinks = 0;
}
static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
@ -3225,6 +3548,24 @@ static void maybe_redirect_std_handles(void)
GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
}
static void adjust_symlink_flags(void)
{
/*
* Starting with Windows 10 Build 14972, symbolic links can be created
* using CreateSymbolicLink() without elevation by passing the flag
* SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x02) as last
* parameter, provided the Developer Mode has been enabled. Some
* earlier Windows versions complain about this flag with an
* ERROR_INVALID_PARAMETER, hence we have to test the build number
* specifically.
*/
if (GetVersion() >= 14972 << 16) {
symlink_file_flags |= 2;
symlink_directory_flags |= 2;
}
}
#ifdef _MSC_VER
#ifdef _DEBUG
#include <crtdbg.h>
@ -3260,6 +3601,7 @@ int wmain(int argc, const wchar_t **wargv)
#endif
maybe_redirect_std_handles();
adjust_symlink_flags();
/* determine size of argv and environ conversion buffer */
maxlen = wcslen(wargv[0]);
@ -3289,6 +3631,7 @@ int wmain(int argc, const wchar_t **wargv)
/* initialize critical section for waitpid pinfo_t list */
InitializeCriticalSection(&pinfo_cs);
InitializeCriticalSection(&phantom_symlinks_cs);
/* set up default file mode and file modes for stdin/out/err */
_fmode = _O_BINARY;

View File

@ -253,6 +253,8 @@ char *gitdirname(char *);
typedef uintmax_t timestamp_t;
#define PRItime PRIuMAX
#define parse_timestamp strtoumax
#define parse_timestamp_from_buf(buf, len, ep, result) \
parse_unsigned_from_buf((buf), (len), (ep), (result), TIME_MAX)
#define TIME_MAX UINTMAX_MAX
#define TIME_MIN 0

View File

@ -6,10 +6,12 @@
#include <windows.h>
#endif
static inline int file_attr_to_st_mode (DWORD attr)
static inline int file_attr_to_st_mode (DWORD attr, DWORD tag)
{
int fMode = S_IREAD;
if (attr & FILE_ATTRIBUTE_DIRECTORY)
if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK)
fMode |= S_IFLNK;
else if (attr & FILE_ATTRIBUTE_DIRECTORY)
fMode |= S_IFDIR;
else
fMode |= S_IFREG;

View File

@ -12,7 +12,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
/* Set file type, based on WIN32_FIND_DATA */
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
&& fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK)
ent->d_type = DT_LNK;
else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
ent->d_type = DT_DIR;
else
ent->d_type = DT_REG;

View File

@ -1513,8 +1513,8 @@ int git_config_system(void)
}
static int do_git_config_sequence(const struct config_options *opts,
const struct repository *repo,
config_fn_t fn, void *data)
const struct repository *repo, config_fn_t fn,
void *data, enum config_scope scope)
{
int ret = 0;
char *system_config = git_system_config();
@ -1539,22 +1539,46 @@ static int do_git_config_sequence(const struct config_options *opts,
worktree_config = NULL;
}
if (git_config_system() && system_config &&
if (!opts->ignore_system && git_config_system() && system_config &&
!access_or_die(system_config, R_OK,
opts->system_gently ? ACCESS_EACCES_OK : 0))
ret += git_config_from_file_with_options(fn, system_config,
data, CONFIG_SCOPE_SYSTEM,
NULL);
if (!opts->ignore_global) {
int global_config_success_count = 0;
int nonzero_ret_on_global_config_error = scope == CONFIG_SCOPE_GLOBAL;
git_global_config_paths(&user_config, &xdg_config);
if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
ret += git_config_from_file_with_options(fn, xdg_config, data,
CONFIG_SCOPE_GLOBAL, NULL);
if (xdg_config &&
!access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file_with_options(fn, xdg_config,
data,
CONFIG_SCOPE_GLOBAL,
NULL);
if (!ret)
global_config_success_count++;
}
if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
ret += git_config_from_file_with_options(fn, user_config, data,
CONFIG_SCOPE_GLOBAL, NULL);
if (user_config &&
!access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file_with_options(fn, user_config,
data,
CONFIG_SCOPE_GLOBAL,
NULL);
if (!ret)
global_config_success_count++;
}
if (nonzero_ret_on_global_config_error &&
!global_config_success_count)
--ret;
free(xdg_config);
free(user_config);
}
if (!opts->ignore_repo && repo_config &&
!access_or_die(repo_config, R_OK, 0))
@ -1573,8 +1597,6 @@ static int do_git_config_sequence(const struct config_options *opts,
die(_("unable to parse command-line config"));
free(system_config);
free(xdg_config);
free(user_config);
free(repo_config);
free(worktree_config);
return ret;
@ -1604,7 +1626,8 @@ int config_with_options(config_fn_t fn, void *data,
*/
if (config_source && config_source->use_stdin) {
ret = git_config_from_stdin(fn, data, config_source->scope);
} else if (config_source && config_source->file) {
} else if (config_source && config_source->file &&
config_source->scope != CONFIG_SCOPE_GLOBAL) {
ret = git_config_from_file_with_options(fn, config_source->file,
data, config_source->scope,
NULL);
@ -1612,7 +1635,10 @@ int config_with_options(config_fn_t fn, void *data,
ret = git_config_from_blob_ref(fn, repo, config_source->blob,
data, config_source->scope);
} else {
ret = do_git_config_sequence(opts, repo, fn, data);
ret = do_git_config_sequence(opts, repo, fn, data,
config_source ?
config_source->scope :
CONFIG_SCOPE_UNKNOWN);
}
if (inc.remote_urls) {

View File

@ -87,6 +87,8 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,
struct config_options {
unsigned int respect_includes : 1;
unsigned int ignore_system : 1;
unsigned int ignore_global : 1;
unsigned int ignore_repo : 1;
unsigned int ignore_worktree : 1;
unsigned int ignore_cmdline : 1;

View File

@ -68,6 +68,16 @@ ifeq ($(uname_S),Linux)
BASIC_CFLAGS += -std=c99
endif
LINK_FUZZ_PROGRAMS = YesPlease
# The builtin FSMonitor on Linux builds upon Simple-IPC. Both require
# Unix domain sockets and PThreads.
ifndef NO_PTHREADS
ifndef NO_UNIX_SOCKETS
FSMONITOR_DAEMON_BACKEND = linux
FSMONITOR_OS_SETTINGS = linux
BASIC_CFLAGS += -DHAVE_LINUX_MAGIC_H
endif
endif
endif
ifeq ($(uname_S),GNU/kFreeBSD)
HAVE_ALLOCA_H = YesPlease
@ -157,6 +167,7 @@ ifeq ($(uname_S),Darwin)
endif
ifeq ($(shell test "$(DARWIN_MAJOR_VERSION)" -ge 24 && echo 1),1)
USE_HOMEBREW_LIBICONV = UnfortunatelyYes
NEEDS_GOOD_LIBICONV = UnfortunatelyYes
endif
# The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require

View File

@ -308,6 +308,16 @@ if(SUPPORTS_SIMPLE_IPC)
add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND)
add_compile_definitions(HAVE_LINUX_MAGIC_H)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-linux.c)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-linux.c)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-ipc-linux.c)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-path-utils-linux.c)
add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-linux.c)
endif()
endif()

View File

@ -3899,7 +3899,7 @@ __git_main ()
;;
esac
case "$cur" in
--*)
-*)
__gitcomp "
--paginate
--no-pager
@ -3915,6 +3915,12 @@ __git_main ()
--namespace=
--no-replace-objects
--help
-C
-P
-c
-h
-p
-v
"
;;
*)

View File

@ -110,7 +110,7 @@ void discard_hashfile(struct hashfile *f)
free_hashfile(f);
}
void hashwrite(struct hashfile *f, const void *buf, unsigned int count)
void hashwrite(struct hashfile *f, const void *buf, uint32_t count)
{
while (count) {
unsigned left = f->buffer_len - f->offset;

View File

@ -63,7 +63,7 @@ void free_hashfile(struct hashfile *f);
*/
int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);
void discard_hashfile(struct hashfile *);
void hashwrite(struct hashfile *, const void *, unsigned int);
void hashwrite(struct hashfile *, const void *, uint32_t);
void hashflush(struct hashfile *f);
void crc32_begin(struct hashfile *);
uint32_t crc32_end(struct hashfile *);

14
dir.c
View File

@ -2263,6 +2263,8 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
const struct pathspec *pathspec)
{
int i;
int matches_exclude_magic = 0;
int matches_pathspec_elem = 0;
if (!pathspec || !pathspec->nr)
return 0;
@ -2279,15 +2281,23 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
for (i = 0; i < pathspec->nr; i++) {
const struct pathspec_item *item = &pathspec->items[i];
int len = item->nowildcard_len;
int *matches;
if (item->magic & PATHSPEC_EXCLUDE)
matches = &matches_exclude_magic;
else
matches = &matches_pathspec_elem;
if (len == pathlen &&
!ps_strncmp(item, item->match, path, pathlen))
return 1;
*matches = 1;
if (len > pathlen &&
item->match[pathlen] == '/' &&
!ps_strncmp(item, item->match, path, pathlen))
return 1;
*matches = 1;
}
if (matches_pathspec_elem && !matches_exclude_magic)
return 1;
return 0;
}

View File

@ -21,6 +21,7 @@
#include "gettext.h"
#include "git-zlib.h"
#include "ident.h"
#include "lockfile.h"
#include "mailmap.h"
#include "object-name.h"
#include "repository.h"
@ -53,7 +54,6 @@ char *git_commit_encoding;
char *git_log_output_encoding;
char *apply_default_whitespace;
char *apply_default_ignorewhitespace;
char *git_attributes_file;
int zlib_compression_level = Z_BEST_SPEED;
int pack_compression_level = Z_DEFAULT_COMPRESSION;
int fsync_object_files = -1;
@ -324,7 +324,7 @@ next_name:
return (current & ~negative) | positive;
}
static int git_default_core_config(const char *var, const char *value,
int git_default_core_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
/* This needs a better name */
@ -363,11 +363,6 @@ static int git_default_core_config(const char *var, const char *value,
return 0;
}
if (!strcmp(var, "core.attributesfile")) {
FREE_AND_NULL(git_attributes_file);
return git_config_pathname(&git_attributes_file, var, value);
}
if (!strcmp(var, "core.bare")) {
is_bare_repository_cfg = git_config_bool(var, value);
return 0;
@ -532,6 +527,11 @@ static int git_default_core_config(const char *var, const char *value,
return 0;
}
if (!strcmp(var, "core.lockfilepid")) {
lockfile_pid_enabled = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.createobject")) {
if (!value)
return config_error_nonbool(var);

View File

@ -42,6 +42,7 @@
#define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
#define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
#define GIT_REF_URI_ENVIRONMENT "GIT_REF_URI"
/*
* Environment variable used to propagate the --no-advice global option to the
@ -106,6 +107,8 @@ const char *strip_namespace(const char *namespaced_ref);
int git_default_config(const char *, const char *,
const struct config_context *, void *);
int git_default_core_config(const char *var, const char *value,
const struct config_context *ctx, void *cb);
/*
* TODO: All the below state either explicitly or implicitly relies on
@ -152,7 +155,6 @@ extern int assume_unchanged;
extern int warn_on_object_refname_ambiguity;
extern char *apply_default_whitespace;
extern char *apply_default_ignorewhitespace;
extern char *git_attributes_file;
extern int zlib_compression_level;
extern int pack_compression_level;
extern unsigned long pack_size_limit_cfg;

View File

@ -35,6 +35,7 @@
#include "sigchain.h"
#include "mergesort.h"
#include "prio-queue.h"
#include "promisor-remote.h"
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
@ -1661,6 +1662,25 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
int i;
struct strvec index_pack_args = STRVEC_INIT;
const char *promisor_remote_config;
if (server_feature_v2("promisor-remote", &promisor_remote_config)) {
char *remote_name = promisor_remote_reply(promisor_remote_config);
free(remote_name);
}
if (args->filter_options.choice == LOFC_AUTO) {
struct strbuf errbuf = STRBUF_INIT;
char *constructed_filter = promisor_remote_construct_filter(r);
list_objects_filter_resolve_auto(&args->filter_options,
constructed_filter, &errbuf);
if (errbuf.len > 0)
die(_("couldn't resolve 'auto' filter: %s"), errbuf.buf);
free(constructed_filter);
strbuf_release(&errbuf);
}
negotiator = &negotiator_alloc;
if (args->refetch)

25
fsck.c
View File

@ -860,28 +860,13 @@ static int verify_headers(const void *data, unsigned long size,
FSCK_MSG_UNTERMINATED_HEADER, "unterminated header");
}
static timestamp_t parse_timestamp_from_buf(const char **start, const char *end)
{
const char *p = *start;
char buf[24]; /* big enough for 2^64 */
size_t i = 0;
while (p < end && isdigit(*p)) {
if (i >= ARRAY_SIZE(buf) - 1)
return TIME_MAX;
buf[i++] = *p++;
}
buf[i] = '\0';
*start = p;
return parse_timestamp(buf, NULL, 10);
}
static int fsck_ident(const char **ident, const char *ident_end,
const struct object_id *oid, enum object_type type,
struct fsck_options *options)
{
const char *p = *ident;
const char *nl;
timestamp_t timestamp;
nl = memchr(p, '\n', ident_end - p);
if (!nl)
@ -933,7 +918,8 @@ static int fsck_ident(const char **ident, const char *ident_end,
"invalid author/committer line - bad date");
if (*p == '0' && p[1] != ' ')
return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date");
if (date_overflows(parse_timestamp_from_buf(&p, ident_end)))
if (!parse_timestamp_from_buf(p, ident_end - p, &p, &timestamp) ||
date_overflows(timestamp))
return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow");
if (*p != ' ')
return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date");
@ -1310,11 +1296,6 @@ int fsck_refs_error_function(struct fsck_options *options UNUSED,
strbuf_addstr(&sb, report->path);
if (report->oid)
strbuf_addf(&sb, " -> (%s)", oid_to_hex(report->oid));
else if (report->referent)
strbuf_addf(&sb, " -> (%s)", report->referent);
if (msg_type == FSCK_WARN)
warning("%s: %s", sb.buf, message);
else

4
fsck.h
View File

@ -30,6 +30,7 @@ enum fsck_msg_type {
FUNC(BAD_DATE_OVERFLOW, ERROR) \
FUNC(BAD_EMAIL, ERROR) \
FUNC(BAD_GPGSIG, ERROR) \
FUNC(BAD_HEAD_TARGET, ERROR) \
FUNC(BAD_NAME, ERROR) \
FUNC(BAD_OBJECT_SHA1, ERROR) \
FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
@ -39,6 +40,7 @@ enum fsck_msg_type {
FUNC(BAD_REF_CONTENT, ERROR) \
FUNC(BAD_REF_FILETYPE, ERROR) \
FUNC(BAD_REF_NAME, ERROR) \
FUNC(BAD_REF_OID, ERROR) \
FUNC(BAD_TIMEZONE, ERROR) \
FUNC(BAD_TREE, ERROR) \
FUNC(BAD_TREE_SHA1, ERROR) \
@ -162,8 +164,6 @@ struct fsck_object_report {
struct fsck_ref_report {
const char *path;
const struct object_id *oid;
const char *referent;
};
struct fsck_options {

Some files were not shown because too many files have changed in this diff Show More