mirror of
https://github.com/git/git.git
synced 2026-01-11 13:23:12 +09:00
Compare commits
292 Commits
9e7a6e1792
...
4fea514e20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fea514e20 | ||
|
|
9df1fbfbc2 | ||
|
|
c88051ca0e | ||
|
|
276ca121e0 | ||
|
|
1247d873e3 | ||
|
|
df7333d8e6 | ||
|
|
b2f2a58a25 | ||
|
|
aa3b256f55 | ||
|
|
8a3de73c64 | ||
|
|
d385bc1b75 | ||
|
|
eb9c42c5a2 | ||
|
|
3a423a3c0c | ||
|
|
0a83639888 | ||
|
|
0213630269 | ||
|
|
3f330b445b | ||
|
|
4144f09d78 | ||
|
|
04bbf2f0d8 | ||
|
|
5c8cf26919 | ||
|
|
328de3c71e | ||
|
|
9262da11b5 | ||
|
|
ec03c75707 | ||
|
|
20f3107931 | ||
|
|
49c668b2fd | ||
|
|
77e128ef7f | ||
|
|
c7e23cd287 | ||
|
|
e21d1ffdbd | ||
|
|
6bf4dc4701 | ||
|
|
635138e8b1 | ||
|
|
39afbf3c5d | ||
|
|
e931506ade | ||
|
|
15598099d3 | ||
|
|
e7da08446f | ||
|
|
22908a59b7 | ||
|
|
4d487d0b7c | ||
|
|
72035efcbb | ||
|
|
6b24017454 | ||
|
|
389343c68f | ||
|
|
0ad3607094 | ||
|
|
d2391a94c9 | ||
|
|
0a5c831c05 | ||
|
|
aaac2643fd | ||
|
|
bc3e4c6c7b | ||
|
|
4eb8fddcd9 | ||
|
|
794e69d021 | ||
|
|
2e186ee137 | ||
|
|
5dbfe67eaf | ||
|
|
f0bdb8978c | ||
|
|
71f9facd0b | ||
|
|
2daf6f0bc8 | ||
|
|
2ad7318f62 | ||
|
|
ecc50847b3 | ||
|
|
8b8e26a05a | ||
|
|
0c32ac7aea | ||
|
|
87e0d405f9 | ||
|
|
2142534239 | ||
|
|
f5b7e90e18 | ||
|
|
61a27ae934 | ||
|
|
5c7955b477 | ||
|
|
66f2b50cb3 | ||
|
|
07ab3aca24 | ||
|
|
5727ec74f3 | ||
|
|
14eceb5b5d | ||
|
|
7a2296736a | ||
|
|
fc869c7958 | ||
|
|
b878089e58 | ||
|
|
6cc4e8b75d | ||
|
|
47b4112c1a | ||
|
|
16580c650a | ||
|
|
ad01f132b4 | ||
|
|
c7a544f09f | ||
|
|
aefd897e60 | ||
|
|
87c8ccb2b5 | ||
|
|
5a3ac92d76 | ||
|
|
2f8b312ee9 | ||
|
|
176e463cac | ||
|
|
e125df7961 | ||
|
|
baf46a880d | ||
|
|
7f3be938d3 | ||
|
|
a9f788acf0 | ||
|
|
6a91f2abfb | ||
|
|
8ad30d58f5 | ||
|
|
a1ac2f8ac4 | ||
|
|
a282a8f163 | ||
|
|
a593373b09 | ||
|
|
6acefa0d2c | ||
|
|
8384cbcb4c | ||
|
|
7b330a11de | ||
|
|
84f0e60b28 | ||
|
|
eb9ec52d95 | ||
|
|
085de91b95 | ||
|
|
0316c63ca4 | ||
|
|
480336a9ce | ||
|
|
2ac93bfcbc | ||
|
|
dbbf6a901b | ||
|
|
02fc44a989 | ||
|
|
be5864e08d | ||
|
|
9e76ed9bdc | ||
|
|
e40e01a75a | ||
|
|
2bfc69e648 | ||
|
|
8cafc305e2 | ||
|
|
3f051fc9c9 | ||
|
|
89d4f3af16 | ||
|
|
f671f5a83b | ||
|
|
285659cc98 | ||
|
|
92a61fe44d | ||
|
|
3d61c1988b | ||
|
|
36d43bef82 | ||
|
|
a612f856ff | ||
|
|
1bb1906270 | ||
|
|
ee437749ff | ||
|
|
7621825c43 | ||
|
|
d5a4b9b73a | ||
|
|
4cf8c114e3 | ||
|
|
7ad97f4bea | ||
|
|
376c94b167 | ||
|
|
a05c78d224 | ||
|
|
aac1ec71fd | ||
|
|
57592cba24 | ||
|
|
ca3b4933e9 | ||
|
|
9c82ba6c32 | ||
|
|
03d894e23c | ||
|
|
56be11f501 | ||
|
|
0ff0f991df | ||
|
|
b3b89b691c | ||
|
|
5913fd26aa | ||
|
|
f42d7c9067 | ||
|
|
2d46e361fd | ||
|
|
1df7743798 | ||
|
|
f1ec43d4d2 | ||
|
|
6c5c7e7071 | ||
|
|
3d09968656 | ||
|
|
b3449b1517 | ||
|
|
9e8b448dd8 | ||
|
|
79d301c767 | ||
|
|
aad1d1c0d5 | ||
|
|
555c8464e5 | ||
|
|
7fb080a790 | ||
|
|
296834217d | ||
|
|
b767867fae | ||
|
|
56b77a687e | ||
|
|
6f693364cc | ||
|
|
f67f7ddbbd | ||
|
|
3074d08cfa | ||
|
|
17b7965a03 | ||
|
|
76eab50f75 | ||
|
|
2f41b6e5e9 | ||
|
|
da96a32b59 | ||
|
|
404b677229 | ||
|
|
32fadb3779 | ||
|
|
8bbe574720 | ||
|
|
083774849b | ||
|
|
efd314b8e8 | ||
|
|
fee078ad4c | ||
|
|
d71dac1e23 | ||
|
|
0e67fd8b4b | ||
|
|
fde7f6ebb1 | ||
|
|
319871c177 | ||
|
|
db8a50ca6b | ||
|
|
bcf8fd6345 | ||
|
|
7ba3a48c3d | ||
|
|
0b495cd390 | ||
|
|
cd607431e1 | ||
|
|
01ae1faa96 | ||
|
|
57f7dd48d7 | ||
|
|
0e445956f4 | ||
|
|
3e456f1d8a | ||
|
|
958a816794 | ||
|
|
506a7b6690 | ||
|
|
065523812f | ||
|
|
2ebaa2b45e | ||
|
|
64dbeefbd2 | ||
|
|
bb3a1ce91f | ||
|
|
4455d4a2ea | ||
|
|
06e1f6467e | ||
|
|
d78039cd50 | ||
|
|
041c557171 | ||
|
|
052efdd60f | ||
|
|
d8a17ef09b | ||
|
|
c01bcb5d00 | ||
|
|
d92ba46d5a | ||
|
|
c8c941181a | ||
|
|
13bf8f5bcb | ||
|
|
0d76d0c600 | ||
|
|
6ac467fdbd | ||
|
|
50cedfdf94 | ||
|
|
09b0cefcd8 | ||
|
|
81dae58f91 | ||
|
|
acffc5e9e5 | ||
|
|
5b35e736dd | ||
|
|
ead7aae0e4 | ||
|
|
20e56300d4 | ||
|
|
f53f133d8d | ||
|
|
e38352c233 | ||
|
|
20684baa34 | ||
|
|
7796c14a1a | ||
|
|
00f117fafe | ||
|
|
6973e5f550 | ||
|
|
a2d5100f7e | ||
|
|
740dce52b6 | ||
|
|
a7afa7a1dd | ||
|
|
09c6a90daa | ||
|
|
43152a2276 | ||
|
|
96b8760220 | ||
|
|
e81cbd184f | ||
|
|
3aeb1cd126 | ||
|
|
1977e5b9de | ||
|
|
8a08d14d05 | ||
|
|
c4314a6aa8 | ||
|
|
47112d6f96 | ||
|
|
761d9427bc | ||
|
|
988ba47d79 | ||
|
|
fc27de047a | ||
|
|
c08814a5c4 | ||
|
|
98e2eaf426 | ||
|
|
8895855a44 | ||
|
|
b2d9214455 | ||
|
|
25866d0c50 | ||
|
|
e5be12952a | ||
|
|
99c20c71bb | ||
|
|
1887b3dd06 | ||
|
|
5f09f93726 | ||
|
|
ac2339de65 | ||
|
|
89d6c35322 | ||
|
|
d08f0011ae | ||
|
|
3d86511c12 | ||
|
|
b9e842ecff | ||
|
|
f1799202ea | ||
|
|
ac3e74d2b7 | ||
|
|
240208233c | ||
|
|
a92df5a0de | ||
|
|
5ae594f30b | ||
|
|
e39a3410c4 | ||
|
|
1e06a72a0a | ||
|
|
98ce832ee6 | ||
|
|
6b7d812696 | ||
|
|
477f9c1b21 | ||
|
|
cad0632e13 | ||
|
|
9ced91c83b | ||
|
|
363485a311 | ||
|
|
35364d7aba | ||
|
|
b40e7cbca2 | ||
|
|
a7c1d30f29 | ||
|
|
9df4325f15 | ||
|
|
3dfc5b5c83 | ||
|
|
ae3770a76e | ||
|
|
10292d39ac | ||
|
|
13980c8225 | ||
|
|
fcf3d688d2 | ||
|
|
3eea3c8023 | ||
|
|
84071a6dea | ||
|
|
2e53d29f53 | ||
|
|
d5e4aef358 | ||
|
|
ecde85d238 | ||
|
|
7ccec5a1f6 | ||
|
|
cc4dcc855b | ||
|
|
247c6598a8 | ||
|
|
7cf9c96646 | ||
|
|
b5b6c11a70 | ||
|
|
fa4fe12fa0 | ||
|
|
9841b05cbc | ||
|
|
925a5befa6 | ||
|
|
cd0f572477 | ||
|
|
87466656fa | ||
|
|
e8c09f2af4 | ||
|
|
f73f951464 | ||
|
|
2026440bab | ||
|
|
f141ee7d7b | ||
|
|
eb3a952ca1 | ||
|
|
eea016940e | ||
|
|
dfd1063876 | ||
|
|
68aace560b | ||
|
|
44ed7c1886 | ||
|
|
ca05bfbae0 | ||
|
|
f15c9f93d5 | ||
|
|
ddeec7a34f | ||
|
|
f29070cb25 | ||
|
|
f00c4ace1e | ||
|
|
0bbba5f98f | ||
|
|
a8dfbc133a | ||
|
|
eae08d8bf7 | ||
|
|
76ee08578e | ||
|
|
0404613aa0 | ||
|
|
f97c49398b | ||
|
|
447480a5a6 | ||
|
|
3a1fd7b09c | ||
|
|
036e2d476c | ||
|
|
534a87d6f4 | ||
|
|
7aeb71a516 | ||
|
|
6145e03295 | ||
|
|
97660b2cfe | ||
|
|
b0380a8ebc | ||
|
|
dd4271e1c5 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -17,3 +17,4 @@ CODE_OF_CONDUCT.md -whitespace
|
|||||||
/Documentation/gitk.adoc conflict-marker-size=32
|
/Documentation/gitk.adoc conflict-marker-size=32
|
||||||
/Documentation/user-manual.adoc conflict-marker-size=32
|
/Documentation/user-manual.adoc conflict-marker-size=32
|
||||||
/t/t????-*.sh conflict-marker-size=32
|
/t/t????-*.sh conflict-marker-size=32
|
||||||
|
/t/unit-tests/clar/test/expected/* whitespace=-blank-at-eof
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -79,6 +79,7 @@
|
|||||||
/git-grep
|
/git-grep
|
||||||
/git-hash-object
|
/git-hash-object
|
||||||
/git-help
|
/git-help
|
||||||
|
/git-history
|
||||||
/git-hook
|
/git-hook
|
||||||
/git-http-backend
|
/git-http-backend
|
||||||
/git-http-fetch
|
/git-http-fetch
|
||||||
|
|||||||
@ -1,105 +1,105 @@
|
|||||||
-b::
|
`-b`::
|
||||||
Show blank SHA-1 for boundary commits. This can also
|
Show blank SHA-1 for boundary commits. This can also
|
||||||
be controlled via the `blame.blankBoundary` config option.
|
be controlled via the `blame.blankBoundary` config option.
|
||||||
|
|
||||||
--root::
|
`--root`::
|
||||||
Do not treat root commits as boundaries. This can also be
|
Do not treat root commits as boundaries. This can also be
|
||||||
controlled via the `blame.showRoot` config option.
|
controlled via the `blame.showRoot` config option.
|
||||||
|
|
||||||
--show-stats::
|
`--show-stats`::
|
||||||
Include additional statistics at the end of blame output.
|
Include additional statistics at the end of blame output.
|
||||||
|
|
||||||
-L <start>,<end>::
|
`-L <start>,<end>`::
|
||||||
-L :<funcname>::
|
`-L :<funcname>`::
|
||||||
Annotate only the line range given by '<start>,<end>',
|
Annotate only the line range given by `<start>,<end>`,
|
||||||
or by the function name regex '<funcname>'.
|
or by the function name regex _<funcname>_.
|
||||||
May be specified multiple times. Overlapping ranges are allowed.
|
May be specified multiple times. Overlapping ranges are allowed.
|
||||||
+
|
+
|
||||||
'<start>' and '<end>' are optional. `-L <start>` or `-L <start>,` spans from
|
_<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>_ to end of file. `-L ,<end>` spans from start of file to _<end>_.
|
||||||
+
|
+
|
||||||
include::line-range-format.adoc[]
|
include::line-range-format.adoc[]
|
||||||
|
|
||||||
-l::
|
`-l`::
|
||||||
Show long rev (Default: off).
|
Show long rev (Default: off).
|
||||||
|
|
||||||
-t::
|
`-t`::
|
||||||
Show raw timestamp (Default: off).
|
Show raw timestamp (Default: off).
|
||||||
|
|
||||||
-S <revs-file>::
|
`-S <revs-file>`::
|
||||||
Use revisions from revs-file instead of calling linkgit:git-rev-list[1].
|
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
|
Walk history forward instead of backward. Instead of showing
|
||||||
the revision in which a line appeared, this shows the last
|
the revision in which a line appeared, this shows the last
|
||||||
revision in which a line has existed. This requires a range of
|
revision in which a line has existed. This requires a range of
|
||||||
revision like START..END where the path to blame exists in
|
revision like `<start>..<end>` where the path to blame exists in
|
||||||
START. `git blame --reverse START` is taken as `git blame
|
_<start>_. `git blame --reverse <start>` is taken as `git blame
|
||||||
--reverse START..HEAD` for convenience.
|
--reverse <start>..HEAD` for convenience.
|
||||||
|
|
||||||
--first-parent::
|
`--first-parent`::
|
||||||
Follow only the first parent commit upon seeing a merge
|
Follow only the first parent commit upon seeing a merge
|
||||||
commit. This option can be used to determine when a line
|
commit. This option can be used to determine when a line
|
||||||
was introduced to a particular integration branch, rather
|
was introduced to a particular integration branch, rather
|
||||||
than when it was introduced to the history overall.
|
than when it was introduced to the history overall.
|
||||||
|
|
||||||
-p::
|
`-p`::
|
||||||
--porcelain::
|
`--porcelain`::
|
||||||
Show in a format designed for machine consumption.
|
Show in a format designed for machine consumption.
|
||||||
|
|
||||||
--line-porcelain::
|
`--line-porcelain`::
|
||||||
Show the porcelain format, but output commit information for
|
Show the porcelain format, but output commit information for
|
||||||
each line, not just the first time a commit is referenced.
|
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
|
Show the result incrementally in a format designed for
|
||||||
machine consumption.
|
machine consumption.
|
||||||
|
|
||||||
--encoding=<encoding>::
|
`--encoding=<encoding>`::
|
||||||
Specifies the encoding used to output author names
|
Specify the encoding used to output author names
|
||||||
and commit summaries. Setting it to `none` makes blame
|
and commit summaries. Setting it to `none` makes blame
|
||||||
output unconverted data. For more information see the
|
output unconverted data. For more information see the
|
||||||
discussion about encoding in the linkgit:git-log[1]
|
discussion about encoding in the linkgit:git-log[1]
|
||||||
manual page.
|
manual page.
|
||||||
|
|
||||||
--contents <file>::
|
`--contents <file>`::
|
||||||
Annotate using the contents from the named file, starting from <rev>
|
Annotate using the contents from _<file>_, starting from _<rev>_
|
||||||
if it is specified, and HEAD otherwise. You may specify '-' to make
|
if it is specified, and `HEAD` otherwise. You may specify `-` to make
|
||||||
the command read from the standard input for the file contents.
|
the command read from the standard input for the file contents.
|
||||||
|
|
||||||
--date <format>::
|
`--date <format>`::
|
||||||
Specifies the format used to output dates. If --date is not
|
Specify the format used to output dates. If `--date` is not
|
||||||
provided, the value of the blame.date config variable is
|
provided, the value of the `blame.date` config variable is
|
||||||
used. If the blame.date config variable is also not set, the
|
used. If the `blame.date` config variable is also not set, the
|
||||||
iso format is used. For supported values, see the discussion
|
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::
|
`--progress`::
|
||||||
--no-progress::
|
`--no-progress`::
|
||||||
Progress status is reported on the standard error stream
|
Enable progress reporting on the standard error stream even if
|
||||||
by default when it is attached to a terminal. This flag
|
not attached to a terminal. By default, progress status is
|
||||||
enables progress reporting even if not attached to a
|
reported only when it is attached. You can't use `--progress`
|
||||||
terminal. Can't use `--progress` together with `--porcelain`
|
together with `--porcelain` or `--incremental`.
|
||||||
or `--incremental`.
|
|
||||||
|
|
||||||
-M[<num>]::
|
`-M[<num>]`::
|
||||||
Detect moved or copied lines within a file. When a commit
|
Detect moved or copied lines within a file. When a commit
|
||||||
moves or copies a block of lines (e.g. the original file
|
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
|
has _A_ and then _B_, and the commit changes it to _B_ and then
|
||||||
A), the traditional 'blame' algorithm notices only half of
|
_A_), the traditional `blame` algorithm notices only half of
|
||||||
the movement and typically blames the lines that were moved
|
the movement and typically blames the lines that were moved
|
||||||
up (i.e. B) to the parent and assigns blame to the lines that
|
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
|
were moved down (i.e. _A_) to the child commit. With this
|
||||||
option, both groups of lines are blamed on the parent by
|
option, both groups of lines are blamed on the parent by
|
||||||
running extra passes of inspection.
|
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
|
alphanumeric characters that Git must detect as moving/copying
|
||||||
within a file for it to associate those lines with the parent
|
within a file for it to associate those lines with the parent
|
||||||
commit. The default value is 20.
|
commit. The default value is 20.
|
||||||
|
|
||||||
-C[<num>]::
|
`-C[<num>]`::
|
||||||
In addition to `-M`, detect lines moved or copied from other
|
In addition to `-M`, detect lines moved or copied from other
|
||||||
files that were modified in the same commit. This is
|
files that were modified in the same commit. This is
|
||||||
useful when you reorganize your program and move code
|
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
|
option is given three times, the command additionally
|
||||||
looks for copies from other files in any commit.
|
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
|
alphanumeric characters that Git must detect as moving/copying
|
||||||
between files for it to associate those lines with the parent
|
between files for it to associate those lines with the parent
|
||||||
commit. And the default value is 40. If there are more than one
|
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.
|
take effect.
|
||||||
|
|
||||||
--ignore-rev <rev>::
|
`--ignore-rev <rev>`::
|
||||||
Ignore changes made by the revision when assigning blame, as if the
|
Ignore changes made by the revision when assigning blame, as if the
|
||||||
change never happened. Lines that were changed or added by an ignored
|
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
|
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
|
another commit will be marked with a `?` in the blame output. If the
|
||||||
`blame.markUnblamableLines` config option is set, then those lines touched
|
`blame.markUnblamableLines` config option is set, then those lines touched
|
||||||
by an ignored commit that we could not attribute to another revision are
|
by an ignored commit that we could not attribute to another revision are
|
||||||
marked with a '*'. In the porcelain modes, we print 'ignored' and
|
marked with a `*`. In the porcelain modes, we print `ignored` and
|
||||||
'unblamable' on a newline respectively.
|
`unblamable` on a newline respectively.
|
||||||
|
|
||||||
--ignore-revs-file <file>::
|
`--ignore-revs-file <file>`::
|
||||||
Ignore revisions listed in `file`, which must be in the same format as an
|
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
|
`fsck.skipList`. This option may be repeated, and these files will be
|
||||||
processed after any files specified with the `blame.ignoreRevsFile` config
|
processed after any files specified with the `blame.ignoreRevsFile` config
|
||||||
option. An empty file name, `""`, will clear the list of revs from
|
option. An empty file name, `""`, will clear the list of revs from
|
||||||
previously processed files.
|
previously processed files.
|
||||||
|
|
||||||
--color-lines::
|
`--color-lines`::
|
||||||
Color line annotations in the default format differently if they come from
|
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
|
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
|
code blocks introduced by different commits. The color defaults to cyan and
|
||||||
can be adjusted using the `color.blame.repeatedLines` config option.
|
can be adjusted using the `color.blame.repeatedLines` config option.
|
||||||
|
|
||||||
--color-by-age::
|
`--color-by-age`::
|
||||||
Color line annotations depending on the age of the line in the default format.
|
Color line annotations depending on the age of the line in
|
||||||
The `color.blame.highlightRecent` config option controls what color is used for
|
the default format. The `color.blame.highlightRecent` config
|
||||||
each range of age.
|
option controls what color is used for each range of age.
|
||||||
|
|
||||||
-h::
|
`-h`::
|
||||||
Show help message.
|
Show help message.
|
||||||
|
|||||||
@ -523,6 +523,8 @@ include::config/sequencer.adoc[]
|
|||||||
|
|
||||||
include::config/showbranch.adoc[]
|
include::config/showbranch.adoc[]
|
||||||
|
|
||||||
|
include::config/sideband.adoc[]
|
||||||
|
|
||||||
include::config/sparse.adoc[]
|
include::config/sparse.adoc[]
|
||||||
|
|
||||||
include::config/splitindex.adoc[]
|
include::config/splitindex.adoc[]
|
||||||
|
|||||||
@ -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
|
read-only snapshot of the same index to a location different from the
|
||||||
repository's usual working tree).
|
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::
|
core.logAllRefUpdates::
|
||||||
Enable the reflog. Updates to a ref <ref> is logged to the file
|
Enable the reflog. Updates to a ref <ref> is logged to the file
|
||||||
"`$GIT_DIR/logs/<ref>`", by appending the new and old
|
"`$GIT_DIR/logs/<ref>`", by appending the new and old
|
||||||
|
|||||||
@ -73,6 +73,35 @@ relativeWorktrees:::
|
|||||||
repaired with either the `--relative-paths` option or with the
|
repaired with either the `--relative-paths` option or with the
|
||||||
`worktree.useRelativePaths` config set to `true`.
|
`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:::
|
worktreeConfig:::
|
||||||
If enabled, then worktrees will load config settings from the
|
If enabled, then worktrees will load config settings from the
|
||||||
`$GIT_DIR/config.worktree` file in addition to the
|
`$GIT_DIR/config.worktree` file in addition to the
|
||||||
|
|||||||
@ -4,8 +4,8 @@ fsmonitor.allowRemote::
|
|||||||
behavior. Only respected when `core.fsmonitor` is set to `true`.
|
behavior. Only respected when `core.fsmonitor` is set to `true`.
|
||||||
|
|
||||||
fsmonitor.socketDir::
|
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
|
which to create the Unix domain socket used for communication
|
||||||
between the fsmonitor daemon and various Git commands. The directory must
|
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`.
|
is set to `true`.
|
||||||
|
|||||||
@ -18,3 +18,9 @@ endif::[]
|
|||||||
See `--ref-format=` in linkgit:git-init[1]. Both the command line
|
See `--ref-format=` in linkgit:git-init[1]. Both the command line
|
||||||
option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
|
option and the `GIT_DEFAULT_REF_FORMAT` environment variable take
|
||||||
precedence over this config.
|
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.
|
||||||
|
|||||||
@ -89,3 +89,36 @@ variable. The fields are checked only if the
|
|||||||
`promisor.acceptFromServer` config variable is not set to "None". If
|
`promisor.acceptFromServer` config variable is not set to "None". If
|
||||||
set to "None", this config variable has no effect. See
|
set to "None", this config variable has no effect. See
|
||||||
linkgit:gitprotocol-v2[5].
|
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.
|
||||||
|
|||||||
24
Documentation/config/sideband.adoc
Normal file
24
Documentation/config/sideband.adoc
Normal 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.
|
||||||
|
--
|
||||||
@ -52,6 +52,13 @@ submodule.<name>.active::
|
|||||||
submodule.active config option. See linkgit:gitsubmodules[7] for
|
submodule.active config option. See linkgit:gitsubmodules[7] for
|
||||||
details.
|
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::
|
submodule.active::
|
||||||
A repeated field which contains a pathspec used to match against a
|
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
|
submodule's path to determine if the submodule is of interest to git
|
||||||
|
|||||||
@ -88,6 +88,25 @@ linkgit:git-config[1].
|
|||||||
This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes
|
This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes
|
||||||
precedence over the `fetch.output` config option.
|
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[]
|
ifndef::git-pull[]
|
||||||
`--write-fetch-head`::
|
`--write-fetch-head`::
|
||||||
`--no-write-fetch-head`::
|
`--no-write-fetch-head`::
|
||||||
|
|||||||
@ -13,6 +13,9 @@
|
|||||||
`badGpgsig`::
|
`badGpgsig`::
|
||||||
(ERROR) A tag contains a bad (truncated) signature (e.g., `gpgsig`) header.
|
(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`::
|
`badHeaderContinuation`::
|
||||||
(ERROR) A continuation header (such as for `gpgsig`) is unexpectedly truncated.
|
(ERROR) A continuation header (such as for `gpgsig`) is unexpectedly truncated.
|
||||||
|
|
||||||
@ -41,6 +44,9 @@
|
|||||||
`badRefName`::
|
`badRefName`::
|
||||||
(ERROR) A ref has an invalid format.
|
(ERROR) A ref has an invalid format.
|
||||||
|
|
||||||
|
`badRefOid`::
|
||||||
|
(ERROR) A ref points to an invalid object ID.
|
||||||
|
|
||||||
`badReferentName`::
|
`badReferentName`::
|
||||||
(ERROR) The referent name of a symref is invalid.
|
(ERROR) The referent name of a symref is invalid.
|
||||||
|
|
||||||
|
|||||||
@ -7,12 +7,12 @@ git-blame - Show what revision and author last modified each line of a file
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[synopsis]
|
||||||
'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental]
|
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>]
|
[-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>]
|
||||||
[--ignore-rev <rev>] [--ignore-revs-file <file>]
|
[--ignore-rev <rev>] [--ignore-revs-file <file>]
|
||||||
[--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
|
[--color-lines] [--color-by-age] [--progress] [--abbrev=<n>]
|
||||||
[ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file>
|
[ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file>
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -30,7 +30,7 @@ lines that were copied and pasted from another file, etc., see the
|
|||||||
`-C` and `-M` options.
|
`-C` and `-M` options.
|
||||||
|
|
||||||
The report does not tell you anything about lines which have been deleted or
|
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.
|
interface briefly mentioned in the following paragraph.
|
||||||
|
|
||||||
Apart from supporting file annotation, Git also supports searching the
|
Apart from supporting file annotation, Git also supports searching the
|
||||||
@ -50,47 +50,47 @@ OPTIONS
|
|||||||
-------
|
-------
|
||||||
include::blame-options.adoc[]
|
include::blame-options.adoc[]
|
||||||
|
|
||||||
-c::
|
`-c`::
|
||||||
Use the same output mode as linkgit:git-annotate[1] (Default: off).
|
Use the same output mode as linkgit:git-annotate[1] (Default: off).
|
||||||
|
|
||||||
--score-debug::
|
`--score-debug`::
|
||||||
Include debugging information related to the movement of
|
Include debugging information related to the movement of
|
||||||
lines between files (see `-C`) and lines moved within a
|
lines between files (see `-C`) and lines moved within a
|
||||||
file (see `-M`). The first number listed is the score.
|
file (see `-M`). The first number listed is the score.
|
||||||
This is the number of alphanumeric characters detected
|
This is the number of alphanumeric characters detected
|
||||||
as having been moved between or within files. This must be above
|
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.
|
of code to have been moved.
|
||||||
|
|
||||||
-f::
|
`-f`::
|
||||||
--show-name::
|
`--show-name`::
|
||||||
Show the filename in the original commit. By default
|
Show the filename in the original commit. By default
|
||||||
the filename is shown if there is any line that came from a
|
the filename is shown if there is any line that came from a
|
||||||
file with a different name, due to rename detection.
|
file with a different name, due to rename detection.
|
||||||
|
|
||||||
-n::
|
`-n`::
|
||||||
--show-number::
|
`--show-number`::
|
||||||
Show the line number in the original commit (Default: off).
|
Show the line number in the original commit (Default: off).
|
||||||
|
|
||||||
-s::
|
`-s`::
|
||||||
Suppress the author name and timestamp from the output.
|
Suppress the author name and timestamp from the output.
|
||||||
|
|
||||||
-e::
|
`-e`::
|
||||||
--show-email::
|
`--show-email`::
|
||||||
Show the author email instead of the author name (Default: off).
|
Show the author email instead of the author name (Default: off).
|
||||||
This can also be controlled via the `blame.showEmail` config
|
This can also be controlled via the `blame.showEmail` config
|
||||||
option.
|
option.
|
||||||
|
|
||||||
-w::
|
`-w`::
|
||||||
Ignore whitespace when comparing the parent's version and
|
Ignore whitespace when comparing the parent's version and
|
||||||
the child's to find where the lines came from.
|
the child's to find where the lines came from.
|
||||||
|
|
||||||
include::diff-algorithm-option.adoc[]
|
include::diff-algorithm-option.adoc[]
|
||||||
|
|
||||||
--abbrev=<n>::
|
`--abbrev=<n>`::
|
||||||
Instead of using the default 7+1 hexadecimal digits as the
|
Instead of using the default _7+1_ hexadecimal digits as the
|
||||||
abbreviated object name, use <m>+1 digits, where <m> is at
|
abbreviated object name, use _<m>+1_ digits, where _<m>_ is at
|
||||||
least <n> but ensures the commit object names are unique.
|
least _<n>_ but ensures the commit object names are unique.
|
||||||
Note that 1 column
|
Note that 1 column
|
||||||
is used for a caret to mark the boundary commit.
|
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
|
This header line is followed by the following information
|
||||||
at least once for each commit:
|
at least once for each commit:
|
||||||
|
|
||||||
- the author name ("author"), email ("author-mail"), time
|
- the author name (`author`), email (`author-mail`), time
|
||||||
("author-time"), and time zone ("author-tz"); similarly
|
(`author-time`), and time zone (`author-tz`); similarly
|
||||||
for committer.
|
for committer.
|
||||||
- the filename in the commit that the line is attributed to.
|
- 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
|
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.
|
header elements later.
|
||||||
|
|
||||||
The porcelain format generally suppresses commit information that has
|
The porcelain format generally suppresses commit information that has
|
||||||
already been seen. For example, two lines that are blamed to the same
|
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
|
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
|
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
|
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
|
`--line-porcelain` option can be used to output full commit information
|
||||||
for each line, allowing simpler (but less efficient) usage like:
|
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
|
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
|
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
|
ranges. The `-L` option, which limits annotation to a range of lines, may be
|
||||||
specified multiple times.
|
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
|
When you are not interested in changes older than version
|
||||||
v2.6.18, or changes older than 3 weeks, you can use revision
|
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 v2.6.18.. -- foo
|
||||||
git blame --since=3.weeks -- foo
|
git blame --since=3.weeks -- foo
|
||||||
@ -212,8 +212,9 @@ does not contain the actual lines from the file that is being
|
|||||||
annotated.
|
annotated.
|
||||||
|
|
||||||
. Each blame entry always starts with a line of:
|
. Each blame entry always starts with a line of:
|
||||||
|
+
|
||||||
<40-byte-hex-sha1> <sourceline> <resultline> <num-lines>
|
[synopsis]
|
||||||
|
<40-byte-hex-sha1> <sourceline> <resultline> <num-lines>
|
||||||
+
|
+
|
||||||
Line numbers count from 1.
|
Line numbers count from 1.
|
||||||
|
|
||||||
@ -224,16 +225,17 @@ Line numbers count from 1.
|
|||||||
|
|
||||||
. Unlike the Porcelain format, the filename information is always
|
. Unlike the Porcelain format, the filename information is always
|
||||||
given and terminates the entry:
|
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
|
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).
|
parser (which should be quite natural for most scripting languages).
|
||||||
+
|
+
|
||||||
[NOTE]
|
[NOTE]
|
||||||
For people who do parsing: to make it more robust, just ignore any
|
For people who do parsing: to make it more robust, just ignore any
|
||||||
lines between the first and last one ("<sha1>" and "filename" lines)
|
lines between the first and last one (_<40-byte-hex-sha1>_ and `filename`
|
||||||
where you do not recognize the tag words (or care about that particular
|
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
|
one) at the beginning of the "extended information" lines. That way, if
|
||||||
there is ever added information (like the commit encoding or extended
|
there is ever added information (like the commit encoding or extended
|
||||||
commit commentary), a blame viewer will not care.
|
commit commentary), a blame viewer will not care.
|
||||||
|
|||||||
@ -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
|
Use the partial clone feature and request that the server sends
|
||||||
a subset of reachable objects according to a given object filter.
|
a subset of reachable objects according to a given object filter.
|
||||||
When using `--filter`, the supplied _<filter-spec>_ is used for
|
When using `--filter`, the supplied _<filter-spec>_ is used for
|
||||||
the partial clone filter. For example, `--filter=blob:none` will
|
the partial clone filter.
|
||||||
filter out all blobs (file contents) until needed by Git. Also,
|
+
|
||||||
`--filter=blob:limit=<size>` will filter out all blobs of size
|
If `--filter=auto` is used the filter specification is determined
|
||||||
at least _<size>_. For more details on filter specifications, see
|
automatically through the 'promisor-remote' protocol (see
|
||||||
the `--filter` option in linkgit:git-rev-list[1].
|
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-filter-submodules`::
|
||||||
Also apply the partial clone filter to any submodules in the repository.
|
Also apply the partial clone filter to any submodules in the repository.
|
||||||
|
|||||||
@ -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
|
correctly with all network-mounted repositories, so such use is considered
|
||||||
experimental.
|
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
|
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
|
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
|
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.
|
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
|
`.git` directory is on a network-mounted filesystem, it will instead be
|
||||||
created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a
|
created at `$HOME/.git-fsmonitor-*` unless `$HOME` itself is on a
|
||||||
network-mounted filesystem, in which case you must set the configuration
|
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.
|
filesystem in which to create the socket file.
|
||||||
|
|
||||||
If none of the above directories (`.git`, `$HOME`, or `fsmonitor.socketDir`)
|
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.
|
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
|
CONFIGURATION
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|||||||
73
Documentation/git-history.adoc
Normal file
73
Documentation/git-history.adoc
Normal 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
|
||||||
@ -9,7 +9,8 @@ git-last-modified - EXPERIMENTAL: Show when files were last modified
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[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
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -26,12 +27,23 @@ OPTIONS
|
|||||||
`--recursive`::
|
`--recursive`::
|
||||||
Instead of showing tree entries, step into subtrees and show all entries
|
Instead of showing tree entries, step into subtrees and show all entries
|
||||||
inside them recursively.
|
inside them recursively.
|
||||||
|
See the section "NOTES ABOUT DEPTH" below for more details.
|
||||||
|
|
||||||
`-t`::
|
`-t`::
|
||||||
`--show-trees`::
|
`--show-trees`::
|
||||||
Show tree entries even when recursing into them. It has no effect
|
Show tree entries even when recursing into them. It has no effect
|
||||||
without `--recursive`.
|
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>`::
|
`<revision-range>`::
|
||||||
Only traverse commits in the specified revision range. When no
|
Only traverse commits in the specified revision range. When no
|
||||||
`<revision-range>` is specified, it defaults to `HEAD` (i.e. the whole
|
`<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
|
Without an optional path parameter, all files and subdirectories
|
||||||
in path traversal the are included in the output.
|
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
|
SEE ALSO
|
||||||
--------
|
--------
|
||||||
linkgit:git-blame[1],
|
linkgit:git-blame[1],
|
||||||
|
|||||||
@ -9,7 +9,14 @@ git-multi-pack-index - Write and verify multi-pack-indexes
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[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
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -18,6 +25,8 @@ Write or verify a multi-pack-index (MIDX) file.
|
|||||||
OPTIONS
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
The following command-line options are applicable to all sub-commands:
|
||||||
|
|
||||||
--object-dir=<dir>::
|
--object-dir=<dir>::
|
||||||
Use given directory for the location of Git objects. We check
|
Use given directory for the location of Git objects. We check
|
||||||
`<dir>/packs/multi-pack-index` for the current MIDX file, and
|
`<dir>/packs/multi-pack-index` for the current MIDX file, and
|
||||||
@ -73,7 +82,18 @@ marker).
|
|||||||
Write an incremental MIDX file containing only objects
|
Write an incremental MIDX file containing only objects
|
||||||
and packs not present in an existing MIDX layer.
|
and packs not present in an existing MIDX layer.
|
||||||
Migrates non-incremental MIDXs to incremental ones when
|
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::
|
verify::
|
||||||
|
|||||||
@ -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.
|
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
|
the fact that the patch is prefixed with the object name of the
|
||||||
commit, and outputs two 40-byte hexadecimal strings. The first
|
commit, and outputs two 40-byte hexadecimal strings. The first
|
||||||
string is the patch ID, and the second string is the commit ID.
|
string is the patch ID, and the second string is the commit ID.
|
||||||
@ -31,8 +31,8 @@ OPTIONS
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
`--verbatim`::
|
`--verbatim`::
|
||||||
Calculate the patch-id of the input as it is given, do not strip
|
Calculate the patch ID of the input as it is given, do not strip
|
||||||
any whitespace.
|
any whitespace. Implies `--stable` and forbids `--unstable`.
|
||||||
+
|
+
|
||||||
This is the default if `patchid.verbatim` is `true`.
|
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
|
with two different settings for `-O<orderfile>` result in the same
|
||||||
patch ID signature, thereby allowing the computed result to be used
|
patch ID signature, thereby allowing the computed result to be used
|
||||||
as a key to index some meta-information about the change between
|
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
|
or produced when an "unstable" hash (see `--unstable` below) is
|
||||||
configured - even when used on a diff output taken without any use
|
configured - even when used on a diff output taken without any use
|
||||||
of `-O<orderfile>`, thereby making existing databases storing such
|
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`.
|
This is the default if `patchid.stable` is set to `true`.
|
||||||
|
|
||||||
`--unstable`::
|
`--unstable`::
|
||||||
Use an "unstable" hash as the patch ID. With this option,
|
Use an "unstable" hash as the patch ID. With this option,
|
||||||
the result produced is compatible with the patch-id value produced
|
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
|
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
|
databases storing patch IDs produced by Git 1.9 and older (who do not deal
|
||||||
with reordered patches) may want to use this option.
|
with reordered patches) may want to use this option.
|
||||||
+
|
+
|
||||||
This is the default.
|
This is the default.
|
||||||
|
|||||||
@ -494,9 +494,16 @@ See also INCOMPATIBLE OPTIONS below.
|
|||||||
Add a `Signed-off-by` trailer to all the rebased commits. Note
|
Add a `Signed-off-by` trailer to all the rebased commits. Note
|
||||||
that if `--interactive` is given then only commits marked to be
|
that if `--interactive` is given then only commits marked to be
|
||||||
picked, edited or reworded will have the trailer added.
|
picked, edited or reworded will have the trailer added.
|
||||||
+
|
|
||||||
See also INCOMPATIBLE OPTIONS below.
|
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 fast‑forwarded commits are also
|
||||||
|
rewritten.
|
||||||
|
|
||||||
-i::
|
-i::
|
||||||
--interactive::
|
--interactive::
|
||||||
Make a list of the commits which are about to be rebased. Let the
|
Make a list of the commits which are about to be rebased. Let the
|
||||||
|
|||||||
@ -8,20 +8,20 @@ git-remote - Manage set of tracked repositories
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[synopsis]
|
||||||
'git remote' [-v | --verbose]
|
git remote [-v | --verbose]
|
||||||
'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>
|
git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>
|
||||||
'git remote rename' [--[no-]progress] <old> <new>
|
git remote rename [--[no-]progress] <old> <new>
|
||||||
'git remote remove' <name>
|
git remote remove <name>
|
||||||
'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>)
|
git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
|
||||||
'git remote set-branches' [--add] <name> <branch>...
|
git remote set-branches [--add] <name> <branch>...
|
||||||
'git remote get-url' [--push] [--all] <name>
|
git remote get-url [--push] [--all] <name>
|
||||||
'git remote set-url' [--push] <name> <newurl> [<oldurl>]
|
git remote set-url [--push] <name> <newurl> [<oldurl>]
|
||||||
'git remote set-url --add' [--push] <name> <newurl>
|
git remote set-url --add [--push] <name> <newurl>
|
||||||
'git remote set-url --delete' [--push] <name> <URL>
|
git remote set-url --delete [--push] <name> <URL>
|
||||||
'git remote' [-v | --verbose] 'show' [-n] <name>...
|
git remote [-v | --verbose] show [-n] <name>...
|
||||||
'git remote prune' [-n | --dry-run] <name>...
|
git remote prune [-n | --dry-run] <name>...
|
||||||
'git remote' [-v | --verbose] 'update' [-p | --prune] [(<group> | <remote>)...]
|
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -32,8 +32,8 @@ Manage the set of repositories ("remotes") whose branches you track.
|
|||||||
OPTIONS
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
|
||||||
-v::
|
`-v`::
|
||||||
--verbose::
|
`--verbose`::
|
||||||
Be a little more verbose and show remote url after name.
|
Be a little more verbose and show remote url after name.
|
||||||
For promisor remotes, also show which filters (`blob:none` etc.)
|
For promisor remotes, also show which filters (`blob:none` etc.)
|
||||||
are configured.
|
are configured.
|
||||||
@ -43,14 +43,14 @@ OPTIONS
|
|||||||
COMMANDS
|
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.
|
subcommands are available to perform operations on the remotes.
|
||||||
|
|
||||||
'add'::
|
`add`::
|
||||||
|
|
||||||
Add a remote named <name> for the repository at
|
Add a remote named _<name>_ for the repository at
|
||||||
<URL>. The command `git fetch <name>` can then be used to create and
|
_<URL>_. The command `git fetch <name>` can then be used to create and
|
||||||
update remote-tracking branches <name>/<branch>.
|
update remote-tracking branches `<name>/<branch>`.
|
||||||
+
|
+
|
||||||
With `-f` option, `git fetch <name>` is run immediately after
|
With `-f` option, `git fetch <name>` is run immediately after
|
||||||
the remote information is set up.
|
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
|
With `-t <branch>` option, instead of the default glob
|
||||||
refspec for the remote to track all branches under
|
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
|
is created. You can give more than one `-t <branch>` to track
|
||||||
multiple branches without grabbing all branches.
|
multiple branches without grabbing all branches.
|
||||||
+
|
+
|
||||||
With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set
|
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
|
When a fetch mirror is created with `--mirror=fetch`, the refs will not
|
||||||
be stored in the 'refs/remotes/' namespace, but rather everything in
|
be stored in the `refs/remotes/` namespace, but rather everything in
|
||||||
'refs/' on the remote will be directly mirrored into 'refs/' in the
|
`refs/` on the remote will be directly mirrored into `refs/` in the
|
||||||
local repository. This option only makes sense in bare repositories,
|
local repository. This option only makes sense in bare repositories,
|
||||||
because a fetch would overwrite any local commits.
|
because a fetch would overwrite any local commits.
|
||||||
+
|
+
|
||||||
When a push mirror is created with `--mirror=push`, then `git push`
|
When a push mirror is created with `--mirror=push`, then `git push`
|
||||||
will always behave as if `--mirror` was passed.
|
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.
|
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
|
`$GIT_DIR/remotes` or `$GIT_DIR/branches`, the remote is converted to
|
||||||
the configuration file format.
|
the configuration file format.
|
||||||
|
|
||||||
'remove'::
|
`remove`::
|
||||||
'rm'::
|
`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.
|
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
|
symbolic-ref `refs/remotes/<name>/HEAD`) for
|
||||||
the named remote. Having a default branch for a remote is not required,
|
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
|
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
|
only work if `refs/remotes/origin/next` already exists; if not it must be
|
||||||
fetched first.
|
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
|
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`. This will only work if
|
||||||
`refs/remotes/origin/master` already exists; if not it must be fetched first.
|
`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
|
This can be used to track a subset of the available remote branches
|
||||||
after the initial setup for a remote.
|
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
|
With `--add`, instead of replacing the list of currently tracked
|
||||||
branches, adds to that list.
|
branches, adds to that list.
|
||||||
|
|
||||||
'get-url'::
|
`get-url`::
|
||||||
|
|
||||||
Retrieves the URLs for a remote. Configurations for `insteadOf` and
|
Retrieves the URLs for a remote. Configurations for `insteadOf` and
|
||||||
`pushInsteadOf` are expanded here. By default, only the first URL is listed.
|
`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.
|
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
|
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
|
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.
|
_<oldurl>_ doesn't match any URL, an error occurs and nothing is changed.
|
||||||
+
|
+
|
||||||
With `--push`, push URLs are manipulated instead of fetch URLs.
|
With `--push`, push URLs are manipulated instead of fetch URLs.
|
||||||
+
|
+
|
||||||
With `--add`, instead of changing existing URLs, new URL is added.
|
With `--add`, instead of changing existing URLs, new URL is added.
|
||||||
+
|
+
|
||||||
With `--delete`, instead of changing existing URLs, all URLs matching
|
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.
|
non-push URLs is an error.
|
||||||
+
|
+
|
||||||
Note that the push URL and the fetch URL, even though they can
|
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.
|
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
|
With `-n` option, the remote heads are not queried first with
|
||||||
`git ls-remote <name>`; cached information is used instead.
|
`git ls-remote <name>`; cached information is used instead.
|
||||||
|
|
||||||
'prune'::
|
`prune`::
|
||||||
|
|
||||||
Deletes stale references associated with <name>. By default, stale
|
Delete stale references associated with _<name>_. By default, stale
|
||||||
remote-tracking branches under <name> are deleted, but depending on
|
remote-tracking branches under _<name>_ are deleted, but depending on
|
||||||
global configuration and the configuration of the remote we might even
|
global configuration and the configuration of the remote we might even
|
||||||
prune local tags that haven't been pushed there. Equivalent to `git
|
prune local tags that haven't been pushed there. Equivalent to `git
|
||||||
fetch --prune <name>`, except that no new references will be fetched.
|
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
|
With `--dry-run` option, report what branches would be pruned, but do not
|
||||||
actually prune them.
|
actually prune them.
|
||||||
|
|
||||||
'update'::
|
`update`::
|
||||||
|
|
||||||
Fetch updates for remotes or remote groups in the repository as defined by
|
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,
|
`remotes.<group>`. If neither group nor remote is specified on the command line,
|
||||||
the configuration parameter remotes.default will be used; if
|
the configuration parameter `remotes.default` will be used; if
|
||||||
remotes.default is not defined, all remotes which do not have the
|
`remotes.default` is not defined, all remotes which do not have the
|
||||||
configuration parameter `remote.<name>.skipDefaultUpdate` set to true will
|
configuration parameter `remote.<name>.skipDefaultUpdate` set to `true` will
|
||||||
be updated. (See linkgit:git-config[1]).
|
be updated. (See linkgit:git-config[1]).
|
||||||
+
|
+
|
||||||
With `--prune` option, run pruning against all the remotes that are updated.
|
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`.
|
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
|
remote in question, the exit status is `2`. When the remote already
|
||||||
exists, the exit status is `3`.
|
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
|
$ mkdir project.git
|
||||||
|
|||||||
@ -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
|
Range of commits to replay; see "Specifying Ranges" in
|
||||||
linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the
|
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
|
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[]
|
include::rev-list-options.adoc[]
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
[synopsis]
|
[synopsis]
|
||||||
git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]
|
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]
|
git repo structure [--format=(table|keyvalue|nul) | -z]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
@ -44,6 +45,16 @@ supported:
|
|||||||
+
|
+
|
||||||
`-z` is an alias for `--format=nul`.
|
`-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]`::
|
`structure [--format=(table|keyvalue|nul) | -z]`::
|
||||||
Retrieve statistics about the current repository structure. The
|
Retrieve statistics about the current repository structure. The
|
||||||
following kinds of information are reported:
|
following kinds of information are reported:
|
||||||
|
|||||||
@ -3,86 +3,67 @@ git-reset(1)
|
|||||||
|
|
||||||
NAME
|
NAME
|
||||||
----
|
----
|
||||||
git-reset - Reset current HEAD to the specified state
|
git-reset - Set `HEAD` or the index to a known state
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[synopsis]
|
[synopsis]
|
||||||
|
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
|
||||||
git reset [-q] [<tree-ish>] [--] <pathspec>...
|
git reset [-q] [<tree-ish>] [--] <pathspec>...
|
||||||
git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
|
git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]
|
||||||
git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
|
git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]
|
||||||
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
In the first three forms, copy entries from _<tree-ish>_ to the index.
|
`git reset` does either of the following:
|
||||||
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 [-q] [<tree-ish>] [--] <pathspec>...`::
|
1. `git reset [<mode>] <commit>` changes which commit `HEAD` points to. This
|
||||||
`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`::
|
makes it possible to undo various Git operations, for example commit, merge,
|
||||||
These forms reset the index entries for all paths that match the
|
rebase, and pull.
|
||||||
_<pathspec>_ to their state at _<tree-ish>_. (It does not affect
|
2. When you specify files or directories or pass `--patch`, `git reset` updates
|
||||||
the working tree or the current branch.)
|
the staged version of the specified files.
|
||||||
+
|
|
||||||
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.
|
|
||||||
|
|
||||||
`git reset [<mode>] [<commit>]`::
|
`git reset [<mode>] [<commit>]`::
|
||||||
This form resets the current branch head to _<commit>_ and
|
Set the current branch head (`HEAD`) to point at _<commit>_.
|
||||||
possibly updates the index (resetting it to the tree of _<commit>_) and
|
Depending on _<mode>_, also update the working directory and/or index
|
||||||
the working tree depending on _<mode>_. Before the operation, `ORIG_HEAD`
|
to match the contents of _<commit>_.
|
||||||
is set to the tip of the current branch. If _<mode>_ is omitted,
|
_<commit>_ defaults to `HEAD`.
|
||||||
defaults to `--mixed`. The _<mode>_ must be one of the following:
|
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`::
|
`--mixed`::
|
||||||
Resets the index but not the working tree (i.e., the changed files
|
Leave your working directory unchanged.
|
||||||
are preserved but not marked for commit) and reports what has not
|
Update the index to match the new `HEAD`, so nothing will be staged.
|
||||||
been updated. This is the default action.
|
|
||||||
+
|
+
|
||||||
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]).
|
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`::
|
`--hard`::
|
||||||
Resets the index and working tree. Any changes to tracked files in the
|
Overwrite all files and directories with the version from _<commit>_,
|
||||||
working tree since _<commit>_ are discarded. Any untracked files or
|
and may overwrite untracked files. Tracked files not in _<commit>_ are
|
||||||
directories in the way of writing any tracked files are simply deleted.
|
removed so that the working tree matches _<commit>_.
|
||||||
|
Update the index to match the new `HEAD`, so nothing will be staged.
|
||||||
|
|
||||||
`--merge`::
|
`--merge`::
|
||||||
Resets the index and updates the files in the working tree that are
|
Reset the index and update the files in the working tree that are
|
||||||
different between _<commit>_ and `HEAD`, but keeps those which are
|
different between _<commit>_ and `HEAD`, but keep those which are
|
||||||
different between the index and working tree (i.e. which have changes
|
different between the index and working tree (i.e. which have changes
|
||||||
which have not been added).
|
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
|
If a file that is different between _<commit>_ and the index has
|
||||||
unstaged changes, reset is aborted.
|
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`::
|
`--keep`::
|
||||||
Resets index entries and updates files in the working tree that are
|
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.
|
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
|
See "Reset, restore and revert" in linkgit:git[1] for the differences
|
||||||
between the three commands.
|
between the three commands.
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,8 @@ git-stage - Add file contents to the staging area
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[synopsis]
|
||||||
'git stage' <arg>...
|
git stage <arg>...
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
|
|||||||
@ -8,8 +8,9 @@ git-status - Show the working tree status
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
|
||||||
'git status' [<options>] [--] [<pathspec>...]
|
[synopsis]
|
||||||
|
git status [<options>] [--] [<pathspec>...]
|
||||||
|
|
||||||
DESCRIPTION
|
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
|
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
|
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
|
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`.
|
`git commit`.
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
-------
|
-------
|
||||||
|
|
||||||
-s::
|
`-s`::
|
||||||
--short::
|
`--short`::
|
||||||
Give the output in the short-format.
|
Give the output in the short-format.
|
||||||
|
|
||||||
-b::
|
`-b`::
|
||||||
--branch::
|
`--branch`::
|
||||||
Show the branch and tracking info even in short-format.
|
Show the branch and tracking info even in short-format.
|
||||||
|
|
||||||
--show-stash::
|
`--show-stash`::
|
||||||
Show the number of entries currently stashed away.
|
Show the number of entries currently stashed away.
|
||||||
|
|
||||||
--porcelain[=<version>]::
|
`--porcelain[=<version>]`::
|
||||||
Give the output in an easy-to-parse format for scripts.
|
Give the output in an easy-to-parse format for scripts.
|
||||||
This is similar to the short output, but will remain stable
|
This is similar to the short output, but will remain stable
|
||||||
across Git versions and regardless of user configuration. See
|
across Git versions and regardless of user configuration. See
|
||||||
below for details.
|
below for details.
|
||||||
+
|
+
|
||||||
The version parameter is used to specify the format version.
|
The _<version>_ parameter is used to specify the format version.
|
||||||
This is optional and defaults to the original version 'v1' format.
|
This is optional and defaults to the original version `v1` format.
|
||||||
|
|
||||||
--long::
|
`--long`::
|
||||||
Give the output in the long-format. This is the default.
|
Give the output in the long-format. This is the default.
|
||||||
|
|
||||||
-v::
|
`-v`::
|
||||||
--verbose::
|
`--verbose`::
|
||||||
In addition to the names of files that have been changed, also
|
In addition to the names of files that have been changed, also
|
||||||
show the textual changes that are staged to be committed
|
show the textual changes that are staged to be committed
|
||||||
(i.e., like the output of `git diff --cached`). If `-v` is specified
|
(i.e., like the output of `git diff --cached`). If `-v` is specified
|
||||||
twice, then also show the changes in the working tree that
|
twice, then also show the changes in the working tree that
|
||||||
have not yet been staged (i.e., like the output of `git diff`).
|
have not yet been staged (i.e., like the output of `git diff`).
|
||||||
|
|
||||||
-u[<mode>]::
|
`-u[<mode>]`::
|
||||||
--untracked-files[=<mode>]::
|
`--untracked-files[=<mode>]`::
|
||||||
Show untracked files.
|
Show untracked files.
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
The mode parameter is used to specify the handling of 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`).
|
stuck to the option (e.g. `-uno`, but not `-u no`).
|
||||||
|
|
||||||
The possible options are:
|
The possible options are:
|
||||||
|
|
||||||
- 'no' - Show no untracked files.
|
`no`:: Show no untracked files.
|
||||||
- 'normal' - Shows untracked files and directories.
|
`normal`:: Show untracked files and directories.
|
||||||
- 'all' - Also shows individual files in untracked directories.
|
`all`:: Also show individual files in untracked directories.
|
||||||
|
|
||||||
When `-u` option is not used, untracked files and directories are
|
When `-u` option is not used, untracked files and directories are
|
||||||
shown (i.e. the same as specifying `normal`), to help you avoid
|
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`
|
All usual spellings for Boolean value `true` are taken as `normal`
|
||||||
and `false` as `no`.
|
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].
|
configuration variable documented in linkgit:git-config[1].
|
||||||
--
|
--
|
||||||
|
|
||||||
--ignore-submodules[=<when>]::
|
`--ignore-submodules[=<when>]`::
|
||||||
Ignore changes to submodules when looking for changes. <when> can be
|
Ignore changes to submodules when looking for changes. _<when>_ can be
|
||||||
either "none", "untracked", "dirty" or "all", which is the default.
|
either `none`, `untracked`, `dirty` or `all`, which is the default.
|
||||||
Using "none" will consider the submodule modified when it either contains
|
`none`;; will consider the submodule modified when it either contains
|
||||||
untracked or modified files or its HEAD differs from the commit recorded
|
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
|
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
|
`ignore` option in linkgit:git-config[1] or linkgit:gitmodules[5].
|
||||||
"untracked" is used submodules are not considered dirty when they only
|
`untracked`;; submodules are not considered dirty when they only
|
||||||
contain untracked content (but they are still scanned for modified
|
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
|
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
|
(and suppresses the output of submodule summaries when the config option
|
||||||
`status.submoduleSummary` is set).
|
`status.submoduleSummary` is set).
|
||||||
|
|
||||||
--ignored[=<mode>]::
|
`--ignored[=<mode>]`::
|
||||||
Show ignored files as well.
|
Show ignored files as well.
|
||||||
+
|
+
|
||||||
--
|
--
|
||||||
The mode parameter is used to specify the handling of ignored files.
|
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:
|
The possible options are:
|
||||||
|
|
||||||
- 'traditional' - Shows ignored files and directories, unless
|
`traditional`:: Show ignored files and directories, unless
|
||||||
--untracked-files=all is specified, in which case
|
`--untracked-files=all` is specified, in which case
|
||||||
individual files in ignored directories are
|
individual files in ignored directories are
|
||||||
displayed.
|
displayed.
|
||||||
- 'no' - Show no ignored files.
|
`no`:: Show no ignored files.
|
||||||
- 'matching' - Shows ignored files and directories matching an
|
`matching`:: Show ignored files and directories matching an
|
||||||
ignore pattern.
|
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,
|
ignored pattern are shown. If a directory matches an ignore pattern,
|
||||||
then it is shown, but not paths contained in the ignored directory. If
|
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
|
a directory does not match an ignore pattern, but all contents are
|
||||||
ignored, then the directory is not shown, but all contents are shown.
|
ignored, then the directory is not shown, but all contents are shown.
|
||||||
--
|
--
|
||||||
|
|
||||||
-z::
|
`-z`::
|
||||||
Terminate entries with NUL, instead of LF. This implies
|
Terminate entries with _NUL_, instead of _LF_. This implies
|
||||||
the `--porcelain=v1` output format if no other format is given.
|
the `--porcelain=v1` output format if no other format is given.
|
||||||
|
|
||||||
--column[=<options>]::
|
`--column[=<options>]`::
|
||||||
--no-column::
|
`--no-column`::
|
||||||
Display untracked files in columns. See configuration variable
|
Display untracked files in columns. See configuration variable
|
||||||
`column.status` for option syntax. `--column` and `--no-column`
|
`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.
|
respectively.
|
||||||
|
|
||||||
--ahead-behind::
|
`--ahead-behind`::
|
||||||
--no-ahead-behind::
|
`--no-ahead-behind`::
|
||||||
Display or do not display detailed ahead/behind counts for the
|
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::
|
`--renames`::
|
||||||
--no-renames::
|
`--no-renames`::
|
||||||
Turn on/off rename detection regardless of user configuration.
|
Turn on/off rename detection regardless of user configuration.
|
||||||
See also linkgit:git-diff[1] `--no-renames`.
|
See also linkgit:git-diff[1] `--no-renames`.
|
||||||
|
|
||||||
--find-renames[=<n>]::
|
`--find-renames[=<n>]`::
|
||||||
Turn on rename detection, optionally setting the similarity
|
Turn on rename detection, optionally setting the similarity
|
||||||
threshold.
|
threshold.
|
||||||
See also linkgit:git-diff[1] `--find-renames`.
|
See also linkgit:git-diff[1] `--find-renames`.
|
||||||
|
|
||||||
<pathspec>...::
|
`<pathspec>...`::
|
||||||
See the 'pathspec' entry in linkgit:gitglossary[7].
|
See the 'pathspec' entry in linkgit:gitglossary[7].
|
||||||
|
|
||||||
OUTPUT
|
OUTPUT
|
||||||
@ -173,12 +176,12 @@ Short Format
|
|||||||
In the short-format, the status of each path is shown as one of these
|
In the short-format, the status of each path is shown as one of these
|
||||||
forms
|
forms
|
||||||
|
|
||||||
XY PATH
|
<xy> <path>
|
||||||
XY ORIG_PATH -> PATH
|
<xy> <orig-path> -> <path>
|
||||||
|
|
||||||
where `ORIG_PATH` is where the renamed/copied contents came
|
where _<orig-path>_ is where the renamed/copied contents came
|
||||||
from. `ORIG_PATH` is only shown when the entry is renamed or
|
from. _<orig-path>_ is only shown when the entry is renamed or
|
||||||
copied. The `XY` is a two-letter status code.
|
copied. The _<xy>_ is a two-letter status code `XY`.
|
||||||
|
|
||||||
The fields (including the `->`) are separated from each other by a
|
The fields (including the `->`) are separated from each other by a
|
||||||
single space. If a filename contains whitespace or other nonprintable
|
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.
|
interior special characters backslash-escaped.
|
||||||
|
|
||||||
There are three different types of states that are shown using this format, and
|
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
|
* 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
|
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
|
these characters are used for `X` and `Y` fields for the first two sections that
|
||||||
show tracked paths:
|
show tracked paths:
|
||||||
|
|
||||||
* ' ' = unmodified
|
' ':: unmodified
|
||||||
* 'M' = modified
|
`M`:: modified
|
||||||
* 'T' = file type changed (regular file, symbolic link or submodule)
|
`T`:: file type changed (regular file, symbolic link or submodule)
|
||||||
* 'A' = added
|
`A`:: added
|
||||||
* 'D' = deleted
|
`D`:: deleted
|
||||||
* 'R' = renamed
|
`R`:: renamed
|
||||||
* 'C' = copied (if config option status.renames is set to "copies")
|
`C`:: copied (if config option status.renames is set to "copies")
|
||||||
* 'U' = updated but unmerged
|
`U`:: updated but unmerged
|
||||||
|
|
||||||
....
|
[cols="^1m,^1m,<2",options="header"]
|
||||||
X Y Meaning
|
|===
|
||||||
-------------------------------------------------
|
|X | Y |Meaning
|
||||||
[AMD] not updated
|
| |[AMD] |not updated
|
||||||
M [ MTD] updated in index
|
|M |[ MTD] |updated in index
|
||||||
T [ MTD] type changed in index
|
|T |[ MTD] |type changed in index
|
||||||
A [ MTD] added to index
|
|A |[ MTD] |added to index
|
||||||
D deleted from index
|
|D | |deleted from index
|
||||||
R [ MTD] renamed in index
|
|R |[ MTD] |renamed in index
|
||||||
C [ MTD] copied in index
|
|C |[ MTD] |copied in index
|
||||||
[MTARC] index and work tree matches
|
|[MTARC] | |index and work tree matches
|
||||||
[ MTARC] M work tree changed since index
|
|[ MTARC] |M |work tree changed since index
|
||||||
[ MTARC] T type changed in work tree since index
|
|[ MTARC] |T |type changed in work tree since index
|
||||||
[ MTARC] D deleted in work tree
|
|[ MTARC] |D |deleted in work tree
|
||||||
R renamed in work tree
|
| |R |renamed in work tree
|
||||||
C copied in work tree
|
| |C |copied in work tree
|
||||||
-------------------------------------------------
|
|D |D |unmerged, both deleted
|
||||||
D D unmerged, both deleted
|
|A |U |unmerged, added by us
|
||||||
A U unmerged, added by us
|
|U |D |unmerged, deleted by them
|
||||||
U D unmerged, deleted by them
|
|U |A |unmerged, added by them
|
||||||
U A unmerged, added by them
|
|D |U |unmerged, deleted by us
|
||||||
D U unmerged, deleted by us
|
|A |A |unmerged, both added
|
||||||
A A unmerged, both added
|
|U |U |unmerged, both modified
|
||||||
U U unmerged, both modified
|
|? |? |untracked
|
||||||
-------------------------------------------------
|
|! |! |ignored
|
||||||
? ? untracked
|
|===
|
||||||
! ! ignored
|
|
||||||
-------------------------------------------------
|
|
||||||
....
|
|
||||||
|
|
||||||
Submodules have more state and instead report
|
Submodules have more state and instead report
|
||||||
|
|
||||||
* 'M' = the submodule has a different HEAD than recorded in the index
|
`M`:: the submodule has a different HEAD than recorded in the index
|
||||||
* 'm' = the submodule has modified content
|
`m`:: the submodule has modified content
|
||||||
* '?' = the submodule has untracked files
|
`?`:: the submodule has untracked files
|
||||||
|
|
||||||
This is since modified content or untracked files in a submodule cannot be added
|
This is since modified content or untracked files in a submodule cannot be added
|
||||||
via `git add` in the superproject to prepare a commit.
|
via `git add` in the superproject to prepare a commit.
|
||||||
|
|
||||||
'm' and '?' are applied recursively. For example if a nested submodule
|
`m` and `?` are applied recursively. For example if a nested submodule
|
||||||
in a submodule contains an untracked file, this is reported as '?' as well.
|
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
|
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
|
The description of the short format above also describes the porcelain
|
||||||
format, with a few exceptions:
|
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.
|
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.
|
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
|
that format, the status field is the same, but some other things
|
||||||
change. First, the '\->' is omitted from rename entries and the field
|
change. First, the `->` is omitted from rename entries and the field
|
||||||
order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL
|
order is reversed (e.g `from -> to` becomes `to from`). Second, a _NUL_
|
||||||
(ASCII 0) follows each filename, replacing space as a field separator
|
(ASCII 0) follows each filename, replacing space as a field separator
|
||||||
and the terminating newline (but a space still separates the status
|
and the terminating newline (but a space still separates the status
|
||||||
field from the first filename). Third, filenames containing special
|
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
|
the worktree and changed items. Version 2 also defines an extensible
|
||||||
set of easy to parse optional headers.
|
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
|
command line arguments. Parsers should ignore headers they
|
||||||
don't recognize.
|
don't recognize.
|
||||||
|
|
||||||
@ -306,16 +308,15 @@ Branch Headers
|
|||||||
If `--branch` is given, a series of header lines are printed with
|
If `--branch` is given, a series of header lines are printed with
|
||||||
information about the current branch.
|
information about the current branch.
|
||||||
|
|
||||||
....
|
[cols="<1,<1",options="header"]
|
||||||
Line Notes
|
|===
|
||||||
------------------------------------------------------------
|
|Line |Notes
|
||||||
# branch.oid <commit> | (initial) Current commit.
|
|`# branch.oid <commit> \| (initial)` |Current commit.
|
||||||
# branch.head <branch> | (detached) Current branch.
|
|`# branch.head <branch> \| (detached)` |Current branch.
|
||||||
# branch.upstream <upstream-branch> If upstream is set.
|
|`# branch.upstream <upstream-branch>` |If upstream is set.
|
||||||
# branch.ab +<ahead> -<behind> If upstream is set and
|
|`# branch.ab +<ahead> -<behind>` |If upstream is set and
|
||||||
the commit is present.
|
the commit is present.
|
||||||
------------------------------------------------------------
|
|===
|
||||||
....
|
|
||||||
|
|
||||||
Stash Information
|
Stash Information
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
@ -336,66 +337,73 @@ line types in any order.
|
|||||||
|
|
||||||
Ordinary changed entries have the following format:
|
Ordinary changed entries have the following format:
|
||||||
|
|
||||||
1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
|
[synopsis]
|
||||||
|
1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
|
||||||
|
|
||||||
Renamed or copied entries have the following format:
|
Renamed or copied entries have the following format:
|
||||||
|
|
||||||
2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
|
[synopsis]
|
||||||
|
2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
|
||||||
|
|
||||||
....
|
[cols="<1,<1a",options="header"]
|
||||||
Field Meaning
|
|===
|
||||||
--------------------------------------------------------
|
|Field | Meaning
|
||||||
<XY> A 2 character field containing the staged and
|
|
||||||
unstaged XY values described in the short format,
|
|_<XY>_
|
||||||
with unchanged indicated by a "." rather than
|
|A 2 character field containing the staged and
|
||||||
a space.
|
unstaged XY values described in the short format,
|
||||||
<sub> A 4 character field describing the submodule state.
|
with unchanged indicated by a "." rather than
|
||||||
"N..." when the entry is not a submodule.
|
a space.
|
||||||
"S<c><m><u>" when the entry is a submodule.
|
|_<sub>_
|
||||||
<c> is "C" if the commit changed; otherwise ".".
|
|A 4 character field describing the submodule state.
|
||||||
<m> is "M" if it has tracked changes; otherwise ".".
|
"N..." when the entry is not a submodule.
|
||||||
<u> is "U" if there are untracked changes; otherwise ".".
|
`S<c><m><u>` when the entry is a submodule.
|
||||||
<mH> The octal file mode in HEAD.
|
|
||||||
<mI> The octal file mode in the index.
|
* _<c>_ is "C" if the commit changed; otherwise ".".
|
||||||
<mW> The octal file mode in the worktree.
|
* _<m>_ is "M" if it has tracked changes; otherwise ".".
|
||||||
<hH> The object name in HEAD.
|
* _<u>_ is "U" if there are untracked changes; otherwise ".".
|
||||||
<hI> The object name in the index.
|
|_<mH>_ |The octal file mode in HEAD.
|
||||||
<X><score> The rename or copy score (denoting the percentage
|
|_<mI>_ |The octal file mode in the index.
|
||||||
of similarity between the source and target of the
|
|_<mW>_ |The octal file mode in the worktree.
|
||||||
move or copy). For example "R100" or "C75".
|
|_<hH>_ |The object name in HEAD.
|
||||||
<path> The pathname. In a renamed/copied entry, this
|
|_<hI>_ |The object name in the index.
|
||||||
is the target path.
|
|_<X><score>_ |The rename or copy score (denoting the percentage
|
||||||
<sep> When the `-z` option is used, the 2 pathnames are separated
|
of similarity between the source and target of the
|
||||||
with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
|
move or copy). For example "R100" or "C75".
|
||||||
byte separates them.
|
|_<path>_
|
||||||
<origPath> The pathname in the commit at HEAD or in the index.
|
|The pathname. In a renamed/copied entry, this is the target path.
|
||||||
This is only present in a renamed/copied entry, and
|
|_<sep>_
|
||||||
tells where the renamed/copied contents came from.
|
|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.
|
||||||
|
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
|
Unmerged entries have the following format; the first character is
|
||||||
a "u" to distinguish from ordinary changed entries.
|
a "u" to distinguish from ordinary changed entries.
|
||||||
|
|
||||||
u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
|
[synopsis]
|
||||||
|
u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
|
||||||
|
|
||||||
....
|
[cols="<1,<1a",options="header"]
|
||||||
Field Meaning
|
|===
|
||||||
--------------------------------------------------------
|
|Field |Meaning
|
||||||
<XY> A 2 character field describing the conflict type
|
|_<XY>_ |A 2 character field describing the conflict type
|
||||||
as described in the short format.
|
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.
|
as described above.
|
||||||
<m1> The octal file mode in stage 1.
|
|_<m1>_ |The octal file mode in stage 1.
|
||||||
<m2> The octal file mode in stage 2.
|
|_<m2>_ |The octal file mode in stage 2.
|
||||||
<m3> The octal file mode in stage 3.
|
|_<m3>_ |The octal file mode in stage 3.
|
||||||
<mW> The octal file mode in the worktree.
|
|_<mW>_ |The octal file mode in the worktree.
|
||||||
<h1> The object name in stage 1.
|
|_<h1>_ |The object name in stage 1.
|
||||||
<h2> The object name in stage 2.
|
|_<h2>_ |The object name in stage 2.
|
||||||
<h3> The object name in stage 3.
|
|_<h3>_ |The object name in stage 3.
|
||||||
<path> The pathname.
|
|_<path>_ |The pathname.
|
||||||
--------------------------------------------------------
|
|===
|
||||||
....
|
|
||||||
|
|
||||||
Other Items
|
Other Items
|
||||||
^^^^^^^^^^^
|
^^^^^^^^^^^
|
||||||
@ -416,7 +424,7 @@ Pathname Format Notes and -z
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
When the `-z` option is given, pathnames are printed as is and
|
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.
|
byte.
|
||||||
|
|
||||||
Without the `-z` option, pathnames with "unusual" characters are
|
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
|
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
|
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
|
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
|
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
|
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
|
line option or the 'git submodule summary' command, which shows a similar
|
||||||
output but does not honor these settings.
|
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
|
setting this variable to `false` disables the warning message
|
||||||
given when enumerating untracked files takes more than 2
|
given when enumerating untracked files takes more than 2
|
||||||
seconds. In a large project, it may take longer and the user
|
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
|
not be an acceptable option for the user), in which case, there
|
||||||
is no point issuing the warning message, and in such a case,
|
is no point issuing the warning message, and in such a case,
|
||||||
disabling the warning may be the best.
|
disabling the warning may be the best.
|
||||||
|
|||||||
@ -119,7 +119,7 @@ verify::
|
|||||||
Verify <ref> against <old-oid> but do not change it. If
|
Verify <ref> against <old-oid> but do not change it. If
|
||||||
<old-oid> is zero or missing, the ref must not exist.
|
<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
|
Create symbolic ref <ref> with <new-target> after verifying that
|
||||||
it does not exist.
|
it does not exist.
|
||||||
|
|
||||||
|
|||||||
@ -79,6 +79,9 @@ with a matching name, treat as equivalent to:
|
|||||||
$ git worktree add --track -b <branch> <path> <remote>/<branch>
|
$ 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
|
If the branch exists in multiple remotes and one of them is named by
|
||||||
the `checkout.defaultRemote` configuration variable, we'll use that
|
the `checkout.defaultRemote` configuration variable, we'll use that
|
||||||
one for the purposes of disambiguation, even if the _<branch>_ isn't
|
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).
|
With `list`, output additional information about worktrees (see below).
|
||||||
|
|
||||||
`--expire <time>`::
|
`--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
|
With `list`, annotate missing worktrees as prunable if they are older than
|
||||||
_<time>_.
|
_<time>_.
|
||||||
@ -502,6 +505,7 @@ locked "reason\nwhy is locked"
|
|||||||
...
|
...
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
[[EXAMPLES]]
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
--------
|
--------
|
||||||
You are in the middle of a refactoring session and your boss comes in and
|
You are in the middle of a refactoring session and your boss comes in and
|
||||||
@ -522,6 +526,16 @@ $ popd
|
|||||||
$ git worktree remove ../temp
|
$ 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
|
CONFIGURATION
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|||||||
@ -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".
|
repositories will be set to this value. The default is "files".
|
||||||
See `--ref-format` in linkgit:git-init[1].
|
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 Commits
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
`GIT_AUTHOR_NAME`::
|
`GIT_AUTHOR_NAME`::
|
||||||
|
|||||||
@ -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
|
the other doesn't have. This can result in important objects becoming
|
||||||
unreferenced and possibly pruned by `git gc`, causing data loss.
|
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
|
Therefore, it's better to push your work to either the other system or a
|
||||||
server using the normal push and pull mechanism. However, this doesn't always
|
central server using the normal push and pull mechanism. In Git 2.51, Git
|
||||||
preserve important data, like stashes, so some people prefer to share a working
|
learned to import and export stashes, so it's possible to synchronize the state
|
||||||
tree across systems.
|
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`
|
You can then push the `stashes` branch (or whatever branch you've exported to),
|
||||||
(ideally with an encrypted connection such as with `ssh`) on the root of
|
fetch them to the local system (such as with `git fetch origin
|
||||||
repository. You should ensure several things when you do this:
|
+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
|
* 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.
|
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
|
any sort are taking place on it, including background operations like `git
|
||||||
gc` and operations invoked by your editor).
|
gc` and operations invoked by your editor).
|
||||||
+
|
+
|
||||||
Be aware that even with these recommendations, syncing in this way has some risk
|
Be aware that even with these recommendations, syncing working trees in this
|
||||||
since it bypasses Git's normal integrity checking for repositories, so having
|
way has some risk since it bypasses Git's normal integrity checking for
|
||||||
backups is advised. You may also wish to do a `git fsck` to verify the
|
repositories, so having backups is advised. You may also wish to do a `git
|
||||||
integrity of your data on the destination system after syncing.
|
fsck` to verify the integrity of your data on the destination system after
|
||||||
|
syncing.
|
||||||
|
|
||||||
Common Issues
|
Common Issues
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@ -10,6 +10,7 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
$GIT_DIR/objects/[0-9a-f][0-9a-f]/*
|
$GIT_DIR/objects/[0-9a-f][0-9a-f]/*
|
||||||
|
$GIT_DIR/objects/object-map/map-*.map
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@ -48,6 +49,83 @@ stored under
|
|||||||
Similarly, a blob containing the contents `abc` would have the uncompressed
|
Similarly, a blob containing the contents `abc` would have the uncompressed
|
||||||
data of `blob 3\0abc`.
|
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
|
GIT
|
||||||
---
|
---
|
||||||
Part of the linkgit:git[1] suite
|
Part of the linkgit:git[1] suite
|
||||||
|
|||||||
@ -812,10 +812,15 @@ MUST appear first in each pr-fields, in that order.
|
|||||||
After these mandatory fields, the server MAY advertise the following
|
After these mandatory fields, the server MAY advertise the following
|
||||||
optional fields in any order:
|
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
|
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").
|
is compatible with their needs (e.g., checking if both use
|
||||||
It corresponds to the "remote.<name>.partialCloneFilter" config setting.
|
"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
|
`token`:: An authentication token that clients can use when
|
||||||
connecting to the remote. It corresponds to the "remote.<name>.token"
|
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
|
above. Clients MUST ignore fields they don't recognize to allow for
|
||||||
future protocol extensions.
|
future protocol extensions.
|
||||||
|
|
||||||
For now, the client can only use information transmitted through these
|
The client can use information transmitted through these fields to
|
||||||
fields to decide if it accepts the advertised promisor remote. In the
|
decide if it accepts the advertised promisor remote. Also, the client
|
||||||
future that information might be used for other purposes though.
|
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.
|
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"
|
On the server side, the "promisor.advertise" and "promisor.sendFields"
|
||||||
configuration options can be used to control what it advertises. On
|
configuration options can be used to control what it advertises. On
|
||||||
the client side, the "promisor.acceptFromServer" configuration option
|
the client side, the "promisor.acceptFromServer" configuration option
|
||||||
can be used to control what it accepts. See the documentation of these
|
can be used to control what it accepts, and the "promisor.storeFields"
|
||||||
configuration options for more information.
|
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"
|
Note that in the future it would be nice if the "promisor-remote"
|
||||||
protocol capability could be used by the server, when responding to
|
protocol capability could be used by the server, when responding to
|
||||||
|
|||||||
@ -64,6 +64,7 @@ manpages = {
|
|||||||
'git-gui.adoc' : 1,
|
'git-gui.adoc' : 1,
|
||||||
'git-hash-object.adoc' : 1,
|
'git-hash-object.adoc' : 1,
|
||||||
'git-help.adoc' : 1,
|
'git-help.adoc' : 1,
|
||||||
|
'git-history.adoc' : 1,
|
||||||
'git-hook.adoc' : 1,
|
'git-hook.adoc' : 1,
|
||||||
'git-http-backend.adoc' : 1,
|
'git-http-backend.adoc' : 1,
|
||||||
'git-http-fetch.adoc' : 1,
|
'git-http-fetch.adoc' : 1,
|
||||||
|
|||||||
17
Makefile
17
Makefile
@ -1116,6 +1116,7 @@ LIB_OBJS += commit-reach.o
|
|||||||
LIB_OBJS += commit.o
|
LIB_OBJS += commit.o
|
||||||
LIB_OBJS += common-exit.o
|
LIB_OBJS += common-exit.o
|
||||||
LIB_OBJS += common-init.o
|
LIB_OBJS += common-init.o
|
||||||
|
LIB_OBJS += compat/ivec.o
|
||||||
LIB_OBJS += compat/nonblock.o
|
LIB_OBJS += compat/nonblock.o
|
||||||
LIB_OBJS += compat/obstack.o
|
LIB_OBJS += compat/obstack.o
|
||||||
LIB_OBJS += compat/open.o
|
LIB_OBJS += compat/open.o
|
||||||
@ -1285,6 +1286,7 @@ LIB_OBJS += repack-geometry.o
|
|||||||
LIB_OBJS += repack-midx.o
|
LIB_OBJS += repack-midx.o
|
||||||
LIB_OBJS += repack-promisor.o
|
LIB_OBJS += repack-promisor.o
|
||||||
LIB_OBJS += replace-object.o
|
LIB_OBJS += replace-object.o
|
||||||
|
LIB_OBJS += replay.o
|
||||||
LIB_OBJS += repo-settings.o
|
LIB_OBJS += repo-settings.o
|
||||||
LIB_OBJS += repository.o
|
LIB_OBJS += repository.o
|
||||||
LIB_OBJS += rerere.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/grep.o
|
||||||
BUILTIN_OBJS += builtin/hash-object.o
|
BUILTIN_OBJS += builtin/hash-object.o
|
||||||
BUILTIN_OBJS += builtin/help.o
|
BUILTIN_OBJS += builtin/help.o
|
||||||
|
BUILTIN_OBJS += builtin/history.o
|
||||||
BUILTIN_OBJS += builtin/hook.o
|
BUILTIN_OBJS += builtin/hook.o
|
||||||
BUILTIN_OBJS += builtin/index-pack.o
|
BUILTIN_OBJS += builtin/index-pack.o
|
||||||
BUILTIN_OBJS += builtin/init-db.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-example-decorate
|
||||||
CLAR_TEST_SUITES += u-hash
|
CLAR_TEST_SUITES += u-hash
|
||||||
CLAR_TEST_SUITES += u-hashmap
|
CLAR_TEST_SUITES += u-hashmap
|
||||||
|
CLAR_TEST_SUITES += u-list-objects-filter-options
|
||||||
CLAR_TEST_SUITES += u-mem-pool
|
CLAR_TEST_SUITES += u-mem-pool
|
||||||
CLAR_TEST_SUITES += u-oid-array
|
CLAR_TEST_SUITES += u-oid-array
|
||||||
CLAR_TEST_SUITES += u-oidmap
|
CLAR_TEST_SUITES += u-oidmap
|
||||||
CLAR_TEST_SUITES += u-oidtree
|
CLAR_TEST_SUITES += u-oidtree
|
||||||
|
CLAR_TEST_SUITES += u-parse-int
|
||||||
CLAR_TEST_SUITES += u-prio-queue
|
CLAR_TEST_SUITES += u-prio-queue
|
||||||
CLAR_TEST_SUITES += u-reftable-basics
|
CLAR_TEST_SUITES += u-reftable-basics
|
||||||
CLAR_TEST_SUITES += u-reftable-block
|
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
|
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/lib.rs
|
||||||
|
RUST_SOURCES += src/loose.rs
|
||||||
RUST_SOURCES += src/varint.rs
|
RUST_SOURCES += src/varint.rs
|
||||||
|
|
||||||
GIT-VERSION-FILE: FORCE
|
GIT-VERSION-FILE: FORCE
|
||||||
@ -1692,6 +1700,7 @@ ifeq ($(uname_S),Darwin)
|
|||||||
ifeq ($(shell test -d /opt/local/lib && echo y),y)
|
ifeq ($(shell test -d /opt/local/lib && echo y),y)
|
||||||
BASIC_CFLAGS += -I/opt/local/include
|
BASIC_CFLAGS += -I/opt/local/include
|
||||||
BASIC_LDFLAGS += -L/opt/local/lib
|
BASIC_LDFLAGS += -L/opt/local/lib
|
||||||
|
HAS_GOOD_LIBICONV = Yes
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
ifndef NO_APPLE_COMMON_CRYPTO
|
ifndef NO_APPLE_COMMON_CRYPTO
|
||||||
@ -1714,6 +1723,7 @@ endif
|
|||||||
ifdef USE_HOMEBREW_LIBICONV
|
ifdef USE_HOMEBREW_LIBICONV
|
||||||
ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/libiconv && echo y),y)
|
ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/libiconv && echo y),y)
|
||||||
ICONVDIR ?= $(HOMEBREW_PREFIX)/opt/libiconv
|
ICONVDIR ?= $(HOMEBREW_PREFIX)/opt/libiconv
|
||||||
|
HAS_GOOD_LIBICONV = Yes
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
@ -1859,6 +1869,11 @@ ifndef NO_ICONV
|
|||||||
endif
|
endif
|
||||||
EXTLIBS += $(ICONV_LINK) -liconv
|
EXTLIBS += $(ICONV_LINK) -liconv
|
||||||
endif
|
endif
|
||||||
|
ifdef NEEDS_GOOD_LIBICONV
|
||||||
|
ifndef HAS_GOOD_LIBICONV
|
||||||
|
BASIC_CFLAGS += -DICONV_RESTART_RESET
|
||||||
|
endif
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
ifdef ICONV_OMITS_BOM
|
ifdef ICONV_OMITS_BOM
|
||||||
BASIC_CFLAGS += -DICONV_OMITS_BOM
|
BASIC_CFLAGS += -DICONV_OMITS_BOM
|
||||||
@ -2991,7 +3006,7 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS)
|
|||||||
$(LIB_FILE): $(LIB_OBJS)
|
$(LIB_FILE): $(LIB_OBJS)
|
||||||
$(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^
|
$(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)
|
$(QUIET_CARGO)cargo build $(CARGO_ARGS)
|
||||||
|
|
||||||
.PHONY: rust
|
.PHONY: rust
|
||||||
|
|||||||
81
add-patch.c
81
add-patch.c
@ -42,10 +42,10 @@ static struct patch_mode patch_mode_add = {
|
|||||||
.apply_args = { "--cached", NULL },
|
.apply_args = { "--cached", NULL },
|
||||||
.apply_check_args = { "--cached", NULL },
|
.apply_check_args = { "--cached", NULL },
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Stage mode change [y,n,q,a,d%s,?]? "),
|
N_("Stage mode change%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Stage deletion [y,n,q,a,d%s,?]? "),
|
N_("Stage deletion%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Stage addition [y,n,q,a,d%s,?]? "),
|
N_("Stage addition%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Stage this hunk [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for staging."),
|
"will immediately be marked for staging."),
|
||||||
@ -64,10 +64,10 @@ static struct patch_mode patch_mode_stash = {
|
|||||||
.apply_args = { "--cached", NULL },
|
.apply_args = { "--cached", NULL },
|
||||||
.apply_check_args = { "--cached", NULL },
|
.apply_check_args = { "--cached", NULL },
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Stash mode change [y,n,q,a,d%s,?]? "),
|
N_("Stash mode change%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Stash deletion [y,n,q,a,d%s,?]? "),
|
N_("Stash deletion%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Stash addition [y,n,q,a,d%s,?]? "),
|
N_("Stash addition%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Stash this hunk [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for stashing."),
|
"will immediately be marked for stashing."),
|
||||||
@ -88,10 +88,10 @@ static struct patch_mode patch_mode_reset_head = {
|
|||||||
.is_reverse = 1,
|
.is_reverse = 1,
|
||||||
.index_only = 1,
|
.index_only = 1,
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Unstage mode change [y,n,q,a,d%s,?]? "),
|
N_("Unstage mode change%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Unstage deletion [y,n,q,a,d%s,?]? "),
|
N_("Unstage deletion%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Unstage addition [y,n,q,a,d%s,?]? "),
|
N_("Unstage addition%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Unstage this hunk [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for unstaging."),
|
"will immediately be marked for unstaging."),
|
||||||
@ -111,10 +111,10 @@ static struct patch_mode patch_mode_reset_nothead = {
|
|||||||
.apply_check_args = { "--cached", NULL },
|
.apply_check_args = { "--cached", NULL },
|
||||||
.index_only = 1,
|
.index_only = 1,
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Apply mode change 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 [y,n,q,a,d%s,?]? "),
|
N_("Apply deletion to index%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Apply addition to index [y,n,q,a,d%s,?]? "),
|
N_("Apply addition to index%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Apply this hunk to index [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for applying."),
|
"will immediately be marked for applying."),
|
||||||
@ -134,10 +134,10 @@ static struct patch_mode patch_mode_checkout_index = {
|
|||||||
.apply_check_args = { "-R", NULL },
|
.apply_check_args = { "-R", NULL },
|
||||||
.is_reverse = 1,
|
.is_reverse = 1,
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Discard mode change 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 [y,n,q,a,d%s,?]? "),
|
N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
|
N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Discard this hunk from worktree [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for discarding."),
|
"will immediately be marked for discarding."),
|
||||||
@ -157,10 +157,10 @@ static struct patch_mode patch_mode_checkout_head = {
|
|||||||
.apply_check_args = { "-R", NULL },
|
.apply_check_args = { "-R", NULL },
|
||||||
.is_reverse = 1,
|
.is_reverse = 1,
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Discard mode change 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 [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 [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 [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for discarding."),
|
"will immediately be marked for discarding."),
|
||||||
@ -179,10 +179,10 @@ static struct patch_mode patch_mode_checkout_nothead = {
|
|||||||
.apply_for_checkout = 1,
|
.apply_for_checkout = 1,
|
||||||
.apply_check_args = { NULL },
|
.apply_check_args = { NULL },
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Apply mode change 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 [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 [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 [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for applying."),
|
"will immediately be marked for applying."),
|
||||||
@ -202,10 +202,10 @@ static struct patch_mode patch_mode_worktree_head = {
|
|||||||
.apply_check_args = { "-R", NULL },
|
.apply_check_args = { "-R", NULL },
|
||||||
.is_reverse = 1,
|
.is_reverse = 1,
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Discard mode change 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 [y,n,q,a,d%s,?]? "),
|
N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Discard addition from worktree [y,n,q,a,d%s,?]? "),
|
N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Discard this hunk from worktree [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for discarding."),
|
"will immediately be marked for discarding."),
|
||||||
@ -224,10 +224,10 @@ static struct patch_mode patch_mode_worktree_nothead = {
|
|||||||
.apply_args = { NULL },
|
.apply_args = { NULL },
|
||||||
.apply_check_args = { NULL },
|
.apply_check_args = { NULL },
|
||||||
.prompt_mode = {
|
.prompt_mode = {
|
||||||
N_("Apply mode change 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 [y,n,q,a,d%s,?]? "),
|
N_("Apply deletion to worktree%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Apply addition to worktree [y,n,q,a,d%s,?]? "),
|
N_("Apply addition to worktree%s [y,n,q,a,d%s,?]? "),
|
||||||
N_("Apply this hunk to worktree [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 "
|
.edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk "
|
||||||
"will immediately be marked for applying."),
|
"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);
|
render_diff_header(s, file_diff, colored, &s->buf);
|
||||||
fputs(s->buf.buf, stdout);
|
fputs(s->buf.buf, stdout);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
const char *hunk_use_decision = "";
|
||||||
enum {
|
enum {
|
||||||
ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
|
ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0,
|
||||||
ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1,
|
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
|
(uintmax_t)(file_diff->hunk_nr
|
||||||
? file_diff->hunk_nr
|
? file_diff->hunk_nr
|
||||||
: 1));
|
: 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]),
|
printf(_(s->mode->prompt_mode[prompt_mode_type]),
|
||||||
s->buf.buf);
|
hunk_use_decision, s->buf.buf);
|
||||||
if (*s->s.reset_color_interactive)
|
if (*s->s.reset_color_interactive)
|
||||||
fputs(s->s.reset_color_interactive, stdout);
|
fputs(s->s.reset_color_interactive, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|||||||
19
attr.c
19
attr.c
@ -879,14 +879,6 @@ const char *git_attr_system_file(void)
|
|||||||
return system_wide;
|
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)
|
int git_attr_system_is_enabled(void)
|
||||||
{
|
{
|
||||||
return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
|
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;
|
struct attr_stack *e;
|
||||||
unsigned flags = READ_ATTR_MACRO_OK;
|
unsigned flags = READ_ATTR_MACRO_OK;
|
||||||
|
const char *attributes_file_path;
|
||||||
|
struct repository *repo;
|
||||||
|
|
||||||
if (*stack)
|
if (*stack)
|
||||||
return;
|
return;
|
||||||
@ -927,8 +921,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* home directory */
|
/* home directory */
|
||||||
if (git_attr_global_file()) {
|
if (istate && istate->repo)
|
||||||
e = read_attr_from_file(git_attr_global_file(), flags);
|
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);
|
push_stack(stack, e, NULL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3
attr.h
3
attr.h
@ -232,9 +232,6 @@ void attr_start(void);
|
|||||||
/* Return the system gitattributes file. */
|
/* Return the system gitattributes file. */
|
||||||
const char *git_attr_system_file(void);
|
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. */
|
/* Return whether the system gitattributes file is enabled and should be used. */
|
||||||
int git_attr_system_is_enabled(void);
|
int git_attr_system_is_enabled(void);
|
||||||
|
|
||||||
|
|||||||
17
build.rs
Normal file
17
build.rs
Normal 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");
|
||||||
|
}
|
||||||
27
builtin.h
27
builtin.h
@ -17,7 +17,8 @@
|
|||||||
* . Define the implementation of the built-in command `foo` with
|
* . Define the implementation of the built-in command `foo` with
|
||||||
* signature:
|
* 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`.
|
* . Add the external declaration for the function to `builtin.h`.
|
||||||
*
|
*
|
||||||
@ -29,12 +30,14 @@
|
|||||||
* where options is the bitwise-or of:
|
* where options is the bitwise-or of:
|
||||||
*
|
*
|
||||||
* `RUN_SETUP`:
|
* `RUN_SETUP`:
|
||||||
|
*
|
||||||
* If there is not a Git directory to work on, abort. If there
|
* 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
|
* 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
|
* invoked in a subdirectory. If there is no work tree, no
|
||||||
* chdir() is done.
|
* chdir() is done.
|
||||||
*
|
*
|
||||||
* `RUN_SETUP_GENTLY`:
|
* `RUN_SETUP_GENTLY`:
|
||||||
|
*
|
||||||
* If there is a Git directory, chdir as per RUN_SETUP, otherwise,
|
* If there is a Git directory, chdir as per RUN_SETUP, otherwise,
|
||||||
* don't chdir anywhere.
|
* don't chdir anywhere.
|
||||||
*
|
*
|
||||||
@ -57,6 +60,12 @@
|
|||||||
* more informed decision, e.g., by ignoring `pager.<cmd>` for
|
* more informed decision, e.g., by ignoring `pager.<cmd>` for
|
||||||
* certain subcommands.
|
* 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`.
|
* . Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`.
|
||||||
*
|
*
|
||||||
* Additionally, if `foo` is a new command, there are 4 more things to do:
|
* 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`.
|
* . 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
|
* 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_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_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_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_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_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);
|
int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);
|
||||||
|
|||||||
@ -487,8 +487,7 @@ static void batch_object_write(const char *obj_name,
|
|||||||
data->info.sizep = &data->size;
|
data->info.sizep = &data->size;
|
||||||
|
|
||||||
if (pack)
|
if (pack)
|
||||||
ret = packed_object_info(the_repository, pack,
|
ret = packed_object_info(pack, offset, &data->info);
|
||||||
offset, &data->info);
|
|
||||||
else
|
else
|
||||||
ret = odb_read_object_info_extended(the_repository->objects,
|
ret = odb_read_object_info_extended(the_repository->objects,
|
||||||
&data->oid, &data->info,
|
&data->oid, &data->info,
|
||||||
@ -846,12 +845,14 @@ static void batch_each_object(struct batch_options *opt,
|
|||||||
.callback = callback,
|
.callback = callback,
|
||||||
.payload = _payload,
|
.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);
|
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 &&
|
||||||
batch_one_object_bitmapped, &payload)) {
|
(bitmap = prepare_bitmap_git(the_repository)) &&
|
||||||
|
!for_each_bitmapped_object(bitmap, &opt->objects_filter,
|
||||||
|
batch_one_object_bitmapped, &payload)) {
|
||||||
struct packed_git *pack;
|
struct packed_git *pack;
|
||||||
|
|
||||||
repo_for_each_pack(the_repository, pack) {
|
repo_for_each_pack(the_repository, pack) {
|
||||||
|
|||||||
@ -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 struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
|
||||||
static int max_jobs = -1;
|
static int max_jobs = -1;
|
||||||
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
|
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 config_filter_submodules = -1; /* unspecified */
|
||||||
static int option_remote_submodules;
|
static int option_remote_submodules;
|
||||||
|
|
||||||
@ -634,7 +633,9 @@ static int git_sparse_checkout_init(const char *repo)
|
|||||||
return result;
|
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)
|
enum ref_storage_format ref_storage_format)
|
||||||
{
|
{
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
@ -723,9 +724,9 @@ static int checkout(int submodule_progress, int filter_submodules,
|
|||||||
strvec_pushf(&cmd.args, "--ref-format=%s",
|
strvec_pushf(&cmd.args, "--ref-format=%s",
|
||||||
ref_storage_format_to_name(ref_storage_format));
|
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",
|
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)
|
if (option_single_branch >= 0)
|
||||||
strvec_push(&cmd.args, option_single_branch ?
|
strvec_push(&cmd.args, option_single_branch ?
|
||||||
@ -903,6 +904,7 @@ int cmd_clone(int argc,
|
|||||||
enum transport_family family = TRANSPORT_FAMILY_ALL;
|
enum transport_family family = TRANSPORT_FAMILY_ALL;
|
||||||
struct string_list option_config = STRING_LIST_INIT_DUP;
|
struct string_list option_config = STRING_LIST_INIT_DUP;
|
||||||
int option_dissociate = 0;
|
int option_dissociate = 0;
|
||||||
|
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||||
int option_filter_submodules = -1; /* unspecified */
|
int option_filter_submodules = -1; /* unspecified */
|
||||||
struct string_list server_options = STRING_LIST_INIT_NODUP;
|
struct string_list server_options = STRING_LIST_INIT_NODUP;
|
||||||
const char *bundle_uri = NULL;
|
const char *bundle_uri = NULL;
|
||||||
@ -999,6 +1001,8 @@ int cmd_clone(int argc,
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filter_options.allow_auto_filter = 1;
|
||||||
|
|
||||||
packet_trace_identity("clone");
|
packet_trace_identity("clone");
|
||||||
|
|
||||||
repo_config(the_repository, git_clone_config, NULL);
|
repo_config(the_repository, git_clone_config, NULL);
|
||||||
@ -1625,9 +1629,13 @@ int cmd_clone(int argc,
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
junk_mode = JUNK_LEAVE_REPO;
|
junk_mode = JUNK_LEAVE_REPO;
|
||||||
err = checkout(submodule_progress, filter_submodules,
|
err = checkout(submodule_progress,
|
||||||
|
&filter_options,
|
||||||
|
filter_submodules,
|
||||||
ref_storage_format);
|
ref_storage_format);
|
||||||
|
|
||||||
|
list_objects_filter_release(&filter_options);
|
||||||
|
|
||||||
string_list_clear(&option_not, 0);
|
string_list_clear(&option_not, 0);
|
||||||
string_list_clear(&option_config, 0);
|
string_list_clear(&option_config, 0);
|
||||||
string_list_clear(&server_options, 0);
|
string_list_clear(&server_options, 0);
|
||||||
|
|||||||
@ -539,8 +539,7 @@ static const char *prepare_index(const char **argv, const char *prefix,
|
|||||||
|
|
||||||
path = repo_git_path(the_repository, "next-index-%"PRIuMAX,
|
path = repo_git_path(the_repository, "next-index-%"PRIuMAX,
|
||||||
(uintmax_t) getpid());
|
(uintmax_t) getpid());
|
||||||
hold_lock_file_for_update(&false_lock, path,
|
hold_lock_file_for_update(&false_lock, path, LOCK_DIE_ON_ERROR);
|
||||||
LOCK_DIE_ON_ERROR);
|
|
||||||
|
|
||||||
create_base_index(current_head);
|
create_base_index(current_head);
|
||||||
add_remove_files(&partial);
|
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, "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_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_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_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
|
||||||
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
|
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
|
||||||
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
|
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
|
||||||
|
|||||||
@ -803,6 +803,18 @@ static void location_options_init(struct config_location_options *opts,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts->use_global_config) {
|
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();
|
opts->source.file = opts->file_to_free = git_global_config();
|
||||||
if (!opts->source.file)
|
if (!opts->source.file)
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "path.h"
|
#include "path.h"
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
|
#include "url.h"
|
||||||
#include "write-or-die.h"
|
#include "write-or-die.h"
|
||||||
|
|
||||||
static struct lock_file credential_lock;
|
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");
|
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)
|
static int is_rfc3986_reserved_or_unreserved(char ch)
|
||||||
{
|
{
|
||||||
if (is_rfc3986_unreserved(ch))
|
if (is_rfc3986_unreserved(ch))
|
||||||
|
|||||||
@ -900,7 +900,7 @@ static void end_packfile(void)
|
|||||||
idx_name = keep_pack(create_index());
|
idx_name = keep_pack(create_index());
|
||||||
|
|
||||||
/* Register the packfile with core git's machinery. */
|
/* 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);
|
idx_name, 1);
|
||||||
if (!new_p)
|
if (!new_p)
|
||||||
die(_("core Git rejected index %s"), idx_name);
|
die(_("core Git rejected index %s"), idx_name);
|
||||||
@ -955,7 +955,7 @@ static int store_object(
|
|||||||
struct object_id *oidout,
|
struct object_id *oidout,
|
||||||
uintmax_t mark)
|
uintmax_t mark)
|
||||||
{
|
{
|
||||||
struct packfile_store *packs = the_repository->objects->packfiles;
|
struct odb_source *source;
|
||||||
void *out, *delta;
|
void *out, *delta;
|
||||||
struct object_entry *e;
|
struct object_entry *e;
|
||||||
unsigned char hdr[96];
|
unsigned char hdr[96];
|
||||||
@ -979,7 +979,11 @@ static int store_object(
|
|||||||
if (e->idx.offset) {
|
if (e->idx.offset) {
|
||||||
duplicate_count_by_type[type]++;
|
duplicate_count_by_type[type]++;
|
||||||
return 1;
|
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->type = type;
|
||||||
e->pack_id = MAX_PACK_ID;
|
e->pack_id = MAX_PACK_ID;
|
||||||
e->idx.offset = 1; /* just not zero! */
|
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)
|
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;
|
size_t in_sz = 64 * 1024, out_sz = 64 * 1024;
|
||||||
unsigned char *in_buf = xmalloc(in_sz);
|
unsigned char *in_buf = xmalloc(in_sz);
|
||||||
unsigned char *out_buf = xmalloc(out_sz);
|
unsigned char *out_buf = xmalloc(out_sz);
|
||||||
|
struct odb_source *source;
|
||||||
struct object_entry *e;
|
struct object_entry *e;
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
unsigned long hdrlen;
|
unsigned long hdrlen;
|
||||||
@ -1179,24 +1183,29 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark)
|
|||||||
if (e->idx.offset) {
|
if (e->idx.offset) {
|
||||||
duplicate_count_by_type[OBJ_BLOB]++;
|
duplicate_count_by_type[OBJ_BLOB]++;
|
||||||
truncate_pack(&checkpoint);
|
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->type = OBJ_BLOB;
|
||||||
e->pack_id = MAX_PACK_ID;
|
e->pack_id = MAX_PACK_ID;
|
||||||
e->idx.offset = 1; /* just not zero! */
|
e->idx.offset = 1; /* just not zero! */
|
||||||
duplicate_count_by_type[OBJ_BLOB]++;
|
duplicate_count_by_type[OBJ_BLOB]++;
|
||||||
truncate_pack(&checkpoint);
|
truncate_pack(&checkpoint);
|
||||||
|
goto out;
|
||||||
} else {
|
|
||||||
e->depth = 0;
|
|
||||||
e->type = OBJ_BLOB;
|
|
||||||
e->pack_id = pack_id;
|
|
||||||
e->idx.offset = offset;
|
|
||||||
e->idx.crc32 = crc32_end(pack_file);
|
|
||||||
object_count++;
|
|
||||||
object_count_by_type[OBJ_BLOB]++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e->depth = 0;
|
||||||
|
e->type = OBJ_BLOB;
|
||||||
|
e->pack_id = pack_id;
|
||||||
|
e->idx.offset = offset;
|
||||||
|
e->idx.crc32 = crc32_end(pack_file);
|
||||||
|
object_count++;
|
||||||
|
object_count_by_type[OBJ_BLOB]++;
|
||||||
|
|
||||||
|
out:
|
||||||
free(in_buf);
|
free(in_buf);
|
||||||
free(out_buf);
|
free(out_buf);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,6 @@ static struct strbuf default_rla = STRBUF_INIT;
|
|||||||
static struct transport *gtransport;
|
static struct transport *gtransport;
|
||||||
static struct transport *gsecondary;
|
static struct transport *gsecondary;
|
||||||
static struct refspec refmap = REFSPEC_INIT_FETCH;
|
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 server_options = STRING_LIST_INIT_DUP;
|
||||||
static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
|
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;
|
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;
|
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");
|
set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
|
||||||
if (refetch)
|
if (refetch)
|
||||||
set_option(transport, TRANS_OPT_REFETCH, "yes");
|
set_option(transport, TRANS_OPT_REFETCH, "yes");
|
||||||
if (filter_options.choice) {
|
if (filter_options->choice) {
|
||||||
const char *spec =
|
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_LIST_OBJECTS_FILTER, spec);
|
||||||
set_option(transport, TRANS_OPT_FROM_PROMISOR, "1");
|
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_transaction *transaction,
|
||||||
struct ref *ref_map,
|
struct ref *ref_map,
|
||||||
struct fetch_head *fetch_head,
|
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;
|
int retcode, cannot_reuse;
|
||||||
|
|
||||||
@ -1507,7 +1508,7 @@ static int backfill_tags(struct display_state *display_state,
|
|||||||
cannot_reuse = transport->cannot_reuse ||
|
cannot_reuse = transport->cannot_reuse ||
|
||||||
deepen_since || deepen_not.nr;
|
deepen_since || deepen_not.nr;
|
||||||
if (cannot_reuse) {
|
if (cannot_reuse) {
|
||||||
gsecondary = prepare_transport(transport->remote, 0);
|
gsecondary = prepare_transport(transport->remote, 0, filter_options);
|
||||||
transport = gsecondary;
|
transport = gsecondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1713,7 +1714,8 @@ out:
|
|||||||
|
|
||||||
static int do_fetch(struct transport *transport,
|
static int do_fetch(struct transport *transport,
|
||||||
struct refspec *rs,
|
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_transaction *transaction = NULL;
|
||||||
struct ref *ref_map = NULL;
|
struct ref *ref_map = NULL;
|
||||||
@ -1873,7 +1875,7 @@ static int do_fetch(struct transport *transport,
|
|||||||
* the transaction and don't commit anything.
|
* the transaction and don't commit anything.
|
||||||
*/
|
*/
|
||||||
if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
|
if (backfill_tags(&display_state, transport, transaction, tags_ref_map,
|
||||||
&fetch_head, config))
|
&fetch_head, config, filter_options))
|
||||||
retcode = 1;
|
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
|
* Fetching from the promisor remote should use the given filter-spec
|
||||||
* or inherit the default filter-spec from the config.
|
* 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
|
* Explicit --no-filter argument overrides everything, regardless
|
||||||
* of any prior partial clones and fetches.
|
* of any prior partial clones and fetches.
|
||||||
*/
|
*/
|
||||||
if (filter_options.no_filter)
|
if (filter_options->no_filter)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If no prior partial clone/fetch and the current fetch DID NOT
|
* If no prior partial clone/fetch and the current fetch DID NOT
|
||||||
* request a partial-fetch, do a normal fetch.
|
* 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;
|
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
|
* filter-spec as the default for subsequent fetches to this
|
||||||
* remote if there is currently no default filter-spec.
|
* remote if there is currently no default filter-spec.
|
||||||
*/
|
*/
|
||||||
if (filter_options.choice) {
|
if (filter_options->choice) {
|
||||||
partial_clone_register(remote->name, &filter_options);
|
partial_clone_register(remote->name, filter_options);
|
||||||
return;
|
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
|
* explicitly given filter-spec or inherit the filter-spec from
|
||||||
* the config.
|
* the config.
|
||||||
*/
|
*/
|
||||||
if (!filter_options.choice)
|
if (!filter_options->choice)
|
||||||
partial_clone_get_default_filter_spec(&filter_options, remote->name);
|
partial_clone_get_default_filter_spec(filter_options, remote->name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fetch_one(struct remote *remote, int argc, const char **argv,
|
static int fetch_one(struct remote *remote, int argc, const char **argv,
|
||||||
int prune_tags_ok, int use_stdin_refspecs,
|
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;
|
struct refspec rs = REFSPEC_INIT_FETCH;
|
||||||
int i;
|
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"
|
die(_("no remote repository specified; please specify either a URL or a\n"
|
||||||
"remote name from which new revisions should be fetched"));
|
"remote name from which new revisions should be fetched"));
|
||||||
|
|
||||||
gtransport = prepare_transport(remote, 1);
|
gtransport = prepare_transport(remote, 1, filter_options);
|
||||||
|
|
||||||
if (prune < 0) {
|
if (prune < 0) {
|
||||||
/* no command line request */
|
/* 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);
|
sigchain_push_common(unlock_pack_on_signal);
|
||||||
atexit(unlock_pack_atexit);
|
atexit(unlock_pack_atexit);
|
||||||
sigchain_push(SIGPIPE, SIG_IGN);
|
sigchain_push(SIGPIPE, SIG_IGN);
|
||||||
exit_code = do_fetch(gtransport, &rs, config);
|
exit_code = do_fetch(gtransport, &rs, config, filter_options);
|
||||||
sigchain_pop(SIGPIPE);
|
sigchain_pop(SIGPIPE);
|
||||||
refspec_clear(&rs);
|
refspec_clear(&rs);
|
||||||
transport_disconnect(gtransport);
|
transport_disconnect(gtransport);
|
||||||
@ -2329,6 +2333,7 @@ int cmd_fetch(int argc,
|
|||||||
const char *submodule_prefix = "";
|
const char *submodule_prefix = "";
|
||||||
const char *bundle_uri;
|
const char *bundle_uri;
|
||||||
struct string_list list = STRING_LIST_INIT_DUP;
|
struct string_list list = STRING_LIST_INIT_DUP;
|
||||||
|
struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
|
||||||
struct remote *remote = NULL;
|
struct remote *remote = NULL;
|
||||||
int all = -1, multiple = 0;
|
int all = -1, multiple = 0;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
@ -2434,6 +2439,8 @@ int cmd_fetch(int argc,
|
|||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filter_options.allow_auto_filter = 1;
|
||||||
|
|
||||||
packet_trace_identity("fetch");
|
packet_trace_identity("fetch");
|
||||||
|
|
||||||
/* Record the command line for the reflog */
|
/* Record the command line for the reflog */
|
||||||
@ -2594,7 +2601,7 @@ int cmd_fetch(int argc,
|
|||||||
trace2_region_enter("fetch", "negotiate-only", the_repository);
|
trace2_region_enter("fetch", "negotiate-only", the_repository);
|
||||||
if (!remote)
|
if (!remote)
|
||||||
die(_("must supply remote when using --negotiate-only"));
|
die(_("must supply remote when using --negotiate-only"));
|
||||||
gtransport = prepare_transport(remote, 1);
|
gtransport = prepare_transport(remote, 1, &filter_options);
|
||||||
if (gtransport->smart_options) {
|
if (gtransport->smart_options) {
|
||||||
gtransport->smart_options->acked_commits = &acked_commits;
|
gtransport->smart_options->acked_commits = &acked_commits;
|
||||||
} else {
|
} else {
|
||||||
@ -2616,12 +2623,12 @@ int cmd_fetch(int argc,
|
|||||||
} else if (remote) {
|
} else if (remote) {
|
||||||
if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
|
if (filter_options.choice || repo_has_promisor_remote(the_repository)) {
|
||||||
trace2_region_enter("fetch", "setup-partial", 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_leave("fetch", "setup-partial", the_repository);
|
||||||
}
|
}
|
||||||
trace2_region_enter("fetch", "fetch-one", the_repository);
|
trace2_region_enter("fetch", "fetch-one", the_repository);
|
||||||
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
|
result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,
|
||||||
&config);
|
&config, &filter_options);
|
||||||
trace2_region_leave("fetch", "fetch-one", the_repository);
|
trace2_region_leave("fetch", "fetch-one", the_repository);
|
||||||
} else {
|
} else {
|
||||||
int max_children = max_jobs;
|
int max_children = max_jobs;
|
||||||
@ -2727,5 +2734,6 @@ int cmd_fetch(int argc,
|
|||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
string_list_clear(&list, 0);
|
string_list_clear(&list, 0);
|
||||||
|
list_objects_filter_release(&filter_options);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
210
builtin/fsck.c
210
builtin/fsck.c
@ -51,6 +51,7 @@ static int show_progress = -1;
|
|||||||
static int show_dangling = 1;
|
static int show_dangling = 1;
|
||||||
static int name_objects;
|
static int name_objects;
|
||||||
static int check_references = 1;
|
static int check_references = 1;
|
||||||
|
static timestamp_t now;
|
||||||
#define ERROR_OBJECT 01
|
#define ERROR_OBJECT 01
|
||||||
#define ERROR_REACHABLE 02
|
#define ERROR_REACHABLE 02
|
||||||
#define ERROR_PACK 04
|
#define ERROR_PACK 04
|
||||||
@ -510,6 +511,9 @@ static int fsck_handle_reflog_ent(const char *refname,
|
|||||||
timestamp_t timestamp, int tz UNUSED,
|
timestamp_t timestamp, int tz UNUSED,
|
||||||
const char *message UNUSED, void *cb_data UNUSED)
|
const char *message UNUSED, void *cb_data UNUSED)
|
||||||
{
|
{
|
||||||
|
if (now && timestamp > now)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
fprintf_ln(stderr, _("Checking reflog %s->%s"),
|
fprintf_ln(stderr, _("Checking reflog %s->%s"),
|
||||||
oid_to_hex(ooid), oid_to_hex(noid));
|
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;
|
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;
|
struct object *obj;
|
||||||
|
|
||||||
obj = parse_object(the_repository, ref->oid);
|
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;
|
errors_found |= ERROR_REFS;
|
||||||
}
|
}
|
||||||
default_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;
|
obj->flags |= USED;
|
||||||
fsck_put_object_name(&fsck_walk_options,
|
fsck_put_object_name(&fsck_walk_options,
|
||||||
ref->oid, "%s", ref->name);
|
ref->oid, "%s", ref->name);
|
||||||
@ -564,18 +596,35 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fsck_head_link(const char *head_ref_name,
|
static void snapshot_refs(struct snapshot *snap, int argc, const char **argv)
|
||||||
const char **head_points_at,
|
|
||||||
struct object_id *head_oid);
|
|
||||||
|
|
||||||
static void get_default_heads(void)
|
|
||||||
{
|
{
|
||||||
struct worktree **worktrees, **p;
|
struct worktree **worktrees, **p;
|
||||||
const char *head_points_at;
|
const char *head_points_at;
|
||||||
struct object_id head_oid;
|
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),
|
refs_for_each_rawref(get_main_ref_store(the_repository),
|
||||||
fsck_handle_ref, NULL);
|
snapshot_ref, snap);
|
||||||
|
|
||||||
worktrees = get_worktrees();
|
worktrees = get_worktrees();
|
||||||
for (p = worktrees; *p; p++) {
|
for (p = worktrees; *p; p++) {
|
||||||
@ -583,23 +632,63 @@ static void get_default_heads(void)
|
|||||||
struct strbuf refname = STRBUF_INIT;
|
struct strbuf refname = STRBUF_INIT;
|
||||||
|
|
||||||
strbuf_worktree_ref(wt, &refname, "HEAD");
|
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)) {
|
if (head_points_at && !is_null_oid(&head_oid)) {
|
||||||
struct reference ref = {
|
struct reference ref = {
|
||||||
.name = refname.buf,
|
.name = refname.buf,
|
||||||
.oid = &head_oid,
|
.oid = &head_oid,
|
||||||
};
|
};
|
||||||
|
|
||||||
fsck_handle_ref(&ref, NULL);
|
snapshot_ref(&ref, snap);
|
||||||
}
|
}
|
||||||
strbuf_release(&refname);
|
strbuf_release(&refname);
|
||||||
|
|
||||||
if (include_reflogs)
|
/*
|
||||||
refs_for_each_reflog(get_worktree_ref_store(wt),
|
* TODO: Could use refs_for_each_reflog(...) to find
|
||||||
fsck_handle_reflog, wt);
|
* latest entry instead of using a global 'now' for that
|
||||||
|
* purpose.
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
free_worktrees(worktrees);
|
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
|
* Not having any default heads isn't really fatal, but
|
||||||
* it does mean that "--unreachable" no longer makes any
|
* it does mean that "--unreachable" no longer makes any
|
||||||
@ -713,43 +802,6 @@ static void fsck_source(struct odb_source *source)
|
|||||||
stop_progress(&progress);
|
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)
|
static int fsck_cache_tree(struct cache_tree *it, const char *index_path)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@ -963,8 +1015,12 @@ int cmd_fsck(int argc,
|
|||||||
const char *prefix,
|
const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo UNUSED)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
struct odb_source *source;
|
struct odb_source *source;
|
||||||
|
struct snapshot snap = {
|
||||||
|
.nr = 0,
|
||||||
|
.alloc = 0,
|
||||||
|
.ref = NULL
|
||||||
|
};
|
||||||
|
|
||||||
/* fsck knows how to handle missing promisor objects */
|
/* fsck knows how to handle missing promisor objects */
|
||||||
fetch_if_missing = 0;
|
fetch_if_missing = 0;
|
||||||
@ -1000,6 +1056,17 @@ int cmd_fsck(int argc,
|
|||||||
if (check_references)
|
if (check_references)
|
||||||
fsck_refs(the_repository);
|
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) {
|
if (connectivity_only) {
|
||||||
for_each_loose_object(the_repository->objects,
|
for_each_loose_object(the_repository->objects,
|
||||||
mark_loose_for_connectivity, NULL, 0);
|
mark_loose_for_connectivity, NULL, 0);
|
||||||
@ -1041,42 +1108,18 @@ int cmd_fsck(int argc,
|
|||||||
errors_found |= ERROR_OBJECT;
|
errors_found |= ERROR_OBJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < argc; i++) {
|
/* Process the snapshotted refs and the reflogs. */
|
||||||
const char *arg = argv[i];
|
process_refs(&snap);
|
||||||
struct object_id oid;
|
|
||||||
if (!repo_get_oid(the_repository, arg, &oid)) {
|
|
||||||
struct object *obj = lookup_object(the_repository,
|
|
||||||
&oid);
|
|
||||||
|
|
||||||
if (!obj || !(obj->flags & HAS_OBJ)) {
|
/* If not given any explicit objects, process index files too. */
|
||||||
if (is_promisor_object(the_repository, &oid))
|
if (!argc)
|
||||||
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();
|
|
||||||
keep_cache_objects = 1;
|
keep_cache_objects = 1;
|
||||||
}
|
|
||||||
|
|
||||||
if (keep_cache_objects) {
|
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;
|
struct worktree **worktrees, **p;
|
||||||
|
|
||||||
verify_index_checksum = 1;
|
verify_index_checksum = 1;
|
||||||
@ -1149,5 +1192,6 @@ int cmd_fsck(int argc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free_snapshot_refs(&snap);
|
||||||
return errors_found;
|
return errors_found;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -671,7 +671,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
|
|||||||
const struct fsmonitor_batch *batch;
|
const struct fsmonitor_batch *batch;
|
||||||
struct fsmonitor_batch *remainder = NULL;
|
struct fsmonitor_batch *remainder = NULL;
|
||||||
intmax_t count = 0, duplicates = 0;
|
intmax_t count = 0, duplicates = 0;
|
||||||
kh_str_t *shown;
|
kh_str_t *shown = NULL;
|
||||||
int hash_ret;
|
int hash_ret;
|
||||||
int do_trivial = 0;
|
int do_trivial = 0;
|
||||||
int do_flush = 0;
|
int do_flush = 0;
|
||||||
@ -909,8 +909,6 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
|
|||||||
total_response_len += payload.len;
|
total_response_len += payload.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
kh_release_str(shown);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&state->main_lock);
|
pthread_mutex_lock(&state->main_lock);
|
||||||
|
|
||||||
if (token_data->client_ref_count > 0)
|
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);
|
trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
kh_destroy_str(shown);
|
||||||
strbuf_release(&response_token);
|
strbuf_release(&response_token);
|
||||||
strbuf_release(&requested_token_id);
|
strbuf_release(&requested_token_id);
|
||||||
strbuf_release(&payload);
|
strbuf_release(&payload);
|
||||||
@ -1405,6 +1404,7 @@ static int fsmonitor_run_daemon(void)
|
|||||||
done:
|
done:
|
||||||
pthread_cond_destroy(&state.cookies_cond);
|
pthread_cond_destroy(&state.cookies_cond);
|
||||||
pthread_mutex_destroy(&state.main_lock);
|
pthread_mutex_destroy(&state.main_lock);
|
||||||
|
hashmap_clear(&state.cookies);
|
||||||
fsm_listen__dtor(&state);
|
fsm_listen__dtor(&state);
|
||||||
fsm_health__dtor(&state);
|
fsm_health__dtor(&state);
|
||||||
|
|
||||||
|
|||||||
14
builtin/gc.c
14
builtin/gc.c
@ -748,8 +748,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid)
|
|||||||
xsnprintf(my_host, sizeof(my_host), "unknown");
|
xsnprintf(my_host, sizeof(my_host), "unknown");
|
||||||
|
|
||||||
pidfile_path = repo_git_path(the_repository, "gc.pid");
|
pidfile_path = repo_git_path(the_repository, "gc.pid");
|
||||||
fd = hold_lock_file_for_update(&lock, pidfile_path,
|
fd = hold_lock_file_for_update(&lock, pidfile_path, LOCK_DIE_ON_ERROR);
|
||||||
LOCK_DIE_ON_ERROR);
|
|
||||||
if (!force) {
|
if (!force) {
|
||||||
static char locking_host[HOST_NAME_MAX + 1];
|
static char locking_host[HOST_NAME_MAX + 1];
|
||||||
static char *scan_fmt;
|
static char *scan_fmt;
|
||||||
@ -1016,8 +1015,7 @@ int cmd_gc(int argc,
|
|||||||
|
|
||||||
if (daemonized) {
|
if (daemonized) {
|
||||||
char *path = repo_git_path(the_repository, "gc.log");
|
char *path = repo_git_path(the_repository, "gc.log");
|
||||||
hold_lock_file_for_update(&log_lock, path,
|
hold_lock_file_for_update(&log_lock, path, LOCK_DIE_ON_ERROR);
|
||||||
LOCK_DIE_ON_ERROR);
|
|
||||||
dup2(get_lock_file_fd(&log_lock), 2);
|
dup2(get_lock_file_fd(&log_lock), 2);
|
||||||
atexit(process_log_file_at_exit);
|
atexit(process_log_file_at_exit);
|
||||||
free(path);
|
free(path);
|
||||||
@ -1130,8 +1128,10 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
commit = lookup_commit(the_repository, maybe_peeled);
|
commit = lookup_commit(the_repository, maybe_peeled);
|
||||||
if (!commit)
|
if (!commit || commit->object.flags & SEEN)
|
||||||
return 0;
|
return 0;
|
||||||
|
commit->object.flags |= SEEN;
|
||||||
|
|
||||||
if (repo_parse_commit(the_repository, commit) ||
|
if (repo_parse_commit(the_repository, commit) ||
|
||||||
commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
|
commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH)
|
||||||
return 0;
|
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)
|
if (data->num_not_in_graph >= data->limit)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
commit_list_append(commit, &stack);
|
commit_list_insert(commit, &stack);
|
||||||
|
|
||||||
while (!result && stack) {
|
while (!result && stack) {
|
||||||
struct commit_list *parent;
|
struct commit_list *parent;
|
||||||
@ -1162,7 +1162,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
commit_list_append(parent->item, &stack);
|
commit_list_insert(parent->item, &stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1213,8 +1213,14 @@ int cmd_grep(int argc,
|
|||||||
*/
|
*/
|
||||||
if (recurse_submodules)
|
if (recurse_submodules)
|
||||||
repo_read_gitmodules(the_repository, 1);
|
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);
|
start_threads(&opt);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
404
builtin/history.c
Normal file
404
builtin/history.c
Normal 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);
|
||||||
|
}
|
||||||
@ -1638,7 +1638,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
|
|||||||
hash, "idx", 1);
|
hash, "idx", 1);
|
||||||
|
|
||||||
if (do_fsck_object && startup_info->have_repository)
|
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);
|
final_index_name, 0);
|
||||||
|
|
||||||
if (!from_stdin) {
|
if (!from_stdin) {
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
#include "gettext.h"
|
#include "gettext.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "string-list.h"
|
#include "string-list.h"
|
||||||
#include "tempfile.h"
|
|
||||||
#include "trailer.h"
|
#include "trailer.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@ -93,37 +92,6 @@ static int parse_opt_parse(const struct option *opt, const char *arg,
|
|||||||
return 0;
|
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)
|
static void read_input_file(struct strbuf *sb, const char *file)
|
||||||
{
|
{
|
||||||
if (file) {
|
if (file) {
|
||||||
@ -140,55 +108,20 @@ static void interpret_trailers(const struct process_trailer_options *opts,
|
|||||||
struct list_head *new_trailer_head,
|
struct list_head *new_trailer_head,
|
||||||
const char *file)
|
const char *file)
|
||||||
{
|
{
|
||||||
LIST_HEAD(head);
|
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
struct strbuf trailer_block_sb = STRBUF_INIT;
|
struct strbuf out = STRBUF_INIT;
|
||||||
struct trailer_block *trailer_block;
|
|
||||||
FILE *outfile = stdout;
|
|
||||||
|
|
||||||
trailer_config_init();
|
|
||||||
|
|
||||||
read_input_file(&sb, file);
|
read_input_file(&sb, file);
|
||||||
|
|
||||||
if (opts->in_place)
|
process_trailers(opts, new_trailer_head, &sb, &out);
|
||||||
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);
|
|
||||||
|
|
||||||
if (opts->in_place)
|
if (opts->in_place)
|
||||||
if (rename_tempfile(&trailers_tempfile, file))
|
write_file_buf(file, out.buf, out.len);
|
||||||
die_errno(_("could not rename temporary file to %s"), file);
|
else
|
||||||
|
strbuf_write(&out, stdout);
|
||||||
|
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
|
strbuf_release(&out);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_interpret_trailers(int argc,
|
int cmd_interpret_trailers(int argc,
|
||||||
@ -232,6 +165,8 @@ int cmd_interpret_trailers(int argc,
|
|||||||
git_interpret_trailers_usage,
|
git_interpret_trailers_usage,
|
||||||
options);
|
options);
|
||||||
|
|
||||||
|
trailer_config_init();
|
||||||
|
|
||||||
if (argc) {
|
if (argc) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < argc; i++)
|
for (i = 0; i < argc; i++)
|
||||||
|
|||||||
@ -23,6 +23,11 @@
|
|||||||
#define PARENT1 (1u<<16) /* used instead of SEEN */
|
#define PARENT1 (1u<<16) /* used instead of SEEN */
|
||||||
#define PARENT2 (1u<<17) /* used instead of BOTTOM, BOUNDARY */
|
#define PARENT2 (1u<<17) /* used instead of BOTTOM, BOUNDARY */
|
||||||
|
|
||||||
|
#define LAST_MODIFIED_INIT { \
|
||||||
|
.line_termination = '\n', \
|
||||||
|
.max_depth = -1, \
|
||||||
|
}
|
||||||
|
|
||||||
struct last_modified_entry {
|
struct last_modified_entry {
|
||||||
struct hashmap_entry hashent;
|
struct hashmap_entry hashent;
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
@ -55,6 +60,8 @@ struct last_modified {
|
|||||||
struct rev_info rev;
|
struct rev_info rev;
|
||||||
bool recursive;
|
bool recursive;
|
||||||
bool show_trees;
|
bool show_trees;
|
||||||
|
int line_termination;
|
||||||
|
int max_depth;
|
||||||
|
|
||||||
const char **all_paths;
|
const char **all_paths;
|
||||||
size_t all_paths_nr;
|
size_t all_paths_nr;
|
||||||
@ -165,7 +172,7 @@ static void last_modified_emit(struct last_modified *lm,
|
|||||||
putchar('^');
|
putchar('^');
|
||||||
printf("%s\t", oid_to_hex(&commit->object.oid));
|
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');
|
write_name_quoted(path, stdout, '\n');
|
||||||
else
|
else
|
||||||
printf("%s%c", path, '\0');
|
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.recursive = lm->recursive;
|
||||||
lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees;
|
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);
|
argc = setup_revisions(argc, argv, &lm->rev, NULL);
|
||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
error(_("unknown last-modified argument: %s"), argv[1]);
|
error(_("unknown last-modified argument: %s"), argv[1]);
|
||||||
@ -507,11 +520,11 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix,
|
|||||||
struct repository *repo)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
struct last_modified lm = { 0 };
|
struct last_modified lm = LAST_MODIFIED_INIT;
|
||||||
|
|
||||||
const char * const last_modified_usage[] = {
|
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>...]"),
|
" [<revision-range>] [[--] <path>...]"),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -520,6 +533,10 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix,
|
|||||||
N_("recurse into subtrees")),
|
N_("recurse into subtrees")),
|
||||||
OPT_BOOL('t', "show-trees", &lm.show_trees,
|
OPT_BOOL('t', "show-trees", &lm.show_trees,
|
||||||
N_("show tree entries when recursing into subtrees")),
|
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()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1896,11 +1896,11 @@ int cmd_format_patch(int argc,
|
|||||||
{
|
{
|
||||||
struct format_config cfg;
|
struct format_config cfg;
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
struct commit **list = NULL;
|
struct commit_stack list = COMMIT_STACK_INIT;
|
||||||
struct rev_info rev;
|
struct rev_info rev;
|
||||||
char *to_free = NULL;
|
char *to_free = NULL;
|
||||||
struct setup_revision_opt s_r_opt;
|
struct setup_revision_opt s_r_opt;
|
||||||
size_t nr = 0, total, i;
|
size_t total, i;
|
||||||
int use_stdout = 0;
|
int use_stdout = 0;
|
||||||
int start_number = -1;
|
int start_number = -1;
|
||||||
int just_numbers = 0;
|
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))
|
if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
nr++;
|
commit_stack_push(&list, commit);
|
||||||
REALLOC_ARRAY(list, nr);
|
|
||||||
list[nr - 1] = commit;
|
|
||||||
}
|
}
|
||||||
if (nr == 0)
|
if (!list.nr)
|
||||||
/* nothing to do */
|
/* nothing to do */
|
||||||
goto done;
|
goto done;
|
||||||
total = nr;
|
total = list.nr;
|
||||||
if (cover_letter == -1) {
|
if (cover_letter == -1) {
|
||||||
if (cfg.config_cover_letter == COVER_AUTO)
|
if (cfg.config_cover_letter == COVER_AUTO)
|
||||||
cover_letter = (total > 1);
|
cover_letter = (total > 1);
|
||||||
@ -2308,7 +2306,7 @@ int cmd_format_patch(int argc,
|
|||||||
if (!cover_letter && total != 1)
|
if (!cover_letter && total != 1)
|
||||||
die(_("--interdiff requires --cover-letter or single patch"));
|
die(_("--interdiff requires --cover-letter or single patch"));
|
||||||
rev.idiff_oid1 = &idiff_prev.oid[idiff_prev.nr - 1];
|
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,
|
rev.idiff_title = diff_title(&idiff_title, reroll_count,
|
||||||
_("Interdiff:"),
|
_("Interdiff:"),
|
||||||
_("Interdiff against v%d:"));
|
_("Interdiff against v%d:"));
|
||||||
@ -2324,7 +2322,7 @@ int cmd_format_patch(int argc,
|
|||||||
die(_("--range-diff requires --cover-letter or single patch"));
|
die(_("--range-diff requires --cover-letter or single patch"));
|
||||||
|
|
||||||
infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev,
|
infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev,
|
||||||
origin, list[0]);
|
origin, list.items[0]);
|
||||||
rev.rdiff1 = rdiff1.buf;
|
rev.rdiff1 = rdiff1.buf;
|
||||||
rev.rdiff2 = rdiff2.buf;
|
rev.rdiff2 = rdiff2.buf;
|
||||||
rev.creation_factor = creation_factor;
|
rev.creation_factor = creation_factor;
|
||||||
@ -2360,11 +2358,11 @@ int cmd_format_patch(int argc,
|
|||||||
}
|
}
|
||||||
|
|
||||||
memset(&bases, 0, sizeof(bases));
|
memset(&bases, 0, sizeof(bases));
|
||||||
base = get_base_commit(&cfg, list, nr);
|
base = get_base_commit(&cfg, list.items, list.nr);
|
||||||
if (base) {
|
if (base) {
|
||||||
reset_revision_walk();
|
reset_revision_walk();
|
||||||
clear_object_flags(the_repository, UNINTERESTING);
|
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) {
|
if (in_reply_to || cfg.thread || cover_letter) {
|
||||||
@ -2381,7 +2379,8 @@ int cmd_format_patch(int argc,
|
|||||||
if (cfg.thread)
|
if (cfg.thread)
|
||||||
gen_message_id(&rev, "cover");
|
gen_message_id(&rev, "cover");
|
||||||
make_cover_letter(&rev, !!output_directory,
|
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_bases(&bases, rev.diffopt.file);
|
||||||
print_signature(signature, rev.diffopt.file);
|
print_signature(signature, rev.diffopt.file);
|
||||||
total++;
|
total++;
|
||||||
@ -2395,12 +2394,12 @@ int cmd_format_patch(int argc,
|
|||||||
if (show_progress)
|
if (show_progress)
|
||||||
progress = start_delayed_progress(the_repository,
|
progress = start_delayed_progress(the_repository,
|
||||||
_("Generating patches"), total);
|
_("Generating patches"), total);
|
||||||
for (i = 0; i < nr; i++) {
|
while (list.nr) {
|
||||||
size_t idx = nr - i - 1;
|
size_t idx = list.nr - 1;
|
||||||
int shown;
|
int shown;
|
||||||
|
|
||||||
display_progress(progress, total - idx);
|
display_progress(progress, total - idx);
|
||||||
commit = list[idx];
|
commit = commit_stack_pop(&list);
|
||||||
rev.nr = total - idx + (start_number - 1);
|
rev.nr = total - idx + (start_number - 1);
|
||||||
|
|
||||||
/* Make the second and subsequent mails replies to the first */
|
/* Make the second and subsequent mails replies to the first */
|
||||||
@ -2469,7 +2468,7 @@ int cmd_format_patch(int argc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
stop_progress(&progress);
|
stop_progress(&progress);
|
||||||
free(list);
|
commit_stack_clear(&list);
|
||||||
if (ignore_if_in_upstream)
|
if (ignore_if_in_upstream)
|
||||||
free_patch_ids(&ids);
|
free_patch_ids(&ids);
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,13 @@
|
|||||||
#include "repository.h"
|
#include "repository.h"
|
||||||
|
|
||||||
#define BUILTIN_MIDX_WRITE_USAGE \
|
#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" \
|
||||||
"[--refs-snapshot=<path>]")
|
" [--[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 \
|
#define BUILTIN_MIDX_VERIFY_USAGE \
|
||||||
N_("git multi-pack-index [<options>] verify")
|
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,
|
BUILTIN_MIDX_WRITE_USAGE,
|
||||||
NULL
|
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[] = {
|
static char const * const builtin_multi_pack_index_verify_usage[] = {
|
||||||
BUILTIN_MIDX_VERIFY_USAGE,
|
BUILTIN_MIDX_VERIFY_USAGE,
|
||||||
NULL
|
NULL
|
||||||
@ -43,6 +52,7 @@ static char const * const builtin_multi_pack_index_repack_usage[] = {
|
|||||||
};
|
};
|
||||||
static char const * const builtin_multi_pack_index_usage[] = {
|
static char const * const builtin_multi_pack_index_usage[] = {
|
||||||
BUILTIN_MIDX_WRITE_USAGE,
|
BUILTIN_MIDX_WRITE_USAGE,
|
||||||
|
BUILTIN_MIDX_COMPACT_USAGE,
|
||||||
BUILTIN_MIDX_VERIFY_USAGE,
|
BUILTIN_MIDX_VERIFY_USAGE,
|
||||||
BUILTIN_MIDX_EXPIRE_USAGE,
|
BUILTIN_MIDX_EXPIRE_USAGE,
|
||||||
BUILTIN_MIDX_REPACK_USAGE,
|
BUILTIN_MIDX_REPACK_USAGE,
|
||||||
@ -84,6 +94,8 @@ static struct option common_opts[] = {
|
|||||||
N_("directory"),
|
N_("directory"),
|
||||||
N_("object directory containing set of packfile and pack-index pairs"),
|
N_("object directory containing set of packfile and pack-index pairs"),
|
||||||
parse_object_dir),
|
parse_object_dir),
|
||||||
|
OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"),
|
||||||
|
MIDX_PROGRESS),
|
||||||
OPT_END(),
|
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")),
|
N_("pack for reuse when computing a multi-pack bitmap")),
|
||||||
OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
|
OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"),
|
||||||
MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX),
|
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,
|
OPT_BIT(0, "incremental", &opts.flags,
|
||||||
N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL),
|
N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL),
|
||||||
OPT_BOOL(0, "stdin-packs", &opts.stdin_packs,
|
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;
|
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,
|
static int cmd_multi_pack_index_verify(int argc, const char **argv,
|
||||||
const char *prefix,
|
const char *prefix,
|
||||||
struct repository *repo UNUSED)
|
struct repository *repo UNUSED)
|
||||||
{
|
{
|
||||||
struct option *options;
|
struct option *options;
|
||||||
static struct option builtin_multi_pack_index_verify_options[] = {
|
static struct option builtin_multi_pack_index_verify_options[] = {
|
||||||
OPT_BIT(0, "progress", &opts.flags,
|
|
||||||
N_("force progress reporting"), MIDX_PROGRESS),
|
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
struct odb_source *source;
|
struct odb_source *source;
|
||||||
@ -231,8 +298,6 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv,
|
|||||||
{
|
{
|
||||||
struct option *options;
|
struct option *options;
|
||||||
static struct option builtin_multi_pack_index_expire_options[] = {
|
static struct option builtin_multi_pack_index_expire_options[] = {
|
||||||
OPT_BIT(0, "progress", &opts.flags,
|
|
||||||
N_("force progress reporting"), MIDX_PROGRESS),
|
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
struct odb_source *source;
|
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[] = {
|
static struct option builtin_multi_pack_index_repack_options[] = {
|
||||||
OPT_UNSIGNED(0, "batch-size", &opts.batch_size,
|
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")),
|
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(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
struct odb_source *source;
|
struct odb_source *source;
|
||||||
@ -300,6 +363,7 @@ int cmd_multi_pack_index(int argc,
|
|||||||
struct option builtin_multi_pack_index_options[] = {
|
struct option builtin_multi_pack_index_options[] = {
|
||||||
OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack),
|
OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack),
|
||||||
OPT_SUBCOMMAND("write", &fn, cmd_multi_pack_index_write),
|
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("verify", &fn, cmd_multi_pack_index_verify),
|
||||||
OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire),
|
OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
|
|||||||
@ -180,8 +180,7 @@ static void name_rev(struct commit *start_commit,
|
|||||||
{
|
{
|
||||||
struct prio_queue queue;
|
struct prio_queue queue;
|
||||||
struct commit *commit;
|
struct commit *commit;
|
||||||
struct commit **parents_to_queue = NULL;
|
struct commit_stack parents_to_queue = COMMIT_STACK_INIT;
|
||||||
size_t parents_to_queue_nr, parents_to_queue_alloc = 0;
|
|
||||||
struct rev_name *start_name;
|
struct rev_name *start_name;
|
||||||
|
|
||||||
repo_parse_commit(the_repository, start_commit);
|
repo_parse_commit(the_repository, start_commit);
|
||||||
@ -206,7 +205,7 @@ static void name_rev(struct commit *start_commit,
|
|||||||
struct commit_list *parents;
|
struct commit_list *parents;
|
||||||
int parent_number = 1;
|
int parent_number = 1;
|
||||||
|
|
||||||
parents_to_queue_nr = 0;
|
parents_to_queue.nr = 0;
|
||||||
|
|
||||||
for (parents = commit->parents;
|
for (parents = commit->parents;
|
||||||
parents;
|
parents;
|
||||||
@ -238,22 +237,18 @@ static void name_rev(struct commit *start_commit,
|
|||||||
string_pool);
|
string_pool);
|
||||||
else
|
else
|
||||||
parent_name->tip_name = name->tip_name;
|
parent_name->tip_name = name->tip_name;
|
||||||
ALLOC_GROW(parents_to_queue,
|
commit_stack_push(&parents_to_queue, parent);
|
||||||
parents_to_queue_nr + 1,
|
|
||||||
parents_to_queue_alloc);
|
|
||||||
parents_to_queue[parents_to_queue_nr] = parent;
|
|
||||||
parents_to_queue_nr++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The first parent must come out first from the prio_queue */
|
/* 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,
|
prio_queue_put(&queue,
|
||||||
parents_to_queue[--parents_to_queue_nr]);
|
commit_stack_pop(&parents_to_queue));
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_prio_queue(&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)
|
static int subpath_matches(const char *path, const char *filter)
|
||||||
|
|||||||
@ -1529,49 +1529,53 @@ static int want_cruft_object_mtime(struct repository *r,
|
|||||||
const struct object_id *oid,
|
const struct object_id *oid,
|
||||||
unsigned flags, uint32_t mtime)
|
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 *p = *cache;
|
struct packed_git **cache = packfile_store_get_kept_pack_cache(source->packfiles, flags);
|
||||||
off_t ofs;
|
|
||||||
uint32_t candidate_mtime;
|
|
||||||
|
|
||||||
ofs = find_pack_entry_one(oid, p);
|
for (; *cache; cache++) {
|
||||||
if (!ofs)
|
struct packed_git *p = *cache;
|
||||||
continue;
|
off_t ofs;
|
||||||
|
uint32_t candidate_mtime;
|
||||||
|
|
||||||
/*
|
ofs = find_pack_entry_one(oid, p);
|
||||||
* We have a copy of the object 'oid' in a non-cruft
|
if (!ofs)
|
||||||
* pack. We can avoid packing an additional copy
|
|
||||||
* regardless of what the existing copy's mtime is since
|
|
||||||
* it is outside of a cruft pack.
|
|
||||||
*/
|
|
||||||
if (!p->is_cruft)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we have a copy of the object 'oid' in a cruft
|
|
||||||
* pack, then either read the cruft pack's mtime for
|
|
||||||
* that object, or, if that can't be loaded, assume the
|
|
||||||
* pack's mtime itself.
|
|
||||||
*/
|
|
||||||
if (!load_pack_mtimes(p)) {
|
|
||||||
uint32_t pos;
|
|
||||||
if (offset_to_pack_pos(p, ofs, &pos) < 0)
|
|
||||||
continue;
|
continue;
|
||||||
candidate_mtime = nth_packed_mtime(p, pos);
|
|
||||||
} else {
|
|
||||||
candidate_mtime = p->mtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We have a surviving copy of the object in a cruft
|
* We have a copy of the object 'oid' in a non-cruft
|
||||||
* pack whose mtime is greater than or equal to the one
|
* pack. We can avoid packing an additional copy
|
||||||
* we are considering. We can thus avoid packing an
|
* regardless of what the existing copy's mtime is since
|
||||||
* additional copy of that object.
|
* it is outside of a cruft pack.
|
||||||
*/
|
*/
|
||||||
if (mtime <= candidate_mtime)
|
if (!p->is_cruft)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have a copy of the object 'oid' in a cruft
|
||||||
|
* pack, then either read the cruft pack's mtime for
|
||||||
|
* that object, or, if that can't be loaded, assume the
|
||||||
|
* pack's mtime itself.
|
||||||
|
*/
|
||||||
|
if (!load_pack_mtimes(p)) {
|
||||||
|
uint32_t pos;
|
||||||
|
if (offset_to_pack_pos(p, ofs, &pos) < 0)
|
||||||
|
continue;
|
||||||
|
candidate_mtime = nth_packed_mtime(p, pos);
|
||||||
|
} else {
|
||||||
|
candidate_mtime = p->mtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have a surviving copy of the object in a cruft
|
||||||
|
* pack whose mtime is greater than or equal to the one
|
||||||
|
* we are considering. We can thus avoid packing an
|
||||||
|
* additional copy of that object.
|
||||||
|
*/
|
||||||
|
if (mtime <= candidate_mtime)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
@ -1624,9 +1628,9 @@ static int want_found_object(const struct object_id *oid, int exclude,
|
|||||||
*/
|
*/
|
||||||
unsigned flags = 0;
|
unsigned flags = 0;
|
||||||
if (ignore_packed_keep_on_disk)
|
if (ignore_packed_keep_on_disk)
|
||||||
flags |= ON_DISK_KEEP_PACKS;
|
flags |= KEPT_PACK_ON_DISK;
|
||||||
if (ignore_packed_keep_in_core)
|
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
|
* If the object is in a pack that we want to ignore, *and* we
|
||||||
@ -1749,13 +1753,15 @@ 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) {
|
||||||
struct packed_git *p = e->pack;
|
for (e = source->packfiles->packs.head; e; e = e->next) {
|
||||||
want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
|
struct packed_git *p = e->pack;
|
||||||
if (!exclude && want > 0)
|
want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime);
|
||||||
packfile_list_prepend(&the_repository->objects->packfiles->packs, p);
|
if (!exclude && want > 0)
|
||||||
if (want != -1)
|
packfile_list_prepend(&source->packfiles->packs, p);
|
||||||
return want;
|
if (want != -1)
|
||||||
|
return want;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri_protocols.nr) {
|
if (uri_protocols.nr) {
|
||||||
@ -2411,7 +2417,7 @@ static void drop_reused_delta(struct object_entry *entry)
|
|||||||
|
|
||||||
oi.sizep = &size;
|
oi.sizep = &size;
|
||||||
oi.typep = &type;
|
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;
|
* We failed to get the info from this pack for some reason;
|
||||||
* fall back to odb_read_object_info, which may find another copy.
|
* 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;
|
struct object_info oi = OBJECT_INFO_INIT;
|
||||||
|
|
||||||
oi.typep = &type;
|
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"),
|
die(_("could not get type of object %s in pack %s"),
|
||||||
oid_to_hex(oid), p->pack_name);
|
oid_to_hex(oid), p->pack_name);
|
||||||
} else if (type == OBJ_COMMIT) {
|
} 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.
|
* an optimization during delta selection.
|
||||||
*/
|
*/
|
||||||
revs.no_kept_objects = 1;
|
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.blob_objects = 1;
|
||||||
revs.tree_objects = 1;
|
revs.tree_objects = 1;
|
||||||
revs.tag_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)
|
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)
|
static int cruft_include_check(struct commit *commit, void *data)
|
||||||
|
|||||||
@ -228,9 +228,9 @@ int cmd_patch_id(int argc,
|
|||||||
int opts = 0;
|
int opts = 0;
|
||||||
struct option builtin_patch_id_options[] = {
|
struct option builtin_patch_id_options[] = {
|
||||||
OPT_CMDMODE(0, "unstable", &opts,
|
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,
|
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,
|
OPT_CMDMODE(0, "verbatim", &opts,
|
||||||
N_("don't strip whitespace from the patch"), 3),
|
N_("don't strip whitespace from the patch"), 3),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
#include "reset.h"
|
#include "reset.h"
|
||||||
#include "trace2.h"
|
#include "trace2.h"
|
||||||
#include "hook.h"
|
#include "hook.h"
|
||||||
|
#include "trailer.h"
|
||||||
|
|
||||||
static char const * const builtin_rebase_usage[] = {
|
static char const * const builtin_rebase_usage[] = {
|
||||||
N_("git rebase [-i] [options] [--exec <cmd>] "
|
N_("git rebase [-i] [options] [--exec <cmd>] "
|
||||||
@ -113,6 +114,7 @@ struct rebase_options {
|
|||||||
enum action action;
|
enum action action;
|
||||||
char *reflog_action;
|
char *reflog_action;
|
||||||
int signoff;
|
int signoff;
|
||||||
|
struct strvec trailer_args;
|
||||||
int allow_rerere_autoupdate;
|
int allow_rerere_autoupdate;
|
||||||
int keep_empty;
|
int keep_empty;
|
||||||
int autosquash;
|
int autosquash;
|
||||||
@ -143,6 +145,7 @@ struct rebase_options {
|
|||||||
.flags = REBASE_NO_QUIET, \
|
.flags = REBASE_NO_QUIET, \
|
||||||
.git_am_opts = STRVEC_INIT, \
|
.git_am_opts = STRVEC_INIT, \
|
||||||
.exec = STRING_LIST_INIT_NODUP, \
|
.exec = STRING_LIST_INIT_NODUP, \
|
||||||
|
.trailer_args = STRVEC_INIT, \
|
||||||
.git_format_patch_opt = STRBUF_INIT, \
|
.git_format_patch_opt = STRBUF_INIT, \
|
||||||
.fork_point = -1, \
|
.fork_point = -1, \
|
||||||
.reapply_cherry_picks = -1, \
|
.reapply_cherry_picks = -1, \
|
||||||
@ -166,6 +169,7 @@ static void rebase_options_release(struct rebase_options *opts)
|
|||||||
free(opts->strategy);
|
free(opts->strategy);
|
||||||
string_list_clear(&opts->strategy_opts, 0);
|
string_list_clear(&opts->strategy_opts, 0);
|
||||||
strbuf_release(&opts->git_format_patch_opt);
|
strbuf_release(&opts->git_format_patch_opt);
|
||||||
|
strvec_clear(&opts->trailer_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct replay_opts get_replay_opts(const struct rebase_options *opts)
|
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);
|
sequencer_init_config(&replay);
|
||||||
|
|
||||||
replay.signoff = opts->signoff;
|
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);
|
replay.allow_ff = !(opts->flags & REBASE_FORCE);
|
||||||
if (opts->allow_rerere_autoupdate)
|
if (opts->allow_rerere_autoupdate)
|
||||||
replay.allow_rerere_auto = 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);
|
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);
|
strbuf_release(&buf);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -528,6 +553,21 @@ static int rebase_write_basic_state(struct rebase_options *opts)
|
|||||||
if (opts->signoff)
|
if (opts->signoff)
|
||||||
write_file(state_dir_path("signoff", 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1132,6 +1172,8 @@ int cmd_rebase(int argc,
|
|||||||
.flags = PARSE_OPT_NOARG,
|
.flags = PARSE_OPT_NOARG,
|
||||||
.defval = REBASE_DIFFSTAT,
|
.defval = REBASE_DIFFSTAT,
|
||||||
},
|
},
|
||||||
|
OPT_STRVEC(0, "trailer", &options.trailer_args, N_("trailer"),
|
||||||
|
N_("add custom trailer(s)")),
|
||||||
OPT_BOOL(0, "signoff", &options.signoff,
|
OPT_BOOL(0, "signoff", &options.signoff,
|
||||||
N_("add a Signed-off-by trailer to each commit")),
|
N_("add a Signed-off-by trailer to each commit")),
|
||||||
OPT_BOOL(0, "committer-date-is-author-date",
|
OPT_BOOL(0, "committer-date-is-author-date",
|
||||||
@ -1285,6 +1327,11 @@ int cmd_rebase(int argc,
|
|||||||
builtin_rebase_options,
|
builtin_rebase_options,
|
||||||
builtin_rebase_usage, 0);
|
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)
|
if (preserve_merges_selected)
|
||||||
die(_("--preserve-merges was replaced by --rebase-merges\n"
|
die(_("--preserve-merges was replaced by --rebase-merges\n"
|
||||||
"Note: Your `pull.rebase` configuration may also be set to 'preserve',\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)
|
if (options.root && !options.onto_name)
|
||||||
imply_merge(&options, "--root without --onto");
|
imply_merge(&options, "--root without --onto");
|
||||||
|
|
||||||
|
if (options.trailer_args.nr)
|
||||||
|
imply_merge(&options, "--trailer");
|
||||||
|
|
||||||
if (isatty(2) && options.flags & REBASE_NO_QUIET)
|
if (isatty(2) && options.flags & REBASE_NO_QUIET)
|
||||||
strbuf_addstr(&options.git_format_patch_opt, " --progress");
|
strbuf_addstr(&options.git_format_patch_opt, " --progress");
|
||||||
|
|
||||||
|
|||||||
@ -1645,7 +1645,6 @@ static void check_aliased_update_internal(struct command *cmd,
|
|||||||
cmd->error_string = "broken symref";
|
cmd->error_string = "broken symref";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dst_name = strip_namespace(dst_name);
|
|
||||||
|
|
||||||
if (!(item = string_list_lookup(list, dst_name)))
|
if (!(item = string_list_lookup(list, dst_name)))
|
||||||
return;
|
return;
|
||||||
@ -1690,10 +1689,13 @@ static void check_aliased_updates(struct command *commands)
|
|||||||
{
|
{
|
||||||
struct command *cmd;
|
struct command *cmd;
|
||||||
struct string_list ref_list = STRING_LIST_INIT_NODUP;
|
struct string_list ref_list = STRING_LIST_INIT_NODUP;
|
||||||
|
struct strbuf ref_name = STRBUF_INIT;
|
||||||
|
|
||||||
for (cmd = commands; cmd; cmd = cmd->next) {
|
for (cmd = commands; cmd; cmd = cmd->next) {
|
||||||
struct string_list_item *item =
|
struct string_list_item *item;
|
||||||
string_list_append(&ref_list, cmd->ref_name);
|
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;
|
item->util = (void *)cmd;
|
||||||
}
|
}
|
||||||
string_list_sort(&ref_list);
|
string_list_sort(&ref_list);
|
||||||
@ -1703,6 +1705,7 @@ static void check_aliased_updates(struct command *commands)
|
|||||||
check_aliased_update(cmd, &ref_list);
|
check_aliased_update(cmd, &ref_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strbuf_release(&ref_name);
|
||||||
string_list_clear(&ref_list, 0);
|
string_list_clear(&ref_list, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
417
builtin/replay.c
417
builtin/replay.c
@ -2,294 +2,22 @@
|
|||||||
* "git replay" builtin command
|
* "git replay" builtin command
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define USE_THE_REPOSITORY_VARIABLE
|
|
||||||
#define DISABLE_SIGN_COMPARE_WARNINGS
|
|
||||||
|
|
||||||
#include "git-compat-util.h"
|
#include "git-compat-util.h"
|
||||||
|
|
||||||
#include "builtin.h"
|
#include "builtin.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "environment.h"
|
|
||||||
#include "hex.h"
|
#include "hex.h"
|
||||||
#include "lockfile.h"
|
|
||||||
#include "merge-ort.h"
|
|
||||||
#include "object-name.h"
|
#include "object-name.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
|
#include "replay.h"
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "strmap.h"
|
|
||||||
#include <oidset.h>
|
|
||||||
#include <tree.h>
|
|
||||||
|
|
||||||
enum ref_action_mode {
|
enum ref_action_mode {
|
||||||
REF_ACTION_UPDATE,
|
REF_ACTION_UPDATE,
|
||||||
REF_ACTION_PRINT,
|
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)
|
static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source)
|
||||||
{
|
{
|
||||||
if (!ref_action || !strcmp(ref_action, "update"))
|
if (!ref_action || !strcmp(ref_action, "update"))
|
||||||
@ -343,21 +71,11 @@ int cmd_replay(int argc,
|
|||||||
const char *prefix,
|
const char *prefix,
|
||||||
struct repository *repo)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
const char *advance_name_opt = NULL;
|
struct replay_revisions_options opts = { 0 };
|
||||||
char *advance_name = NULL;
|
struct replay_result result = { 0 };
|
||||||
struct commit *onto = NULL;
|
|
||||||
const char *onto_name = NULL;
|
|
||||||
int contained = 0;
|
|
||||||
const char *ref_action = NULL;
|
const char *ref_action = NULL;
|
||||||
enum ref_action_mode ref_mode;
|
enum ref_action_mode ref_mode;
|
||||||
|
|
||||||
struct rev_info revs;
|
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 ref_transaction *transaction = NULL;
|
||||||
struct strbuf transaction_err = STRBUF_INIT;
|
struct strbuf transaction_err = STRBUF_INIT;
|
||||||
struct strbuf reflog_msg = STRBUF_INIT;
|
struct strbuf reflog_msg = STRBUF_INIT;
|
||||||
@ -370,13 +88,13 @@ int cmd_replay(int argc,
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
struct option replay_options[] = {
|
struct option replay_options[] = {
|
||||||
OPT_STRING(0, "advance", &advance_name_opt,
|
OPT_STRING(0, "advance", &opts.advance,
|
||||||
N_("branch"),
|
N_("branch"),
|
||||||
N_("make replay advance given branch")),
|
N_("make replay advance given branch")),
|
||||||
OPT_STRING(0, "onto", &onto_name,
|
OPT_STRING(0, "onto", &opts.onto,
|
||||||
N_("revision"),
|
N_("revision"),
|
||||||
N_("replay onto given commit")),
|
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>")),
|
N_("update all branches that point at commits in <revision-range>")),
|
||||||
OPT_STRING(0, "ref-action", &ref_action,
|
OPT_STRING(0, "ref-action", &ref_action,
|
||||||
N_("mode"),
|
N_("mode"),
|
||||||
@ -387,19 +105,17 @@ int cmd_replay(int argc,
|
|||||||
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
|
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
|
||||||
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
|
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"));
|
error(_("option --onto or --advance is mandatory"));
|
||||||
usage_with_options(replay_usage, replay_options);
|
usage_with_options(replay_usage, replay_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
die_for_incompatible_opt2(!!advance_name_opt, "--advance",
|
die_for_incompatible_opt2(!!opts.advance, "--advance",
|
||||||
contained, "--contained");
|
opts.contained, "--contained");
|
||||||
|
|
||||||
/* Parse ref action mode from command line or config */
|
/* Parse ref action mode from command line or config */
|
||||||
ref_mode = get_ref_action_mode(repo, ref_action);
|
ref_mode = get_ref_action_mode(repo, ref_action);
|
||||||
|
|
||||||
advance_name = xstrdup_or_null(advance_name_opt);
|
|
||||||
|
|
||||||
repo_init_revisions(repo, &revs, prefix);
|
repo_init_revisions(repo, &revs, prefix);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -451,18 +167,19 @@ int cmd_replay(int argc,
|
|||||||
revs.simplify_history = 0;
|
revs.simplify_history = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name,
|
ret = replay_revisions(repo, &revs, &opts, &result);
|
||||||
&onto, &update_refs);
|
if (ret)
|
||||||
|
goto cleanup;
|
||||||
if (!onto) /* FIXME: Should handle replaying down to root commit */
|
|
||||||
die("Replaying down to root commit is not supported yet!");
|
|
||||||
|
|
||||||
/* Build reflog message */
|
/* Build reflog message */
|
||||||
if (advance_name_opt)
|
if (opts.advance) {
|
||||||
strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt);
|
strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
|
||||||
else
|
} else {
|
||||||
strbuf_addf(&reflog_msg, "replay --onto %s",
|
struct object_id oid;
|
||||||
oid_to_hex(&onto->object.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 */
|
/* Initialize ref transaction if using update mode */
|
||||||
if (ref_mode == REF_ACTION_UPDATE) {
|
if (ref_mode == REF_ACTION_UPDATE) {
|
||||||
@ -475,78 +192,19 @@ int cmd_replay(int argc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prepare_revision_walk(&revs) < 0) {
|
for (size_t i = 0; i < result.updates_nr; i++) {
|
||||||
ret = error(_("error preparing revisions"));
|
ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
|
||||||
goto cleanup;
|
&result.updates[i].new_oid, &result.updates[i].old_oid,
|
||||||
}
|
reflog_msg.buf, &transaction_err);
|
||||||
|
if (ret) {
|
||||||
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) {
|
|
||||||
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"),
|
ret = error(_("failed to update ref '%s': %s"),
|
||||||
advance_name, transaction_err.buf);
|
result.updates[i].refname, transaction_err.buf);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Commit the ref transaction if we have one */
|
/* Commit the ref transaction if we have one */
|
||||||
if (transaction && result.clean == 1) {
|
if (transaction) {
|
||||||
if (ref_transaction_commit(transaction, &transaction_err)) {
|
if (ref_transaction_commit(transaction, &transaction_err)) {
|
||||||
ret = error(_("failed to commit ref transaction: %s"),
|
ret = error(_("failed to commit ref transaction: %s"),
|
||||||
transaction_err.buf);
|
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:
|
cleanup:
|
||||||
if (transaction)
|
if (transaction)
|
||||||
ref_transaction_free(transaction);
|
ref_transaction_free(transaction);
|
||||||
|
replay_result_release(&result);
|
||||||
strbuf_release(&transaction_err);
|
strbuf_release(&transaction_err);
|
||||||
strbuf_release(&reflog_msg);
|
strbuf_release(&reflog_msg);
|
||||||
release_revisions(&revs);
|
release_revisions(&revs);
|
||||||
free(advance_name);
|
|
||||||
|
|
||||||
/* Return */
|
if (ret) {
|
||||||
if (ret < 0)
|
if (result.merge_conflict)
|
||||||
exit(128);
|
return 1;
|
||||||
return ret ? 0 : 1;
|
return 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
static const char *const repo_usage[] = {
|
static const char *const repo_usage[] = {
|
||||||
"git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]",
|
"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]",
|
"git repo structure [--format=(table|keyvalue|nul) | -z]",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
@ -25,6 +26,7 @@ static const char *const repo_usage[] = {
|
|||||||
typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
|
typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
|
||||||
|
|
||||||
enum output_format {
|
enum output_format {
|
||||||
|
FORMAT_DEFAULT,
|
||||||
FORMAT_TABLE,
|
FORMAT_TABLE,
|
||||||
FORMAT_KEYVALUE,
|
FORMAT_KEYVALUE,
|
||||||
FORMAT_NUL_TERMINATED,
|
FORMAT_NUL_TERMINATED,
|
||||||
@ -148,6 +150,29 @@ static int print_all_fields(struct repository *repo,
|
|||||||
return 0;
|
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,
|
static int parse_format_cb(const struct option *opt,
|
||||||
const char *arg, int unset UNUSED)
|
const char *arg, int unset UNUSED)
|
||||||
{
|
{
|
||||||
@ -161,6 +186,8 @@ static int parse_format_cb(const struct option *opt,
|
|||||||
*format = FORMAT_KEYVALUE;
|
*format = FORMAT_KEYVALUE;
|
||||||
else if (!strcmp(arg, "table"))
|
else if (!strcmp(arg, "table"))
|
||||||
*format = FORMAT_TABLE;
|
*format = FORMAT_TABLE;
|
||||||
|
else if (!strcmp(arg, "default"))
|
||||||
|
*format = FORMAT_DEFAULT;
|
||||||
else
|
else
|
||||||
die(_("invalid format '%s'"), arg);
|
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,
|
static int cmd_repo_info(int argc, const char **argv, const char *prefix,
|
||||||
struct repository *repo)
|
struct repository *repo)
|
||||||
{
|
{
|
||||||
enum output_format format = FORMAT_KEYVALUE;
|
enum output_format format = FORMAT_DEFAULT;
|
||||||
int all_keys = 0;
|
int all_keys = 0;
|
||||||
|
int show_keys = 0;
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT_CALLBACK_F(0, "format", &format, N_("format"),
|
OPT_CALLBACK_F(0, "format", &format, N_("format"),
|
||||||
N_("output 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_OPT_NONEG | PARSE_OPT_NOARG,
|
||||||
parse_format_cb),
|
parse_format_cb),
|
||||||
OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
|
OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
|
||||||
|
OPT_BOOL(0, "keys", &show_keys, N_("show keys")),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
|
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)
|
if (format != FORMAT_KEYVALUE && format != FORMAT_NUL_TERMINATED)
|
||||||
die(_("unsupported output format"));
|
die(_("unsupported output format"));
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
#include "list-objects-filter-options.h"
|
#include "list-objects-filter-options.h"
|
||||||
#include "wildmatch.h"
|
#include "wildmatch.h"
|
||||||
#include "strbuf.h"
|
#include "strbuf.h"
|
||||||
|
#include "url.h"
|
||||||
|
|
||||||
#define OPT_QUIET (1 << 0)
|
#define OPT_QUIET (1 << 0)
|
||||||
#define OPT_CACHED (1 << 1)
|
#define OPT_CACHED (1 << 1)
|
||||||
@ -435,6 +436,102 @@ struct init_cb {
|
|||||||
};
|
};
|
||||||
#define INIT_CB_INIT { 0 }
|
#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,
|
static void init_submodule(const char *path, const char *prefix,
|
||||||
const char *super_prefix,
|
const char *super_prefix,
|
||||||
unsigned int flags)
|
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))
|
if (repo_config_set_gently(the_repository, sb.buf, upd))
|
||||||
die(_("Failed to register update mode for submodule path '%s'"), displaypath);
|
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);
|
strbuf_release(&sb);
|
||||||
free(displaypath);
|
free(displaypath);
|
||||||
free(url);
|
free(url);
|
||||||
@ -1204,6 +1305,82 @@ static int module_summary(int argc, const char **argv, const char *prefix,
|
|||||||
return ret;
|
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 {
|
struct sync_cb {
|
||||||
const char *prefix;
|
const char *prefix;
|
||||||
const char *super_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 = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository),
|
||||||
clone_data->path);
|
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 (!file_exists(sm_gitdir)) {
|
||||||
if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
||||||
!is_empty_dir(clone_data_path))
|
!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);
|
char *head = xstrfmt("%s/HEAD", sm_gitdir);
|
||||||
unlink(head);
|
unlink(head);
|
||||||
free(head);
|
free(head);
|
||||||
die(_("refusing to create/use '%s' in another submodule's "
|
die(_("refusing to create/use '%s' in another submodule's git dir. "
|
||||||
"git dir"), sm_gitdir);
|
"Enabling extensions.submodulePathConfig should fix this."),
|
||||||
|
sm_gitdir);
|
||||||
}
|
}
|
||||||
|
|
||||||
connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
|
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)
|
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 module_clone_data clone_data = MODULE_CLONE_DATA_INIT;
|
||||||
struct string_list reference = STRING_LIST_INIT_NODUP;
|
struct string_list reference = STRING_LIST_INIT_NODUP;
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
|
|
||||||
/* perhaps the path already exists and is already a git repo, else clone it */
|
/* perhaps the path already exists and is already a git repo, else clone it */
|
||||||
if (is_directory(add_data->sm_path)) {
|
if (is_directory(add_data->sm_path)) {
|
||||||
|
char *submod_gitdir_path;
|
||||||
struct strbuf sm_path = STRBUF_INIT;
|
struct strbuf sm_path = STRBUF_INIT;
|
||||||
strbuf_addstr(&sm_path, add_data->sm_path);
|
strbuf_addstr(&sm_path, add_data->sm_path);
|
||||||
submod_gitdir_path = xstrfmt("%s/.git", 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);
|
free(submod_gitdir_path);
|
||||||
} else {
|
} else {
|
||||||
struct child_process cp = CHILD_PROCESS_INIT;
|
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) {
|
if (!add_data->force) {
|
||||||
struct strbuf msg = STRBUF_INIT;
|
struct strbuf msg = STRBUF_INIT;
|
||||||
char *die_msg;
|
char *die_msg;
|
||||||
@ -3222,8 +3397,8 @@ static int add_submodule(const struct add_data *add_data)
|
|||||||
"locally with remote(s):\n"),
|
"locally with remote(s):\n"),
|
||||||
add_data->sm_name);
|
add_data->sm_name);
|
||||||
|
|
||||||
append_fetch_remotes(&msg, submod_gitdir_path);
|
append_fetch_remotes(&msg, submod_gitdir.buf);
|
||||||
free(submod_gitdir_path);
|
strbuf_release(&submod_gitdir);
|
||||||
|
|
||||||
strbuf_addf(&msg, _("If you want to reuse this local git "
|
strbuf_addf(&msg, _("If you want to reuse this local git "
|
||||||
"directory instead of cloning again from\n"
|
"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);
|
"submodule '%s'\n"), add_data->sm_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(submod_gitdir_path);
|
strbuf_release(&submod_gitdir);
|
||||||
|
|
||||||
clone_data.prefix = add_data->prefix;
|
clone_data.prefix = add_data->prefix;
|
||||||
clone_data.path = add_data->sm_path;
|
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.progress = !!progress;
|
||||||
add_data.dissociate = !!dissociate;
|
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))
|
if (add_submodule(&add_data))
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
configure_added_submodule(&add_data);
|
configure_added_submodule(&add_data);
|
||||||
@ -3594,6 +3772,8 @@ int cmd_submodule__helper(int argc,
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
struct option options[] = {
|
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("clone", &fn, module_clone),
|
||||||
OPT_SUBCOMMAND("add", &fn, module_add),
|
OPT_SUBCOMMAND("add", &fn, module_add),
|
||||||
OPT_SUBCOMMAND("update", &fn, module_update),
|
OPT_SUBCOMMAND("update", &fn, module_update),
|
||||||
|
|||||||
@ -499,8 +499,7 @@ int cmd_tag(int argc,
|
|||||||
OPT_CALLBACK_F('m', "message", &msg, N_("message"),
|
OPT_CALLBACK_F('m', "message", &msg, N_("message"),
|
||||||
N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
|
N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
|
||||||
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
|
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
|
||||||
OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"),
|
OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, parse_opt_strvec),
|
||||||
N_("add custom trailer(s)"), PARSE_OPT_NONEG),
|
|
||||||
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
|
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_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
|
||||||
OPT_CLEANUP(&cleanup_arg),
|
OPT_CLEANUP(&cleanup_arg),
|
||||||
|
|||||||
@ -72,7 +72,7 @@ static char *git_attr_val_system(int ident_flag UNUSED)
|
|||||||
|
|
||||||
static char *git_attr_val_global(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) {
|
if (file) {
|
||||||
normalize_path_copy(file, file);
|
normalize_path_copy(file, file);
|
||||||
return file;
|
return 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__DRY_RUN(&show_only, N_("do not remove, show only")),
|
||||||
OPT__VERBOSE(&verbose, N_("report pruned working trees")),
|
OPT__VERBOSE(&verbose, N_("report pruned working trees")),
|
||||||
OPT_EXPIRY_DATE(0, "expire", &expire,
|
OPT_EXPIRY_DATE(0, "expire", &expire,
|
||||||
N_("expire working trees older than <time>")),
|
N_("prune missing working trees older than <time>")),
|
||||||
OPT_END()
|
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_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
|
||||||
OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
|
OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
|
||||||
OPT_EXPIRY_DATE(0, "expire", &expire,
|
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,
|
OPT_SET_INT('z', NULL, &line_terminator,
|
||||||
N_("terminate records with a NUL character"), '\0'),
|
N_("terminate records with a NUL character"), '\0'),
|
||||||
OPT_END()
|
OPT_END()
|
||||||
|
|||||||
24
bundle-uri.c
24
bundle-uri.c
@ -89,7 +89,10 @@ static int summarize_bundle(struct remote_bundle_info *info, void *data)
|
|||||||
{
|
{
|
||||||
FILE *fp = data;
|
FILE *fp = data;
|
||||||
fprintf(fp, "[bundle \"%s\"]\n", info->id);
|
fprintf(fp, "[bundle \"%s\"]\n", info->id);
|
||||||
fprintf(fp, "\turi = %s\n", info->uri);
|
if (info->uri)
|
||||||
|
fprintf(fp, "\turi = %s\n", info->uri);
|
||||||
|
else
|
||||||
|
fprintf(fp, "\t# uri = (missing)\n");
|
||||||
|
|
||||||
if (info->creationToken)
|
if (info->creationToken)
|
||||||
fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken);
|
fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken);
|
||||||
@ -267,6 +270,19 @@ int bundle_uri_parse_config_format(const char *uri,
|
|||||||
result = 1;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,6 +767,12 @@ static int fetch_bundle_uri_internal(struct repository *r,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!bundle->uri) {
|
||||||
|
error(_("bundle '%s' has no uri"),
|
||||||
|
bundle->id ? bundle->id : "<unknown>");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (!bundle->file &&
|
if (!bundle->file &&
|
||||||
!(bundle->file = find_temp_filename())) {
|
!(bundle->file = find_temp_filename())) {
|
||||||
result = -1;
|
result = -1;
|
||||||
|
|||||||
28
cache-tree.c
28
cache-tree.c
@ -16,6 +16,7 @@
|
|||||||
#include "promisor-remote.h"
|
#include "promisor-remote.h"
|
||||||
#include "trace.h"
|
#include "trace.h"
|
||||||
#include "trace2.h"
|
#include "trace2.h"
|
||||||
|
#include "parse.h"
|
||||||
|
|
||||||
#ifndef DEBUG_CACHE_TREE
|
#ifndef DEBUG_CACHE_TREE
|
||||||
#define DEBUG_CACHE_TREE 0
|
#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)
|
static int parse_int(const char **ptr, unsigned long *len_p, int *out)
|
||||||
{
|
{
|
||||||
const char *s = *ptr;
|
const char *ep;
|
||||||
unsigned long len = *len_p;
|
|
||||||
int ret = 0;
|
|
||||||
int sign = 1;
|
|
||||||
|
|
||||||
while (len && *s == '-') {
|
if (!parse_int_from_buf(*ptr, *len_p, &ep, out))
|
||||||
sign *= -1;
|
|
||||||
s++;
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (len) {
|
|
||||||
if (!isdigit(*s))
|
|
||||||
break;
|
|
||||||
ret *= 10;
|
|
||||||
ret += *s - '0';
|
|
||||||
s++;
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s == *ptr)
|
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
*ptr = s;
|
*len_p -= ep - *ptr;
|
||||||
*len_p = len;
|
*ptr = ep;
|
||||||
*out = sign * ret;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,7 @@ git-grep mainporcelain info
|
|||||||
git-gui mainporcelain
|
git-gui mainporcelain
|
||||||
git-hash-object plumbingmanipulators
|
git-hash-object plumbingmanipulators
|
||||||
git-help ancillaryinterrogators complete
|
git-help ancillaryinterrogators complete
|
||||||
|
git-history mainporcelain history
|
||||||
git-hook purehelpers
|
git-hook purehelpers
|
||||||
git-http-backend synchingrepositories
|
git-http-backend synchingrepositories
|
||||||
git-http-fetch synchelpers
|
git-http-fetch synchelpers
|
||||||
|
|||||||
@ -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);
|
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 write_commit_graph_context {
|
||||||
struct repository *r;
|
struct repository *r;
|
||||||
struct odb_source *odb_source;
|
struct odb_source *odb_source;
|
||||||
char *graph_name;
|
char *graph_name;
|
||||||
struct oid_array oids;
|
struct oid_array oids;
|
||||||
struct packed_commit_list commits;
|
struct commit_stack commits;
|
||||||
int num_extra_edges;
|
int num_extra_edges;
|
||||||
int num_generation_data_overflows;
|
int num_generation_data_overflows;
|
||||||
unsigned long approx_nr_objects;
|
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;
|
struct write_commit_graph_context *ctx = data;
|
||||||
int i, count = 0;
|
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,
|
* Write the first-level table (the list is sorted,
|
||||||
@ -1206,7 +1200,7 @@ static int write_graph_chunk_oids(struct hashfile *f,
|
|||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
struct write_commit_graph_context *ctx = data;
|
struct write_commit_graph_context *ctx = data;
|
||||||
struct commit **list = ctx->commits.list;
|
struct commit **list = ctx->commits.items;
|
||||||
int count;
|
int count;
|
||||||
for (count = 0; count < ctx->commits.nr; count++, list++) {
|
for (count = 0; count < ctx->commits.nr; count++, list++) {
|
||||||
display_progress(ctx->progress, ++ctx->progress_cnt);
|
display_progress(ctx->progress, ++ctx->progress_cnt);
|
||||||
@ -1226,8 +1220,8 @@ static int write_graph_chunk_data(struct hashfile *f,
|
|||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
struct write_commit_graph_context *ctx = data;
|
struct write_commit_graph_context *ctx = data;
|
||||||
struct commit **list = ctx->commits.list;
|
struct commit **list = ctx->commits.items;
|
||||||
struct commit **last = ctx->commits.list + ctx->commits.nr;
|
struct commit **last = ctx->commits.items + ctx->commits.nr;
|
||||||
uint32_t num_extra_edges = 0;
|
uint32_t num_extra_edges = 0;
|
||||||
|
|
||||||
while (list < last) {
|
while (list < last) {
|
||||||
@ -1249,7 +1243,7 @@ static int write_graph_chunk_data(struct hashfile *f,
|
|||||||
edge_value = GRAPH_PARENT_NONE;
|
edge_value = GRAPH_PARENT_NONE;
|
||||||
else {
|
else {
|
||||||
edge_value = oid_pos(&parent->item->object.oid,
|
edge_value = oid_pos(&parent->item->object.oid,
|
||||||
ctx->commits.list,
|
ctx->commits.items,
|
||||||
ctx->commits.nr,
|
ctx->commits.nr,
|
||||||
commit_to_oid);
|
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;
|
edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges;
|
||||||
else {
|
else {
|
||||||
edge_value = oid_pos(&parent->item->object.oid,
|
edge_value = oid_pos(&parent->item->object.oid,
|
||||||
ctx->commits.list,
|
ctx->commits.items,
|
||||||
ctx->commits.nr,
|
ctx->commits.nr,
|
||||||
commit_to_oid);
|
commit_to_oid);
|
||||||
|
|
||||||
@ -1332,7 +1326,7 @@ static int write_graph_chunk_generation_data(struct hashfile *f,
|
|||||||
int i, num_generation_data_overflows = 0;
|
int i, num_generation_data_overflows = 0;
|
||||||
|
|
||||||
for (i = 0; i < ctx->commits.nr; 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;
|
timestamp_t offset;
|
||||||
repo_parse_commit(ctx->r, c);
|
repo_parse_commit(ctx->r, c);
|
||||||
offset = commit_graph_data_at(c)->generation - c->date;
|
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;
|
struct write_commit_graph_context *ctx = data;
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < ctx->commits.nr; 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;
|
timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
|
||||||
display_progress(ctx->progress, ++ctx->progress_cnt);
|
display_progress(ctx->progress, ++ctx->progress_cnt);
|
||||||
|
|
||||||
@ -1372,8 +1366,8 @@ static int write_graph_chunk_extra_edges(struct hashfile *f,
|
|||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
struct write_commit_graph_context *ctx = data;
|
struct write_commit_graph_context *ctx = data;
|
||||||
struct commit **list = ctx->commits.list;
|
struct commit **list = ctx->commits.items;
|
||||||
struct commit **last = ctx->commits.list + ctx->commits.nr;
|
struct commit **last = ctx->commits.items + ctx->commits.nr;
|
||||||
struct commit_list *parent;
|
struct commit_list *parent;
|
||||||
|
|
||||||
while (list < last) {
|
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. */
|
/* Since num_parents > 2, this initializer is safe. */
|
||||||
for (parent = (*list)->parents->next; parent; parent = parent->next) {
|
for (parent = (*list)->parents->next; parent; parent = parent->next) {
|
||||||
int edge_value = oid_pos(&parent->item->object.oid,
|
int edge_value = oid_pos(&parent->item->object.oid,
|
||||||
ctx->commits.list,
|
ctx->commits.items,
|
||||||
ctx->commits.nr,
|
ctx->commits.nr,
|
||||||
commit_to_oid);
|
commit_to_oid);
|
||||||
|
|
||||||
@ -1427,8 +1421,8 @@ static int write_graph_chunk_bloom_indexes(struct hashfile *f,
|
|||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
struct write_commit_graph_context *ctx = data;
|
struct write_commit_graph_context *ctx = data;
|
||||||
struct commit **list = ctx->commits.list;
|
struct commit **list = ctx->commits.items;
|
||||||
struct commit **last = ctx->commits.list + ctx->commits.nr;
|
struct commit **last = ctx->commits.items + ctx->commits.nr;
|
||||||
uint32_t cur_pos = 0;
|
uint32_t cur_pos = 0;
|
||||||
|
|
||||||
while (list < last) {
|
while (list < last) {
|
||||||
@ -1463,8 +1457,8 @@ static int write_graph_chunk_bloom_data(struct hashfile *f,
|
|||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
struct write_commit_graph_context *ctx = data;
|
struct write_commit_graph_context *ctx = data;
|
||||||
struct commit **list = ctx->commits.list;
|
struct commit **list = ctx->commits.items;
|
||||||
struct commit **last = ctx->commits.list + ctx->commits.nr;
|
struct commit **last = ctx->commits.items + ctx->commits.nr;
|
||||||
|
|
||||||
trace2_bloom_filter_settings(ctx);
|
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);
|
display_progress(ctx->progress, ++ctx->progress_done);
|
||||||
|
|
||||||
oi.typep = &type;
|
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));
|
die(_("unable to get type of object %s"), oid_to_hex(oid));
|
||||||
|
|
||||||
if (type != OBJ_COMMIT)
|
if (type != OBJ_COMMIT)
|
||||||
@ -1585,7 +1579,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
|
|||||||
|
|
||||||
struct compute_generation_info {
|
struct compute_generation_info {
|
||||||
struct repository *r;
|
struct repository *r;
|
||||||
struct packed_commit_list *commits;
|
struct commit_stack *commits;
|
||||||
struct progress *progress;
|
struct progress *progress;
|
||||||
int progress_cnt;
|
int progress_cnt;
|
||||||
|
|
||||||
@ -1622,7 +1616,7 @@ static void compute_reachable_generation_numbers(
|
|||||||
struct commit_list *list = NULL;
|
struct commit_list *list = NULL;
|
||||||
|
|
||||||
for (i = 0; i < info->commits->nr; i++) {
|
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;
|
timestamp_t gen;
|
||||||
repo_parse_commit(info->r, c);
|
repo_parse_commit(info->r, c);
|
||||||
gen = info->get_generation(c, info->data);
|
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) {
|
if (!ctx->trust_generation_numbers) {
|
||||||
for (i = 0; i < ctx->commits.nr; i++) {
|
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);
|
repo_parse_commit(ctx->r, c);
|
||||||
commit_graph_data_at(c)->generation = GENERATION_NUMBER_ZERO;
|
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);
|
compute_reachable_generation_numbers(&info, 2);
|
||||||
|
|
||||||
for (i = 0; i < ctx->commits.nr; 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;
|
timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
|
||||||
if (offset > GENERATION_NUMBER_V2_OFFSET_MAX)
|
if (offset > GENERATION_NUMBER_V2_OFFSET_MAX)
|
||||||
ctx->num_generation_data_overflows++;
|
ctx->num_generation_data_overflows++;
|
||||||
@ -1760,8 +1754,8 @@ void ensure_generations_valid(struct repository *r,
|
|||||||
struct commit **commits, size_t nr)
|
struct commit **commits, size_t nr)
|
||||||
{
|
{
|
||||||
int generation_version = get_configured_generation_version(r);
|
int generation_version = get_configured_generation_version(r);
|
||||||
struct packed_commit_list list = {
|
struct commit_stack list = {
|
||||||
.list = commits,
|
.items = commits,
|
||||||
.alloc = nr,
|
.alloc = nr,
|
||||||
.nr = nr,
|
.nr = nr,
|
||||||
};
|
};
|
||||||
@ -1804,7 +1798,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx)
|
|||||||
_("Computing commit changed paths Bloom filters"),
|
_("Computing commit changed paths Bloom filters"),
|
||||||
ctx->commits.nr);
|
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)
|
if (ctx->order_by_pack)
|
||||||
QSORT(sorted_commits, ctx->commits.nr, commit_pos_cmp);
|
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);
|
oid_array_sort(&ctx->oids);
|
||||||
for (i = 0; i < ctx->oids.nr; i = oid_array_next_unique(&ctx->oids, i)) {
|
for (i = 0; i < ctx->oids.nr; i = oid_array_next_unique(&ctx->oids, i)) {
|
||||||
unsigned int num_parents;
|
unsigned int num_parents;
|
||||||
|
struct commit *commit;
|
||||||
|
|
||||||
display_progress(ctx->progress, i + 1);
|
display_progress(ctx->progress, i + 1);
|
||||||
|
|
||||||
ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc);
|
commit = lookup_commit(ctx->r, &ctx->oids.oid[i]);
|
||||||
ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.oid[i]);
|
|
||||||
|
|
||||||
if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE &&
|
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;
|
continue;
|
||||||
|
|
||||||
if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE)
|
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
|
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)
|
if (num_parents > 2)
|
||||||
ctx->num_extra_edges += num_parents - 1;
|
ctx->num_extra_edges += num_parents - 1;
|
||||||
|
|
||||||
ctx->commits.nr++;
|
commit_stack_push(&ctx->commits, commit);
|
||||||
}
|
}
|
||||||
stop_progress(&ctx->progress);
|
stop_progress(&ctx->progress);
|
||||||
}
|
}
|
||||||
@ -2330,7 +2324,7 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx,
|
|||||||
oid_to_hex(&g->oid),
|
oid_to_hex(&g->oid),
|
||||||
(uintmax_t)st_add(ctx->commits.nr, g->num_commits));
|
(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++) {
|
for (i = 0; i < g->num_commits; i++) {
|
||||||
struct object_id oid;
|
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 */
|
/* only add commits if they still exist in the repo */
|
||||||
result = lookup_commit_reference_gently(ctx->r, &oid, 1);
|
result = lookup_commit_reference_gently(ctx->r, &oid, 1);
|
||||||
|
|
||||||
if (result) {
|
if (result)
|
||||||
ctx->commits.list[ctx->commits.nr] = result;
|
commit_stack_push(&ctx->commits, result);
|
||||||
ctx->commits.nr++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2367,14 +2359,14 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx)
|
|||||||
_("Scanning merged commits"),
|
_("Scanning merged commits"),
|
||||||
ctx->commits.nr);
|
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;
|
ctx->num_extra_edges = 0;
|
||||||
for (i = 0; i < ctx->commits.nr; i++) {
|
for (i = 0; i < ctx->commits.nr; i++) {
|
||||||
display_progress(ctx->progress, i + 1);
|
display_progress(ctx->progress, i + 1);
|
||||||
|
|
||||||
if (i && oideq(&ctx->commits.list[i - 1]->object.oid,
|
if (i && oideq(&ctx->commits.items[i - 1]->object.oid,
|
||||||
&ctx->commits.list[i]->object.oid)) {
|
&ctx->commits.items[i]->object.oid)) {
|
||||||
/*
|
/*
|
||||||
* Silently ignore duplicates. These were likely
|
* Silently ignore duplicates. These were likely
|
||||||
* created due to a commit appearing in multiple
|
* 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 {
|
} else {
|
||||||
unsigned int num_parents;
|
unsigned int num_parents;
|
||||||
|
|
||||||
ctx->commits.list[dedup_i] = ctx->commits.list[i];
|
ctx->commits.items[dedup_i] = ctx->commits.items[i];
|
||||||
dedup_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)
|
if (num_parents > 2)
|
||||||
ctx->num_extra_edges += num_parents - 1;
|
ctx->num_extra_edges += num_parents - 1;
|
||||||
}
|
}
|
||||||
@ -2666,7 +2658,7 @@ int write_commit_graph(struct odb_source *source,
|
|||||||
cleanup:
|
cleanup:
|
||||||
free(ctx.graph_name);
|
free(ctx.graph_name);
|
||||||
free(ctx.base_graph_name);
|
free(ctx.base_graph_name);
|
||||||
free(ctx.commits.list);
|
commit_stack_clear(&ctx.commits);
|
||||||
oid_array_clear(&ctx.oids);
|
oid_array_clear(&ctx.oids);
|
||||||
clear_topo_level_slab(&topo_levels);
|
clear_topo_level_slab(&topo_levels);
|
||||||
|
|
||||||
|
|||||||
@ -283,8 +283,8 @@ static int remove_redundant_with_gen(struct repository *r,
|
|||||||
{
|
{
|
||||||
size_t i, count_non_stale = 0, count_still_independent = cnt;
|
size_t i, count_non_stale = 0, count_still_independent = cnt;
|
||||||
timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
|
timestamp_t min_generation = GENERATION_NUMBER_INFINITY;
|
||||||
struct commit **walk_start, **sorted;
|
struct commit **sorted;
|
||||||
size_t walk_start_nr = 0, walk_start_alloc = cnt;
|
struct commit_stack walk_start = COMMIT_STACK_INIT;
|
||||||
size_t min_gen_pos = 0;
|
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);
|
QSORT(sorted, cnt, compare_commits_by_gen);
|
||||||
min_generation = commit_graph_generation(sorted[0]);
|
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 */
|
/* Mark all parents of the input as STALE */
|
||||||
for (i = 0; i < cnt; i++) {
|
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);
|
repo_parse_commit(r, parents->item);
|
||||||
if (!(parents->item->object.flags & STALE)) {
|
if (!(parents->item->object.flags & STALE)) {
|
||||||
parents->item->object.flags |= STALE;
|
parents->item->object.flags |= STALE;
|
||||||
ALLOC_GROW(walk_start, walk_start_nr + 1, walk_start_alloc);
|
commit_stack_push(&walk_start, parents->item);
|
||||||
walk_start[walk_start_nr++] = parents->item;
|
|
||||||
}
|
}
|
||||||
parents = parents->next;
|
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 */
|
/* remove STALE bit for now to allow walking through parents */
|
||||||
for (i = 0; i < walk_start_nr; i++)
|
for (i = 0; i < walk_start.nr; i++)
|
||||||
walk_start[i]->object.flags &= ~STALE;
|
walk_start.items[i]->object.flags &= ~STALE;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Start walking from the highest generation. Hopefully, it will
|
* 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
|
* terminate early. Otherwise, we will do the same amount of work
|
||||||
* as before.
|
* 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 */
|
/* push the STALE bits up to min generation */
|
||||||
struct commit_list *stack = NULL;
|
struct commit_list *stack = NULL;
|
||||||
|
|
||||||
commit_list_insert(walk_start[i - 1], &stack);
|
commit_list_insert(walk_start.items[i - 1], &stack);
|
||||||
walk_start[i - 1]->object.flags |= STALE;
|
walk_start.items[i - 1]->object.flags |= STALE;
|
||||||
|
|
||||||
while (stack) {
|
while (stack) {
|
||||||
struct commit_list *parents;
|
struct commit_list *parents;
|
||||||
@ -390,8 +389,8 @@ static int remove_redundant_with_gen(struct repository *r,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* clear marks */
|
/* clear marks */
|
||||||
clear_commit_marks_many(walk_start_nr, walk_start, STALE);
|
clear_commit_marks_many(walk_start.nr, walk_start.items, STALE);
|
||||||
free(walk_start);
|
commit_stack_clear(&walk_start);
|
||||||
|
|
||||||
*dedup_cnt = count_non_stale;
|
*dedup_cnt = count_non_stale;
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
28
commit.c
28
commit.c
@ -1984,3 +1984,31 @@ int run_commit_hook(int editor_is_used, const char *index_file,
|
|||||||
opt.invoked_hook = invoked_hook;
|
opt.invoked_hook = invoked_hook;
|
||||||
return run_hooks_opt(the_repository, name, &opt);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
12
commit.h
12
commit.h
@ -381,4 +381,16 @@ int parse_buffer_signed_by_header(const char *buffer,
|
|||||||
const struct git_hash_algo *algop);
|
const struct git_hash_algo *algop);
|
||||||
int add_header_signature(struct strbuf *buf, struct strbuf *sig, const struct git_hash_algo *algo);
|
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 */
|
#endif /* COMMIT_H */
|
||||||
|
|||||||
33
compat/fsmonitor/fsm-health-linux.c
Normal file
33
compat/fsmonitor/fsm-health-linux.c
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
61
compat/fsmonitor/fsm-ipc-linux.c
Normal file
61
compat/fsmonitor/fsm-ipc-linux.c
Normal 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;
|
||||||
|
}
|
||||||
740
compat/fsmonitor/fsm-listen-linux.c
Normal file
740
compat/fsmonitor/fsm-listen-linux.c
Normal 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(¤t);
|
||||||
|
strbuf_addf(¤t, "%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(¤t);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
223
compat/fsmonitor/fsm-path-utils-linux.c
Normal file
223
compat/fsmonitor/fsm-path-utils-linux.c
Normal 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;
|
||||||
|
}
|
||||||
71
compat/fsmonitor/fsm-settings-linux.c
Normal file
71
compat/fsmonitor/fsm-settings-linux.c
Normal 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
111
compat/ivec.c
Normal 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
52
compat/ivec.h
Normal 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 */
|
||||||
@ -121,10 +121,6 @@ struct utsname {
|
|||||||
* trivial stubs
|
* 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)
|
static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED)
|
||||||
{ errno = ENOSYS; return -1; }
|
{ errno = ENOSYS; return -1; }
|
||||||
#ifndef __MINGW64_VERSION_MAJOR
|
#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 sigaction(int sig, struct sigaction *in, struct sigaction *out);
|
||||||
int link(const char *oldpath, const char *newpath);
|
int link(const char *oldpath, const char *newpath);
|
||||||
int uname(struct utsname *buf);
|
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
|
* replacements of existing functions
|
||||||
|
|||||||
663
compat/mingw.c
663
compat/mingw.c
@ -21,14 +21,13 @@
|
|||||||
#define SECURITY_WIN32
|
#define SECURITY_WIN32
|
||||||
#include <sspi.h>
|
#include <sspi.h>
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
|
#include <winioctl.h>
|
||||||
#include <winternl.h>
|
#include <winternl.h>
|
||||||
|
|
||||||
#define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
|
#define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056)
|
||||||
|
|
||||||
#define HCAST(type, handle) ((type)(intptr_t)handle)
|
#define HCAST(type, handle) ((type)(intptr_t)handle)
|
||||||
|
|
||||||
static const int delay[] = { 0, 1, 10, 20, 40 };
|
|
||||||
|
|
||||||
void open_in_gdb(void)
|
void open_in_gdb(void)
|
||||||
{
|
{
|
||||||
static struct child_process cp = CHILD_PROCESS_INIT;
|
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_PARAMETER: error = EINVAL; break;
|
||||||
case ERROR_INVALID_PASSWORD: error = EPERM; break;
|
case ERROR_INVALID_PASSWORD: error = EPERM; break;
|
||||||
case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; 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_SIGNAL_NUMBER: error = EINVAL; break;
|
||||||
case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
|
case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
|
||||||
case ERROR_INVALID_WORKSTATION: error = EACCES; 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_NEGATIVE_SEEK: error = ESPIPE; break;
|
||||||
case ERROR_NOACCESS: error = EFAULT; break;
|
case ERROR_NOACCESS: error = EFAULT; break;
|
||||||
case ERROR_NONE_MAPPED: error = EINVAL; 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_ENOUGH_MEMORY: error = ENOMEM; break;
|
||||||
case ERROR_NOT_READY: error = EAGAIN; break;
|
case ERROR_NOT_READY: error = EAGAIN; break;
|
||||||
case ERROR_NOT_SAME_DEVICE: error = EXDEV; 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_PIPE_NOT_CONNECTED: error = EPIPE; break;
|
||||||
case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
|
case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
|
||||||
case ERROR_READ_FAULT: error = EIO; 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: error = EIO; break;
|
||||||
case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
|
case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
|
||||||
case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
|
case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
|
||||||
@ -204,15 +208,12 @@ static int read_yes_no_answer(void)
|
|||||||
return -1;
|
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];
|
char question[4096];
|
||||||
const char *retry_hook;
|
const char *retry_hook;
|
||||||
va_list args;
|
|
||||||
|
|
||||||
va_start(args, format);
|
|
||||||
vsnprintf(question, sizeof(question), format, args);
|
vsnprintf(question, sizeof(question), format, args);
|
||||||
va_end(args);
|
|
||||||
|
|
||||||
retry_hook = mingw_getenv("GIT_ASK_YESNO");
|
retry_hook = mingw_getenv("GIT_ASK_YESNO");
|
||||||
if (retry_hook) {
|
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 */
|
/* Windows only */
|
||||||
enum hide_dotfiles_type {
|
enum hide_dotfiles_type {
|
||||||
HIDE_DOTFILES_FALSE = 0,
|
HIDE_DOTFILES_FALSE = 0,
|
||||||
@ -270,6 +296,134 @@ int mingw_core_config(const char *var, const char *value,
|
|||||||
return 0;
|
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 = ¤t->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. */
|
/* Normalizes NT paths as returned by some low-level APIs. */
|
||||||
static wchar_t *normalize_ntpath(wchar_t *wbuf)
|
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 mingw_unlink(const char *pathname, int handle_in_use_error)
|
||||||
{
|
{
|
||||||
int ret, tries = 0;
|
int tries = 0;
|
||||||
wchar_t wpathname[MAX_PATH];
|
wchar_t wpathname[MAX_PATH];
|
||||||
if (xutftowcs_path(wpathname, pathname) < 0)
|
if (xutftowcs_path(wpathname, pathname) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
@ -305,29 +459,26 @@ int mingw_unlink(const char *pathname, int handle_in_use_error)
|
|||||||
if (DeleteFileW(wpathname))
|
if (DeleteFileW(wpathname))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* read-only files cannot be removed */
|
do {
|
||||||
_wchmod(wpathname, 0666);
|
/* read-only files cannot be removed */
|
||||||
while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
|
_wchmod(wpathname, 0666);
|
||||||
|
if (!_wunlink(wpathname))
|
||||||
|
return 0;
|
||||||
if (!is_file_in_use_error(GetLastError()))
|
if (!is_file_in_use_error(GetLastError()))
|
||||||
break;
|
break;
|
||||||
if (!handle_in_use_error)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We assume that some other process had the source or
|
* _wunlink() / DeleteFileW() for directory symlinks fails with
|
||||||
* destination file open at the wrong moment and retry.
|
* ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the
|
||||||
* In order to give the other process a higher chance to
|
* same error we get if a file is in use (already checked above).
|
||||||
* complete its operation, we give up our time slice now.
|
|
||||||
* If we have to retry again, we do sleep a bit.
|
|
||||||
*/
|
*/
|
||||||
Sleep(delay[tries]);
|
if (!_wrmdir(wpathname))
|
||||||
tries++;
|
return 0;
|
||||||
}
|
|
||||||
while (ret == -1 && is_file_in_use_error(GetLastError()) &&
|
if (!handle_in_use_error)
|
||||||
ask_yes_no_if_possible("Unlink of file '%s' failed. "
|
return -1;
|
||||||
"Should I try again?", pathname))
|
} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
|
||||||
ret = _wunlink(wpathname);
|
"Should I try again?", pathname));
|
||||||
return ret;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int is_dir_empty(const wchar_t *wpath)
|
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 mingw_rmdir(const char *pathname)
|
||||||
{
|
{
|
||||||
int ret, tries = 0;
|
int tries = 0;
|
||||||
wchar_t wpathname[MAX_PATH];
|
wchar_t wpathname[MAX_PATH];
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
@ -380,7 +531,11 @@ int mingw_rmdir(const char *pathname)
|
|||||||
if (xutftowcs_path(wpathname, pathname) < 0)
|
if (xutftowcs_path(wpathname, pathname) < 0)
|
||||||
return -1;
|
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()))
|
if (!is_file_in_use_error(GetLastError()))
|
||||||
errno = err_win_to_posix(GetLastError());
|
errno = err_win_to_posix(GetLastError());
|
||||||
if (errno != EACCES)
|
if (errno != EACCES)
|
||||||
@ -389,23 +544,9 @@ int mingw_rmdir(const char *pathname)
|
|||||||
errno = ENOTEMPTY;
|
errno = ENOTEMPTY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/*
|
} while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. "
|
||||||
* We assume that some other process had the source or
|
"Should I try again?", pathname));
|
||||||
* destination file open at the wrong moment and retry.
|
return -1;
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int needs_hiding(const char *path)
|
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)
|
if (xutftowcs_path(wpath, path) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
ret = _wmkdir(wpath);
|
ret = _wmkdir(wpath);
|
||||||
|
if (!ret)
|
||||||
|
process_phantom_symlinks();
|
||||||
if (!ret && needs_hiding(path))
|
if (!ret && needs_hiding(path))
|
||||||
return set_hidden_flag(wpath, 1);
|
return set_hidden_flag(wpath, 1);
|
||||||
return ret;
|
return ret;
|
||||||
@ -853,9 +996,27 @@ int mingw_access(const char *filename, int mode)
|
|||||||
int mingw_chdir(const char *dirname)
|
int mingw_chdir(const char *dirname)
|
||||||
{
|
{
|
||||||
wchar_t wdirname[MAX_PATH];
|
wchar_t wdirname[MAX_PATH];
|
||||||
|
|
||||||
if (xutftowcs_path(wdirname, dirname) < 0)
|
if (xutftowcs_path(wdirname, dirname) < 0)
|
||||||
return -1;
|
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)
|
int mingw_chmod(const char *filename, int mode)
|
||||||
@ -917,53 +1078,139 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We keep the do_lstat code in a separate function to avoid recursion.
|
#ifndef _WINNT_H
|
||||||
* When a path ends with a slash, the stat will fail with ENOENT. In
|
/*
|
||||||
* this case, we strip the trailing slashes and stat again.
|
* 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
|
||||||
* If follow is true then act like stat() and report on the link
|
* it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_).
|
||||||
* target. Otherwise report on the link itself.
|
|
||||||
*/
|
*/
|
||||||
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;
|
WIN32_FILE_ATTRIBUTE_DATA fdata;
|
||||||
|
DWORD reparse_tag = 0;
|
||||||
|
int link_len = 0;
|
||||||
wchar_t wfilename[MAX_PATH];
|
wchar_t wfilename[MAX_PATH];
|
||||||
if (xutftowcs_path(wfilename, file_name) < 0)
|
int wlen = xutftowcs_path(wfilename, file_name);
|
||||||
|
if (wlen < 0)
|
||||||
return -1;
|
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)) {
|
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_ino = 0;
|
||||||
buf->st_gid = 0;
|
buf->st_gid = 0;
|
||||||
buf->st_uid = 0;
|
buf->st_uid = 0;
|
||||||
buf->st_nlink = 1;
|
buf->st_nlink = 1;
|
||||||
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
|
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
|
||||||
buf->st_size = fdata.nFileSizeLow |
|
reparse_tag);
|
||||||
(((off_t)fdata.nFileSizeHigh)<<32);
|
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 */
|
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
||||||
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
|
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
|
||||||
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
|
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
|
||||||
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (GetLastError()) {
|
switch (GetLastError()) {
|
||||||
case ERROR_ACCESS_DENIED:
|
case ERROR_ACCESS_DENIED:
|
||||||
case ERROR_SHARING_VIOLATION:
|
case ERROR_SHARING_VIOLATION:
|
||||||
@ -990,39 +1237,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
|
|||||||
return -1;
|
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)
|
static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
|
||||||
{
|
{
|
||||||
BY_HANDLE_FILE_INFORMATION fdata;
|
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_gid = 0;
|
||||||
buf->st_uid = 0;
|
buf->st_uid = 0;
|
||||||
buf->st_nlink = 1;
|
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 |
|
buf->st_size = fdata.nFileSizeLow |
|
||||||
(((off_t)fdata.nFileSizeHigh)<<32);
|
(((off_t)fdata.nFileSizeHigh)<<32);
|
||||||
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
|
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;
|
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)
|
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)
|
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];
|
wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
|
||||||
DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
|
DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
|
||||||
|
HANDLE hnd;
|
||||||
|
|
||||||
if (!ret || ret >= ARRAY_SIZE(cwd)) {
|
if (!ret || ret >= ARRAY_SIZE(cwd)) {
|
||||||
errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
|
errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError());
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer));
|
hnd = CreateFileW(cwd, 0,
|
||||||
if (!ret && GetLastError() == ERROR_ACCESS_DENIED) {
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
||||||
HANDLE hnd = CreateFileW(cwd, 0,
|
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
||||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
if (hnd != INVALID_HANDLE_VALUE) {
|
||||||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
||||||
if (hnd == INVALID_HANDLE_VALUE)
|
|
||||||
return NULL;
|
|
||||||
ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
|
ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0);
|
||||||
CloseHandle(hnd);
|
CloseHandle(hnd);
|
||||||
if (!ret || ret >= ARRAY_SIZE(wpointer))
|
if (!ret || ret >= ARRAY_SIZE(wpointer))
|
||||||
@ -1259,13 +1495,11 @@ char *mingw_getcwd(char *pointer, int len)
|
|||||||
return NULL;
|
return NULL;
|
||||||
return pointer;
|
return pointer;
|
||||||
}
|
}
|
||||||
if (!ret || ret >= ARRAY_SIZE(wpointer))
|
if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) {
|
||||||
return NULL;
|
|
||||||
if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) {
|
|
||||||
errno = ENOENT;
|
errno = ENOENT;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (xwcstoutf(pointer, wpointer, len) < 0)
|
if (xwcstoutf(pointer, cwd, len) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
convert_slashes(pointer);
|
convert_slashes(pointer);
|
||||||
return pointer;
|
return pointer;
|
||||||
@ -1986,6 +2220,16 @@ int mingw_kill(pid_t pid, int sig)
|
|||||||
CloseHandle(h);
|
CloseHandle(h);
|
||||||
return 0;
|
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;
|
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)
|
int mingw_rename(const char *pold, const char *pnew)
|
||||||
{
|
{
|
||||||
static int supports_file_rename_info_ex = 1;
|
static int supports_file_rename_info_ex = 1;
|
||||||
DWORD attrs, gle;
|
DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
|
||||||
int tries = 0;
|
int tries = 0;
|
||||||
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
|
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
|
||||||
int wpnew_len;
|
int wpnew_len;
|
||||||
@ -2212,15 +2456,6 @@ int mingw_rename(const char *pold, const char *pnew)
|
|||||||
if (wpnew_len < 0)
|
if (wpnew_len < 0)
|
||||||
return -1;
|
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:
|
repeat:
|
||||||
if (supports_file_rename_info_ex) {
|
if (supports_file_rename_info_ex) {
|
||||||
/*
|
/*
|
||||||
@ -2296,13 +2531,22 @@ repeat:
|
|||||||
* to retry.
|
* to retry.
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
if (MoveFileExW(wpold, wpnew,
|
||||||
|
MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
|
||||||
return 0;
|
return 0;
|
||||||
gle = GetLastError();
|
gle = GetLastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: translate more errors */
|
/* revert file attributes on failure */
|
||||||
if (gle == ERROR_ACCESS_DENIED &&
|
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) {
|
(attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
|
||||||
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
DWORD attrsold = GetFileAttributesW(wpold);
|
DWORD attrsold = GetFileAttributesW(wpold);
|
||||||
@ -2314,28 +2558,10 @@ repeat:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
|
if ((attrs & FILE_ATTRIBUTE_READONLY) &&
|
||||||
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
|
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
|
||||||
if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
|
goto repeat;
|
||||||
return 0;
|
|
||||||
gle = GetLastError();
|
|
||||||
/* revert file attributes on failure */
|
|
||||||
SetFileAttributesW(wpnew, attrs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
|
if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
|
||||||
/*
|
|
||||||
* 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++;
|
|
||||||
goto repeat;
|
|
||||||
}
|
|
||||||
if (gle == ERROR_ACCESS_DENIED &&
|
|
||||||
ask_yes_no_if_possible("Rename from '%s' to '%s' failed. "
|
|
||||||
"Should I try again?", pold, pnew))
|
"Should I try again?", pold, pnew))
|
||||||
goto repeat;
|
goto repeat;
|
||||||
|
|
||||||
@ -2624,6 +2850,94 @@ int link(const char *oldpath, const char *newpath)
|
|||||||
return 0;
|
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)
|
pid_t waitpid(pid_t pid, int *status, int options)
|
||||||
{
|
{
|
||||||
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
|
HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
|
||||||
@ -2812,6 +3126,15 @@ static void setup_windows_environment(void)
|
|||||||
if (!tmp && (tmp = getenv("USERPROFILE")))
|
if (!tmp && (tmp = getenv("USERPROFILE")))
|
||||||
setenv("HOME", tmp, 1);
|
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)
|
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);
|
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 _MSC_VER
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#include <crtdbg.h>
|
#include <crtdbg.h>
|
||||||
@ -3260,6 +3601,7 @@ int wmain(int argc, const wchar_t **wargv)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
maybe_redirect_std_handles();
|
maybe_redirect_std_handles();
|
||||||
|
adjust_symlink_flags();
|
||||||
|
|
||||||
/* determine size of argv and environ conversion buffer */
|
/* determine size of argv and environ conversion buffer */
|
||||||
maxlen = wcslen(wargv[0]);
|
maxlen = wcslen(wargv[0]);
|
||||||
@ -3289,6 +3631,7 @@ int wmain(int argc, const wchar_t **wargv)
|
|||||||
|
|
||||||
/* initialize critical section for waitpid pinfo_t list */
|
/* initialize critical section for waitpid pinfo_t list */
|
||||||
InitializeCriticalSection(&pinfo_cs);
|
InitializeCriticalSection(&pinfo_cs);
|
||||||
|
InitializeCriticalSection(&phantom_symlinks_cs);
|
||||||
|
|
||||||
/* set up default file mode and file modes for stdin/out/err */
|
/* set up default file mode and file modes for stdin/out/err */
|
||||||
_fmode = _O_BINARY;
|
_fmode = _O_BINARY;
|
||||||
|
|||||||
@ -253,6 +253,8 @@ char *gitdirname(char *);
|
|||||||
typedef uintmax_t timestamp_t;
|
typedef uintmax_t timestamp_t;
|
||||||
#define PRItime PRIuMAX
|
#define PRItime PRIuMAX
|
||||||
#define parse_timestamp strtoumax
|
#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_MAX UINTMAX_MAX
|
||||||
#define TIME_MIN 0
|
#define TIME_MIN 0
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,12 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#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;
|
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;
|
fMode |= S_IFDIR;
|
||||||
else
|
else
|
||||||
fMode |= S_IFREG;
|
fMode |= S_IFREG;
|
||||||
|
|||||||
@ -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));
|
xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name));
|
||||||
|
|
||||||
/* Set file type, based on WIN32_FIND_DATA */
|
/* 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;
|
ent->d_type = DT_DIR;
|
||||||
else
|
else
|
||||||
ent->d_type = DT_REG;
|
ent->d_type = DT_REG;
|
||||||
|
|||||||
54
config.c
54
config.c
@ -1513,8 +1513,8 @@ int git_config_system(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int do_git_config_sequence(const struct config_options *opts,
|
static int do_git_config_sequence(const struct config_options *opts,
|
||||||
const struct repository *repo,
|
const struct repository *repo, config_fn_t fn,
|
||||||
config_fn_t fn, void *data)
|
void *data, enum config_scope scope)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
char *system_config = git_system_config();
|
char *system_config = git_system_config();
|
||||||
@ -1539,22 +1539,46 @@ static int do_git_config_sequence(const struct config_options *opts,
|
|||||||
worktree_config = NULL;
|
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,
|
!access_or_die(system_config, R_OK,
|
||||||
opts->system_gently ? ACCESS_EACCES_OK : 0))
|
opts->system_gently ? ACCESS_EACCES_OK : 0))
|
||||||
ret += git_config_from_file_with_options(fn, system_config,
|
ret += git_config_from_file_with_options(fn, system_config,
|
||||||
data, CONFIG_SCOPE_SYSTEM,
|
data, CONFIG_SCOPE_SYSTEM,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
git_global_config_paths(&user_config, &xdg_config);
|
if (!opts->ignore_global) {
|
||||||
|
int global_config_success_count = 0;
|
||||||
|
int nonzero_ret_on_global_config_error = scope == CONFIG_SCOPE_GLOBAL;
|
||||||
|
|
||||||
if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
|
git_global_config_paths(&user_config, &xdg_config);
|
||||||
ret += git_config_from_file_with_options(fn, xdg_config, data,
|
|
||||||
CONFIG_SCOPE_GLOBAL, NULL);
|
|
||||||
|
|
||||||
if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
|
if (xdg_config &&
|
||||||
ret += git_config_from_file_with_options(fn, user_config, data,
|
!access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
|
||||||
CONFIG_SCOPE_GLOBAL, NULL);
|
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 (!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 &&
|
if (!opts->ignore_repo && repo_config &&
|
||||||
!access_or_die(repo_config, R_OK, 0))
|
!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"));
|
die(_("unable to parse command-line config"));
|
||||||
|
|
||||||
free(system_config);
|
free(system_config);
|
||||||
free(xdg_config);
|
|
||||||
free(user_config);
|
|
||||||
free(repo_config);
|
free(repo_config);
|
||||||
free(worktree_config);
|
free(worktree_config);
|
||||||
return ret;
|
return ret;
|
||||||
@ -1604,7 +1626,8 @@ int config_with_options(config_fn_t fn, void *data,
|
|||||||
*/
|
*/
|
||||||
if (config_source && config_source->use_stdin) {
|
if (config_source && config_source->use_stdin) {
|
||||||
ret = git_config_from_stdin(fn, data, config_source->scope);
|
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,
|
ret = git_config_from_file_with_options(fn, config_source->file,
|
||||||
data, config_source->scope,
|
data, config_source->scope,
|
||||||
NULL);
|
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,
|
ret = git_config_from_blob_ref(fn, repo, config_source->blob,
|
||||||
data, config_source->scope);
|
data, config_source->scope);
|
||||||
} else {
|
} 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) {
|
if (inc.remote_urls) {
|
||||||
|
|||||||
2
config.h
2
config.h
@ -87,6 +87,8 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,
|
|||||||
|
|
||||||
struct config_options {
|
struct config_options {
|
||||||
unsigned int respect_includes : 1;
|
unsigned int respect_includes : 1;
|
||||||
|
unsigned int ignore_system : 1;
|
||||||
|
unsigned int ignore_global : 1;
|
||||||
unsigned int ignore_repo : 1;
|
unsigned int ignore_repo : 1;
|
||||||
unsigned int ignore_worktree : 1;
|
unsigned int ignore_worktree : 1;
|
||||||
unsigned int ignore_cmdline : 1;
|
unsigned int ignore_cmdline : 1;
|
||||||
|
|||||||
@ -68,6 +68,16 @@ ifeq ($(uname_S),Linux)
|
|||||||
BASIC_CFLAGS += -std=c99
|
BASIC_CFLAGS += -std=c99
|
||||||
endif
|
endif
|
||||||
LINK_FUZZ_PROGRAMS = YesPlease
|
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
|
endif
|
||||||
ifeq ($(uname_S),GNU/kFreeBSD)
|
ifeq ($(uname_S),GNU/kFreeBSD)
|
||||||
HAVE_ALLOCA_H = YesPlease
|
HAVE_ALLOCA_H = YesPlease
|
||||||
@ -157,6 +167,7 @@ ifeq ($(uname_S),Darwin)
|
|||||||
endif
|
endif
|
||||||
ifeq ($(shell test "$(DARWIN_MAJOR_VERSION)" -ge 24 && echo 1),1)
|
ifeq ($(shell test "$(DARWIN_MAJOR_VERSION)" -ge 24 && echo 1),1)
|
||||||
USE_HOMEBREW_LIBICONV = UnfortunatelyYes
|
USE_HOMEBREW_LIBICONV = UnfortunatelyYes
|
||||||
|
NEEDS_GOOD_LIBICONV = UnfortunatelyYes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require
|
# The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require
|
||||||
|
|||||||
@ -308,6 +308,16 @@ if(SUPPORTS_SIMPLE_IPC)
|
|||||||
|
|
||||||
add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
|
add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS)
|
||||||
list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
|
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()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@ -3899,7 +3899,7 @@ __git_main ()
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
--*)
|
-*)
|
||||||
__gitcomp "
|
__gitcomp "
|
||||||
--paginate
|
--paginate
|
||||||
--no-pager
|
--no-pager
|
||||||
@ -3915,6 +3915,12 @@ __git_main ()
|
|||||||
--namespace=
|
--namespace=
|
||||||
--no-replace-objects
|
--no-replace-objects
|
||||||
--help
|
--help
|
||||||
|
-C
|
||||||
|
-P
|
||||||
|
-c
|
||||||
|
-h
|
||||||
|
-p
|
||||||
|
-v
|
||||||
"
|
"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@ -110,7 +110,7 @@ void discard_hashfile(struct hashfile *f)
|
|||||||
free_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) {
|
while (count) {
|
||||||
unsigned left = f->buffer_len - f->offset;
|
unsigned left = f->buffer_len - f->offset;
|
||||||
|
|||||||
@ -63,7 +63,7 @@ void free_hashfile(struct hashfile *f);
|
|||||||
*/
|
*/
|
||||||
int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);
|
int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int);
|
||||||
void discard_hashfile(struct hashfile *);
|
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 hashflush(struct hashfile *f);
|
||||||
void crc32_begin(struct hashfile *);
|
void crc32_begin(struct hashfile *);
|
||||||
uint32_t crc32_end(struct hashfile *);
|
uint32_t crc32_end(struct hashfile *);
|
||||||
|
|||||||
14
dir.c
14
dir.c
@ -2263,6 +2263,8 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
|
|||||||
const struct pathspec *pathspec)
|
const struct pathspec *pathspec)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
int matches_exclude_magic = 0;
|
||||||
|
int matches_pathspec_elem = 0;
|
||||||
|
|
||||||
if (!pathspec || !pathspec->nr)
|
if (!pathspec || !pathspec->nr)
|
||||||
return 0;
|
return 0;
|
||||||
@ -2279,15 +2281,23 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
|
|||||||
for (i = 0; i < pathspec->nr; i++) {
|
for (i = 0; i < pathspec->nr; i++) {
|
||||||
const struct pathspec_item *item = &pathspec->items[i];
|
const struct pathspec_item *item = &pathspec->items[i];
|
||||||
int len = item->nowildcard_len;
|
int len = item->nowildcard_len;
|
||||||
|
int *matches;
|
||||||
|
|
||||||
|
if (item->magic & PATHSPEC_EXCLUDE)
|
||||||
|
matches = &matches_exclude_magic;
|
||||||
|
else
|
||||||
|
matches = &matches_pathspec_elem;
|
||||||
|
|
||||||
if (len == pathlen &&
|
if (len == pathlen &&
|
||||||
!ps_strncmp(item, item->match, path, pathlen))
|
!ps_strncmp(item, item->match, path, pathlen))
|
||||||
return 1;
|
*matches = 1;
|
||||||
if (len > pathlen &&
|
if (len > pathlen &&
|
||||||
item->match[pathlen] == '/' &&
|
item->match[pathlen] == '/' &&
|
||||||
!ps_strncmp(item, item->match, path, pathlen))
|
!ps_strncmp(item, item->match, path, pathlen))
|
||||||
return 1;
|
*matches = 1;
|
||||||
}
|
}
|
||||||
|
if (matches_pathspec_elem && !matches_exclude_magic)
|
||||||
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
#include "gettext.h"
|
#include "gettext.h"
|
||||||
#include "git-zlib.h"
|
#include "git-zlib.h"
|
||||||
#include "ident.h"
|
#include "ident.h"
|
||||||
|
#include "lockfile.h"
|
||||||
#include "mailmap.h"
|
#include "mailmap.h"
|
||||||
#include "object-name.h"
|
#include "object-name.h"
|
||||||
#include "repository.h"
|
#include "repository.h"
|
||||||
@ -53,7 +54,6 @@ char *git_commit_encoding;
|
|||||||
char *git_log_output_encoding;
|
char *git_log_output_encoding;
|
||||||
char *apply_default_whitespace;
|
char *apply_default_whitespace;
|
||||||
char *apply_default_ignorewhitespace;
|
char *apply_default_ignorewhitespace;
|
||||||
char *git_attributes_file;
|
|
||||||
int zlib_compression_level = Z_BEST_SPEED;
|
int zlib_compression_level = Z_BEST_SPEED;
|
||||||
int pack_compression_level = Z_DEFAULT_COMPRESSION;
|
int pack_compression_level = Z_DEFAULT_COMPRESSION;
|
||||||
int fsync_object_files = -1;
|
int fsync_object_files = -1;
|
||||||
@ -324,8 +324,8 @@ next_name:
|
|||||||
return (current & ~negative) | positive;
|
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)
|
const struct config_context *ctx, void *cb)
|
||||||
{
|
{
|
||||||
/* This needs a better name */
|
/* This needs a better name */
|
||||||
if (!strcmp(var, "core.filemode")) {
|
if (!strcmp(var, "core.filemode")) {
|
||||||
@ -363,11 +363,6 @@ static int git_default_core_config(const char *var, const char *value,
|
|||||||
return 0;
|
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")) {
|
if (!strcmp(var, "core.bare")) {
|
||||||
is_bare_repository_cfg = git_config_bool(var, value);
|
is_bare_repository_cfg = git_config_bool(var, value);
|
||||||
return 0;
|
return 0;
|
||||||
@ -532,6 +527,11 @@ static int git_default_core_config(const char *var, const char *value,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!strcmp(var, "core.lockfilepid")) {
|
||||||
|
lockfile_pid_enabled = git_config_bool(var, value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!strcmp(var, "core.createobject")) {
|
if (!strcmp(var, "core.createobject")) {
|
||||||
if (!value)
|
if (!value)
|
||||||
return config_error_nonbool(var);
|
return config_error_nonbool(var);
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
#define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
|
#define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS"
|
||||||
#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
|
#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
|
||||||
#define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
|
#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
|
* 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 *,
|
int git_default_config(const char *, const char *,
|
||||||
const struct config_context *, void *);
|
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
|
* 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 int warn_on_object_refname_ambiguity;
|
||||||
extern char *apply_default_whitespace;
|
extern char *apply_default_whitespace;
|
||||||
extern char *apply_default_ignorewhitespace;
|
extern char *apply_default_ignorewhitespace;
|
||||||
extern char *git_attributes_file;
|
|
||||||
extern int zlib_compression_level;
|
extern int zlib_compression_level;
|
||||||
extern int pack_compression_level;
|
extern int pack_compression_level;
|
||||||
extern unsigned long pack_size_limit_cfg;
|
extern unsigned long pack_size_limit_cfg;
|
||||||
|
|||||||
20
fetch-pack.c
20
fetch-pack.c
@ -35,6 +35,7 @@
|
|||||||
#include "sigchain.h"
|
#include "sigchain.h"
|
||||||
#include "mergesort.h"
|
#include "mergesort.h"
|
||||||
#include "prio-queue.h"
|
#include "prio-queue.h"
|
||||||
|
#include "promisor-remote.h"
|
||||||
|
|
||||||
static int transfer_unpack_limit = -1;
|
static int transfer_unpack_limit = -1;
|
||||||
static int fetch_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;
|
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
|
||||||
int i;
|
int i;
|
||||||
struct strvec index_pack_args = STRVEC_INIT;
|
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;
|
negotiator = &negotiator_alloc;
|
||||||
if (args->refetch)
|
if (args->refetch)
|
||||||
|
|||||||
25
fsck.c
25
fsck.c
@ -860,28 +860,13 @@ static int verify_headers(const void *data, unsigned long size,
|
|||||||
FSCK_MSG_UNTERMINATED_HEADER, "unterminated header");
|
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,
|
static int fsck_ident(const char **ident, const char *ident_end,
|
||||||
const struct object_id *oid, enum object_type type,
|
const struct object_id *oid, enum object_type type,
|
||||||
struct fsck_options *options)
|
struct fsck_options *options)
|
||||||
{
|
{
|
||||||
const char *p = *ident;
|
const char *p = *ident;
|
||||||
const char *nl;
|
const char *nl;
|
||||||
|
timestamp_t timestamp;
|
||||||
|
|
||||||
nl = memchr(p, '\n', ident_end - p);
|
nl = memchr(p, '\n', ident_end - p);
|
||||||
if (!nl)
|
if (!nl)
|
||||||
@ -933,7 +918,8 @@ static int fsck_ident(const char **ident, const char *ident_end,
|
|||||||
"invalid author/committer line - bad date");
|
"invalid author/committer line - bad date");
|
||||||
if (*p == '0' && p[1] != ' ')
|
if (*p == '0' && p[1] != ' ')
|
||||||
return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date");
|
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, ×tamp) ||
|
||||||
|
date_overflows(timestamp))
|
||||||
return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow");
|
return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow");
|
||||||
if (*p != ' ')
|
if (*p != ' ')
|
||||||
return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date");
|
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);
|
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)
|
if (msg_type == FSCK_WARN)
|
||||||
warning("%s: %s", sb.buf, message);
|
warning("%s: %s", sb.buf, message);
|
||||||
else
|
else
|
||||||
|
|||||||
4
fsck.h
4
fsck.h
@ -30,6 +30,7 @@ enum fsck_msg_type {
|
|||||||
FUNC(BAD_DATE_OVERFLOW, ERROR) \
|
FUNC(BAD_DATE_OVERFLOW, ERROR) \
|
||||||
FUNC(BAD_EMAIL, ERROR) \
|
FUNC(BAD_EMAIL, ERROR) \
|
||||||
FUNC(BAD_GPGSIG, ERROR) \
|
FUNC(BAD_GPGSIG, ERROR) \
|
||||||
|
FUNC(BAD_HEAD_TARGET, ERROR) \
|
||||||
FUNC(BAD_NAME, ERROR) \
|
FUNC(BAD_NAME, ERROR) \
|
||||||
FUNC(BAD_OBJECT_SHA1, ERROR) \
|
FUNC(BAD_OBJECT_SHA1, ERROR) \
|
||||||
FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
|
FUNC(BAD_PACKED_REF_ENTRY, ERROR) \
|
||||||
@ -39,6 +40,7 @@ enum fsck_msg_type {
|
|||||||
FUNC(BAD_REF_CONTENT, ERROR) \
|
FUNC(BAD_REF_CONTENT, ERROR) \
|
||||||
FUNC(BAD_REF_FILETYPE, ERROR) \
|
FUNC(BAD_REF_FILETYPE, ERROR) \
|
||||||
FUNC(BAD_REF_NAME, ERROR) \
|
FUNC(BAD_REF_NAME, ERROR) \
|
||||||
|
FUNC(BAD_REF_OID, ERROR) \
|
||||||
FUNC(BAD_TIMEZONE, ERROR) \
|
FUNC(BAD_TIMEZONE, ERROR) \
|
||||||
FUNC(BAD_TREE, ERROR) \
|
FUNC(BAD_TREE, ERROR) \
|
||||||
FUNC(BAD_TREE_SHA1, ERROR) \
|
FUNC(BAD_TREE_SHA1, ERROR) \
|
||||||
@ -162,8 +164,6 @@ struct fsck_object_report {
|
|||||||
|
|
||||||
struct fsck_ref_report {
|
struct fsck_ref_report {
|
||||||
const char *path;
|
const char *path;
|
||||||
const struct object_id *oid;
|
|
||||||
const char *referent;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fsck_options {
|
struct fsck_options {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user