Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow --dest to be an absolute path (v4) #478

Merged
merged 1 commit into from
Jan 23, 2022
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
82 changes: 46 additions & 36 deletions cmd/git-sync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ var flSubmodules = pflag.String("submodules", envString("GIT_SYNC_SUBMODULES", "
var flRoot = pflag.String("root", envString("GIT_SYNC_ROOT", ""),
"the root directory for git-sync operations, under which --link will be created")
var flLink = pflag.String("link", envString("GIT_SYNC_LINK", ""),
"the name of a symlink, under --root, which points to a directory in which --repo is checked out (defaults to the leaf dir of --repo)")
"the path (absolute or relative to --root) at which to create a symlink to the directory holding the checked-out files (defaults to the leaf dir of --repo)")
var flErrorFile = pflag.String("error-file", envString("GIT_SYNC_ERROR_FILE", ""),
"the name of a file into which errors will be written under --root (defaults to \"\", disabling error reporting)")
var flPeriod = pflag.Duration("period", envDuration("GIT_SYNC_PERIOD", 10*time.Second),
Expand Down Expand Up @@ -328,10 +328,10 @@ func main() {
parts := strings.Split(strings.Trim(*flRepo, "/"), "/")
*flLink = parts[len(parts)-1]
}
if strings.Contains(*flLink, "/") {
handleError(log, true, "ERROR: --link must not contain '/'")
if !filepath.IsAbs(*flLink) {
*flLink = filepath.Join(*flRoot, *flLink)
}
if strings.HasPrefix(*flLink, ".") {
if strings.HasPrefix(filepath.Base(*flLink), ".") {
handleError(log, true, "ERROR: --link must not start with '.'")
}

Expand Down Expand Up @@ -829,28 +829,36 @@ func addUser() error {
// directory and cleans up the previous worktree. If there was a previous
// worktree, this returns the path to it.
func (git *repoSync) UpdateSymlink(ctx context.Context, newDir string) (string, error) {
linkDir, linkFile := filepath.Split(git.link)

// Make sure the link directory exists. We do this here, rather than at
// startup because it might be under --root and that gets wiped in some
// circumstances.
if err := os.MkdirAll(filepath.Dir(linkDir), os.FileMode(int(0755))); err != nil {
return "", fmt.Errorf("error making symlink dir: %v", err)
}

// Get currently-linked repo directory (to be removed), unless it doesn't exist
linkPath := filepath.Join(git.root, git.link)
oldWorktreePath, err := filepath.EvalSymlinks(linkPath)
oldWorktreePath, err := filepath.EvalSymlinks(git.link)
if err != nil && !os.IsNotExist(err) {
return "", fmt.Errorf("error accessing current worktree: %v", err)
}

// newDir is absolute, so we need to change it to a relative path. This is
// so it can be volume-mounted at another path and the symlink still works.
newDirRelative, err := filepath.Rel(git.root, newDir)
newDirRelative, err := filepath.Rel(linkDir, newDir)
if err != nil {
return "", fmt.Errorf("error converting to relative path: %v", err)
}

const tmplink = "tmp-link"
git.log.V(1).Info("creating tmp symlink", "root", git.root, "dst", newDirRelative, "src", tmplink)
if _, err := git.run.Run(ctx, git.root, "ln", "-snf", newDirRelative, tmplink); err != nil {
git.log.V(1).Info("creating tmp symlink", "root", linkDir, "dst", newDirRelative, "src", tmplink)
if _, err := git.run.Run(ctx, linkDir, "ln", "-snf", newDirRelative, tmplink); err != nil {
return "", fmt.Errorf("error creating symlink: %v", err)
}

git.log.V(1).Info("renaming symlink", "root", git.root, "oldName", tmplink, "newName", git.link)
if _, err := git.run.Run(ctx, git.root, "mv", "-T", tmplink, git.link); err != nil {
git.log.V(1).Info("renaming symlink", "root", linkDir, "oldName", tmplink, "newName", linkFile)
if _, err := git.run.Run(ctx, linkDir, "mv", "-T", tmplink, linkFile); err != nil {
return "", fmt.Errorf("error replacing symlink: %v", err)
}

Expand Down Expand Up @@ -1188,8 +1196,7 @@ func (git *repoSync) SyncRepo(ctx context.Context) (bool, string, error) {
askpassCount.WithLabelValues(metricKeySuccess).Inc()
}

target := filepath.Join(git.root, git.link)
gitRepoPath := filepath.Join(target, ".git")
gitRepoPath := filepath.Join(git.root, ".git")
var hash string
_, err := os.Stat(gitRepoPath)
switch {
Expand Down Expand Up @@ -1562,7 +1569,7 @@ DESCRIPTION
git-sync can pull over HTTP(S) (with authentication or not) or SSH.

git-sync can also be configured to make a webhook call upon successful git
repo synchronization. The call is made after the symlink is updated.
repo synchronization. The call is made after the symlink is updated.

OPTIONS

Expand All @@ -1575,12 +1582,12 @@ OPTIONS
/etc/passwd is writable by the current UID.

--askpass-url <string>, $GIT_ASKPASS_URL
A URL to query for git credentials. The query must return success
A URL to query for git credentials. The query must return success
(200) and produce a series of key=value lines, including
"username=<value>" and "password=<value>".

--branch <string>, $GIT_SYNC_BRANCH
The git branch to check out. (default: <repo's default branch>)
The git branch to check out. (default: <repo's default branch>)

--change-permissions <int>, $GIT_SYNC_PERMISSIONS
Optionally change permissions on the checked-out files to the
Expand All @@ -1596,8 +1603,8 @@ OPTIONS

--error-file, $GIT_SYNC_ERROR_FILE
The name of a file (under --root) into which errors will be
written. This must be a filename, not a path, and may not start
with a period. (default: "", which means error reporting will be
written. This must be a filename, not a path, and may not start
with a period. (default: "", which means error reporting will be
disabled)

--exechook-backoff <duration>, $GIT_SYNC_EXECHOOK_BACKOFF
Expand All @@ -1614,7 +1621,7 @@ OPTIONS
specified, it will take precedence.

--exechook-timeout <duration>, $GIT_SYNC_EXECHOOK_TIMEOUT
The timeout for the --exechook-command. (default: 30s)
The timeout for the --exechook-command. (default: 30s)

--git <string>, $GIT_SYNC_GIT
The git command to run (subject to PATH search, mostly for testing).
Expand All @@ -1628,7 +1635,7 @@ OPTIONS
supported: '\n' => [newline], '\t' => [tab], '\"' => '"', '\,' =>
',', '\\' => '\'. Within unquoted values, commas MUST be escaped.
Within quoted values, commas MAY be escaped, but are not required
to be. Any other escape sequence is an error. (default: "")
to be. Any other escape sequence is an error. (default: "")

-h, --help
Print help text and exit.
Expand All @@ -1643,22 +1650,25 @@ OPTIONS

--http-pprof, $GIT_SYNC_HTTP_PPROF
Enable the pprof debug endpoints on git-sync's HTTP endpoint (see
--http-bind). (default: false)
--http-bind). (default: false)

--link <string>, $GIT_SYNC_LINK
The name of the final symlink (under --root) which will point to the
current git worktree. This must be a filename, not a path, and may
not start with a period. The destination of this link (i.e.
readlink()) is the currently checked out SHA. (default: the leaf
dir of --repo)
The path to at which to create a symlink which points to the
current git directory, at the currently synced SHA. This may be an
absolute path or a relative path, in which case it is relative to
--root. The last path element is the name of the link and must not
start with a period. Consumers of the synced files should always
use this link. It is updated atomically and should always be
valid. The basename of the target of the link is the current SHA).
(default: the leaf dir of --repo)

--man
Print this manual and exit.

--max-sync-failures <int>, $GIT_SYNC_MAX_SYNC_FAILURES
The number of consecutive failures allowed before aborting (the
first sync must succeed), Setting this to -1 will retry forever
after the initial sync. (default: 0)
after the initial sync. (default: 0)

--one-time, $GIT_SYNC_ONE_TIME
Exit after the first sync.
Expand All @@ -1677,17 +1687,17 @@ OPTIONS
--period <duration>, $GIT_SYNC_PERIOD
How long to wait between sync attempts. This must be at least
10ms. This flag obsoletes --wait, but if --wait is specified, it
will take precedence. (default: 10s)
will take precedence. (default: 10s)

--repo <string>, $GIT_SYNC_REPO
The git repository to sync.

--rev <string>, $GIT_SYNC_REV
The git revision (tag or hash) to check out. (default: HEAD)
The git revision (tag or hash) to check out. (default: HEAD)

--root <string>, $GIT_SYNC_ROOT
The root directory for git-sync operations, under which --link will
be created. This flag is required.
be created. This flag is required.

--sparse-checkout-file, $GIT_SYNC_SPARSE_CHECKOUT_FILE
The path to a git sparse-checkout file (see git documentation for
Expand All @@ -1698,7 +1708,7 @@ OPTIONS
Use SSH for git authentication and operations.

--ssh-key-file <string>, $GIT_SSH_KEY_FILE
The SSH key to use when using --ssh. (default: /etc/git-secret/ssh)
The SSH key to use when using --ssh. (default: /etc/git-secret/ssh)

--ssh-known-hosts, $GIT_KNOWN_HOSTS
Enable SSH known_hosts verification when using --ssh.
Expand All @@ -1715,15 +1725,15 @@ OPTIONS
--sync-timeout <duration>, $GIT_SYNC_SYNC_TIMEOUT
The total time allowed for one complete sync. This must be at least
10ms. This flag obsoletes --timeout, but if --timeout is specified,
it will take precedence. (default: 120s)
it will take precedence. (default: 120s)

--username <string>, $GIT_SYNC_USERNAME
The username to use for git authentication (see --password-file or
--password).

-v, --verbose <int>
Set the log verbosity level. Logs at this level and lower will be
printed. (default: 0)
printed. (default: 0)

--version
Print the version and exit.
Expand All @@ -1741,7 +1751,7 @@ OPTIONS
(default: 200)

--webhook-timeout <duration>, $GIT_SYNC_WEBHOOK_TIMEOUT
The timeout for the --webhook-url. (default: 1s)
The timeout for the --webhook-url. (default: 1s)

--webhook-url <string>, $GIT_SYNC_WEBHOOK_URL
A URL for optional webhook notifications when syncs complete.
Expand Down Expand Up @@ -1785,9 +1795,9 @@ AUTHENTICATION

WEBHOOKS

Webhooks are executed asynchronously from the main git-sync process. If a
Webhooks are executed asynchronously from the main git-sync process. If a
--webhook-url is configured, whenever a new hash is synced a call is sent
using the method defined in --webhook-method. Git-sync will retry this
using the method defined in --webhook-method. Git-sync will retry this
webhook call until it succeeds (based on --webhook-success-status). If
unsuccessful, git-sync will wait --webhook-backoff (default 3s) before
re-attempting the webhook call.
Expand Down
42 changes: 42 additions & 0 deletions test_e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,48 @@ function e2e::head_once_root_exists_but_fails_sanity() {
## FIXME: test when repo is valid git, but not ar ref we need
## FIXME: test when repo is valid git, and is already correct

##############################################
# Test HEAD one-time with an absolute-path link
##############################################
function e2e::absolute_link() {
echo "$FUNCNAME" > "$REPO"/file
git -C "$REPO" commit -qam "$FUNCNAME"

GIT_SYNC \
--one-time \
--repo="file://$REPO" \
--branch="$MAIN_BRANCH" \
--rev="HEAD" \
--root="$ROOT/root" \
--link="$ROOT/other/dir/link" \
>> "$1" 2>&1
assert_file_absent "$ROOT"/root/link
assert_link_exists "$ROOT"/other/dir/link
assert_file_exists "$ROOT"/other/dir/link/file
assert_file_eq "$ROOT"/other/dir/link/file "$FUNCNAME"
}

##############################################
# Test HEAD one-time with a subdir-path link
##############################################
function e2e::subdir_link() {
echo "$FUNCNAME" > "$REPO"/file
git -C "$REPO" commit -qam "$FUNCNAME"

GIT_SYNC \
--one-time \
--repo="file://$REPO" \
--branch="$MAIN_BRANCH" \
--rev="HEAD" \
--root="$ROOT" \
--link="other/dir/link" \
>> "$1" 2>&1
assert_file_absent "$ROOT"/link
assert_link_exists "$ROOT"/other/dir/link
assert_file_exists "$ROOT"/other/dir/link/file
assert_file_eq "$ROOT"/other/dir/link/file "$FUNCNAME"
}

##############################################
# Test default-branch syncing
##############################################
Expand Down