Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/cli/commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ It needs a clean working tree, and a worktree of the nested branch.
| `-f`, `--force` | commit even if the branch does not contain the fetched upstream commit |
| `-m`, `--message <message>` | the commit message |
| `--file <file>` | read the commit message from a file |
| `--pin <commit>` | record this pin in `.gitnested`. The help of a conflicted `pull --pin` passes it |
| `-S`, `--gpg-sign` | GPG-sign the commit |
| `--signoff` | add a `Signed-off-by:` trailer to the commit |

Expand Down
3 changes: 3 additions & 0 deletions doc/guides/conflicts.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ git nested commit ext/lib
`git nested commit` puts the result into `ext/lib`, updates `.gitnested` and
makes one commit. It also removes the worktree.

After `pull --pin <commit>`, step 7 is `git nested commit --pin=<commit> ext/lib`.
The pin then goes into `.gitnested` as well.

### With the rebase method

Step 4 is `git rebase --continue`. Repeat steps 2 to 4 until the rebase is
Expand Down
2 changes: 1 addition & 1 deletion src/git_nested/cli/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
'recurse_submodules',
'signoff',
],
'commit': ['fetch', 'force', 'gpg_sign', 'message', 'msg_file', 'signoff'],
'commit': ['fetch', 'force', 'gpg_sign', 'message', 'msg_file', 'pin', 'signoff'],
'completion': [],
'config': [],
'diff': ['all', 'branch', 'remote'],
Expand Down
2 changes: 1 addition & 1 deletion src/git_nested/commands/pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def do_pull(
try:
git.run(cmd, cwd=subdir_worktree)
except GitNestedError as e:
help_text = messages.pull_conflict_help(subdir, subdir_worktree, method, flags.message_file, subref)
help_text = messages.pull_conflict_help(subdir, subdir_worktree, method, flags.message_file, flags.pin, subref)
# e.message ends with git's own stderr, and so with however many
# newlines git felt like; rstrip normalises that, and the one added
# here plus the one help_text opens with leave a blank line between
Expand Down
14 changes: 8 additions & 6 deletions src/git_nested/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,21 @@ def branch_exists(subdir: Path, branch: str, facts: list[tuple[str, str | None]]
return "\n".join(lines)


def pull_conflict_help(subdir: Path, worktree: Path, method: str, message_file: str | None, subref: str) -> str:
def pull_conflict_help(
subdir: Path, worktree: Path, method: str, message_file: str | None, pin: str | None, subref: str
) -> str:
"""How to finish a pull by hand after its merge or rebase hit conflicts.

git-nested has already left the half-finished operation in a worktree of
its own, so the recovery is an ordinary conflict resolution followed by
handing the result back -- which is what the numbered steps spell out.
"""
resume = "git rebase --continue" if method == 'rebase' else "git commit"
commit = (
f"git nested commit --file={message_file} {subdir.as_posix()}"
if message_file
else f"git nested commit {subdir.as_posix()}"
)
options = [f'--file={message_file}'] if message_file else []
if pin:
# Only the pull got it: without it, commit would not record the pin in .gitnested
options.append(f'--pin={pin}')
commit = ' '.join(['git nested commit', *options, subdir.as_posix()])
text = textwrap.dedent(f"""\

The conflicts are in a worktree of their own at {worktree.as_posix()}.
Expand Down
26 changes: 26 additions & 0 deletions tests/e2e/test_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,29 @@ def test_clone_pin_must_be_on_the_branch(foo_bar_cloned):

assert result.returncode == 1
assert result.output.strip() == f"git-nested: {side} is not on master of {(env.upstream / 'bar').as_posix()}"


def test_pull_conflict_keeps_the_pin(foo_bar_cloned):
"""A pull with --pin that conflicts records the pin once it is finished as printed."""
env = foo_bar_cloned
foo, bar = env.workspace / 'foo', env.workspace / 'bar'
cmd_git_nested(['clone', '-b', 'master', '--pin', 'A', (env.upstream / 'bar').as_posix()], foo)
env.modify_files('bar/Bar', text='foo', cwd=foo)
env.modify_files('Bar', text='bar', cwd=bar)
pin = git_rev_parse(['HEAD'], cwd=bar)
env.add_new_files('Newer', cwd=bar)
env.run(['git', 'push', '-q'], cwd=bar)

result = cmd_git_nested(['pull', '--pin', pin, 'bar'], foo, check=False)
assert result.returncode == 1
worktree = foo / '.git' / 'tmp' / 'nested' / 'bar'
(worktree / 'Bar').write_text('merged\n')
env.run(['git', 'add', 'Bar'], cwd=worktree)
env.run(['git', 'commit', '-q', '--no-edit'], cwd=worktree)
# the last step the pull printed
commit_step = result.output.split('7. ')[1].splitlines()[0]
cmd_git_nested(commit_step.removeprefix('git nested '), foo)

assert _nested(foo, 'pin') == pin
assert _nested(foo, 'commit') == pin
assert "already up to date" in cmd_git_nested('pull bar', cwd=foo).output
Loading