mirror of
https://github.com/jj-vcs/jj.git
synced 2025-12-23 06:01:01 +00:00
cli: git fetch: fall back to remote fetch refspecs when -b not specified
This changes the behavior of git fetch to respect the fetch refspecs configured on the remote. This is handy for projects which use customized fetch refspecs (e.g. only fetch certain patterns, but not all branches) but without having to remember and repeat all the patterns by hand on the CLI Fixes #5323
This commit is contained in:
parent
4563621f89
commit
75fa7de001
5 changed files with 103 additions and 34 deletions
|
|
@ -31,6 +31,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
many people to be negatively affected, because running `jj undo` twice was
|
||||
previously a no-op.
|
||||
|
||||
* `jj git fetch` will now only fetch the refspec patterns configured on remotes
|
||||
when the `--bookmark` option is omitted. Only simple refspec patterns
|
||||
are currently supported, and anything else (like refspecs which rename
|
||||
branches) will be ignored.
|
||||
|
||||
### Deprecations
|
||||
|
||||
* The on-disk index format has changed. `jj` will write index files in both old
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@ use itertools::Itertools as _;
|
|||
use jj_lib::config::ConfigGetResultExt as _;
|
||||
use jj_lib::git;
|
||||
use jj_lib::git::GitFetch;
|
||||
use jj_lib::git::IgnoredRefspec;
|
||||
use jj_lib::git::IgnoredRefspecs;
|
||||
use jj_lib::git::expand_default_fetch_refspecs;
|
||||
use jj_lib::git::expand_fetch_refspecs;
|
||||
use jj_lib::git::get_git_backend;
|
||||
use jj_lib::ref_name::RemoteName;
|
||||
use jj_lib::repo::Repo as _;
|
||||
use jj_lib::str_util::StringPattern;
|
||||
|
|
@ -51,7 +55,6 @@ pub struct GitFetchArgs {
|
|||
#[arg(
|
||||
long, short,
|
||||
alias = "bookmark",
|
||||
default_value = "glob:*",
|
||||
value_parser = StringPattern::parse,
|
||||
add = ArgValueCandidates::new(complete::bookmarks),
|
||||
)]
|
||||
|
|
@ -126,44 +129,40 @@ pub fn cmd_git_fetch(
|
|||
.sorted()
|
||||
.collect_vec();
|
||||
|
||||
let branches_by_remote: Vec<(&RemoteName, Vec<StringPattern>)> = if args.tracked {
|
||||
remotes
|
||||
.iter()
|
||||
.map(|&remote| {
|
||||
let tracked_branches = workspace_command
|
||||
.repo()
|
||||
.view()
|
||||
.local_remote_bookmarks(remote)
|
||||
.filter(|(_, targets)| targets.remote_ref.is_tracked())
|
||||
.map(|(name, _)| StringPattern::exact(name))
|
||||
.collect_vec();
|
||||
(remote, tracked_branches)
|
||||
})
|
||||
.collect_vec()
|
||||
let mut tx = workspace_command.start_transaction();
|
||||
|
||||
let mut expansions = Vec::with_capacity(remotes.len());
|
||||
if args.tracked {
|
||||
for remote in &remotes {
|
||||
let tracked_branches = tx
|
||||
.repo()
|
||||
.view()
|
||||
.local_remote_bookmarks(remote)
|
||||
.filter(|(_, targets)| targets.remote_ref.is_tracked())
|
||||
.map(|(name, _)| StringPattern::exact(name))
|
||||
.collect_vec();
|
||||
expansions.push((remote, expand_fetch_refspecs(remote, tracked_branches)?));
|
||||
}
|
||||
} else if args.branch.is_empty() {
|
||||
let git_repo = get_git_backend(tx.repo_mut().store())?.git_repo();
|
||||
for remote in &remotes {
|
||||
let (ignored, expanded) = expand_default_fetch_refspecs(remote, &git_repo)?;
|
||||
warn_ignored_refspecs(ui, remote, ignored)?;
|
||||
expansions.push((remote, expanded));
|
||||
}
|
||||
} else {
|
||||
remotes
|
||||
.iter()
|
||||
.map(|&remote| (remote, args.branch.clone()))
|
||||
.collect_vec()
|
||||
for remote in &remotes {
|
||||
let expanded = expand_fetch_refspecs(remote, args.branch.clone())?;
|
||||
expansions.push((remote, expanded));
|
||||
}
|
||||
};
|
||||
|
||||
let mut tx = workspace_command.start_transaction();
|
||||
let git_settings = tx.settings().git_settings()?;
|
||||
let mut git_fetch = GitFetch::new(tx.repo_mut(), &git_settings)?;
|
||||
|
||||
for (remote, branches) in branches_by_remote {
|
||||
// Skip remotes with no branches to fetch
|
||||
if branches.is_empty() {
|
||||
continue;
|
||||
}
|
||||
for (remote, expanded) in expansions {
|
||||
with_remote_git_callbacks(ui, |callbacks| {
|
||||
git_fetch.fetch(
|
||||
remote,
|
||||
expand_fetch_refspecs(remote, branches)?,
|
||||
callbacks,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
git_fetch.fetch(remote, expanded, callbacks, None, None)
|
||||
})?;
|
||||
}
|
||||
|
||||
|
|
@ -251,3 +250,19 @@ fn warn_if_branches_not_found(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn warn_ignored_refspecs(
|
||||
ui: &Ui,
|
||||
remote_name: &RemoteName,
|
||||
IgnoredRefspecs(ignored_refspecs): IgnoredRefspecs,
|
||||
) -> Result<(), CommandError> {
|
||||
let remote_name = remote_name.as_symbol();
|
||||
for IgnoredRefspec { refspec, reason } in ignored_refspecs {
|
||||
writeln!(
|
||||
ui.warning_default(),
|
||||
"Ignored refspec `{refspec}` from `{remote_name}`: {reason}",
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ mod push;
|
|||
mod remote;
|
||||
mod root;
|
||||
|
||||
use std::io::Write as _;
|
||||
use std::path::Path;
|
||||
|
||||
use clap::Subcommand;
|
||||
|
|
|
|||
|
|
@ -1259,8 +1259,6 @@ If a working-copy commit gets abandoned, it will be given a new, empty commit. T
|
|||
* `-b`, `--branch <BRANCH>` — Fetch only some of the branches
|
||||
|
||||
By default, the specified name matches exactly. Use `glob:` prefix to expand `*` as a glob, e.g. `--branch 'glob:push-*'`. Other wildcard characters such as `?` are *not* supported. Can be repeated to specify multiple branches.
|
||||
|
||||
Default value: `glob:*`
|
||||
* `--tracked` — Fetch only tracked bookmarks
|
||||
|
||||
This fetches only bookmarks that are already tracked from the specified remote(s).
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::io::Write as _;
|
||||
|
||||
use testutils::git;
|
||||
|
||||
use crate::common::CommandOutput;
|
||||
|
|
@ -228,6 +230,54 @@ fn test_git_fetch_multiple_remotes() {
|
|||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_fetch_with_ignored_refspecs() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.add_config("git.auto-local-bookmark = true");
|
||||
test_env
|
||||
.run_jj_in(".", ["git", "init", "--colocate", "repo"])
|
||||
.success();
|
||||
let source_repo = init_git_remote(&test_env, "origin");
|
||||
|
||||
for branch in ["main", "foo", "foobar", "foobaz", "bar"] {
|
||||
add_commit_to_branch(&source_repo, &format!("refs/heads/{branch}"), branch);
|
||||
}
|
||||
|
||||
let work_dir = test_env.work_dir("repo");
|
||||
std::fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.open(work_dir.root().join("./.git/config"))
|
||||
.expect("failed to open config file")
|
||||
.write_all(
|
||||
br#"
|
||||
[remote "origin"]
|
||||
url = ../origin/.git
|
||||
fetch = +refs/heads/main:refs/remotes/origin/main
|
||||
fetch = +refs/heads/foo*:refs/remotes/origin/baz*
|
||||
fetch = +refs/heads/bar*:refs/tags/bar*
|
||||
fetch = refs/heads/bar
|
||||
"#,
|
||||
)
|
||||
.expect("failed to update config file");
|
||||
|
||||
let output = work_dir.run_jj(["git", "fetch"]).success();
|
||||
|
||||
insta::assert_snapshot!(output.stdout, @r"");
|
||||
insta::assert_snapshot!(output.stderr, @r"
|
||||
Warning: Ignored refspec `refs/heads/bar` from `origin`: fetch-only refspecs are not supported
|
||||
Warning: Ignored refspec `+refs/heads/bar*:refs/tags/bar*` from `origin`: only refs/remotes/ is supported for fetch destinations
|
||||
Warning: Ignored refspec `+refs/heads/foo*:refs/remotes/origin/baz*` from `origin`: renaming is not supported
|
||||
bookmark: main@origin [new] tracked
|
||||
[EOF]
|
||||
");
|
||||
insta::assert_snapshot!(get_bookmark_output(&work_dir), @r"
|
||||
main: kpukqsrq f803461e main
|
||||
@git: kpukqsrq f803461e main
|
||||
@origin: kpukqsrq f803461e main
|
||||
[EOF]
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_fetch_with_glob() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue