path: extract format_path() and use in rev-parse

Path formatting logic in builtin/rev-parse.c writes directly to stdout. Other builtins cannot reuse it. Extract this logic into format_path() in path.c and expose a path_format enum in path.h. Convert rev-parse to use the new helper in the same step to validate the API against existing tests and avoid introducing dead code. Mentored-by: Justin Tobler <jltobler@gmail.com> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

K Jayatheerth committed Jun 24, 2026 at 09:07 UTC 60cafea9078097f051f39c234fb6aa8f6051b2bd
3 files changed +135 -43
builtin/rev-parse.c
+36 -43
@@ -653,53 +653,46 @@ enum default_type {
653 DEFAULT_UNMODIFIED,
654 };
655
656 -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
656 +static void print_path(const char *path, const char *prefix,
657 + enum format_type format, enum default_type def)
658 {
658 - char *cwd = NULL;
659 - /*
660 - * We don't ever produce a relative path if prefix is NULL, so set the
661 - * prefix to the current directory so that we can produce a relative
662 - * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then
663 - * we want an absolute path unless the two share a common prefix, so don't
664 - * set it in that case, since doing so causes a relative path to always
665 - * be produced if possible.
666 - */
667 - if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
668 - prefix = cwd = xgetcwd();
669 - if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
670 - puts(path);
671 - } else if (format == FORMAT_RELATIVE ||
672 - (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
673 - /*
674 - * In order for relative_path to work as expected, we need to
675 - * make sure that both paths are absolute paths. If we don't,
676 - * we can end up with an unexpected absolute path that the user
677 - * didn't want.
678 - */
679 - struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
680 - if (!is_absolute_path(path)) {
681 - strbuf_realpath_forgiving(&realbuf, path, 1);
682 - path = realbuf.buf;
683 - }
684 - if (!is_absolute_path(prefix)) {
685 - strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
686 - prefix = prefixbuf.buf;
659 + struct strbuf sb = STRBUF_INIT;
660 + enum path_format fmt;
661 +
662 + if (format == FORMAT_DEFAULT) {
663 + switch (def) {
664 + case DEFAULT_RELATIVE:
665 + fmt = PATH_FORMAT_RELATIVE;
666 + break;
667 + case DEFAULT_RELATIVE_IF_SHARED:
668 + fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
669 + break;
670 + case DEFAULT_CANONICAL:
671 + fmt = PATH_FORMAT_CANONICAL;
672 + break;
673 + case DEFAULT_UNMODIFIED:
674 + default:
675 + fmt = PATH_FORMAT_UNMODIFIED;
676 + break;
677 }
688 - puts(relative_path(path, prefix, &buf));
689 - strbuf_release(&buf);
690 - strbuf_release(&realbuf);
691 - strbuf_release(&prefixbuf);
692 - } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
693 - struct strbuf buf = STRBUF_INIT;
694 - puts(relative_path(path, prefix, &buf));
695 - strbuf_release(&buf);
678 } else {
697 - struct strbuf buf = STRBUF_INIT;
698 - strbuf_realpath_forgiving(&buf, path, 1);
699 - puts(buf.buf);
700 - strbuf_release(&buf);
679 + switch (format) {
680 + case FORMAT_RELATIVE:
681 + fmt = PATH_FORMAT_RELATIVE;
682 + break;
683 + case FORMAT_CANONICAL:
684 + fmt = PATH_FORMAT_CANONICAL;
685 + break;
686 + default:
687 + fmt = PATH_FORMAT_UNMODIFIED;
688 + break;
689 + }
690 }
702 - free(cwd);
691 +
692 + format_path(&sb, path, prefix, fmt);
693 + puts(sb.buf);
694 +
695 + strbuf_release(&sb);
696 }
697
698 int cmd_rev_parse(int argc,
path.c
+69
@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
1579 return NULL;
1580 }
1581
1582 +void format_path(struct strbuf *dest, const char *path,
1583 + const char *prefix, enum path_format format)
1584 +{
1585 + strbuf_reset(dest);
1586 +
1587 + switch (format) {
1588 + case PATH_FORMAT_UNMODIFIED:
1589 + strbuf_addstr(dest, path);
1590 + break;
1591 +
1592 + case PATH_FORMAT_RELATIVE: {
1593 + struct strbuf relative_buf = STRBUF_INIT;
1594 + struct strbuf real_path = STRBUF_INIT;
1595 + struct strbuf real_prefix = STRBUF_INIT;
1596 + char *cwd = NULL;
1597 +
1598 + /*
1599 + * We don't ever produce a relative path if prefix is NULL,
1600 + * so set the prefix to the current directory so that we can
1601 + * produce a relative path whenever possible.
1602 + */
1603 + if (!prefix)
1604 + prefix = cwd = xgetcwd();
1605 +
1606 + if (!is_absolute_path(path)) {
1607 + strbuf_realpath_forgiving(&real_path, path, 1);
1608 + path = real_path.buf;
1609 + }
1610 + if (!is_absolute_path(prefix)) {
1611 + strbuf_realpath_forgiving(&real_prefix, prefix, 1);
1612 + prefix = real_prefix.buf;
1613 + }
1614 +
1615 + strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
1616 +
1617 + strbuf_release(&relative_buf);
1618 + strbuf_release(&real_path);
1619 + strbuf_release(&real_prefix);
1620 + free(cwd);
1621 + break;
1622 + }
1623 +
1624 + case PATH_FORMAT_RELATIVE_IF_SHARED: {
1625 + struct strbuf relative_buf = STRBUF_INIT;
1626 +
1627 + /*
1628 + * If we're using RELATIVE_IF_SHARED mode, then we want an
1629 + * absolute path unless the two share a common prefix, so don't
1630 + * default the prefix to the current working directory. Doing so
1631 + * would cause a relative path to always be produced if possible.
1632 + */
1633 + strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
1634 + strbuf_release(&relative_buf);
1635 + break;
1636 + }
1637 +
1638 + case PATH_FORMAT_CANONICAL:
1639 + /*
1640 + * strbuf_realpath_forgiving inherently resets the destination
1641 + * buffer, safely aligning with our replace semantics.
1642 + */
1643 + strbuf_realpath_forgiving(dest, path, 1);
1644 + break;
1645 +
1646 + default:
1647 + BUG("unknown path_format value %d", format);
1648 + }
1649 +}
1650 +
1651 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
1652 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
1653 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
path.h
+30
@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
262 int safe_create_file_with_leading_directories(struct repository *repo,
263 const char *path);
264
265 +/**
266 + * The formatting strategy to apply when writing a path into a buffer.
267 + */
268 +enum path_format {
269 + /* Output the path exactly as-is without any modifications. */
270 + PATH_FORMAT_UNMODIFIED,
271 +
272 + /* Output a path relative to the provided directory prefix. */
273 + PATH_FORMAT_RELATIVE,
274 +
275 + /* Output a relative path only if the path shares a root with the prefix. */
276 + PATH_FORMAT_RELATIVE_IF_SHARED,
277 +
278 + /* Output a fully resolved, absolute canonical path. */
279 + PATH_FORMAT_CANONICAL
280 +};
281 +
282 +/**
283 + * Format a path according to the specified formatting strategy and store
284 + * the result in the given strbuf, replacing any existing contents.
285 + *
286 + * `dest` : The string buffer to store the formatted path into.
287 + * `path` : The path string that needs to be formatted.
288 + * `prefix` : The directory prefix to calculate relative offsets against.
289 + * Pass NULL to default to the current working directory where applicable.
290 + * `format` : The formatting behavior rule to execute.
291 + */
292 +void format_path(struct strbuf *dest, const char *path,
293 + const char *prefix, enum path_format format);
294 +
295 # ifdef USE_THE_REPOSITORY_VARIABLE
296 # include "strbuf.h"
297 # include "repository.h"