Migrate the automation of the merging-rebases of the shears/* branches from Azure Pipelines to GitHub workflows#152
Conversation
|
For the record: https://github.com/git-for-windows/git-for-windows-automation/actions/runs/22094271117 is a run of an unpolished version of this automation. It did rebase I have no illusions about this automation working as intended for anything but relatively trivial cases. My hope is that I can get it to the point where it handles more and more complex scenarios over time, incrementally, when the occasion arises (read: when merge conflicts cause this workflow to fail). |
|
Could we try running this against prior rebase efforts that did produce more complicated merge conflicts and see how it did? Maybe even provide prior conflict-resolutions as examples for it to learn from? A kind of |
| # shears-branch - The branch to rebase (e.g., shears/seen) | ||
| # upstream-branch - The upstream branch to rebase onto (e.g., upstream/seen) | ||
| # | ||
| # Preconditions: |
There was a problem hiding this comment.
Another precondition is the presence of the origin remote that we want to operate on ;)
There was a problem hiding this comment.
I hoped this would be implicitly clear by the precondition that origin/shears/<name> must be fetched already...
| You may want to examine these with 'git show <oid>' for hints on how to resolve." | ||
| fi | ||
|
|
||
| prompt="Resolve merge conflict during rebase of commit REBASE_HEAD. |
There was a problem hiding this comment.
How do we know that this invocation is using the conflict-resolver.agent.md AGENTS.md file, and not another in the .github/agents directory (should another one be added in the future)?
There was a problem hiding this comment.
How do we know [...]
@mjcheetham we do not. I do not currently see any way to enforce that :-(
There was a problem hiding this comment.
You should be able to specify agent instructions to preload using the --agent flag. It takes the agent id (the part before the .agent.md in the filename)
So in this case copilot --agent conflict-resolver --prompt "Resolve merge conflict during rebase of commit REBASE_HEAD."should do the trick.
There was a problem hiding this comment.
Oh, that's nice! @niik is there some sort of search path for the agents, or a way to provide a relative path so as to avoid having a potential future ~/.copilot/agents/conflict-resolver/ interfere?
| echo "::endgroup::" | ||
| echo "::warning::Build failed after conflict resolution, giving AI another chance" | ||
|
|
||
| retry_prompt="Build failed after your conflict resolution. Fix the compilation error. |
There was a problem hiding this comment.
Maybe it's just me, but I always feel a little weirded out by mixing the prompts within the shell script. Is it possible to extract the prompts to their own 'prompts/retry.txt' and 'prompts/rebase.txt' files?
I realise also that these prompts are being dynamically built with interpolated $var shell variables (which also makes me concerned).
To move the prompts out would require more stringent replacement markers like "Run <<<CHECK_CMD>>> to check something first." or something.
There was a problem hiding this comment.
@mjcheetham my original plan was that, but I encountered a bug and I'm wary to reopen that can of worms again because sometimes I do want to be able to debug this on Windows, even if it doesn't run on Windows in CI.
mjcheetham
left a comment
There was a problem hiding this comment.
This (conflict resolution) is a very interesting and clever use of AI agents! Just a few comments around keeping prompts separate from the shell scripts.
Good idea! I did exactly that. In particular, I went through my Range-diff between pre-/post-rebase tip commits
I let Claude Opus write the throw-away commit f9d870a that temporarily overrides the refs so as to pretend that the remote branches are in the same state as before I performed above-mentioned rebase. When I pushed that, it triggered this run: https://github.com/git-for-windows/git-for-windows-automation/actions/runs/22109791261. The result is in the attached To my great surprise, both post-rebase tip commits are tree-same! And not only that, the range-diff shows identical patches (modulo commit OIDs), which I found really surprising: Range-diff between my rebase and the automated one
So this looks a bit suspicious, doesn't it? Therefore, I had a bit of a deeper look at the logs: The commit 519b619b85 (pack-objects (mingw): demonstrate a segmentation fault with large deltas) required AI to resolve: AI outputThe reasoning is sound! However, this was the only commit that was resolved using AI, which is a bit suspicious because the trickier merge conflicts were actually in the FSCache vs symlink patches, e.g. a83fccbf2112 (mingw: Windows Docker volumes are not symbolic links). The reason for that was a merging-rebase of So while this was an okay test run, it still did not require any hard answers to merge conflicts. I will perform another test that is a lot harder: re-do that merging-rebase that was performed between above-mentioned pre-/post-rebase tip commits of |
Okay, this is between 5f32b8db3cac437d96ce181fdd70aa3d1ac92f30 and 3832dd2481ce7e25d18511420fca0e5567c6ac54: Range-diff
Here is the workflow run: https://github.com/git-for-windows/git-for-windows-automation/actions/runs/22113557351; It took almost half an hour to run. And this is a much more interesting case, indeed, as it did not do the exact identical thing as I did in my corresponding rebase: git diff 3832dd2481ce7e25d18511420fca0e5567c6ac54 ee62a84b1d0a0a1482c0ef7791babc579228f7b0 (my rebase vs this run's)diff --git a/compat/mingw.c b/compat/mingw.c
index 5a9807e7a42b..f6173c4b1d55 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -382,7 +382,7 @@ process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink)
{
HANDLE hnd;
BY_HANDLE_FILE_INFORMATION fdata;
- wchar_t relative[MAX_LONG_PATH];
+ wchar_t relative[MAX_PATH];
const wchar_t *rel;
/* check that wlink is still a file symlink */
@@ -1119,6 +1119,7 @@ int mingw_chdir(const char *dirname)
{
int result;
wchar_t wdirname[MAX_LONG_PATH];
+
if (xutftowcs_long_path(wdirname, dirname) < 0)
return -1;
@@ -1296,7 +1297,7 @@ int mingw_lstat(const char *file_name, struct stat *buf)
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
/* for reparse points, get the link tag and length */
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
- char tmpbuf[MAX_LONG_PATH];
+ char tmpbuf[MAX_PATH];
if (read_reparse_point(wfilename, FALSE, tmpbuf,
&link_len, &reparse_tag) < 0)
@@ -1377,12 +1378,12 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf)
int mingw_stat(const char *file_name, struct stat *buf)
{
- wchar_t wfile_name[MAX_LONG_PATH];
+ wchar_t wfile_name[MAX_PATH];
HANDLE hnd;
int result;
/* open the file and let Windows resolve the links */
- if (xutftowcs_long_path(wfile_name, file_name) < 0)
+ 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,
@@ -3381,7 +3382,7 @@ static enum symlink_type check_symlink_attr(struct index_state *index, const cha
int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
{
- wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
+ wchar_t wtarget[MAX_PATH], wlink[MAX_PATH];
int len;
/* fail if symlinks are disabled or API is not supported (WinXP) */
@@ -3390,8 +3391,8 @@ int mingw_create_symlink(struct index_state *index, const char *target, const ch
return -1;
}
- if ((len = xutftowcs_long_path(wtarget, target)) < 0
- || xutftowcs_long_path(wlink, link) < 0)
+ if ((len = xutftowcs_path(wtarget, target)) < 0
+ || xutftowcs_path(wlink, link) < 0)
return -1;
/* convert target dir separators to backslashes */
@@ -3428,12 +3429,12 @@ int mingw_create_symlink(struct index_state *index, const char *target, const ch
int readlink(const char *path, char *buf, size_t bufsiz)
{
- WCHAR wpath[MAX_LONG_PATH];
- char tmpbuf[MAX_LONG_PATH];
+ WCHAR wpath[MAX_PATH];
+ char tmpbuf[MAX_PATH];
int len;
DWORD tag;
- if (xutftowcs_long_path(wpath, path) < 0)
+ if (xutftowcs_path(wpath, path) < 0)
return -1;
if (read_reparse_point(wpath, TRUE, tmpbuf, &len, &tag) < 0)
@@ -3726,6 +3727,15 @@ static void setup_windows_environment(void)
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;
+
if (!getenv("PLINK_PROTOCOL"))
setenv("PLINK_PROTOCOL", "ssh", 0);
diff --git a/compat/win32/fscache.c b/compat/win32/fscache.c
index cbd90ececf6b..cb97bddfecad 100644
--- a/compat/win32/fscache.c
+++ b/compat/win32/fscache.c
@@ -632,18 +632,6 @@ int fscache_lstat(const char *filename, struct stat *st)
return -1;
}
- /*
- * Special case symbolic links: FindFirstFile()/FindNextFile() did not
- * provide us with the length of the target path.
- */
- if (fse->u.s.st_size == MAX_LONG_PATH && S_ISLNK(fse->st_mode)) {
- char buf[MAX_LONG_PATH];
- int len = readlink(filename, buf, sizeof(buf) - 1);
-
- if (len > 0)
- fse->u.s.st_size = len;
- }
-
/* copy stat data */
st->st_ino = 0;
st->st_gid = 0;
diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh
index 25b8d50c9428..a11b20e37822 100755
--- a/t/t5571-pre-push-hook.sh
+++ b/t/t5571-pre-push-hook.sh
@@ -138,16 +138,4 @@ test_expect_success 'sigpipe does not cause pre-push hook failure' '
git push parent1 "refs/heads/b/*:refs/heads/b/*"
'
-test_expect_success 'can write to stderr' '
- test_hook --clobber pre-push <<-\EOF &&
- echo foo >/dev/stderr &&
- exit 0
- EOF
-
- test_commit third &&
- echo foo >expect &&
- git push --quiet parent1 HEAD 2>actual &&
- test_cmp expect actual
-'
-
test_doneThe differences are mostly missed Similarly, an FSCache/symlink special-case that uses Another difference is the "Win32: change default of 'core.symlinks' to false" patch, which the automated run contains twice, once from upstream, once from downstream, because it applied without conflicts. A red herring is the "dropped" 541d54bc0121 (t/t5571-prep-push-hook.sh: Add test with writing to stderr, 2026-01-26): I simply used a fake With that in mind, here is the range-diff between my manual rebase and the rebase performed by that workflow run: Range-diff manual vs automated rebase
This range-diff basically recapitulates the changes that were already obvious from the overall diff, plus some changes in the merge conflict resolutions in the I have to admit that I am quite happy with this state, if this is the worst the automation does, it will still be a great help trying to reduce friction between upstream and downstream patches. The step summary on the workflow run's page also looks quite nice, I find. I will replicate it here for the reader's viewing pleasure: Rebase Summary: mainFrom: 5f32b8db3c (Merge 'readme' into HEAD, 2018-06-07) (3de3111a1b..5f32b8db3c) Resolved: 8b99bae1ec (mingw: change core.fsyncObjectFiles = 1 by default, 2017-09-04)kept both adjust_symlink_flags() and fsync_object_files = 1 after maybe_redirect_std_handles() Range-diffResolved: 04aa4bc48e (max_tree_depth: lower it for clang builds in general on Windows, 2025-11-21)Resolved conflict: upstream moved max_allowed_tree_depth from environment.c to git-compat-util.h; dropped the variable from environment.c (HEAD side) and applied the clang x86_64 limit (1152) change to git-compat-util.h instead Range-diffResolved: 28dd1b6f63 (mingw: ensure valid CTYPE, 2017-02-21)kept both HEAD's symlinks block and REBASE_HEAD's LC_CTYPE environment setup; pure context conflict resolved by including both changes Range-diffResolved: 32b25ffea7 (mingw: allow
|
| Metric | Count |
|---|---|
| Total conflicts | 22 |
| Skipped (upstreamed) | 13 |
| Resolved surgically | 9 |
Range-diff (click to expand)
1: b7572cc797 = 1: 9dd95041af sideband: mask control characters
2: c9101ecf79 = 2: 085308dec8 sideband: introduce an "escape hatch" to allow control characters
3: 201a9e8cb7 = 3: 74d30094bf sideband: do allow ANSI color sequences by default
4: e2b8e90103 = 4: 7399a18903 unix-socket: avoid leak when initialization fails
5: 0db877b1c1 = 5: a64e5c841f grep: prevent `^$` false match at end of file
6: e5598f48d0 = 6: 970aa0d9b3 t9350: point out that refs are not updated correctly
7: 3d87e0a9f8 = 7: 23b2457744 transport-helper: add trailing --
9: ddd5b65816 = 8: de138a2821 remote-helper: check helper status after import/export
10: 50eaec49b1 = 9: 208b324665 clean: do not traverse mount points
12: 2fc76a5efa = 10: ecb16b1eab Always auto-gc after calling a fast-import transport
13: e428fcd9f4 = 11: 6c01bb5c71 mingw: prevent regressions with "drive-less" absolute paths
14: 64cc5c0809 = 12: 801f26bc09 clean: remove mount points when possible
22: ba8efe4645 = 13: d77e2f64d2 vcpkg_install: detect lack of Git
24: 1a4656368d = 14: cf66d02957 vcpkg_install: add comment regarding slow network connections
31: 8d6eca973f = 15: 9282194972 vcbuild: install ARM64 dependencies when building ARM64 binaries
32: b9a643e905 = 16: fa7c95733c vcbuild: add an option to install individual 'features'
33: b74f300ff7 = 17: ef4c024a7c cmake: allow building for Windows/ARM64
8: c132a44d8a = 18: 11907042cd mingw: include the Python parts in the build
34: deee242c90 = 19: 29f5358a50 ci(vs-build) also build Windows/ARM64 artifacts
11: 77e482b305 = 20: f00cfdb590 win32/pthread: avoid name clashes with winpthread
35: 15d349650d = 21: ecca847756 Add schannel to curl installation
40: 26d62a9144 = 22: 361624f6aa hash-object: demonstrate a >4GB/LLP64 problem
15: 6af4208c72 = 23: 1bccbf3b89 git-compat-util: avoid redeclaring _DEFAULT_SOURCE
36: 981b752446 = 24: 73bfe8d60c cmake(): allow setting HOST_CPU for cross-compilation
42: 6a4b7be272 = 25: beb05b1f8e object-file.c: use size_t for header lengths
92: 6ea9c7be14 = 26: 891103a95d Import the source code of mimalloc v2.2.7
41: 1c43a63a0d = 27: 3ca88e921f CMake: default Visual Studio generator has changed
44: bf99aaf199 = 28: ddee9a0d88 hash algorithms: use size_t for section lengths
93: 9e36739c12 = 29: d7d7ea39bc mimalloc: adjust for building inside Git
16: b6ed1cc104 = 30: de383fbbd7 mingw: demonstrate a `git add` issue with NTFS junctions
43: 12cda97585 = 31: 57c312c471 .gitignore: add Visual Studio CMakeSetting.json file
47: 59957d333f = 32: 53f1e5c18c hash-object --stdin: verify that it works with >4GB/LLP64
94: 8fec3de90a = 33: 2664c7924b mimalloc: offer a build-time option to enable it
23: 52fa2ffb2c = 34: ea230d47ec t5505/t5516: allow running without `.git/branches/` in the templates
17: 8efcfa2c86 = 35: 80f0e00b4d strbuf_realpath(): use platform-dependent API if available
25: 993102277a = 36: 9e4e8a51e3 http: use new "best effort" strategy for Secure Channel revoke checking
45: ea7b2b444a = 37: 3ebbf69a56 subtree: update `contrib/subtree` `test` target
46: d7f4f1ec49 = 38: bc553a8df0 CMakeLists: add default "x64-windows" arch for Visual Studio
50: 35f6b163c0 = 39: a1fb068483 hash-object: add another >4GB/LLP64 test case
51: a0c7290dbf = 40: f348bfc3d5 setup: properly use "%(prefix)/" when in WSL
95: e12539bab8 = 41: 91a9f14c43 mingw: use mimalloc
52: 3f5f17764e = 42: 31befe2477 Add config option `windows.appendAtomically`
26: d941d761e9 = 43: c64dd235a7 t5505/t5516: fix white-space around redirectors
18: 8d3faa143e = 44: 292be1f04f transport: optionally disable side-band-64k
19: 7b837f59c7 = 45: 91c8f85c31 mingw: fix fatal error working on mapped network drives on Windows
20: dafc08f6b4 = 46: 6a7a79e459 clink.pl: fix MSVC compile script to handle libcurl-d.lib
21: a69a076023 = 47: 0111bdbaca mingw: implement a platform-specific `strbuf_realpath()`
27: feb30ece07 = 48: 3f7ab56b30 t3701: verify that we can add *lots* of files interactively
28: 49bb910a77 = 49: 9ee20f240e commit: accept "scissors" with CR/LF line endings
29: 9a3cde11b6 = 50: c11713f74d t0014: fix indentation
30: 977e14f630 = 51: b2868c46a9 git-gui: accommodate for intent-to-add files
37: e618f99181 = 52: 9f84c2100c mingw: allow for longer paths in `parse_interpreter()`
38: 2d23682159 = 53: 3e8836c74f compat/vcbuild: document preferred way to build in Visual Studio
39: 378c4b16f2 = 54: b782a751c7 http: optionally send SSL client certificate
48: 24dcc3df8b = 55: acaa974178 ci: run `contrib/subtree` tests in CI builds
49: c19744fc3e = 56: e1a2cb45e8 CMake: show Win32 and Generator_platform build-option values
53: 536a9c2a52 = 57: b7e8fde69f hash-object: add a >4GB/LLP64 test case using filtered input
54: cd35bf4b33 = 58: 92f4389050 compat/mingw.c: do not warn when failing to get owner
55: 3b71603592 = 59: 894f02296f mingw: $env:TERM="xterm-256color" for newer OSes
56: 51a43eed50 = 60: 6226116030 winansi: check result and Buffer before using Name
96: f474630f6a = 61: 6e11345705 windows: skip linking `git-<command>` for built-ins
97: c1ef2a6c16 = 62: b6822f7f99 mingw: stop hard-coding `CC = gcc`
98: 05de8af920 = 63: 0f179516bc mingw: drop the -D_USE_32BIT_TIME_T option
99: 918ba0775c = 64: 7bd9abe978 mingw: only use -Wl,--large-address-aware for 32-bit builds
100: 1aa530107e = 65: 09cfd29901 mingw: avoid over-specifying `--pic-executable`
101: 72e167c2ad = 66: 351961b38d mingw: set the prefix and HOST_CPU as per MSYS2's settings
102: 0b2e0b1ea5 = 67: 169fa2d509 mingw: only enable the MSYS2-specific stuff when compiling in MSYS2
103: d9d089e9fd = 68: 7dfa024b2f mingw: rely on MSYS2's metadata instead of hard-coding it
57: 9a55aac24d = 69: b21302cbde MinGW: link as terminal server aware
58: 8b99bae1ec ! 70: a3bbeba35e mingw: change core.fsyncObjectFiles = 1 by default
@@ compat/mingw.c
#include <conio.h>
#include <sddl.h>
@@ compat/mingw.c: int wmain(int argc, const wchar_t **wargv)
- #endif
maybe_redirect_std_handles();
+ adjust_symlink_flags();
+ fsync_object_files = 1;
/* determine size of argv and environ conversion buffer */
59: 26f1d08e22 = 71: 238ace9b31 Fix Windows version resources
60: e30b81fd3f = 72: 27b439df18 status: fix for old-style submodules with commondir
104: 296bcb14c0 = 73: 6b826867d5 mingw: always define `ETC_*` for MSYS2 environments
-: ---------- > 74: 270e3dd15f max_tree_depth: lower it for clang builds in general on Windows
-: ---------- > 75: 85d86e262a mingw: ensure valid CTYPE
107: 32b25ffea7 ! 76: 1f50763b5c mingw: allow `git.exe` to be used instead of the "Git wrapper"
@@ compat/mingw.c: int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
{
char *tmp = getenv("TMPDIR");
@@ compat/mingw.c: static void setup_windows_environment(void)
- setenv("HOME", tmp, 1);
- }
+ if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
+ has_symlinks = 0;
+ if (!getenv("PLINK_PROTOCOL"))
+ setenv("PLINK_PROTOCOL", "ssh", 0);
108: 82251529c3 = 77: dd5ccb9546 mingw: ignore HOMEDRIVE/HOMEPATH if it points to Windows' system directory
109: 50fbcd2bcd = 78: ede958d3f6 clink.pl: fix libexpatd.lib link error when using MSVC
110: 725cb5896e = 79: 2ca464428f Makefile: clean up .ilk files when MSVC=1
111: f19d985da8 = 80: ea1e3b8efb vcbuild: add support for compiling Windows resource files
112: c6db63d689 = 81: d65d716a77 config.mak.uname: add git.rc to MSVC builds
113: edbffbbcda = 82: e385d9d03c clink.pl: ignore no-stack-protector arg on MSVC=1 builds
61: 5e3de953b2 = 83: 4306e04b6f http: optionally load libcurl lazily
114: 2f01002018 = 84: 1b19d13f9b clink.pl: move default linker options for MSVC=1 builds
62: 34ee3680a7 = 85: 4bcd8aaa45 http: support lazy-loading libcurl also on Windows
115: 0f05ae184a = 86: 0ae6adfb6b cmake: install headless-git.
63: 6fa7c1f470 = 87: 86cba65a7b http: when loading libcurl lazily, allow for multiple SSL backends
116: 5f107688b5 = 88: 7db1b90c0e git.rc: include winuser.h
64: b7e20647d7 = 89: 66013c1ab3 mingw: do load libcurl dynamically by default
65: 61df8a70fb = 90: 86dbc5f3f0 Add a GitHub workflow to verify that Git/Scalar work in Nano Server
66: 3ee2cd67ce = 91: 945d059068 mingw: suggest `windows.appendAtomically` in more cases
69: 8af6f16849 = 92: bac2efbb8f win32: use native ANSI sequence processing, if possible
70: 503d48a907 = 93: 30e26886f6 common-main.c: fflush stdout buffer upon exit
71: 232239bfaa = 94: 06baf4db28 t5601/t7406(mingw): do run tests with symlink support
72: 5c8e9d6083 = 95: 917b2c71e0 win32: ensure that `localtime_r()` is declared even in i686 builds
73: 95025b7407 = 96: 1a7274e62c Fallback to AppData if XDG_CONFIG_HOME is unset
74: 4b35363ba0 = 97: b37c052e77 run-command: be helpful with Git LFS fails on Windows 7
67: d9992b0a49 = 98: b07f2dcd2b ci: work around a problem with HTTP/2 vs libcurl v8.10.0
68: 0ca7e765ad = 99: be846d1646 revision: create mark_trees_uninteresting_dense()
75: 1e07325385 = 100: f29e1afe0d survey: stub in new experimental 'git-survey' command
76: 64e2fe8dff = 101: 2b82bd8151 survey: add command line opts to select references
77: 9ffe2fa8ee = 102: eab4df9b1f survey: start pretty printing data in table form
78: 07d54398bd = 103: de71dbe438 survey: add object count summary
79: cb3a6493c5 = 104: df4139a485 survey: summarize total sizes by object type
80: aff3c05c57 = 105: 22761f7b24 survey: show progress during object walk
82: f68c8747a2 = 106: ef2a6bb70b mingw: make sure `errno` is set correctly when socket operations fail
81: 825465c6f7 = 107: a8ae16b26d survey: add ability to track prioritized lists
84: 59cf350f67 = 108: abfb8f3c31 compat/mingw: handle WSA errors in strerror
83: 9236c2948d = 109: e50b0b560f survey: add report of "largest" paths
86: ab302c5b82 = 110: 867079abb2 compat/mingw: drop outdated comment
85: 72e6d68a74 = 111: eb8a772a86 survey: add --top=<N> option and config
88: 3c8010b6c3 = 112: 5892f2307e t0301: actually test credential-cache on Windows
87: a7462d2cc1 = 113: 5b1d3b488d survey: clearly note the experimental nature in the output
89: 2058ce7f71 = 114: 2d91dab7f5 credential-cache: handle ECONNREFUSED gracefully
90: 7cd468e8f6 = 115: 9b802c44bf reftable: do make sure to use custom allocators
91: 8d4233e162 = 116: d6113d2c2d check-whitespace: avoid alerts about upstream commits
105: 04aa4bc48e < -: ---------- max_tree_depth: lower it for clang builds in general on Windows
106: 28dd1b6f63 < -: ---------- mingw: ensure valid CTYPE
117: f5ec36ebc9 = 117: c9ab63f495 git-svn: mark it as unsupported by the Git for Windows project
118: e9815f74d0 = 118: 979bb88779 ci(macos): skip the `git p4` tests
121: 7f7c7928b8 = 119: a3f19c6d18 Win32: make FILETIME conversion functions public
119: e644b3c397 = 120: 4ce8d77c5b git-gui--askyesno: fix funny text wrapping
122: 91f7f63284 = 121: 0fe5d258be Win32: dirent.c: Move opendir down
120: 1459c281eb = 122: 490aedaf22 git-gui--askyesno (mingw): use Git for Windows' icon, if available
123: 9f7087c4de ! 123: 4e2b975669 mingw: make the dirent implementation pluggable
@@ compat/win32/dirent.c
+ xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
/* Set file type, based on WIN32_FIND_DATA */
- if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
@@ compat/win32/dirent.c: static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
ent->d_type = DT_REG;
}
124: b7965dae55 ! 124: 95c05c30ed Win32: make the lstat implementation pluggable
@@ compat/mingw-posix.h: int mingw_fstat(int fd, struct stat *buf);
int mingw_utime(const char *file_name, const struct utimbuf *times);
## compat/mingw.c ##
-@@ compat/mingw.c: static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
- return do_lstat(follow, alt_name, buf);
+@@ compat/mingw.c: int mingw_lstat(const char *file_name, struct stat *buf)
+ return -1;
}
+int (*lstat)(const char *file_name, struct stat *buf) = mingw_lstat;
125: 84d356004c = 125: 043b570496 mingw: add infrastructure for read-only file system level caches
126: 7c775c7eda = 126: b289e13142 mingw: add a cache below mingw's lstat and dirent implementations
127: 45711437f1 = 127: 30ae17cb26 fscache: load directories only once
128: e51b4ed418 = 128: c4f6aee6ce fscache: add key for GIT_TRACE_FSCACHE
129: 7db33ae6b7 = 129: 839da7d65d fscache: remember not-found directories
130: 3c8662454d = 130: 71f9dd9b1a fscache: add a test for the dir-not-found optimization
131: cc29af6d36 = 131: c7c3e467ad add: use preload-index and fscache for performance
132: bebad966d0 = 132: 1f1adad69a dir.c: make add_excludes aware of fscache during status
133: b922002c7c = 133: d48448a02e fscache: make fscache_enabled() public
134: 14874a1158 = 134: 1afe61233f dir.c: regression fix for add_excludes with fscache
135: 92c2cee2bc = 135: a4ea037feb fetch-pack.c: enable fscache for stats under .git/objects
136: a583117f1b = 136: 62fe43d9f7 checkout.c: enable fscache for checkout again
137: 183a9fd2fe = 137: eb42884394 Enable the filesystem cache (fscache) in refresh_index().
138: 57a05d9dbc = 138: 75b54250a2 fscache: use FindFirstFileExW to avoid retrieving the short name
139: 1623b4bf9a = 139: a2150cf37b fscache: add GIT_TEST_FSCACHE support
140: 80ad05d4e6 = 140: 25e633bc36 fscache: add fscache hit statistics
141: 9087916a3f = 141: 4ad3d6b45d unpack-trees: enable fscache for sparse-checkout
142: f80f1228b6 = 142: f872349027 status: disable and free fscache at the end of the status command
143: d677459ec2 = 143: 1ffd26a6ac mem_pool: add GIT_TRACE_MEMPOOL support
144: 5a6185162e = 144: 1003f0f809 fscache: fscache takes an initial size
145: d3f50e9738 = 145: 028612a8bf fscache: update fscache to be thread specific instead of global
146: 06d8c74397 = 146: b4bcab7c7d fscache: teach fscache to use mempool
147: afa186da63 ! 147: 88f6c1bb14 fscache: make fscache_enable() thread safe
@@ compat/mingw.c
#include "wrapper.h"
#include "write-or-die.h"
@@ compat/mingw.c: int wmain(int argc, const wchar_t **wargv)
- /* initialize critical section for waitpid pinfo_t list */
InitializeCriticalSection(&pinfo_cs);
+ InitializeCriticalSection(&phantom_symlinks_cs);
+ /* initialize critical section for fscache */
+ InitializeCriticalSection(&fscache_cs);
148: a0173342a6 = 148: 597bb72f67 fscache: teach fscache to use NtQueryDirectoryFile
149: 664e5ab39d = 149: 5a16a22f0e fscache: remember the reparse tag for each entry
150: 463483aeb0 = 150: e309c2ff9f fscache: implement an FSCache-aware is_mount_point()
151: ed191e3f82 = 151: c577edd029 clean: make use of FSCache
152: 2183eb6221 = 152: 3fc98df7d8 pack-objects (mingw): demonstrate a segmentation fault with large deltas
153: 0d86933e61 ! 153: 2406860e5a mingw: support long paths
@@ compat/mingw.c: static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOT
@@ compat/mingw.c: static wchar_t *normalize_ntpath(wchar_t *wbuf)
int mingw_unlink(const char *pathname, int handle_in_use_error)
{
- int ret, tries = 0;
+ int tries = 0;
- wchar_t wpathname[MAX_PATH];
- if (xutftowcs_path(wpathname, pathname) < 0)
+ wchar_t wpathname[MAX_LONG_PATH];
@@ compat/mingw.c: static int is_dir_empty(const wchar_t *wpath)
@@ compat/mingw.c: static int is_dir_empty(const wchar_t *wpath)
int mingw_rmdir(const char *pathname)
{
- int ret, tries = 0;
+ int tries = 0;
- wchar_t wpathname[MAX_PATH];
+ wchar_t wpathname[MAX_LONG_PATH];
struct stat st;
@@ compat/mingw.c: int mingw_rmdir(const char *pathname)
+ if (xutftowcs_long_path(wpathname, pathname) < 0)
return -1;
- while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
+ do {
@@ compat/mingw.c: static int set_hidden_flag(const wchar_t *path, int set)
int mingw_mkdir(const char *path, int mode UNUSED)
{
@@ compat/mingw.c: static int set_hidden_flag(const wchar_t *path, int set)
return -1;
+
ret = _wmkdir(wpath);
- if (!ret && needs_hiding(path))
- return set_hidden_flag(wpath, 1);
+ if (!ret)
+ process_phantom_symlinks();
@@ compat/mingw.c: int mingw_open (const char *filename, int oflags, ...)
va_list args;
unsigned mode;
@@ compat/mingw.c: ssize_t mingw_write(int fd, const void *buf, size_t len)
int mingw_chdir(const char *dirname)
{
- wchar_t wdirname[MAX_PATH];
-- if (xutftowcs_path(wdirname, dirname) < 0)
+ int result;
+ wchar_t wdirname[MAX_LONG_PATH];
+
+- if (xutftowcs_path(wdirname, dirname) < 0)
+ if (xutftowcs_long_path(wdirname, dirname) < 0)
return -1;
-- return _wchdir(wdirname);
-+ result = _wchdir(wdirname);
+
+ if (has_symlinks) {
+@@ compat/mingw.c: int mingw_chdir(const char *dirname)
+ CloseHandle(hnd);
+ }
+
+- return _wchdir(normalize_ntpath(wdirname));
++ result = _wchdir(normalize_ntpath(wdirname));
+ current_directory_len = GetCurrentDirectoryW(0, NULL);
+ return result;
}
@@ compat/mingw.c: ssize_t mingw_write(int fd, const void *buf, size_t len)
return -1;
return _wchmod(wfilename, mode);
}
-@@ compat/mingw.c: static int has_valid_directory_prefix(wchar_t *wfilename)
- static int do_lstat(int follow, const char *file_name, struct stat *buf)
- {
+@@ compat/mingw.c: int mingw_lstat(const char *file_name, struct stat *buf)
WIN32_FILE_ATTRIBUTE_DATA fdata;
+ DWORD reparse_tag = 0;
+ int link_len = 0;
- wchar_t wfilename[MAX_PATH];
-- if (xutftowcs_path(wfilename, file_name) < 0)
+- int wlen = xutftowcs_path(wfilename, file_name);
+ wchar_t wfilename[MAX_LONG_PATH];
-+ if (xutftowcs_long_path(wfilename, file_name) < 0)
++ int wlen = xutftowcs_long_path(wfilename, file_name);
+ if (wlen < 0)
return -1;
- if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ compat/mingw.c: int mingw_utime (const char *file_name, const struct utimbuf *times)
FILETIME mft, aft;
int rc;
@@ compat/mingw.c: static pid_t mingw_spawnve_fd(const char *cmd, const char **argv
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ compat/mingw.c: int mingw_rename(const char *pold, const char *pnew)
static int supports_file_rename_info_ex = 1;
- DWORD attrs, gle;
+ DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
int tries = 0;
- wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
+ wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
154: 694f810e82 < -: ---------- Win32: fix 'lstat("dir/")' with long paths
155: bc025c8d63 ! 154: 5e3ab7fa4d win32(long path support): leave drive-less absolute paths intact
@@ Commit message
Signed-off-by: Johannes Schindelin <[email protected]>
## compat/mingw.c ##
-@@ compat/mingw.c: int mingw_core_config(const char *var, const char *value,
- return 0;
- }
-
-+static inline int is_wdir_sep(wchar_t wchar)
-+{
-+ return wchar == L'/' || wchar == L'\';
-+}
-+
- /* Normalizes NT paths as returned by some low-level APIs. */
- static wchar_t *normalize_ntpath(wchar_t *wbuf)
- {
@@ compat/mingw.c: int handle_long_path(wchar_t *path, int len, int max_path, int expand)
* "cwd + path" doesn't due to '..' components)
*/
156: f87de62e94 = 155: e68240d43d compat/fsmonitor/fsm-*-win32: support long paths
157: 9943bd66d9 = 156: a2bd414d42 clean: suggest using `core.longPaths` if paths are too long to remove
158: 88524ea323 = 157: f419814131 mingw: Support `git_terminal_prompt` with more terminals
159: 6b65235ccc = 158: a01d48b9b1 compat/terminal.c: only use the Windows console if bash 'read -r' fails
160: cd6db54876 = 159: 665368e695 mingw (git_terminal_prompt): do fall back to CONIN$/CONOUT$ method
161: 86581d67da < -: ---------- Win32: don't call GetFileAttributes twice in mingw_lstat()
162: efb8e66996 < -: ---------- Win32: implement stat() with symlink support
163: f30c79a02a < -: ---------- Win32: remove separate do_lstat() function
164: e5004ccec7 < -: ---------- Win32: let mingw_lstat() error early upon problems with reparse points
165: 8b0e6c192d < -: ---------- mingw: teach fscache and dirent about symlinks
166: ee52791ddc < -: ---------- Win32: lstat(): return adequate stat.st_size for symlinks
167: 6ceb5ef587 < -: ---------- Win32: factor out retry logic
168: ae6ec2e615 = 160: c12a49d537 Win32: change default of 'core.symlinks' to false
169: bd618741bd < -: ---------- Win32: add symlink-specific error codes
170: 830ffaca99 < -: ---------- Win32: mingw_unlink: support symlinks to directories
171: 365d9ef0d5 < -: ---------- Win32: mingw_rename: support renaming symlinks
172: ef76ad774a < -: ---------- Win32: mingw_chdir: change to symlink-resolved directory
173: 38cb381da6 < -: ---------- Win32: implement readlink()
174: 55408628ef < -: ---------- mingw: lstat: compute correct size for symlinks
175: c92295de6b < -: ---------- Win32: implement basic symlink() functionality (file symlinks only)
176: 6c58ba208c < -: ---------- Win32: symlink: add support for symlinks to directories
177: 797f4d27d1 < -: ---------- mingw: try to create symlinks without elevated permissions
178: daac5811a8 < -: ---------- mingw: emulate stat() a little more faithfully
179: 01d54a428b < -: ---------- mingw: special-case index entries for symlinks with buggy size
180: 4958bab6ad = 161: 6acc4b63be mingw: introduce code to detect whether we're inside a Windows container
181: 1cb648f4da = 162: 440e1c5d56 mingw: when running in a Windows container, try to rename() harder
182: a86a9ba5e1 = 163: bcdccb7611 mingw: move the file_attr_to_st_mode() function definition
183: eb053981d6 ! 164: f8c2c32be3 mingw: Windows Docker volumes are *not* symbolic links
@@ compat/win32/fscache.c: static struct fsentry *fseentry_create_entry(struct fsca
fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ?
fdata->EaSize : 0;
+- fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes);
+- fse->dirent.d_type = S_ISDIR(fse->st_mode) ? DT_DIR : DT_REG;
+- fse->u.s.st_size = fdata->EndOfFile.LowPart |
+- (((off_t)fdata->EndOfFile.HighPart) << 32);
+ /*
+ * On certain Windows versions, host directories mapped into
+ * Windows Containers ("Volumes", see https://docs.docker.com/storage/volumes/)
@@ compat/win32/fscache.c: static struct fsentry *fseentry_create_entry(struct fsca
+ buf[off + fse->len] = '';
+ }
+
- fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes,
-- fdata->EaSize);
++ fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes,
+ fdata->EaSize, buf);
- fse->dirent.d_type = S_ISREG(fse->st_mode) ? DT_REG :
- S_ISDIR(fse->st_mode) ? DT_DIR : DT_LNK;
- fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
++ fse->dirent.d_type = S_ISREG(fse->st_mode) ? DT_REG :
++ S_ISDIR(fse->st_mode) ? DT_DIR : DT_LNK;
++ fse->u.s.st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH :
++ fdata->EndOfFile.LowPart |
++ (((off_t)fdata->EndOfFile.HighPart) << 32);
+ filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime),
+ &(fse->u.s.st_atim));
+ filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime),
185: 82b409f971 = 165: e58a0e0deb mingw: work around rename() failing on a read-only file
184: 8fddb92a79 ! 166: bd7c450357 Win32: symlink: move phantom symlink creation to a separate function
@@ compat/mingw.c: int symlink(const char *target, const char *link)
- switch (process_phantom_symlink(wtarget, wlink)) {
- case PHANTOM_SYMLINK_RETRY: {
- /* if target doesn't exist, add to phantom symlinks list */
-- wchar_t wfullpath[MAX_LONG_PATH];
+- wchar_t wfullpath[MAX_PATH];
- struct phantom_symlink_info *psi;
-
- /* convert to absolute path to be independent of cwd */
-- len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL);
-- if (!len || len >= MAX_LONG_PATH) {
+- len = GetFullPathNameW(wlink, MAX_PATH, wfullpath, NULL);
+- if (!len || len >= MAX_PATH) {
- errno = err_win_to_posix(GetLastError());
- return -1;
- }
@@ compat/mingw.c: int symlink(const char *target, const char *link)
+ return create_phantom_symlink(wtarget, wlink);
}
- #ifndef _WINNT_H
+ int readlink(const char *path, char *buf, size_t bufsiz)
186: ceb439c192 ! 167: 83e1480fad Introduce helper to create symlinks that knows about index_state
@@ compat/mingw.c: int link(const char *oldpath, const char *newpath)
-int symlink(const char *target, const char *link)
+int mingw_create_symlink(struct index_state *index UNUSED, const char *target, const char *link)
{
- wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
+ wchar_t wtarget[MAX_PATH], wlink[MAX_PATH];
int len;
## entry.c ##
187: 2b8f87faea ! 168: 0e72f52f29 mingw: allow to specify the symlink type in .gitattributes
@@ compat/mingw.c: int link(const char *oldpath, const char *newpath)
+
+int mingw_create_symlink(struct index_state *index, const char *target, const char *link)
{
- wchar_t wtarget[MAX_LONG_PATH], wlink[MAX_LONG_PATH];
+ wchar_t wtarget[MAX_PATH], wlink[MAX_PATH];
int len;
@@ compat/mingw.c: int mingw_create_symlink(struct index_state *index UNUSED, const char *target, c
if (wtarget[len] == '/')
@@ compat/mingw.c: int mingw_create_symlink(struct index_state *index UNUSED, const
+ return -1;
}
- #ifndef _WINNT_H
+ int readlink(const char *path, char *buf, size_t bufsiz)
188: 746dcb6552 = 169: b2230eff26 Win32: symlink: add test for `symlink` attribute
189: 1d994c000c = 170: 54aa73e67d mingw: explicitly specify with which cmd to prefix the cmdline
190: f69430461b = 171: e31bba0684 mingw: when path_lookup() failed, try BusyBox
191: c39fd215fe = 172: 098badcfc3 test-tool: learn to act as a drop-in replacement for `iconv`
192: a3df806c92 = 173: efb595de96 tests(mingw): if `iconv` is unavailable, use `test-helper --iconv`
193: 06e2aff66d = 174: 46d885e2cb gitattributes: mark .png files as binary
194: 1173e4bb9c = 175: ef580a3a89 tests: move test PNGs into t/lib-diff/
195: 5e360db826 = 176: d7ae11199b tests: only override sort & find if there are usable ones in /usr/bin/
196: 4ff2d8e14e = 177: 959a456132 tests: use the correct path separator with BusyBox
197: 1e820ff0f1 = 178: b2804e3706 mingw: only use Bash-ism `builtin pwd -W` when available
198: 8a93857eac = 179: ae1aa3fb18 tests (mingw): remove Bash-specific pwd option
199: f77554da92 = 180: d7b3002a04 test-lib: add BUSYBOX prerequisite
206: 07fca2f81a = 181: 8dde67c13d Describe Git for Windows' architecture [no ci]
200: da2dc16cc7 = 182: 46320651ed t5003: use binary file from t/lib-diff/
207: e488bbd3cd = 183: 10378d8aba Modify the Code of Conduct for Git for Windows
201: cf34617055 = 184: 0f826fa3f3 t5532: workaround for BusyBox on Windows
208: 6fdb617bd4 = 185: 45d71614ee CONTRIBUTING.md: add guide for first-time contributors
202: 11fdc41094 = 186: 1bae046381 t5605: special-case hardlink test for BusyBox-w32
209: ae446853c5 = 187: 399dcfc358 README.md: Add a Windows-specific preamble
203: e52cd4afce = 188: c951a50e42 t5813: allow for $PWD to be a Windows path
210: e54f74d98d = 189: d3e65d0461 Add an issue template
204: 25b6a2d38a = 190: ed610d02e5 t9200: skip tests when $PWD contains a colon
211: 4292bc3ca4 = 191: ac84fa7629 mingw: kill child processes in a gentler way
212: 70d123f805 = 192: c25c8eba84 Add a GitHub workflow to monitor component updates
213: 67eb5e48ad = 193: 5458453c44 Modify the GitHub Pull Request template (to reflect Git for Windows)
205: 997c3b5ffe = 194: 9489ca32f4 mingw: optionally enable wsl compability file mode bits
214: 5ab983f589 = 195: 952b5beac4 mingw: really handle SIGINT
215: 902e343c3d = 196: d3c6d11492 Partially un-revert "editor: save and reset terminal after calling EDITOR"
216: 51e1360629 = 197: 2792c1a164 reset: reinstate support for the deprecated --stdin option
217: 5ea59fd7f3 = 198: 8ab6252006 fsmonitor: reintroduce core.useBuiltinFSMonitor
218: 95d9d1ce1e = 199: 018ed9cd5e dependabot: help keeping GitHub Actions versions up to date
219: b810f3da87 = 200: bb8289c2bd SECURITY.md: document Git for Windows' policies
|
While collecting the material for my two preceding PR comments, I noticed a couple of issues that I fixed via this push. Range-diff
|
|
After thinking about it a bit more, I am concerned that the way the scheduled trigger is implemented right now will result in a long list of workflow runs that are totally uninteresting ("skipped" but not skipped). And in between, sparsely sprinkled and hard to find, there are the actual runs that do something. This is a terrible user experience. I would not be able to find all the runs that did actually resolve some merge conflicts, but it is highly important that those are closely monitored. After all, I compare full Git for Windows rebases against the Here are a couple of ideas I had, but none of them strike me as a really good silver bullet:
Does anybody have any better idea? I don't really like any of the ones I've just presented. |
That might actually be the best idea: Fork Thoughts? |
A separate repository to hold the PRs (or issues) would make the most sense IMO. |
@mjcheetham Et voilà: git-for-windows/shears-builds#2 |
Add a script that rebases a shears/* branch onto a new upstream base using the merging-rebase strategy. This is the foundation for automating the Git for Windows branch maintenance workflow. The script creates a worktree, finds the existing merging-rebase marker, creates a new marker commit with the upstream and origin/main parents, temporarily grafts it to look single-parent during rebase, runs the rebase, then verifies the marker has the expected two parents. No conflict resolution is included yet; the rebase will simply fail if there are conflicts. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Add a GitHub Actions workflow that rebases shears/seen onto upstream/seen using the rebase-branch.sh script. This is a starting point for testing the automation end-to-end. The workflow checks out the automation repo (for the script) and clones git-for-windows/git (the repo to rebase). Since git-for-windows/git is public, no special token is needed for read-only access at this stage. For now this hardcodes the seen branch and has no push step; later commits will generalize to all branches and add push capability. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Upload the rebased branch as a bundle artifact so the result can be inspected (and potentially fetched) without needing push access. On failure, upload both a bundle and a tar archive of the worktree so the in-progress rebase state is preserved for debugging. The failure bundle includes all refs (to capture refs/rewritten/* from the in-progress rebase) while excluding origin/main to keep the size manageable. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Allow selecting which shears branch to rebase (seen, next, main, or maint) via workflow_dispatch input. The push trigger defaults to seen. The main branch is mapped to upstream/master since that is what upstream Git calls its primary branch. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Add -x to the set flags so that every command is echoed before execution. This is invaluable for debugging CI failures where the log is the only way to understand what happened. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
After a successful rebase, generate a range-diff comparing the original patches with the rebased result. This is written to a markdown report file and, when running in GitHub Actions, appended to the job summary so the result is visible directly in the workflow run UI. The original marker and tip OIDs are saved before the rebase starts so the range-diff compares the right commit ranges even if future commits add sync or adoption logic that modifies them beforehand. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Before rebasing onto the new upstream, check if origin/main has commits that are not yet in the shears branch. All shears branches contain the GfW-specific patches from origin/main, so new patches pushed there need to be incorporated. If origin/main was itself rebased (detected by the presence of a merging-rebase marker in the new commits), adopt its state directly rather than trying to rebase on top of it. Otherwise, rebase the new commits from origin/main onto the shears branch tip. After syncing, re-check whether upstream still has new commits; if not, exit early since there is nothing left to rebase. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
When the upstream branch has no new commits, the script exits early without producing any job summary. This leaves the GitHub Actions run with a blank summary, giving no indication of what happened or whether the branch is in good shape. Write a concise summary section for this case too, with compare links from the current tip to both `origin/main` and the upstream branch. The links are oriented so that an up-to-date branch shows zero commits in the comparison, making correctness easy to verify at a glance. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Introduce a thin `run_rebase()`` wrapper around the bare `git rebase` call. For now, this function does nothing more than set GIT_EDITOR=: to prevent the editor from opening during rebase, but it lays the groundwork for adding conflict resolution logic in a later commit without having to touch the main script flow. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
|
I squashed a couple of changes in and force-pushed. These are the themes:
Full range-diff between pre-/post-force-push
|
|
Sorry, there was an incorrectly-indented heredoc, still, which I realized only after this run failed. After I fixed it, this run succeeded (but it did not need to update any Range-diff
|
When rebasing onto a new upstream, some of our commits may have been upstreamed verbatim (cherry-picked or applied identically). These commits produce conflicts during rebase that are trivially resolvable by skipping them entirely. To detect this automatically, generate a "correspondence map" using `git range-diff` between our commit range and the upstream commit range before starting the rebase. The `find_correspondence()` function then looks up each conflicting REBASE_HEAD in this map: if the range-diff reports an exact match (`=`), the commit is skipped without any AI involvement. The `run_rebase()` function is extended from a simple wrapper into a conflict-handling loop: it runs the initial rebase, and if conflicts arise, iterates over REBASE_HEAD states. For now, only exact upstream matches are handled automatically; any other conflict is a fatal error. Later commits will add sibling/previous correspondence maps and AI resolution for the remaining cases. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Git for Windows maintains a hierarchy of shears branches (seen, next, main, maint) that track the corresponding upstream branches. When rebasing `main`, the `next` branch has likely already been rebased onto the same upstream and resolved the same conflicts. Similarly, `seen` is the sibling for `next`, and `main` for `maint`. Generate a sibling correspondence map using range-diff between our commits and the sibling branch's commits. When a conflict arises and the upstream map does not yield an exact match, look up the conflicting commit in the sibling map and try `git merge-tree` with the sibling's version of the commit to produce a clean resolution without AI. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
The sibling correspondence map needs access to sibling shears branches (e.g. shears/next when rebasing shears/main). Fetch all four shears branches up front instead of only the one being rebased. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
When origin/main has been rebased (e.g. onto a new Git version, or to restructure the branch thicket), the current shears branch needs to adopt that state. Before doing so, generate a correspondence map between origin/main's rebased commits and our current commits, so that when the subsequent rebase hits a conflict, we can try reusing origin/main's resolution via `git merge-tree`. The `run_rebase()` conflict loop now iterates over both the previous map and the sibling map (in that order) when looking for reusable resolutions, using `continue 2` to break out of both the for loop and the while loop iteration on success. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Add an agent instructions file that teaches an LLM how to think about merge conflicts during rebases. The key insight is that resolving a conflict is not about mechanically choosing "ours" or "theirs" but about understanding the motivation behind both changes: what the downstream patch intended to accomplish, and how upstream independently modified the same lines. Once both motivations are understood, the resolution follows naturally by reapplying the patch's intent on top of the upstream's current state. The agent also learns to detect upstreamed patches via `git range-diff` before attempting any surgical resolution, to avoid wasted effort on commits that can simply be skipped. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
When a conflict cannot be resolved by the correspondence maps (exact upstream match or merge-tree from a sibling/previous rebase), invoke the Copilot CLI to attempt a surgical resolution. The AI receives the list of conflicting files and a small set of investigation commands, then outputs one of `skip <oid>`, `continue`, or `fail`. This is intentionally minimal: the prompt just points the AI at the right commands and decision rules. Later commits will enrich the prompt with `git log -L` commands for understanding upstream changes, correspondence context from failed merge-tree attempts, and build verification after resolution. The workflow gains a Copilot CLI installation step (via npm), a COPILOT_MODEL environment variable (defaulting to claude-opus-4.6), and GH_TOKEN set to the COPILOT_GITHUB_TOKEN secret (a fine-grained PAT with the "Copilot Requests" permission). The script validates that both `copilot` and a GitHub token are available. Copilot must be launched from the automation repository directory so `--agent conflict-resolver` resolves to the trusted profile in `.github/agents/`. Launching from the rebase worktree can fail agent discovery and may load untrusted agent definitions from rebased content. Running from automation and exposing the worktree only via `--add-dir` keeps discovery and trust boundaries explicit. For that reason, Copilot must be told explicitly to run every command prefixed by `cd $WORKTREE_DIR` (and we need to pass that environment variable to Copilot, too). Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Give the AI two more investigation tools for understanding conflicts: `git range-diff --creation-factor=200` as a fallback when the default creation factor does not find a correspondence (useful for heavily modified patches that were upstreamed with significant changes). `git log -L <start>,<end>:<file> REBASE_HEAD..HEAD` for each conflicting hunk, which shows exactly how upstream modified the lines that conflict. This is invaluable for understanding the upstream's intent when resolving surgically: the AI can see the progression of changes that led to the current state of those lines. The line ranges are extracted from the diff hunk headers of each conflicting file by the new `generate_log_l_commands()` function. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
When the merge-tree approach fails for a sibling or previous correspondence (the three-way merge still has conflicts), pass the OIDs of those tried commits to the AI as context. The AI can then examine them with `git show` to understand how a related rebase resolved the same commit, which often provides strong hints for the surgical resolution even when the merge-tree could not apply cleanly. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
After the AI resolves a conflict surgically and before letting `git rebase --continue` commit the result, run `make` to catch compilation errors introduced by the resolution. If the build fails, give the AI one more chance by feeding it the last 15 lines of the build log and asking it to fix the error. If the retry also fails to produce a clean build, abort with exit code 2. This catches a class of errors where the AI's resolution is semantically reasonable but syntactically broken (e.g. a missing comma, a mismatched brace, or a reference to a renamed symbol). The ideal case would be, of course, to build on Windows. But I had so many troubles, hitting so many bugs, trying to run GitHub Copilot CLI on a Windows runner, that I gave up on that. For now. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
The Copilot CLI appends a statistics trailer to its output (lines like `Model: claude-opus-4.6`, `Total turns: 5`, etc.) after blank lines. The simple `sed -n | tail -1` parser would miss the decision if it appeared before these trailing stats lines. Replace it with a sed state machine that remembers the last line matching `continue`, `skip <oid>`, or `fail`, then validates that everything after it until EOF consists only of blank lines and stats-like `key: value` lines (including indented continuation lines). This ensures the decision is extracted correctly regardless of what Copilot appends after the agent's final output. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Track how each conflict was resolved and produce a structured report. Each resolution path (trivial upstream skip, merge-tree correspondence, AI skip, AI surgical) now appends a section to the conflict report documenting what happened. The report concludes with a statistics summary counting skipped vs. surgically resolved conflicts. Additionally, maintain a skipped-commits map file that records which original commit OIDs were skipped and their upstream equivalents. After the rebase completes, this map is used to annotate the range-diff so that skipped commits show their upstream OID inline, making it easy to verify that the right commits were skipped. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Downstream patches can become obsolete when upstream removes or rewrites the code they were fixing, without there being a specific upstream commit that corresponds to the downstream one. In this case, the AI should be able to skip the patch and explain why. The new `skip -- <reason>` decision format allows exactly that: the reason is surfaced in the conflict report and in the GitHub Actions log annotations, making it easy to audit why a commit was dropped during a rebase. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
While the logs contain all the information about a rebase run, they
are too verbose to quickly judge whether an AI conflict resolution
was correct. The step summary exists precisely to give the reviewer
that quick overview — but until now, `continue` verdicts produced no
summary entry at all, forcing the reviewer back into the logs to find
out what happened.
The AI now provides a brief rationale alongside its verdict, and a
range-diff between the original patch and the resolved one gives the
reviewer the most direct view of how the conflict resolution altered
the commit.
A conflict can legitimately resolve to nothing — for example when a
patch adapted code that was removed upstream. This is a real outcome
that deserves its own report entry ("Dropped"), not a silent empty
commit.
Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <[email protected]>
Add an 'all' option (the new default) that iterates over seen, next, main, and maint in sequence. Each branch is rebased independently so a failure in one does not prevent the others from proceeding. The bundle/archive and push steps are updated to handle outputs from both the single-branch and all-branches rebase steps. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Generate an installation access token from the GitHub App credentials only right before pushing and only when a push is actually needed. The token is passed via GIT_CONFIG_PARAMETERS setting an http.<url>.extraHeader Authorization header, avoiding the need to persist credentials from the checkout step. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
Run the rebase workflow every six hours via cron. To avoid wasting runner time when nothing has changed, each run records the ref tips it observed (git-for-windows/git main and the upstream seen, next, master and maint branches) in its check run output via the Checks API. On schedule triggers, a lightweight pre-check step compares current refs against the previous run's recorded state and skips the expensive clone and rebase entirely if nothing moved. The ref state flows through a REF_STATE environment variable: the pre-check sets it from the previous run's check run summary, and the fetch step overwrites it with the actual cloned refs when the rebase proceeds. The "Record ref state" step writes whichever value survived to the current run's check run output, ensuring the next scheduled run can compare against it. The push trigger was only used for debugging and is removed now that the workflow has a proper schedule. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
When several consecutive scheduled runs find nothing to do, each one skips the expensive rebase and re-records the same ref state it inherited from the previous run. Without any further information, the skip notice can only point at the immediately preceding run, which itself may have been a no-op skip. This makes it hard to find the run that actually performed the last real rebase. To fix this, append a "run <url>" line to the ref state recorded in the check run output. The "Record ref state" step adds this line when writing fresh state from a real rebase (i.e. when no "run" line is present yet). When a subsequent skip inherits the state, the "run" line passes through unchanged, always pointing back to the original run that produced it. The pre-check notice then uses this URL instead of the previous run's URL, giving a direct link to the run that matters. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
The Azure Pipelines that previously maintained the shears branches created check runs on git-for-windows/git's main branch, giving maintainers a visible record of each rebase's outcome directly in the target repository. Without an equivalent in the GitHub Actions replacement, the only way to discover whether a rebase succeeded or failed would be to navigate to the automation repository and find the right workflow run — an unnecessary detour. This creates a check run per branch (named `shears-seen`, `shears-next`, etc.) on the `origin/main` commit, mirroring what Azure Pipelines did. The check run's `details_url` links back to the workflow run, and its summary contains the full step summary report so that reviewers can inspect the range-diff and conflict resolutions without leaving the git-for-windows/git commit page. The installation access token is now obtained whenever `PUSH` is true and the precheck did not skip, rather than only when there are branches to push. This is necessary both for the check runs (which must be created even when a rebase fails) and for the push step (which should push successful branches even when other branches failed in the same run). Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
The rebase automation needs to capture the full state of a failed rebase — including unmerged index entries with conflict markers in the worktree — so that it can be pushed to a builds repository for inspection. Unfortunately, `git stash` cannot handle unmerged index entries at all; it simply refuses to operate when there are conflicts in the index. This is a known limitation that has never been addressed. These two scripts work around that limitation while staying as close as possible to how `git stash` and `git stash apply` work: `stash-with-conflicts.sh` creates a fake octopus merge commit with exactly four parents: HEAD (the base, like the first parent of a regular stash commit), plus one commit per index stage (1 = common ancestor, 2 = ours, 3 = theirs). Each stage tree contains all resolved (stage 0) files alongside the stage-specific blobs for conflicted paths, built using temporary index files so that the real index is never modified. The commit's own tree captures the worktree as-is, conflict markers and all. After creating the commit, the script runs `git reset --hard` just like `git stash` would. `apply-stash-with-conflicts.sh` reverses the process: it verifies the index is clean and that HEAD's tree matches the stash's base tree, then reconstructs the unmerged index entries and overlays the worktree with the stashed conflict markers, all without moving HEAD — just like `git stash apply`. Unlike `git stash apply`, it deliberately requires the current HEAD tree to match the stash's base tree exactly, because this allows the implementation to simply replay the recorded index and worktree state without any merging. This limitation could be lifted in the future by using `git merge-tree` to perform a virtual cherry-pick of the difference between the stash's base and the current HEAD onto each of the four parent trees (the worktree tree and the three stage trees). If all four merge-tree operations succeed without conflicts, the adjusted trees can be used in place of the originals. This is analogous to how the original shell script version of `git stash apply` used `merge-recursive` to apply the stashed changes onto a potentially different HEAD. The fixed four-parent layout means the unstash side does not need to parse commit messages or guess which parent corresponds to which stage; the positions are defined by convention. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
To give maintainers a durable, reviewable record of each rebase outcome — including the exact commits produced and the full conflict report — the workflow now creates pull requests in the dedicated git-for-windows/shears-builds fork. For each successfully rebased branch, a regular PR is opened whose head ref is the rebase result and whose base ref is the marker commit that anchors the merging-rebase. The PR description starts with a link back to the originating workflow run, followed by the full step summary (range-diff, conflict resolutions, build results). For failed rebases, a draft PR is opened instead. Because `git stash` cannot handle unmerged index entries, the failed worktree state is captured using the new `stash-with-conflicts.sh` script (introduced in the previous commit). The bundle and worktree archive are uploaded as release assets on a pre-release tag pointing at the PR's head commit. GitHub's API does not offer a way to attach binary files directly to pull request comments or descriptions, so a release is the natural alternative — release assets have no retention limit (unlike workflow artifacts, which expire after 90 days) and the pre-release tag on the PR's head commit creates an implicit link back to the PR. A comment on the draft PR links to those release assets so that the conflict state can be inspected even after the ephemeral runner is gone. The installation access token is now scoped to both `git` and `shears-builds` so that the workflow can push refs and create PRs and releases in the builds repository. Each step that calls `gh api`, `push_to_builds`, or `gh release` has explicit error handling: failures are reported as `::error::` annotations and tracked via an `errors` counter, but processing continues for the remaining branches so that a single failure does not prevent the other branches from being mirrored. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
@mjcheetham your analysis is correct. I was mistaken to believe AI when it suggested that this method would require to pass Also, I fixed an infinite loop where With these fixes, I kicked off another run, which correctly resolved the merge conflict (I added a Range-diff
|
|
Here is a nice win: git-for-windows/shears-builds#12. Essentially, the merge conflict between git-for-windows/git@f1824ba and git/git@d74aacd#diff-5709c9f0cfda9fa887afadc58746bc95fd1946ff9878e0552d3e7073f9484f76R119-R122 was resolved as git-for-windows/shears-builds@890df9c. Personally, I would have moved the |
|
And here's another non-trivial merge conflict successfully resolved: git-for-windows/shears-builds#19 |
|
I promise to stop adding these items soon, but for now I plan on adding these as a way to determine how much this automation took off of my shoulders. As a general rule, each of these merge conflicts would require between 10-30 minutes to investigate, I would guess.
|
While reviewing the MSYS2 sync automation in PR git-for-windows#152, we discovered that multi-line content inside `run: |` and `script: |` blocks silently gets truncated when any line falls below the block's base indentation level. This is particularly treacherous for heredoc bodies and JS template literals whose content naturally starts at column 0. Document the pitfall and the two mitigations that worked: indenting heredoc bodies to match the YAML block's base indentation, and using `[...].join('\n')` for multi-line JS strings. See git-for-windows#152 (comment) for the context that prompted this. Assisted-by: Claude Opus 4.6 Signed-off-by: Johannes Schindelin <[email protected]>
The
shears/*branches (also known as "ever-green" branches) maintain Git for Windows' downstream patches, continuously rebased, on top of upstream Git'smaster,next,seenandmaintbranches. By always reflecting the current state of upstream, we are notified early of any problems between our downstream patches and in-flight upstream changes. This also helps identify upstream regressions that only surface in our downstream testing, and since we rebase continuously, we can react much more quickly than if we waited for upstream releases.Until now, these rebases were handled by two Azure Pipelines (triggered by Git for Windows'
mainupdates and triggered by upstream branch updates). This was the last piece of Git for Windows' automation still running on Azure Pipelines. After converting the Coverity workflow, I was eager to finish the migration, but it took over a year to get started on the shears automation. AI assistance (in the beginning, Claude Sonnet, recently Claude Opus) made that feasible by doing much of the work in the background — slower than if I would have done it myself, but not on my clock, and much of my feedback was dictated using AI without much in the way of bothering to correct typos (modern LLMs are quite lenient with typos, saving my time).This PR adds a GitHub Actions workflow, a merge conflict resolving "agent", and a POSIX shell script that together automate the full merging-rebase cycle:
rebase-branch.sh): creates a worktree, finds the merging-rebase marker, syncs withorigin/main, builds correspondence maps (upstream, sibling, previous) to skip or reuse resolutions for already-upstreamed patches, and runs the interactive rebase with automatic trivial-skip detection.git range-diffandgit log -Lto understand how upstream modified conflicting lines, and either skip the commit (reporting a corresponding upstream commit's OID, or a reason), surgically resolve the conflict, or report failure. A build verification step catches resolution mistakes and gives the AI a second chance.skips for obsolete patches, andskip -- <reason>for patches made redundant by upstream changes without a directly corresponding upstream version of said patch.Note: This is not a straight conversion of the Azure Pipelines, but instead a rewrite from scratch, mostly written by Claude Opus 4.6 (and very closely monitored by me).
Features that were added in the process:
Note: To my great disappointment, I had to move from build checks running on Windows (which would be incredibly helpful) to running this entire workflow on Linux, including the test builds. The reason is that there are just too many rough edges around trying to run GitHub Copilot on Windows. My initial attempt with https://github.com/github/gh-aw caused utter frustration when I realized that it simply falls over its own feet, badly, already very early, when you dare to try this with Windows runners. But even after switching to "real" GitHub workflows (which
gh-aw"compiles" to, requiring you to do this locally after every change and committing it, anyway, an improvable flow if you ask me), there were just too many problems with apparently too few engineers having to handle all of them (a pattern I observed previously at that company), with github/copilot-cli#1401 being the nail in the coffin. In the wise words of Representative Binks: My give up. Maybe at some stage I might try again to get this whole thing working on Windows, it should work fine, it Works On My Machine, after all, but for now I need to heal the scars.In any case, I am quite hopeful that this workflow will help with Git for Windows' maintenance quite a bit, as one of the most tedious things is trying to fix things when upstream patches break Git for Windows functionality that worked for years. My expectation is that this automation will be able to handle quite a bit more of that load than the previous automation could.